From f1a4c683f235d453d7ebe8654e23aa59bc5b967c Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 7 Feb 2018 14:17:33 +0200 Subject: [PATCH 001/171] Rule Engine draft --- .../src/main/resources/thingsboard.yml | 2 +- dao/pom.xml | 1 - pom.xml | 1 + rule-engine/pom.xml | 42 ++++++++++++ rule-engine/rule-engine-api/pom.xml | 67 +++++++++++++++++++ .../rule/engine/api/TbContext.java | 29 ++++++++ .../thingsboard/rule/engine/api/TbMsg.java | 22 ++++++ .../rule/engine/api/TbMsgMetaData.java | 24 +++++++ .../thingsboard/rule/engine/api/TbNode.java | 16 +++++ .../rule/engine/api/TbNodeConfiguration.java | 14 ++++ .../rule/engine/api/TbNodeException.java | 14 ++++ .../rule/engine/api/TbNodeState.java | 7 ++ rule-engine/rule-engine-components/pom.xml | 67 +++++++++++++++++++ .../thingsboard/rule/engine/TbNodeUtils.java | 23 +++++++ .../engine/filter/TbMsgTypeFilterNode.java | 35 ++++++++++ .../TbMsgTypeFilterNodeConfiguration.java | 15 +++++ .../engine/metadata/TbGetAttributesNode.java | 51 ++++++++++++++ .../TbGetAttributesNodeConfiguration.java | 17 +++++ .../engine/transform/TbTransformNode.java | 52 ++++++++++++++ 19 files changed, 497 insertions(+), 2 deletions(-) create mode 100644 rule-engine/pom.xml create mode 100644 rule-engine/rule-engine-api/pom.xml create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java create mode 100644 rule-engine/rule-engine-components/pom.xml create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 18e35c6744..6bb3917652 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -247,7 +247,7 @@ spring: database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.HSQLDialect}" datasource: driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.hsqldb.jdbc.JDBCDriver}" - url: "${SPRING_DATASOURCE_URL:jdbc:hsqldb:file:${SQL_DATA_FOLDER:/tmp}/thingsboardDb;sql.enforce_size=false}" + url: "${SPRING_DATASOURCE_URL:jdbc:hsqldb:file:${SQL_DATA_FOLDER:/tmp}/thingsboardDb;sql.enforce_size=false;hsqldb.log_size=5}" username: "${SPRING_DATASOURCE_USERNAME:sa}" password: "${SPRING_DATASOURCE_PASSWORD:}" diff --git a/dao/pom.xml b/dao/pom.xml index d9463e4f9c..8d43822342 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -23,7 +23,6 @@ 1.4.0-SNAPSHOT thingsboard - org.thingsboard dao jar diff --git a/pom.xml b/pom.xml index 4b77abbd98..27e5216897 100755 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,7 @@ extensions-api extensions-core extensions + rule-engine transport ui tools diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml new file mode 100644 index 0000000000..e23f8714e3 --- /dev/null +++ b/rule-engine/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.thingsboard + 1.4.0-SNAPSHOT + thingsboard + + org.thingsboard + rule-engine + pom + + Thingsboard Extensions + https://thingsboard.io + + + ${basedir}/.. + + + + rule-engine-api + rule-engine-components + + + diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml new file mode 100644 index 0000000000..69cbc65965 --- /dev/null +++ b/rule-engine/rule-engine-api/pom.xml @@ -0,0 +1,67 @@ + + + + 4.0.0 + + org.thingsboard + 1.4.0-SNAPSHOT + rule-engine + + org.thingsboard.rule-engine + rule-engine-api + jar + + Thingsboard Rule Engine API + https://thingsboard.io + + + UTF-8 + ${basedir}/../.. + + + + + org.thingsboard.common + message + provided + + + org.thingsboard + extensions-api + provided + + + org.thingsboard + dao + provided + + + ch.qos.logback + logback-core + provided + + + ch.qos.logback + logback-classic + provided + + + \ No newline at end of file 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 new file mode 100644 index 0000000000..b833a97759 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -0,0 +1,29 @@ +package org.thingsboard.rule.engine.api; + +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.dao.attributes.AttributesService; + +import java.util.UUID; + +/** + * Created by ashvayka on 13.01.18. + */ +public interface TbContext { + + void tellNext(TbMsg msg); + + void tellNext(TbMsg msg, String relationType); + + void tellSelf(TbMsg msg, long delayMs); + + void tellOthers(TbMsg msg); + + void tellSibling(TbMsg msg, ServerAddress address); + + void spawn(TbMsg msg); + + void ack(UUID msg); + + AttributesService getAttributesService(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java new file mode 100644 index 0000000000..9917c6a505 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java @@ -0,0 +1,22 @@ +package org.thingsboard.rule.engine.api; + +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; + +import java.io.Serializable; +import java.util.UUID; + +/** + * Created by ashvayka on 13.01.18. + */ +@Data +public final class TbMsg implements Serializable { + + private final UUID id; + private final String type; + private final EntityId originator; + private final TbMsgMetaData metaData; + + private final byte[] data; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java new file mode 100644 index 0000000000..56abf2a12d --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java @@ -0,0 +1,24 @@ +package org.thingsboard.rule.engine.api; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Map; + +/** + * Created by ashvayka on 13.01.18. + */ +@Data +public final class TbMsgMetaData implements Serializable { + + private Map data; + + public String getValue(String key) { + return data.get(key); + } + + public void putValue(String key, String value) { + data.put(key, value); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java new file mode 100644 index 0000000000..70993df47a --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java @@ -0,0 +1,16 @@ +package org.thingsboard.rule.engine.api; + +import java.util.concurrent.ExecutionException; + +/** + * Created by ashvayka on 19.01.18. + */ +public interface TbNode { + + void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException; + + void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException; + + void destroy(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java new file mode 100644 index 0000000000..4ab84aaeed --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java @@ -0,0 +1,14 @@ +package org.thingsboard.rule.engine.api; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; + +/** + * Created by ashvayka on 19.01.18. + */ +@Data +public class TbNodeConfiguration { + + private JsonNode data; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java new file mode 100644 index 0000000000..5a3744b08c --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java @@ -0,0 +1,14 @@ +package org.thingsboard.rule.engine.api; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Created by ashvayka on 19.01.18. + */ +public class TbNodeException extends Exception { + + public TbNodeException(Exception e) { + super(e); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java new file mode 100644 index 0000000000..15f4c30774 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java @@ -0,0 +1,7 @@ +package org.thingsboard.rule.engine.api; + +/** + * Created by ashvayka on 19.01.18. + */ +public class TbNodeState { +} diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml new file mode 100644 index 0000000000..ee0d83f9a7 --- /dev/null +++ b/rule-engine/rule-engine-components/pom.xml @@ -0,0 +1,67 @@ + + + + 4.0.0 + + org.thingsboard + 1.4.0-SNAPSHOT + rule-engine + + org.thingsboard.rule-engine + rule-engine-components + jar + + Thingsboard Rule Engine Components + https://thingsboard.io + + + UTF-8 + ${basedir}/../.. + + + + + org.thingsboard + dao + provided + + + ch.qos.logback + logback-core + provided + + + ch.qos.logback + logback-classic + provided + + + org.thingsboard + extensions-api + provided + + + org.thingsboard.rule-engine + rule-engine-api + 1.4.0-SNAPSHOT + + + \ No newline at end of file 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 new file mode 100644 index 0000000000..962ea10c9c --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java @@ -0,0 +1,23 @@ +package org.thingsboard.rule.engine; + +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; + +/** + * Created by ashvayka on 19.01.18. + */ +public class TbNodeUtils { + + private static final ObjectMapper mapper = new ObjectMapper(); + + public static T convert(TbNodeConfiguration configuration, Class clazz) throws TbNodeException { + try { + return mapper.treeToValue(configuration.getData(), clazz); + } catch (JsonProcessingException e) { + throw new TbNodeException(e); + } + } + +} 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 new file mode 100644 index 0000000000..034ee48e58 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -0,0 +1,35 @@ +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.rule.engine.metadata.TbGetAttributesNodeConfiguration; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.dao.attributes.AttributesService; + +import java.util.List; + +/** + * Created by ashvayka on 19.01.18. + */ +@Slf4j +public class TbMsgTypeFilterNode implements TbNode { + + TbMsgTypeFilterNodeConfiguration config; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbMsgTypeFilterNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { + ctx.tellNext(msg, Boolean.toString(config.getMessageTypes().contains(msg.getType()))); + } + + @Override + public void destroy() { + + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java new file mode 100644 index 0000000000..e7662070c4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java @@ -0,0 +1,15 @@ +package org.thingsboard.rule.engine.filter; + +import lombok.Data; + +import java.util.List; + +/** + * Created by ashvayka on 19.01.18. + */ +@Data +public class TbMsgTypeFilterNodeConfiguration { + + private List messageTypes; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java new file mode 100644 index 0000000000..b623af7609 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -0,0 +1,51 @@ +package org.thingsboard.rule.engine.metadata; + +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.kv.AttributeKvEntry; +import org.thingsboard.server.dao.attributes.AttributesService; + +import java.util.List; + +/** + * Created by ashvayka on 19.01.18. + */ +@Slf4j +public class TbGetAttributesNode implements TbNode { + + TbGetAttributesNodeConfiguration config; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { + try { + //TODO: refactor this to work async and fetch attributes from cache. + AttributesService service = ctx.getAttributesService(); + fetchAttributes(msg, service, config.getClientAttributeNames(), DataConstants.CLIENT_SCOPE, "cs."); + fetchAttributes(msg, service, config.getServerAttributeNames(), DataConstants.SERVER_SCOPE, "ss."); + fetchAttributes(msg, service, config.getSharedAttributeNames(), DataConstants.SHARED_SCOPE, "shared."); + ctx.tellNext(msg); + } catch (Exception e) { + log.warn("[{}][{}] Failed to fetch attributes", msg.getOriginator(), msg.getId(), e); + throw new TbNodeException(e); + } + } + + private void fetchAttributes(TbMsg msg, AttributesService service, List attributeNames, String scope, String prefix) throws InterruptedException, java.util.concurrent.ExecutionException { + if (attributeNames != null && attributeNames.isEmpty()) { + List attributes = service.find(msg.getOriginator(), scope, attributeNames).get(); + attributes.forEach(attr -> msg.getMetaData().putValue(prefix + attr.getKey(), attr.getValueAsString())); + } + } + + @Override + public void destroy() { + + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java new file mode 100644 index 0000000000..076500c7ec --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java @@ -0,0 +1,17 @@ +package org.thingsboard.rule.engine.metadata; + +import lombok.Data; + +import java.util.List; + +/** + * Created by ashvayka on 19.01.18. + */ +@Data +public class TbGetAttributesNodeConfiguration { + + private List clientAttributeNames; + private List sharedAttributeNames; + private List serverAttributeNames; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java new file mode 100644 index 0000000000..837f33342f --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java @@ -0,0 +1,52 @@ +package org.thingsboard.rule.engine.transform; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.dao.attributes.AttributesService; + +import java.util.List; + +/** + * Created by ashvayka on 19.01.18. + */ +@Slf4j +public class TbTransformNode implements TbNode { + + TbGetAttributesNodeConfiguration config; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { + try { + //TODO: refactor this to work async and fetch attributes from cache. + AttributesService service = ctx.getAttributesService(); + fetchAttributes(msg, service, config.getClientAttributeNames(), DataConstants.CLIENT_SCOPE, "cs."); + fetchAttributes(msg, service, config.getServerAttributeNames(), DataConstants.SERVER_SCOPE, "ss."); + fetchAttributes(msg, service, config.getSharedAttributeNames(), DataConstants.SHARED_SCOPE, "shared."); + ctx.tellNext(msg); + } catch (Exception e) { + log.warn("[{}][{}] Failed to fetch attributes", msg.getOriginator(), msg.getId(), e); + throw new TbNodeException(e); + } + } + + private void fetchAttributes(TbMsg msg, AttributesService service, List attributeNames, String scope, String prefix) throws InterruptedException, java.util.concurrent.ExecutionException { + if (attributeNames != null && attributeNames.isEmpty()) { + List attributes = service.find(msg.getOriginator(), scope, attributeNames).get(); + attributes.forEach(attr -> msg.getMetaData().putValue(prefix + attr.getKey(), attr.getValueAsString())); + } + } + + @Override + public void destroy() { + + } +} From 1da32793e1d3a9968fc7ce524988d12a6e7ba1af Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 16 Feb 2018 18:24:58 +0200 Subject: [PATCH 002/171] RuleEngine Cassandra queue API --- dao/src/main/resources/cassandra/schema.cql | 52 ++++++++++++ .../thingsboard/rule/engine/api/MsqQueue.java | 29 +++++++ .../rule/engine/api/TbContext.java | 15 ++++ .../thingsboard/rule/engine/api/TbMsg.java | 15 ++++ .../rule/engine/api/TbMsgMetaData.java | 15 ++++ .../thingsboard/rule/engine/api/TbNode.java | 15 ++++ .../rule/engine/api/TbNodeConfiguration.java | 15 ++++ .../rule/engine/api/TbNodeException.java | 15 ++++ .../rule/engine/api/TbNodeState.java | 15 ++++ .../thingsboard/rule/engine/TbNodeUtils.java | 15 ++++ .../engine/filter/TbMsgTypeFilterNode.java | 15 ++++ .../TbMsgTypeFilterNodeConfiguration.java | 15 ++++ .../engine/metadata/TbGetAttributesNode.java | 15 ++++ .../TbGetAttributesNodeConfiguration.java | 15 ++++ .../engine/queue/cassandra/AckBuilder.java | 29 +++++++ .../queue/cassandra/CassandraMsqQueue.java | 82 +++++++++++++++++++ .../rule/engine/queue/cassandra/MsgAck.java | 31 +++++++ .../queue/cassandra/UnprocessedMsgFilter.java | 28 +++++++ .../cassandra/repository/AckRepository.java | 28 +++++++ .../cassandra/repository/MsgRepository.java | 29 +++++++ .../ProcessedPartitionRepository.java | 12 +++ .../rule/engine/queue/jpa/SqlMsgQueue.java | 20 +++++ .../engine/transform/TbTransformNode.java | 15 ++++ 23 files changed, 535 insertions(+) create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MsqQueue.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/jpa/SqlMsgQueue.java diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index dda80676e2..876c9f74da 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -548,3 +548,55 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS AND event_type IS NOT NULL AND event_uid IS NOT NULL PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); + +CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + ts bigint, + msg blob, + PRIMARY KEY ((node_id, cluster_hash, partition), ts) + WITH CLUSTERING ORDER BY (ts DESC) + AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200' + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true', + }; +); + +CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + ts bigint, + msg_id timeuuid, + PRIMARY KEY ((node_id, cluster_hash, partition), ts) + WITH CLUSTERING ORDER BY (ts DESC) + AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200' + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true', + }; +); + +CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + PRIMARY KEY ((node_id, cluster_hash), partition) + WITH CLUSTERING ORDER BY (partition DESC) + AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200' + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true', + }; +); \ No newline at end of file diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MsqQueue.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MsqQueue.java new file mode 100644 index 0000000000..33bd32d35b --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MsqQueue.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2017 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 com.google.common.util.concurrent.ListenableFuture; + +import java.util.UUID; + +public interface MsqQueue { + + ListenableFuture put(TbMsg msg, UUID nodeId, long clusteredHash); + + ListenableFuture ack(TbMsg msg, UUID nodeId, long clusteredHash); + + Iterable findUnprocessed(UUID nodeId, long clusteredHash); +} 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 b833a97759..eef5ff3b29 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 @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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 org.thingsboard.server.common.msg.cluster.ServerAddress; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java index 9917c6a505..f79a5eaeda 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java index 56abf2a12d..92d3b8e099 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java index 70993df47a..d5f6deb84d 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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 java.util.concurrent.ExecutionException; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java index 4ab84aaeed..540c9a6a2e 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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 com.fasterxml.jackson.databind.JsonNode; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java index 5a3744b08c..86d3d03d22 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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 com.fasterxml.jackson.core.JsonProcessingException; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java index 15f4c30774..7311b761a9 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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; /** 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 962ea10c9c..e8837be62b 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 @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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; import com.fasterxml.jackson.core.JsonProcessingException; 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 034ee48e58..4f1bef45b6 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 @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java index e7662070c4..54d005293c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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.Data; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index b623af7609..5afbb80627 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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.metadata; import lombok.extern.slf4j.Slf4j; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java index 076500c7ec..6996af687e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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.metadata; import lombok.Data; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java new file mode 100644 index 0000000000..4f1e187c97 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra; + +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.TbMsg; + +import java.util.UUID; + +@Component +public class AckBuilder { + + public MsgAck build(TbMsg msg, UUID nodeId, long clusteredHash) { + return null; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java new file mode 100644 index 0000000000..38ae62788b --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.MsqQueue; +import org.thingsboard.rule.engine.api.TbMsg; +import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; +import org.thingsboard.rule.engine.queue.cassandra.repository.MsgRepository; +import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Component +public class CassandraMsqQueue implements MsqQueue { + + @Autowired + private MsgRepository msgRepository; + + @Autowired + private AckRepository ackRepository; + + @Autowired + private AckBuilder ackBuilder; + + @Autowired + private UnprocessedMsgFilter unprocessedMsgFilter; + + @Autowired + private ProcessedPartitionRepository processedPartitionRepository; + + @Override + public ListenableFuture put(TbMsg msg, UUID nodeId, long clusteredHash) { + return msgRepository.save(msg, nodeId, clusteredHash, getPartition(msg)); + } + + @Override + public ListenableFuture ack(TbMsg msg, UUID nodeId, long clusteredHash) { + MsgAck ack = ackBuilder.build(msg, nodeId, clusteredHash); + return ackRepository.ack(ack); + } + + @Override + public Iterable findUnprocessed(UUID nodeId, long clusteredHash) { + List unprocessedMsgs = Lists.newArrayList(); + for (Long partition : findUnprocessedPartitions(nodeId, clusteredHash)) { + Iterable msgs = msgRepository.findMsgs(nodeId, clusteredHash, partition); + Iterable acks = ackRepository.findAcks(nodeId, clusteredHash, partition); + unprocessedMsgs.addAll(unprocessedMsgFilter.filter(msgs, acks)); + } + return unprocessedMsgs; + } + + private List findUnprocessedPartitions(UUID nodeId, long clusteredHash) { + Optional lastPartition = processedPartitionRepository.findLastProcessedPartition(nodeId, clusteredHash); + return Collections.emptyList(); + } + + private long getPartition(TbMsg msg) { + return Long.MIN_VALUE; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java new file mode 100644 index 0000000000..ca8d820ef7 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra; + +import lombok.Data; + +import java.util.UUID; + +@Data +public class MsgAck { + + private final UUID msgId; + private final UUID nodeId; + private final long clusteredHash; + private final long partition; + private final long ts; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java new file mode 100644 index 0000000000..99e9a92ba4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra; + +import org.thingsboard.rule.engine.api.TbMsg; + +import java.util.Collection; +import java.util.Collections; + +public class UnprocessedMsgFilter { + + public Collection filter(Iterable msgs, Iterable acks) { + return Collections.emptyList(); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java new file mode 100644 index 0000000000..3d9b55fa28 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.rule.engine.queue.cassandra.MsgAck; + +import java.util.UUID; + +public interface AckRepository { + + ListenableFuture ack(MsgAck msgAck); + + Iterable findAcks(UUID nodeId, long clusteredHash, long partition); +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java new file mode 100644 index 0000000000..57e501eba9 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.rule.engine.api.TbMsg; + +import java.util.UUID; + +public interface MsgRepository { + + ListenableFuture save(TbMsg msg, UUID nodeId, long clusteredHash, long partition); + + Iterable findMsgs(UUID nodeId, long clusteredHash, long partition); + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java new file mode 100644 index 0000000000..bc29050dad --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java @@ -0,0 +1,12 @@ +package org.thingsboard.rule.engine.queue.cassandra.repository; + +import java.util.Optional; +import java.util.UUID; + +public interface ProcessedPartitionRepository { + + void partitionProcessed(UUID nodeId, long clusteredHash, long partition); + + Optional findLastProcessedPartition(UUID nodeId, long clusteredHash); + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/jpa/SqlMsgQueue.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/jpa/SqlMsgQueue.java new file mode 100644 index 0000000000..d1bf73cbf4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/jpa/SqlMsgQueue.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2017 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.queue.jpa; + +//@todo-vp: implement +public class SqlMsgQueue { +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java index 837f33342f..c9d34fa394 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2017 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.transform; import lombok.extern.slf4j.Slf4j; From 3b5a4941ff6161acfc2fba95fc6a91af059231cc Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 2 Mar 2018 13:27:59 +0200 Subject: [PATCH 003/171] Cassandra MsqQueue initial implementation --- .../src/main/resources/thingsboard.yml | 3 + dao/src/main/resources/cassandra/schema.cql | 65 ++++----- .../rule/engine/api/TbMsgMetaData.java | 3 +- rule-engine/rule-engine-components/pom.xml | 102 +++++++++++++ .../queue/cassandra/CassandraMsqQueue.java | 53 ++++--- .../rule/engine/queue/cassandra/MsgAck.java | 6 +- .../queue/cassandra/QueuePartitioner.java | 83 +++++++++++ .../queue/cassandra/UnprocessedMsgFilter.java | 12 +- .../cassandra/repository/AckRepository.java | 3 +- .../cassandra/repository/MsgRepository.java | 5 +- .../ProcessedPartitionRepository.java | 19 ++- .../impl/CassandraAckRepository.java | 64 +++++++++ .../impl/CassandraMsgRepository.java | 109 ++++++++++++++ ...CassandraProcessedPartitionRepository.java | 60 ++++++++ .../impl/SimpleAbstractCassandraDao.java | 77 ++++++++++ .../rule/engine/tool/QueueBenchmark.java | 136 ++++++++++++++++++ .../AckBuilder.java => proto/msgqueue.proto} | 25 ++-- .../cassandra/CassandraMsqQueueTest.java | 48 +++++++ .../queue/cassandra/QueuePartitionerTest.java | 81 +++++++++++ .../cassandra/UnprocessedMsgFilterTest.java | 45 ++++++ .../impl/CassandraAckRepositoryTest.java | 81 +++++++++++ .../impl/CassandraMsgRepositoryTest.java | 82 +++++++++++ ...andraProcessedPartitionRepositoryTest.java | 80 +++++++++++ .../impl/SimpleAbstractCassandraDaoTest.java | 30 ++++ .../test/resources/cassandra/system-test.cql | 75 ++++++++++ 25 files changed, 1267 insertions(+), 80 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitioner.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepository.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepository.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDao.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/tool/QueueBenchmark.java rename rule-engine/rule-engine-components/src/main/{java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java => proto/msgqueue.proto} (59%) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueueTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitionerTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilterTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDaoTest.java create mode 100644 rule-engine/rule-engine-components/src/test/resources/cassandra/system-test.cql diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6bb3917652..7eef95aad5 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -251,6 +251,9 @@ spring: username: "${SPRING_DATASOURCE_USERNAME:sa}" password: "${SPRING_DATASOURCE_PASSWORD:}" +rule: + queue: + msg_partitioning: "${QUEUE_MSG_PARTITIONING:HOURS}" # PostgreSQL DAO Configuration #spring: diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index 876c9f74da..05e6cfed62 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -555,48 +555,45 @@ CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( partition bigint, ts bigint, msg blob, - PRIMARY KEY ((node_id, cluster_hash, partition), ts) - WITH CLUSTERING ORDER BY (ts DESC) - AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200' - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true', - }; -); + PRIMARY KEY ((node_id, clustered_hash, partition), ts)) +WITH CLUSTERING ORDER BY (ts DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( node_id timeuuid, clustered_hash bigint, partition bigint, - ts bigint, msg_id timeuuid, - PRIMARY KEY ((node_id, cluster_hash, partition), ts) - WITH CLUSTERING ORDER BY (ts DESC) - AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200' - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true', - }; -); + PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) +WITH CLUSTERING ORDER BY (msg_id DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( node_id timeuuid, clustered_hash bigint, partition bigint, - PRIMARY KEY ((node_id, cluster_hash), partition) - WITH CLUSTERING ORDER BY (partition DESC) - AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200' - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true', - }; -); \ No newline at end of file + PRIMARY KEY ((node_id, clustered_hash), partition)) +WITH CLUSTERING ORDER BY (partition DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java index 92d3b8e099..8dd4840a84 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java @@ -19,6 +19,7 @@ import lombok.Data; import java.io.Serializable; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Created by ashvayka on 13.01.18. @@ -26,7 +27,7 @@ import java.util.Map; @Data public final class TbMsgMetaData implements Serializable { - private Map data; + private Map data = new ConcurrentHashMap<>(); public String getValue(String key) { return data.get(key); diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index ee0d83f9a7..5ee1d94358 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -63,5 +63,107 @@ rule-engine-api 1.4.0-SNAPSHOT + + com.google.protobuf + protobuf-java + provided + + + com.google.guava + guava + + + com.datastax.cassandra + cassandra-driver-core + + + com.datastax.cassandra + cassandra-driver-mapping + + + com.datastax.cassandra + cassandra-driver-extras + + + + junit + junit + ${junit.version} + test + + + org.cassandraunit + cassandra-unit + + + org.slf4j + slf4j-log4j12 + + + test + + + org.mockito + mockito-all + test + + + org.junit.jupiter + junit-jupiter-api + RELEASE + + + + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + org.codehaus.mojo + build-helper-maven-plugin + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + org.thingsboard.rule.engine.tool.QueueBenchmark + boot + ZIP + true + true + + + + + + + + + + + repackage + + + + + + + + \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java index 38ae62788b..4839761c67 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java @@ -15,68 +15,65 @@ */ package org.thingsboard.rule.engine.queue.cassandra; +import com.datastax.driver.core.utils.UUIDs; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MsqQueue; import org.thingsboard.rule.engine.api.TbMsg; import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; import org.thingsboard.rule.engine.queue.cassandra.repository.MsgRepository; -import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.common.data.UUIDConverter; -import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.UUID; @Component +@Slf4j public class CassandraMsqQueue implements MsqQueue { - @Autowired - private MsgRepository msgRepository; + private final MsgRepository msgRepository; + private final AckRepository ackRepository; + private final UnprocessedMsgFilter unprocessedMsgFilter; + private final QueuePartitioner queuePartitioner; - @Autowired - private AckRepository ackRepository; - - @Autowired - private AckBuilder ackBuilder; - - @Autowired - private UnprocessedMsgFilter unprocessedMsgFilter; + public CassandraMsqQueue(MsgRepository msgRepository, AckRepository ackRepository, + UnprocessedMsgFilter unprocessedMsgFilter, QueuePartitioner queuePartitioner) { + this.msgRepository = msgRepository; + this.ackRepository = ackRepository; + this.unprocessedMsgFilter = unprocessedMsgFilter; + this.queuePartitioner = queuePartitioner; + } - @Autowired - private ProcessedPartitionRepository processedPartitionRepository; @Override public ListenableFuture put(TbMsg msg, UUID nodeId, long clusteredHash) { - return msgRepository.save(msg, nodeId, clusteredHash, getPartition(msg)); + long msgTime = getMsgTime(msg); + long partition = queuePartitioner.getPartition(msgTime); + return msgRepository.save(msg, nodeId, clusteredHash, partition, msgTime); } @Override public ListenableFuture ack(TbMsg msg, UUID nodeId, long clusteredHash) { - MsgAck ack = ackBuilder.build(msg, nodeId, clusteredHash); + long partition = queuePartitioner.getPartition(getMsgTime(msg)); + MsgAck ack = new MsgAck(msg.getId(), nodeId, clusteredHash, partition); return ackRepository.ack(ack); } @Override public Iterable findUnprocessed(UUID nodeId, long clusteredHash) { List unprocessedMsgs = Lists.newArrayList(); - for (Long partition : findUnprocessedPartitions(nodeId, clusteredHash)) { - Iterable msgs = msgRepository.findMsgs(nodeId, clusteredHash, partition); - Iterable acks = ackRepository.findAcks(nodeId, clusteredHash, partition); + for (Long partition : queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash)) { + List msgs = msgRepository.findMsgs(nodeId, clusteredHash, partition); + List acks = ackRepository.findAcks(nodeId, clusteredHash, partition); unprocessedMsgs.addAll(unprocessedMsgFilter.filter(msgs, acks)); } return unprocessedMsgs; } - private List findUnprocessedPartitions(UUID nodeId, long clusteredHash) { - Optional lastPartition = processedPartitionRepository.findLastProcessedPartition(nodeId, clusteredHash); - return Collections.emptyList(); - } - - private long getPartition(TbMsg msg) { - return Long.MIN_VALUE; + private long getMsgTime(TbMsg msg) { + return UUIDs.unixTimestamp(msg.getId()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java index ca8d820ef7..c6885ad41b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java @@ -15,17 +15,21 @@ */ package org.thingsboard.rule.engine.queue.cassandra; +import com.datastax.driver.core.utils.UUIDs; import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.rule.engine.api.TbMsg; +import org.thingsboard.server.common.data.UUIDConverter; import java.util.UUID; @Data +@EqualsAndHashCode public class MsgAck { private final UUID msgId; private final UUID nodeId; private final long clusteredHash; private final long partition; - private final long ts; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitioner.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitioner.java new file mode 100644 index 0000000000..9a7886f54c --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitioner.java @@ -0,0 +1,83 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra; + +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.dao.timeseries.TsPartitionDate; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Component +@Slf4j +public class QueuePartitioner { + + private ProcessedPartitionRepository processedPartitionRepository; + + private final TsPartitionDate tsFormat; + private Clock clock = Clock.systemUTC(); + + public QueuePartitioner(@Value("${rule.queue.msg_partitioning}") String partitioning, + ProcessedPartitionRepository processedPartitionRepository) { + this.processedPartitionRepository = processedPartitionRepository; + Optional partition = TsPartitionDate.parse(partitioning); + if (partition.isPresent()) { + tsFormat = partition.get(); + } else { + log.warn("Incorrect configuration of partitioning {}", "MINUTES"); + throw new RuntimeException("Failed to parse partitioning property: " + "MINUTES" + "!"); + } + } + + public long getPartition(long ts) { + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); + return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); + } + + public List findUnprocessedPartitions(UUID nodeId, long clusteredHash) { + Optional lastPartitionOption = processedPartitionRepository.findLastProcessedPartition(nodeId, clusteredHash); + long lastPartition = lastPartitionOption.orElse(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 100); + List unprocessedPartitions = Lists.newArrayList(); + + LocalDateTime current = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastPartition), ZoneOffset.UTC); + LocalDateTime end = LocalDateTime.ofInstant(Instant.now(clock), ZoneOffset.UTC) + .plus(1L, tsFormat.getTruncateUnit()); + + while (current.isBefore(end)) { + current = current.plus(1L, tsFormat.getTruncateUnit()); + unprocessedPartitions.add(tsFormat.truncatedTo(current).toInstant(ZoneOffset.UTC).toEpochMilli()); + } + + return unprocessedPartitions; + } + + public void setClock(Clock clock) { + this.clock = clock; + } + + public void checkProcessedPartitions() { + //todo-vp: we need to implement this + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java index 99e9a92ba4..e114a8507d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java @@ -15,14 +15,20 @@ */ package org.thingsboard.rule.engine.queue.cassandra; +import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.TbMsg; import java.util.Collection; -import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +@Component public class UnprocessedMsgFilter { - public Collection filter(Iterable msgs, Iterable acks) { - return Collections.emptyList(); + public Collection filter(List msgs, List acks) { + Set processedIds = acks.stream().map(MsgAck::getMsgId).collect(Collectors.toSet()); + return msgs.stream().filter(i -> !processedIds.contains(i.getId())).collect(Collectors.toList()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java index 3d9b55fa28..40c0416957 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java @@ -18,11 +18,12 @@ package org.thingsboard.rule.engine.queue.cassandra.repository; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.queue.cassandra.MsgAck; +import java.util.List; import java.util.UUID; public interface AckRepository { ListenableFuture ack(MsgAck msgAck); - Iterable findAcks(UUID nodeId, long clusteredHash, long partition); + List findAcks(UUID nodeId, long clusteredHash, long partition); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java index 57e501eba9..5d34d84c3b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java @@ -18,12 +18,13 @@ package org.thingsboard.rule.engine.queue.cassandra.repository; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.api.TbMsg; +import java.util.List; import java.util.UUID; public interface MsgRepository { - ListenableFuture save(TbMsg msg, UUID nodeId, long clusteredHash, long partition); + ListenableFuture save(TbMsg msg, UUID nodeId, long clusteredHash, long partition, long msgTs); - Iterable findMsgs(UUID nodeId, long clusteredHash, long partition); + List findMsgs(UUID nodeId, long clusteredHash, long partition); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java index bc29050dad..807c0017c6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java @@ -1,11 +1,28 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository; +import com.google.common.util.concurrent.ListenableFuture; + import java.util.Optional; import java.util.UUID; public interface ProcessedPartitionRepository { - void partitionProcessed(UUID nodeId, long clusteredHash, long partition); + ListenableFuture partitionProcessed(UUID nodeId, long clusteredHash, long partition); Optional findLastProcessedPartition(UUID nodeId, long clusteredHash); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepository.java new file mode 100644 index 0000000000..57dc79c49b --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepository.java @@ -0,0 +1,64 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository.impl; + +import com.datastax.driver.core.*; +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.queue.cassandra.MsgAck; +import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Component +public class CassandraAckRepository extends SimpleAbstractCassandraDao implements AckRepository { + + private final int ackQueueTtl; + + public CassandraAckRepository(Session session, int ackQueueTtl) { + super(session); + this.ackQueueTtl = ackQueueTtl; + } + + @Override + public ListenableFuture ack(MsgAck msgAck) { + String insert = "INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) VALUES (?, ?, ?, ?) USING TTL ?"; + PreparedStatement statement = prepare(insert); + BoundStatement boundStatement = statement.bind(msgAck.getNodeId(), msgAck.getClusteredHash(), + msgAck.getPartition(), msgAck.getMsgId(), ackQueueTtl); + ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); + return Futures.transform(resultSetFuture, (Function) input -> null); + } + + @Override + public List findAcks(UUID nodeId, long clusteredHash, long partition) { + String select = "SELECT msg_id FROM msg_ack_queue WHERE " + + "node_id = ? AND clustered_hash = ? AND partition = ?"; + PreparedStatement statement = prepare(select); + BoundStatement boundStatement = statement.bind(nodeId, clusteredHash, partition); + ResultSet rows = executeRead(boundStatement); + List msgs = new ArrayList<>(); + for (Row row : rows) { + msgs.add(new MsgAck(row.getUUID("msg_id"), nodeId, clusteredHash, partition)); + } + return msgs; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepository.java new file mode 100644 index 0000000000..bd3ef21c99 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepository.java @@ -0,0 +1,109 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository.impl; + +import com.datastax.driver.core.*; +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.TbMsg; +import org.thingsboard.rule.engine.api.TbMsgMetaData; +import org.thingsboard.rule.engine.queue.cassandra.repository.MsgRepository; +import org.thingsboard.rule.engine.queue.cassandra.repository.gen.MsgQueueProtos; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Component +public class CassandraMsgRepository extends SimpleAbstractCassandraDao implements MsgRepository { + + private final int msqQueueTtl; + + + public CassandraMsgRepository(Session session, int msqQueueTtl) { + super(session); + this.msqQueueTtl = msqQueueTtl; + } + + @Override + public ListenableFuture save(TbMsg msg, UUID nodeId, long clusteredHash, long partition, long msgTs) { + String insert = "INSERT INTO msg_queue (node_id, clustered_hash, partition, ts, msg) VALUES (?, ?, ?, ?, ?) USING TTL ?"; + PreparedStatement statement = prepare(insert); + BoundStatement boundStatement = statement.bind(nodeId, clusteredHash, partition, msgTs, toBytes(msg), msqQueueTtl); + ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); + return Futures.transform(resultSetFuture, (Function) input -> null); + } + + @Override + public List findMsgs(UUID nodeId, long clusteredHash, long partition) { + String select = "SELECT node_id, clustered_hash, partition, ts, msg FROM msg_queue WHERE " + + "node_id = ? AND clustered_hash = ? AND partition = ?"; + PreparedStatement statement = prepare(select); + BoundStatement boundStatement = statement.bind(nodeId, clusteredHash, partition); + ResultSet rows = executeRead(boundStatement); + List msgs = new ArrayList<>(); + for (Row row : rows) { + msgs.add(fromBytes(row.getBytes("msg"))); + } + return msgs; + } + + private ByteBuffer toBytes(TbMsg msg) { + MsgQueueProtos.TbMsgProto.Builder builder = MsgQueueProtos.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()); + } + + if (msg.getMetaData() != null) { + MsgQueueProtos.TbMsgProto.TbMsgMetaDataProto.Builder metadataBuilder = MsgQueueProtos.TbMsgProto.TbMsgMetaDataProto.newBuilder(); + metadataBuilder.putAllData(msg.getMetaData().getData()); + builder.addMetaData(metadataBuilder.build()); + } + + builder.setData(ByteString.copyFrom(msg.getData())); + byte[] bytes = builder.build().toByteArray(); + return ByteBuffer.wrap(bytes); + } + + private TbMsg fromBytes(ByteBuffer buffer) { + try { + MsgQueueProtos.TbMsgProto proto = MsgQueueProtos.TbMsgProto.parseFrom(buffer.array()); + TbMsgMetaData metaData = new TbMsgMetaData(); + if (proto.getMetaDataCount() > 0) { + metaData.setData(proto.getMetaData(0).getDataMap()); + } + + EntityId entityId = null; + if (proto.getEntityId() != null) { + entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()); + } + + return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, proto.getData().toByteArray()); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException("Could not parse protobuf for TbMsg", e); + } + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java new file mode 100644 index 0000000000..7fc15d8207 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository.impl; + +import com.datastax.driver.core.*; +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; + +import java.util.Optional; +import java.util.UUID; + +@Component +public class CassandraProcessedPartitionRepository extends SimpleAbstractCassandraDao implements ProcessedPartitionRepository { + + private final int repositoryTtl; + + public CassandraProcessedPartitionRepository(Session session, int repositoryTtl) { + super(session); + this.repositoryTtl = repositoryTtl; + } + + @Override + public ListenableFuture partitionProcessed(UUID nodeId, long clusteredHash, long partition) { + String insert = "INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) VALUES (?, ?, ?) USING TTL ?"; + PreparedStatement prepared = prepare(insert); + BoundStatement boundStatement = prepared.bind(nodeId, clusteredHash, partition, repositoryTtl); + ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); + return Futures.transform(resultSetFuture, (Function) input -> null); + } + + @Override + public Optional findLastProcessedPartition(UUID nodeId, long clusteredHash) { + String select = "SELECT partition FROM processed_msg_partitions WHERE " + + "node_id = ? AND clustered_hash = ?"; + PreparedStatement prepared = prepare(select); + BoundStatement boundStatement = prepared.bind(nodeId, clusteredHash); + Row row = executeRead(boundStatement).one(); + if (row == null) { + return Optional.empty(); + } + + return Optional.of(row.getLong("partition")); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDao.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDao.java new file mode 100644 index 0000000000..8f01c188ea --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDao.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository.impl; + +import com.datastax.driver.core.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@Slf4j +public abstract class SimpleAbstractCassandraDao { + + private ConsistencyLevel defaultReadLevel = ConsistencyLevel.QUORUM; + private ConsistencyLevel defaultWriteLevel = ConsistencyLevel.QUORUM; + private Session session; + private Map preparedStatementMap = new ConcurrentHashMap<>(); + + public SimpleAbstractCassandraDao(Session session) { + this.session = session; + } + + protected Session getSession() { + return session; + } + + protected ResultSet executeRead(Statement statement) { + return execute(statement, defaultReadLevel); + } + + protected ResultSet executeWrite(Statement statement) { + return execute(statement, defaultWriteLevel); + } + + protected ResultSetFuture executeAsyncRead(Statement statement) { + return executeAsync(statement, defaultReadLevel); + } + + protected ResultSetFuture executeAsyncWrite(Statement statement) { + return executeAsync(statement, defaultWriteLevel); + } + + protected PreparedStatement prepare(String query) { + return preparedStatementMap.computeIfAbsent(query, i -> getSession().prepare(i)); + } + + private ResultSet execute(Statement statement, ConsistencyLevel level) { + log.debug("Execute cassandra statement {}", statement); + if (statement.getConsistencyLevel() == null) { + statement.setConsistencyLevel(level); + } + return getSession().execute(statement); + } + + private ResultSetFuture executeAsync(Statement statement, ConsistencyLevel level) { + log.debug("Execute cassandra async statement {}", statement); + if (statement.getConsistencyLevel() == null) { + statement.setConsistencyLevel(level); + } + return getSession().executeAsync(statement); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/tool/QueueBenchmark.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/tool/QueueBenchmark.java new file mode 100644 index 0000000000..58510d721c --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/tool/QueueBenchmark.java @@ -0,0 +1,136 @@ +package org.thingsboard.rule.engine.tool; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.HostDistance; +import com.datastax.driver.core.PoolingOptions; +import com.datastax.driver.core.Session; +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 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.rule.engine.api.MsqQueue; +import org.thingsboard.rule.engine.api.TbMsg; +import org.thingsboard.rule.engine.api.TbMsgMetaData; + +import javax.annotation.Nullable; +import java.net.InetSocketAddress; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +@SpringBootApplication +@EnableAutoConfiguration +@ComponentScan({"org.thingsboard.rule.engine"}) +//@PropertySource("classpath:processing-pipeline.properties") +@Slf4j +public class QueueBenchmark implements CommandLineRunner { + + public static void main(String[] args) { + try { + SpringApplication.run(QueueBenchmark.class, args); + } catch (Throwable th) { + th.printStackTrace(); + System.exit(0); + } + } + + @Autowired + private MsqQueue msqQueue; + + @Override + public void run(String... strings) throws Exception { + System.out.println("It works + " + msqQueue); + + + long start = System.currentTimeMillis(); + int msgCount = 10000000; + AtomicLong count = new AtomicLong(0); + ExecutorService service = Executors.newFixedThreadPool(100); + + CountDownLatch latch = new CountDownLatch(msgCount); + for (int i = 0; i < msgCount; i++) { + service.submit(() -> { + boolean isFinished = false; + while (!isFinished) { + try { + TbMsg msg = randomMsg(); + UUID nodeId = UUIDs.timeBased(); + ListenableFuture put = msqQueue.put(msg, nodeId, 100L); +// ListenableFuture put = msqQueue.ack(msg, nodeId, 100L); + Futures.addCallback(put, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + latch.countDown(); + } + + @Override + public void onFailure(Throwable t) { +// t.printStackTrace(); + System.out.println("onFailure, because:" + t.getMessage()); + latch.countDown(); + } + }); + isFinished = true; + } catch (Throwable th) { +// th.printStackTrace(); + System.out.println("Repeat query, because:" + th.getMessage()); +// latch.countDown(); + } + } + }); + } + + long prev = 0L; + while (latch.getCount() != 0) { + TimeUnit.SECONDS.sleep(1); + long curr = latch.getCount(); + long rps = prev - curr; + prev = curr; + System.out.println("rps = " + rps); + } + + long end = System.currentTimeMillis(); + System.out.println("final rps = " + (msgCount / (end - start) * 1000)); + + System.out.println("Finished"); + + } + + private TbMsg randomMsg() { + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("key", "value"); + String dataStr = "someContent"; + return new TbMsg(UUIDs.timeBased(), "type", null, metaData, dataStr.getBytes()); + } + + @Bean + public Session session() { + Cluster thingsboard = Cluster.builder() + .addContactPointsWithPorts(new InetSocketAddress("127.0.0.1", 9042)) + .withClusterName("thingsboard") +// .withSocketOptions(socketOpts.getOpts()) + .withPoolingOptions(new PoolingOptions() + .setMaxRequestsPerConnection(HostDistance.LOCAL, 32768) + .setMaxRequestsPerConnection(HostDistance.REMOTE, 32768)).build(); + + Session session = thingsboard.connect("thingsboard"); + return session; + } + + @Bean + public int defaultTtl() { + return 6000; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java b/rule-engine/rule-engine-components/src/main/proto/msgqueue.proto similarity index 59% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java rename to rule-engine/rule-engine-components/src/main/proto/msgqueue.proto index 4f1e187c97..1105aeb664 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java +++ b/rule-engine/rule-engine-components/src/main/proto/msgqueue.proto @@ -13,17 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra; +syntax = "proto3"; +package msgqueue; -import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.api.TbMsg; +option java_package = "org.thingsboard.rule.engine.queue.cassandra.repository.gen"; +option java_outer_classname = "MsgQueueProtos"; -import java.util.UUID; -@Component -public class AckBuilder { +message TbMsgProto { + string id = 1; + string type = 2; + string entityType = 3; + string entityId = 4; - public MsgAck build(TbMsg msg, UUID nodeId, long clusteredHash) { - return null; + message TbMsgMetaDataProto { + map data = 1; } -} + + repeated TbMsgMetaDataProto metaData = 5; + + bytes data = 6; +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueueTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueueTest.java new file mode 100644 index 0000000000..8ce3355b09 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueueTest.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; +import org.thingsboard.rule.engine.queue.cassandra.repository.MsgRepository; + +public class CassandraMsqQueueTest { + + private CassandraMsqQueue msqQueue; + + @Mock + private MsgRepository msgRepository; + @Mock + private AckRepository ackRepository; + @Mock + private UnprocessedMsgFilter unprocessedMsgFilter; + @Mock + private QueuePartitioner queuePartitioner; + + @Before + public void init() { + msqQueue = new CassandraMsqQueue(msgRepository, ackRepository, unprocessedMsgFilter, queuePartitioner); + } + + @Test + public void msgCanBeSaved() { +// todo-vp: implement + } + + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitionerTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitionerTest.java new file mode 100644 index 0000000000..a71a737f74 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitionerTest.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra; + + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class QueuePartitionerTest { + + private QueuePartitioner queuePartitioner; + + @Mock + private ProcessedPartitionRepository partitionRepo; + + private Instant startInstant; + private Instant endInstant; + + @Before + public void init() { + queuePartitioner = new QueuePartitioner("MINUTES", partitionRepo); + startInstant = Instant.now(); + endInstant = startInstant.plus(2, ChronoUnit.MINUTES); + queuePartitioner.setClock(Clock.fixed(endInstant, ZoneOffset.UTC)); + } + + @Test + public void partitionCalculated() { + long time = 1519390191425L; + long partition = queuePartitioner.getPartition(time); + assertEquals(1519390140000L, partition); + } + + @Test + public void unprocessedPartitionsReturned() { + UUID nodeId = UUID.randomUUID(); + long clusteredHash = 101L; + when(partitionRepo.findLastProcessedPartition(nodeId, clusteredHash)).thenReturn(Optional.of(startInstant.toEpochMilli())); + List actual = queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash); + assertEquals(3, actual.size()); + } + + @Test + public void defaultShiftUsedIfNoPartitionWasProcessed() { + UUID nodeId = UUID.randomUUID(); + long clusteredHash = 101L; + when(partitionRepo.findLastProcessedPartition(nodeId, clusteredHash)).thenReturn(Optional.empty()); + List actual = queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash); + assertEquals(1011, actual.size()); + } + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilterTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilterTest.java new file mode 100644 index 0000000000..dec8e35b2b --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilterTest.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra; + +import com.google.common.collect.Lists; +import org.junit.Test; +import org.thingsboard.rule.engine.api.TbMsg; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UnprocessedMsgFilterTest { + + private UnprocessedMsgFilter msgFilter = new UnprocessedMsgFilter(); + + @Test + public void acknowledgedMsgsAreFilteredOut() { + UUID id1 = UUID.randomUUID(); + UUID id2 = UUID.randomUUID(); + TbMsg msg1 = new TbMsg(id1, "T", null, null, null); + TbMsg msg2 = new TbMsg(id2, "T", null, null, null); + List msgs = Lists.newArrayList(msg1, msg2); + List acks = Lists.newArrayList(new MsgAck(id2, UUID.randomUUID(), 1L, 1L)); + Collection actual = msgFilter.filter(msgs, acks); + assertEquals(1, actual.size()); + assertEquals(msg1, actual.iterator().next()); + } + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java new file mode 100644 index 0000000000..38b7b9de8f --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository.impl; + +import com.datastax.driver.core.utils.UUIDs; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.rule.engine.queue.cassandra.MsgAck; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CassandraAckRepositoryTest extends SimpleAbstractCassandraDaoTest { + + private CassandraAckRepository ackRepository; + + @Before + public void init() { + ackRepository = new CassandraAckRepository(cassandraUnit.session, 1); + } + + @Test + public void acksInPartitionCouldBeFound() { + UUID nodeId = UUID.fromString("055eee50-1883-11e8-b380-65b5d5335ba9"); + + List extectedAcks = Lists.newArrayList( + new MsgAck(UUID.fromString("bebaeb60-1888-11e8-bf21-65b5d5335ba9"), nodeId, 101L, 300L), + new MsgAck(UUID.fromString("12baeb60-1888-11e8-bf21-65b5d5335ba9"), nodeId, 101L, 300L) + ); + + List actualAcks = ackRepository.findAcks(nodeId, 101L, 300L); + assertEquals(extectedAcks, actualAcks); + } + + @Test + public void ackCanBeSavedAndRead() throws ExecutionException, InterruptedException { + UUID msgId = UUIDs.timeBased(); + UUID nodeId = UUIDs.timeBased(); + MsgAck ack = new MsgAck(msgId, nodeId, 10L, 20L); + ListenableFuture future = ackRepository.ack(ack); + future.get(); + List actualAcks = ackRepository.findAcks(nodeId, 10L, 20L); + assertEquals(1, actualAcks.size()); + assertEquals(ack, actualAcks.get(0)); + } + + @Test + public void expiredAcksAreNotReturned() throws ExecutionException, InterruptedException { + UUID msgId = UUIDs.timeBased(); + UUID nodeId = UUIDs.timeBased(); + MsgAck ack = new MsgAck(msgId, nodeId, 30L, 40L); + ListenableFuture future = ackRepository.ack(ack); + future.get(); + List actualAcks = ackRepository.findAcks(nodeId, 30L, 40L); + assertEquals(1, actualAcks.size()); + TimeUnit.SECONDS.sleep(2); + assertTrue(ackRepository.findAcks(nodeId, 30L, 40L).isEmpty()); + } + + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java new file mode 100644 index 0000000000..a0827fc5ac --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository.impl; + +//import static org.junit.jupiter.api.Assertions.*; + +import com.datastax.driver.core.utils.UUIDs; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.rule.engine.api.TbMsg; +import org.thingsboard.rule.engine.api.TbMsgMetaData; +import org.thingsboard.server.common.data.id.DeviceId; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CassandraMsgRepositoryTest extends SimpleAbstractCassandraDaoTest { + + private CassandraMsgRepository msgRepository; + + @Before + public void init() { + msgRepository = new CassandraMsgRepository(cassandraUnit.session, 1); + } + + @Test + public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException { + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]); + UUID nodeId = UUIDs.timeBased(); + ListenableFuture future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); + future.get(); + List msgs = msgRepository.findMsgs(nodeId, 1L, 1L); + assertEquals(1, msgs.size()); + } + + @Test + public void expiredMsgsAreNotReturned() throws ExecutionException, InterruptedException { + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]); + UUID nodeId = UUIDs.timeBased(); + ListenableFuture future = msgRepository.save(msg, nodeId, 2L, 2L, 2L); + future.get(); + List msgs = msgRepository.findMsgs(nodeId, 2L, 2L); + assertEquals(1, msgs.size()); + TimeUnit.SECONDS.sleep(2); + assertTrue(msgRepository.findMsgs(nodeId, 2L, 2L).isEmpty()); + } + + @Test + public void protoBufConverterWorkAsExpected() throws ExecutionException, InterruptedException { + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("key", "value"); + String dataStr = "someContent"; + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, dataStr.getBytes()); + UUID nodeId = UUIDs.timeBased(); + ListenableFuture future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); + future.get(); + List msgs = msgRepository.findMsgs(nodeId, 1L, 1L); + assertEquals(1, msgs.size()); + assertEquals(msg, msgs.get(0)); + } + + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java new file mode 100644 index 0000000000..a731452f24 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java @@ -0,0 +1,80 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository.impl; + +import com.datastax.driver.core.utils.UUIDs; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class CassandraProcessedPartitionRepositoryTest extends SimpleAbstractCassandraDaoTest { + + private CassandraProcessedPartitionRepository partitionRepository; + + @Before + public void init() { + partitionRepository = new CassandraProcessedPartitionRepository(cassandraUnit.session, 1); + } + + @Test + public void lastProcessedPartitionCouldBeFound() { + UUID nodeId = UUID.fromString("055eee50-1883-11e8-b380-65b5d5335ba9"); + Optional lastProcessedPartition = partitionRepository.findLastProcessedPartition(nodeId, 101L); + assertTrue(lastProcessedPartition.isPresent()); + assertEquals((Long) 777L, lastProcessedPartition.get()); + } + + @Test + public void highestProcessedPartitionReturned() throws ExecutionException, InterruptedException { + UUID nodeId = UUIDs.timeBased(); + ListenableFuture future1 = partitionRepository.partitionProcessed(nodeId, 303L, 100L); + ListenableFuture future2 = partitionRepository.partitionProcessed(nodeId, 303L, 200L); + ListenableFuture future3 = partitionRepository.partitionProcessed(nodeId, 303L, 10L); + ListenableFuture> allFutures = Futures.allAsList(future1, future2, future3); + allFutures.get(); + Optional actual = partitionRepository.findLastProcessedPartition(nodeId, 303L); + assertTrue(actual.isPresent()); + assertEquals((Long) 200L, actual.get()); + } + + @Test + public void expiredPartitionsAreNotReturned() throws ExecutionException, InterruptedException { + UUID nodeId = UUIDs.timeBased(); + ListenableFuture future = partitionRepository.partitionProcessed(nodeId, 404L, 10L); + future.get(); + Optional actual = partitionRepository.findLastProcessedPartition(nodeId, 404L); + assertEquals((Long) 10L, actual.get()); + TimeUnit.SECONDS.sleep(2); + assertFalse(partitionRepository.findLastProcessedPartition(nodeId, 404L).isPresent()); + } + + @Test + public void ifNoPartitionsWereProcessedEmptyResultReturned() { + UUID nodeId = UUIDs.timeBased(); + Optional actual = partitionRepository.findLastProcessedPartition(nodeId, 505L); + assertFalse(actual.isPresent()); + } + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDaoTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDaoTest.java new file mode 100644 index 0000000000..1f248e8c90 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDaoTest.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2017 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.queue.cassandra.repository.impl; + +import org.cassandraunit.CassandraCQLUnit; +import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; +import org.junit.ClassRule; + + +public abstract class SimpleAbstractCassandraDaoTest { + + @ClassRule + public static CassandraCQLUnit cassandraUnit = new CassandraCQLUnit( + new ClassPathCQLDataSet("cassandra/system-test.cql", "thingsboard")); + + +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/resources/cassandra/system-test.cql b/rule-engine/rule-engine-components/src/test/resources/cassandra/system-test.cql new file mode 100644 index 0000000000..7ba9f26d79 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/resources/cassandra/system-test.cql @@ -0,0 +1,75 @@ +CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + ts bigint, + msg blob, + PRIMARY KEY ((node_id, clustered_hash, partition), ts)) +WITH CLUSTERING ORDER BY (ts DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + + +CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + msg_id timeuuid, + PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) +WITH CLUSTERING ORDER BY (msg_id DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + +CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + PRIMARY KEY ((node_id, clustered_hash), partition)) +WITH CLUSTERING ORDER BY (partition DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + + + +-- msg_queue dataset + +INSERT INTO thingsboard.msg_queue (node_id, clustered_hash, partition, ts, msg) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 201, null); +INSERT INTO thingsboard.msg_queue (node_id, clustered_hash, partition, ts, msg) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 202, null); +INSERT INTO thingsboard.msg_queue (node_id, clustered_hash, partition, ts, msg) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, 301, null); + +-- ack_queue dataset +INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, bebaeb60-1888-11e8-bf21-65b5d5335ba9); +INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, 12baeb60-1888-11e8-bf21-65b5d5335ba9); + INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 32baeb60-1888-11e8-bf21-65b5d5335ba9); + +-- processed partition dataset +INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 100); +INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 777); +INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 202, 200); \ No newline at end of file From 9764f27a1085c4d6f69d452e86eba050a61f6fba Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Mar 2018 12:28:16 +0200 Subject: [PATCH 004/171] Rule Chain and Rule Node DAO. --- .../main/data/upgrade/1.5.0/schema_update.cql | 93 ++++++++++ .../main/data/upgrade/1.5.0/schema_update.sql | 35 ++++ .../install/ThingsboardInstallService.java | 7 +- .../CassandraDatabaseUpgradeService.java | 8 + .../install/SqlDatabaseUpgradeService.java | 9 + .../server/common/data/EntityType.java | 2 +- .../common/data/id/EntityIdFactory.java | 4 + .../server/common/data/id/RuleChainId.java | 37 ++++ .../server/common/data/id/RuleNodeId.java | 37 ++++ .../server/common/data/rule/RuleChain.java | 79 +++++++++ .../server/common/data/rule/RuleNode.java | 74 ++++++++ .../server/dao/model/ModelConstants.java | 20 +++ .../dao/model/nosql/RuleChainEntity.java | 165 ++++++++++++++++++ .../dao/model/nosql/RuleNodeEntity.java | 134 ++++++++++++++ .../server/dao/model/sql/RuleChainEntity.java | 111 ++++++++++++ .../server/dao/model/sql/RuleNodeEntity.java | 93 ++++++++++ .../dao/rule/CassandraRuleChainDao.java | 60 +++++++ .../server/dao/rule/CassandraRuleNodeDao.java | 42 +++++ .../server/dao/rule/RuleChainDao.java | 40 +++++ .../server/dao/rule/RuleChainService.java | 28 +++ .../server/dao/rule/RuleNodeDao.java | 27 +++ .../server/dao/sql/rule/JpaRuleChainDao.java | 66 +++++++ .../server/dao/sql/rule/JpaRuleNodeDao.java | 46 +++++ .../dao/sql/rule/RuleChainRepository.java | 38 ++++ .../dao/sql/rule/RuleNodeRepository.java | 25 +++ dao/src/main/resources/cassandra/schema.cql | 128 ++++++++------ dao/src/main/resources/sql/schema.sql | 22 ++- 27 files changed, 1376 insertions(+), 54 deletions(-) create mode 100644 application/src/main/data/upgrade/1.5.0/schema_update.cql create mode 100644 application/src/main/data/upgrade/1.5.0/schema_update.sql create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleNodeDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java 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 new file mode 100644 index 0000000000..826373d0ae --- /dev/null +++ b/application/src/main/data/upgrade/1.5.0/schema_update.cql @@ -0,0 +1,93 @@ +-- +-- Copyright © 2016-2018 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + ts bigint, + msg blob, + PRIMARY KEY ((node_id, clustered_hash, partition), ts)) +WITH CLUSTERING ORDER BY (ts DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + +CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + msg_id timeuuid, + PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) +WITH CLUSTERING ORDER BY (msg_id DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + +CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + PRIMARY KEY ((node_id, clustered_hash), partition)) +WITH CLUSTERING ORDER BY (partition DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + +CREATE TABLE IF NOT EXISTS thingsboard.rule_chain ( + id uuid, + tenant_id uuid, + name text, + search_text text, + first_rule_node_id uuid, + is_root boolean, + configuration text, + additional_info text, + PRIMARY KEY (id, tenant_id) +); + +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS + SELECT * + from thingsboard.rule_chain + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL + PRIMARY KEY ( tenant_id, search_text, id ) + WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); + +CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( + id uuid, + type text, + name text, + search_text text, + configuration text, + additional_info text, + PRIMARY KEY (id) +); + 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 new file mode 100644 index 0000000000..12b80b3072 --- /dev/null +++ b/application/src/main/data/upgrade/1.5.0/schema_update.sql @@ -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. +-- + +CREATE TABLE IF NOT EXISTS rule_chain ( + id varchar(31) NOT NULL CONSTRAINT rule_chain_pkey PRIMARY KEY, + additional_info varchar, + configuration varchar(10000000), + name varchar(255), + first_rule_node_id varchar(31), + is_root boolean, + search_text varchar(255), + tenant_id varchar(31) +); + +CREATE TABLE IF NOT EXISTS rule_node ( + id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY, + additional_info varchar, + configuration varchar(10000000), + type varchar(255), + name varchar(255), + search_text varchar(255) +); \ No newline at end of file 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 e765c40c86..1835a937e7 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -77,11 +77,16 @@ public class ThingsboardInstallService { databaseUpgradeService.upgradeDatabase("1.3.0"); - case "1.3.1": + case "1.3.1": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ..."); databaseUpgradeService.upgradeDatabase("1.3.1"); + case "1.4.0": + log.info("Upgrading ThingsBoard from version 1.4.0 to 1.5.0 ..."); + + databaseUpgradeService.upgradeDatabase("1.4.0"); + log.info("Updating system data..."); systemDataLoaderService.deleteSystemWidgetBundle("charts"); 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 e6826ec736..8dd969839d 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 @@ -185,6 +185,14 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { Files.deleteIfExists(dashboardsDump); } log.info("Dashboards restored."); + break; + case "1.4.0": + + log.info("Updating schema ..."); + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_CQL); + loadCql(schemaUpdateFile); + log.info("Schema updated."); + break; default: throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); 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 cdd3103358..412ea8b9e6 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 @@ -97,6 +97,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Dashboards restored."); } break; + 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); + 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."); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 05b558e6d7..d28a53ffa4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, RULE, PLUGIN, DASHBOARD, ASSET, DEVICE, ALARM + TENANT, CUSTOMER, USER, RULE, PLUGIN, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index e45cb91613..76b3e336d8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -53,6 +53,10 @@ public class EntityIdFactory { return new AssetId(uuid); case ALARM: return new AlarmId(uuid); + case RULE_CHAIN: + return new RuleChainId(uuid); + case RULE_NODE: + return new RuleNodeId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java new file mode 100644 index 0000000000..a1a5075d27 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.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.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class RuleChainId extends UUIDBased implements EntityId { + + @JsonCreator + public RuleChainId(@JsonProperty("id") UUID id) { + super(id); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.RULE_CHAIN; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java new file mode 100644 index 0000000000..ec5ecce0d8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.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.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class RuleNodeId extends UUIDBased implements EntityId { + + @JsonCreator + public RuleNodeId(@JsonProperty("id") UUID id) { + super(id); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.RULE_NODE; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java new file mode 100644 index 0000000000..48bc5de4de --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java @@ -0,0 +1,79 @@ +/** + * 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.data.rule; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +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; + +@Data +@EqualsAndHashCode(callSuper = true) +@Slf4j +public class RuleChain extends SearchTextBasedWithAdditionalInfo implements HasName { + + private static final long serialVersionUID = -5656679015121935465L; + + private TenantId tenantId; + private String name; + private RuleNodeId firstRuleNodeId; + private boolean isRoot; + private transient JsonNode configuration; + @JsonIgnore + private byte[] configurationBytes; + + public RuleChain() { + super(); + } + + public RuleChain(RuleChainId id) { + super(id); + } + + public RuleChain(RuleChain ruleChain) { + super(ruleChain); + this.tenantId = ruleChain.getTenantId(); + this.name = ruleChain.getName(); + this.firstRuleNodeId = ruleChain.getFirstRuleNodeId(); + this.isRoot = ruleChain.isRoot(); + this.setConfiguration(ruleChain.getConfiguration()); + } + + @Override + public String getSearchText() { + return getName(); + } + + @Override + public String getName() { + return name; + } + + public JsonNode getConfiguration() { + return SearchTextBasedWithAdditionalInfo.getJson(() -> configuration, () -> configurationBytes); + } + + public void setConfiguration(JsonNode data) { + setJson(data, json -> this.configuration = json, bytes -> this.configurationBytes = bytes); + } + +} 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 new file mode 100644 index 0000000000..d044000117 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java @@ -0,0 +1,74 @@ +/** + * 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.data.rule; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +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.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +@EqualsAndHashCode(callSuper = true) +@Slf4j +public class RuleNode extends SearchTextBasedWithAdditionalInfo implements HasName { + + private static final long serialVersionUID = -5656679015121235465L; + + private String type; + private String name; + private transient JsonNode configuration; + @JsonIgnore + private byte[] configurationBytes; + + public RuleNode() { + super(); + } + + public RuleNode(RuleNodeId id) { + super(id); + } + + public RuleNode(RuleNode ruleNode) { + super(ruleNode); + this.type = ruleNode.getType(); + this.name = ruleNode.getName(); + this.setConfiguration(ruleNode.getConfiguration()); + } + + @Override + public String getSearchText() { + return getName(); + } + + @Override + public String getName() { + return name; + } + + public JsonNode getConfiguration() { + return SearchTextBasedWithAdditionalInfo.getJson(() -> configuration, () -> configurationBytes); + } + + public void setConfiguration(JsonNode data) { + setJson(data, json -> this.configuration = json, bytes -> this.configurationBytes = bytes); + } + +} 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 c2b55c956a..c68c23dc91 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 @@ -332,6 +332,26 @@ public class ModelConstants { public static final String EVENT_BY_TYPE_AND_ID_VIEW_NAME = "event_by_type_and_id"; public static final String EVENT_BY_ID_VIEW_NAME = "event_by_id"; + /** + * Cassandra rule chain constants. + */ + public static final String RULE_CHAIN_COLUMN_FAMILY_NAME = "rule_chain"; + public static final String RULE_CHAIN_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; + public static final String RULE_CHAIN_NAME_PROPERTY = "name"; + public static final String RULE_CHAIN_FIRST_RULE_NODE_ID_PROPERTY = "first_rule_node_id"; + public static final String RULE_CHAIN_IS_ROOT_PROPERTY = "is_root"; + public static final String RULE_CHAIN_CONFIGURATION_PROPERTY = "configuration"; + + public static final String RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_and_search_text"; + + /** + * Cassandra rule node constants. + */ + public static final String RULE_NODE_COLUMN_FAMILY_NAME = "rule_node"; + 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"; + /** * Cassandra attributes and timeseries constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java new file mode 100644 index 0000000000..962da0a68c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java @@ -0,0 +1,165 @@ +/** + * 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.model.nosql; + +import com.datastax.driver.core.utils.UUIDs; +import com.datastax.driver.mapping.annotations.ClusteringColumn; +import com.datastax.driver.mapping.annotations.Column; +import com.datastax.driver.mapping.annotations.PartitionKey; +import com.datastax.driver.mapping.annotations.Table; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.EqualsAndHashCode; +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.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.model.type.JsonCodec; + +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.*; + +@Table(name = RULE_CHAIN_COLUMN_FAMILY_NAME) +@EqualsAndHashCode +@ToString +public class RuleChainEntity implements SearchTextEntity { + + @PartitionKey + @Column(name = ID_PROPERTY) + private UUID id; + @ClusteringColumn + @Column(name = RULE_CHAIN_TENANT_ID_PROPERTY) + private UUID tenantId; + @Column(name = RULE_CHAIN_NAME_PROPERTY) + private String name; + @Column(name = SEARCH_TEXT_PROPERTY) + private String searchText; + @Column(name = RULE_CHAIN_FIRST_RULE_NODE_ID_PROPERTY) + private UUID firstRuleNodeId; + @Column(name = RULE_CHAIN_IS_ROOT_PROPERTY) + private boolean isRoot; + @Column(name = RULE_CHAIN_CONFIGURATION_PROPERTY, codec = JsonCodec.class) + private JsonNode configuration; + @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) + private JsonNode additionalInfo; + + public RuleChainEntity() { + } + + public RuleChainEntity(RuleChain ruleChain) { + if (ruleChain.getId() != null) { + this.id = ruleChain.getUuidId(); + } + this.tenantId = DaoUtil.getId(ruleChain.getTenantId()); + this.name = ruleChain.getName(); + this.searchText = ruleChain.getName(); + this.firstRuleNodeId = DaoUtil.getId(ruleChain.getFirstRuleNodeId()); + this.isRoot = ruleChain.isRoot(); + this.configuration = ruleChain.getConfiguration(); + this.additionalInfo = ruleChain.getAdditionalInfo(); + } + + @Override + public String getSearchTextSource() { + return getSearchText(); + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + @Override + public UUID getId() { + return id; + } + + @Override + public void setId(UUID id) { + this.id = id; + } + + public UUID getTenantId() { + return tenantId; + } + + public void setTenantId(UUID tenantId) { + this.tenantId = tenantId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public UUID getFirstRuleNodeId() { + return firstRuleNodeId; + } + + public void setFirstRuleNodeId(UUID firstRuleNodeId) { + this.firstRuleNodeId = firstRuleNodeId; + } + + public boolean isRoot() { + return isRoot; + } + + public void setRoot(boolean isRoot) { + this.isRoot = isRoot; + } + + public String getSearchText() { + return searchText; + } + + public JsonNode getConfiguration() { + return configuration; + } + + public void setConfiguration(JsonNode configuration) { + this.configuration = configuration; + } + + public JsonNode getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(JsonNode additionalInfo) { + this.additionalInfo = additionalInfo; + } + + @Override + public RuleChain toData() { + RuleChain ruleChain = new RuleChain(new RuleChainId(id)); + ruleChain.setCreatedTime(UUIDs.unixTimestamp(id)); + ruleChain.setTenantId(new TenantId(tenantId)); + ruleChain.setName(name); + if (this.firstRuleNodeId != null) { + ruleChain.setFirstRuleNodeId(new RuleNodeId(this.firstRuleNodeId)); + } + ruleChain.setRoot(this.isRoot); + ruleChain.setConfiguration(this.configuration); + ruleChain.setAdditionalInfo(this.additionalInfo); + return ruleChain; + } + +} 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 new file mode 100644 index 0000000000..ba96e4b98e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java @@ -0,0 +1,134 @@ +/** + * 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.model.nosql; + +import com.datastax.driver.core.utils.UUIDs; +import com.datastax.driver.mapping.annotations.Column; +import com.datastax.driver.mapping.annotations.PartitionKey; +import com.datastax.driver.mapping.annotations.Table; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.model.type.JsonCodec; + +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.*; + +@Table(name = RULE_NODE_COLUMN_FAMILY_NAME) +@EqualsAndHashCode +@ToString +public class RuleNodeEntity implements SearchTextEntity { + + @PartitionKey + @Column(name = ID_PROPERTY) + private UUID id; + @Column(name = RULE_NODE_TYPE_PROPERTY) + private String type; + @Column(name = RULE_NODE_NAME_PROPERTY) + private String name; + @Column(name = SEARCH_TEXT_PROPERTY) + private String searchText; + @Column(name = RULE_NODE_CONFIGURATION_PROPERTY, codec = JsonCodec.class) + private JsonNode configuration; + @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) + private JsonNode additionalInfo; + + public RuleNodeEntity() { + } + + public RuleNodeEntity(RuleNode ruleNode) { + if (ruleNode.getId() != null) { + this.id = ruleNode.getUuidId(); + } + this.type = ruleNode.getType(); + this.name = ruleNode.getName(); + this.searchText = ruleNode.getName(); + this.configuration = ruleNode.getConfiguration(); + this.additionalInfo = ruleNode.getAdditionalInfo(); + } + + @Override + public String getSearchTextSource() { + return getSearchText(); + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + @Override + public UUID getId() { + return id; + } + + @Override + public void setId(UUID id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSearchText() { + return searchText; + } + + public JsonNode getConfiguration() { + return configuration; + } + + public void setConfiguration(JsonNode configuration) { + this.configuration = configuration; + } + + public JsonNode getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(JsonNode additionalInfo) { + this.additionalInfo = additionalInfo; + } + + @Override + public RuleNode toData() { + RuleNode ruleNode = new RuleNode(new RuleNodeId(id)); + ruleNode.setCreatedTime(UUIDs.unixTimestamp(id)); + ruleNode.setType(this.type); + ruleNode.setName(this.name); + ruleNode.setConfiguration(this.configuration); + ruleNode.setAdditionalInfo(this.additionalInfo); + return ruleNode; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java new file mode 100644 index 0000000000..518eda7821 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java @@ -0,0 +1,111 @@ +/** + * 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.model.sql; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.UUIDConverter; +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.dao.DaoUtil; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.RULE_CHAIN_COLUMN_FAMILY_NAME) +public class RuleChainEntity extends BaseSqlEntity implements SearchTextEntity { + + @Column(name = ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY) + private String tenantId; + + @Column(name = ModelConstants.RULE_CHAIN_NAME_PROPERTY) + private String name; + + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) + private String searchText; + + @Column(name = ModelConstants.RULE_CHAIN_FIRST_RULE_NODE_ID_PROPERTY) + private String firstRuleNodeId; + + @Column(name = ModelConstants.RULE_CHAIN_IS_ROOT_PROPERTY) + private boolean isRoot; + + @Type(type = "json") + @Column(name = ModelConstants.RULE_CHAIN_CONFIGURATION_PROPERTY) + private JsonNode configuration; + + @Type(type = "json") + @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) + private JsonNode additionalInfo; + + public RuleChainEntity() { + } + + public RuleChainEntity(RuleChain ruleChain) { + if (ruleChain.getId() != null) { + this.setId(ruleChain.getUuidId()); + } + this.tenantId = toString(DaoUtil.getId(ruleChain.getTenantId())); + this.name = ruleChain.getName(); + this.searchText = ruleChain.getName(); + if (ruleChain.getFirstRuleNodeId() != null) { + this.firstRuleNodeId = UUIDConverter.fromTimeUUID(ruleChain.getFirstRuleNodeId().getId()); + } + this.isRoot = ruleChain.isRoot(); + this.configuration = ruleChain.getConfiguration(); + this.additionalInfo = ruleChain.getAdditionalInfo(); + } + + @Override + public String getSearchTextSource() { + return searchText; + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + @Override + public RuleChain toData() { + RuleChain ruleChain = new RuleChain(new RuleChainId(getId())); + ruleChain.setCreatedTime(UUIDs.unixTimestamp(getId())); + ruleChain.setTenantId(new TenantId(toUUID(tenantId))); + ruleChain.setName(name); + if (firstRuleNodeId != null) { + ruleChain.setFirstRuleNodeId(new RuleNodeId(UUIDConverter.fromString(firstRuleNodeId))); + } + ruleChain.setRoot(isRoot); + ruleChain.setConfiguration(configuration); + ruleChain.setAdditionalInfo(additionalInfo); + return ruleChain; + } +} 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 new file mode 100644 index 0000000000..d96048756c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java @@ -0,0 +1,93 @@ +/** + * 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.model.sql; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.RULE_NODE_COLUMN_FAMILY_NAME) +public class RuleNodeEntity extends BaseSqlEntity implements SearchTextEntity { + + @Column(name = ModelConstants.RULE_NODE_TYPE_PROPERTY) + private String type; + + @Column(name = ModelConstants.RULE_NODE_NAME_PROPERTY) + private String name; + + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) + private String searchText; + + @Type(type = "json") + @Column(name = ModelConstants.RULE_NODE_CONFIGURATION_PROPERTY) + private JsonNode configuration; + + @Type(type = "json") + @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) + private JsonNode additionalInfo; + + public RuleNodeEntity() { + } + + public RuleNodeEntity(RuleNode ruleNode) { + if (ruleNode.getId() != null) { + this.setId(ruleNode.getUuidId()); + } + this.type = ruleNode.getType(); + this.name = ruleNode.getName(); + this.searchText = ruleNode.getName(); + this.configuration = ruleNode.getConfiguration(); + this.additionalInfo = ruleNode.getAdditionalInfo(); + } + + @Override + public String getSearchTextSource() { + return searchText; + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + @Override + public RuleNode toData() { + RuleNode ruleNode = new RuleNode(new RuleNodeId(getId())); + ruleNode.setCreatedTime(UUIDs.unixTimestamp(getId())); + ruleNode.setType(type); + ruleNode.setName(name); + ruleNode.setConfiguration(configuration); + ruleNode.setAdditionalInfo(additionalInfo); + return ruleNode; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java new file mode 100644 index 0000000000..863904be65 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.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.dao.rule; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.nosql.RuleChainEntity; +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; +import org.thingsboard.server.dao.util.NoSqlDao; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static org.thingsboard.server.dao.model.ModelConstants.*; + +@Component +@Slf4j +@NoSqlDao +public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao implements RuleChainDao { + + @Override + protected Class getColumnFamilyClass() { + return RuleChainEntity.class; + } + + @Override + protected String getColumnFamilyName() { + return RULE_CHAIN_COLUMN_FAMILY_NAME; + } + + @Override + public List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { + log.debug("Try to find rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink); + List ruleChainEntities = findPageWithTextSearch(RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, + Collections.singletonList(eq(RULE_CHAIN_TENANT_ID_PROPERTY, tenantId)), + pageLink); + + log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink); + return DaoUtil.convertDataList(ruleChainEntities); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleNodeDao.java new file mode 100644 index 0000000000..9e34531a71 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleNodeDao.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.dao.rule; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.model.nosql.RuleNodeEntity; +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; +import org.thingsboard.server.dao.util.NoSqlDao; + +import static org.thingsboard.server.dao.model.ModelConstants.RULE_NODE_COLUMN_FAMILY_NAME; + +@Component +@Slf4j +@NoSqlDao +public class CassandraRuleNodeDao extends CassandraAbstractSearchTextDao implements RuleNodeDao { + + @Override + protected Class getColumnFamilyClass() { + return RuleNodeEntity.class; + } + + @Override + protected String getColumnFamilyName() { + return RULE_NODE_COLUMN_FAMILY_NAME; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java new file mode 100644 index 0000000000..4a7cfaea7c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.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.dao.rule; + +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.Dao; + +import java.util.List; +import java.util.UUID; + +/** + * Created by igor on 3/12/18. + */ +public interface RuleChainDao extends Dao { + + /** + * Find rule chains by tenantId and page link. + * + * @param tenantId the tenantId + * @param pageLink the page link + * @return the list of rule chain objects + */ + List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); + +} 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 new file mode 100644 index 0000000000..c6c947ba9f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -0,0 +1,28 @@ +/** + * 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.rule; + +import org.thingsboard.server.common.data.rule.RuleChain; + +/** + * Created by igor on 3/12/18. + */ +public interface RuleChainService { + + RuleChain saveRuleChain(RuleChain ruleChain); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java new file mode 100644 index 0000000000..7ef4b3e204 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java @@ -0,0 +1,27 @@ +/** + * 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.rule; + +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.Dao; + +/** + * Created by igor on 3/12/18. + */ +public interface RuleNodeDao extends Dao { + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java new file mode 100644 index 0000000000..fb5d20a702 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -0,0 +1,66 @@ +/** + * 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.rule; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.RuleChainEntity; +import org.thingsboard.server.dao.rule.RuleChainDao; +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; + +@Slf4j +@Component +@SqlDao +public class JpaRuleChainDao extends JpaAbstractSearchTextDao implements RuleChainDao { + + @Autowired + private RuleChainRepository ruleChainRepository; + + @Override + protected Class getEntityClass() { + return RuleChainEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return ruleChainRepository; + } + + @Override + public List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { + return DaoUtil.convertDataList(ruleChainRepository + .findByTenantId( + UUIDConverter.fromTimeUUID(tenantId), + Objects.toString(pageLink.getTextSearch(), ""), + pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), + new PageRequest(0, pageLink.getLimit()))); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java new file mode 100644 index 0000000000..eec28946a8 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java @@ -0,0 +1,46 @@ +/** + * 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.rule; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.model.sql.RuleNodeEntity; +import org.thingsboard.server.dao.rule.RuleNodeDao; +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; +import org.thingsboard.server.dao.util.SqlDao; + +@Slf4j +@Component +@SqlDao +public class JpaRuleNodeDao extends JpaAbstractSearchTextDao implements RuleNodeDao { + + @Autowired + private RuleNodeRepository ruleNodeRepository; + + @Override + protected Class getEntityClass() { + return RuleNodeEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return ruleNodeRepository; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java new file mode 100644 index 0000000000..58d4aac70b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.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.dao.sql.rule; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.dao.model.sql.RuleChainEntity; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.List; + +@SqlDao +public interface RuleChainRepository extends CrudRepository { + + @Query("SELECT rc FROM RuleChainEntity rc WHERE rc.tenantId = :tenantId " + + "AND LOWER(rc.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + + "AND rc.id > :idOffset ORDER BY rc.id") + List findByTenantId(@Param("tenantId") String tenantId, + @Param("searchText") String searchText, + @Param("idOffset") String idOffset, + Pageable pageable); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java new file mode 100644 index 0000000000..574aedf118 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java @@ -0,0 +1,25 @@ +/** + * 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.rule; + +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sql.RuleNodeEntity; +import org.thingsboard.server.dao.util.SqlDao; + +@SqlDao +public interface RuleNodeRepository extends CrudRepository { + +} diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index 0c8a644944..6c62c89395 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -542,55 +542,6 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); -CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( - node_id timeuuid, - clustered_hash bigint, - partition bigint, - ts bigint, - msg blob, - PRIMARY KEY ((node_id, clustered_hash, partition), ts)) -WITH CLUSTERING ORDER BY (ts DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - - -CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( - node_id timeuuid, - clustered_hash bigint, - partition bigint, - msg_id timeuuid, - PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) -WITH CLUSTERING ORDER BY (msg_id DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - -CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( - node_id timeuuid, - clustered_hash bigint, - partition bigint, - PRIMARY KEY ((node_id, clustered_hash), partition)) -WITH CLUSTERING ORDER BY (partition DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( tenant_id timeuuid, id timeuuid, @@ -639,8 +590,6 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id ( PRIMARY KEY ((tenant_id, user_id), id) ); - - CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( tenant_id timeuuid, id timeuuid, @@ -664,3 +613,80 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( PRIMARY KEY (( tenant_id ), partition) ) WITH CLUSTERING ORDER BY ( partition ASC ) AND compaction = { 'class' : 'LeveledCompactionStrategy' }; + +CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + ts bigint, + msg blob, + PRIMARY KEY ((node_id, clustered_hash, partition), ts)) +WITH CLUSTERING ORDER BY (ts DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + +CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + msg_id timeuuid, + PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) +WITH CLUSTERING ORDER BY (msg_id DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + +CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( + node_id timeuuid, + clustered_hash bigint, + partition bigint, + PRIMARY KEY ((node_id, clustered_hash), partition)) +WITH CLUSTERING ORDER BY (partition DESC) +AND compaction = { + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', + 'min_threshold': '5', + 'base_time_seconds': '43200', + 'max_window_size_seconds': '43200', + 'tombstone_threshold': '0.9', + 'unchecked_tombstone_compaction': 'true' +}; + +CREATE TABLE IF NOT EXISTS thingsboard.rule_chain ( + id uuid, + tenant_id uuid, + name text, + search_text text, + first_rule_node_id uuid, + is_root boolean, + configuration text, + additional_info text, + PRIMARY KEY (id, tenant_id) +); + +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS + SELECT * + from thingsboard.rule_chain + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL + PRIMARY KEY ( tenant_id, search_text, id ) + WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); + +CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( + id uuid, + type text, + name text, + search_text text, + configuration text, + additional_info text, + PRIMARY KEY (id) +); \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql index 9f03dc8a1b..e525007080 100644 --- a/dao/src/main/resources/sql/schema.sql +++ b/dao/src/main/resources/sql/schema.sql @@ -254,4 +254,24 @@ CREATE TABLE IF NOT EXISTS widgets_bundle ( search_text varchar(255), tenant_id varchar(31), title varchar(255) -); \ No newline at end of file +); + +CREATE TABLE IF NOT EXISTS rule_chain ( + id varchar(31) NOT NULL CONSTRAINT rule_chain_pkey PRIMARY KEY, + additional_info varchar, + configuration varchar(10000000), + name varchar(255), + first_rule_node_id varchar(31), + is_root boolean, + search_text varchar(255), + tenant_id varchar(31) +); + +CREATE TABLE IF NOT EXISTS rule_node ( + id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY, + additional_info varchar, + configuration varchar(10000000), + type varchar(255), + name varchar(255), + search_text varchar(255) +); From 5bfdf9decf760bed5f67672890ba52e9c7c4e826 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Mar 2018 10:46:12 +0200 Subject: [PATCH 005/171] Rule Chain Service --- .../server/dao/rule/BaseRuleChainService.java | 130 ++++++++++++++++++ .../dao/rule/CassandraRuleChainDao.java | 14 ++ .../server/dao/rule/RuleChainDao.java | 9 ++ .../server/dao/rule/RuleChainService.java | 20 +++ .../server/dao/sql/rule/JpaRuleChainDao.java | 11 ++ .../dao/sql/rule/RuleChainRepository.java | 8 ++ 6 files changed, 192 insertions(+) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java 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 new file mode 100644 index 0000000000..eb6be43fcf --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -0,0 +1,130 @@ +/** + * 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.rule; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.RuleChainId; +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.common.data.rule.RuleChain; +import org.thingsboard.server.dao.entity.AbstractEntityService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.service.Validator; + +import java.util.List; + +/** + * Created by igor on 3/12/18. + */ +@Service +@Slf4j +public class BaseRuleChainService extends AbstractEntityService implements RuleChainService { + + public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); + + @Autowired + private RuleChainDao ruleChainDao; + + @Override + public RuleChain saveRuleChain(RuleChain ruleChain) { + ruleChainValidator.validate(ruleChain); + if (ruleChain.getTenantId() == null) { + log.trace("Save system rule chain with predefined id {}", SYSTEM_TENANT); + ruleChain.setTenantId(SYSTEM_TENANT); + } + return ruleChainDao.save(ruleChain); + } + + @Override + public RuleChain findRuleChainById(RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id for search request."); + return ruleChainDao.findById(ruleChainId.getId()); + } + + @Override + public TextPageData findSystemRuleChains(TextPageLink pageLink) { + Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system rule chain request."); + List ruleChains = ruleChainDao.findRuleChainsByTenantId(SYSTEM_TENANT.getId(), pageLink); + return new TextPageData<>(ruleChains, pageLink); + } + + @Override + public TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink) { + Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request."); + Validator.validatePageLink(pageLink, "Incorrect PageLink object for search rule chain request."); + List ruleChains = ruleChainDao.findRuleChainsByTenantId(tenantId.getId(), pageLink); + return new TextPageData<>(ruleChains, pageLink); + } + + @Override + public TextPageData findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) { + log.trace("Executing findAllTenantRuleChainsByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink); + Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); + Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); + List ruleChains = ruleChainDao.findAllRuleChainsByTenantId(tenantId.getId(), pageLink); + return new TextPageData<>(ruleChains, pageLink); + } + + @Override + public void deleteRuleChainById(RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request."); + checkRuleNodesAndDelete(ruleChainId); + } + + private void checkRuleNodesAndDelete(RuleChainId ruleChainId) { + //TODO: + deleteEntityRelations(ruleChainId); + ruleChainDao.removeById(ruleChainId.getId()); + } + + @Override + public void deleteRuleChainsByTenantId(TenantId tenantId) { + Validator.validateId(tenantId, "Incorrect tenant id for delete rule chains request."); + tenantRuleChainsRemover.removeEntities(tenantId); + } + + private DataValidator ruleChainValidator = + new DataValidator() { + @Override + protected void validateDataImpl(RuleChain ruleChain) { + if (StringUtils.isEmpty(ruleChain.getName())) { + throw new DataValidationException("Rule chain name should be specified!."); + } + } + }; + + private PaginatedRemover tenantRuleChainsRemover = + new PaginatedRemover() { + + @Override + protected List findEntities(TenantId id, TextPageLink pageLink) { + return ruleChainDao.findRuleChainsByTenantId(id.getId(), pageLink); + } + + @Override + protected void removeEntity(RuleChain entity) { + checkRuleNodesAndDelete(entity.getId()); + } + }; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java index 863904be65..47c8e86d61 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java @@ -20,15 +20,18 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.nosql.RuleChainEntity; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; import org.thingsboard.server.dao.util.NoSqlDao; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static com.datastax.driver.core.querybuilder.QueryBuilder.in; import static org.thingsboard.server.dao.model.ModelConstants.*; @Component @@ -57,4 +60,15 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { + log.debug("Try to find all rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink); + List ruleChainEntities = findPageWithTextSearch(RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, + Arrays.asList(in(ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))), + pageLink); + + log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink); + return DaoUtil.convertDataList(ruleChainEntities); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index 4a7cfaea7c..b9a9932ad3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -37,4 +37,13 @@ public interface RuleChainDao extends Dao { */ List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); + /** + * Find all rule chains by tenantId and page link. + * + * @param tenantId the tenantId + * @param pageLink the page link + * @return the list of rule chain objects + */ + List findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); + } 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 c6c947ba9f..00dc97315e 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 @@ -16,8 +16,16 @@ package org.thingsboard.server.dao.rule; +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.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.rule.RuleChain; +import java.util.List; + /** * Created by igor on 3/12/18. */ @@ -25,4 +33,16 @@ public interface RuleChainService { RuleChain saveRuleChain(RuleChain ruleChain); + RuleChain findRuleChainById(RuleChainId ruleChainId); + + TextPageData findSystemRuleChains(TextPageLink pageLink); + + TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); + + TextPageData findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink); + + void deleteRuleChainById(RuleChainId ruleChainId); + + void deleteRuleChainsByTenantId(TenantId tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index fb5d20a702..425369fa2e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -63,4 +63,15 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { + return DaoUtil.convertDataList(ruleChainRepository + .findAllTenantRuleChainsByTenantId( + UUIDConverter.fromTimeUUID(tenantId), + NULL_UUID_STR, + Objects.toString(pageLink.getTextSearch(), ""), + pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), + new PageRequest(0, pageLink.getLimit()))); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java index 58d4aac70b..3b5e4dbcd7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java @@ -35,4 +35,12 @@ public interface RuleChainRepository extends CrudRepository :idOffset ORDER BY rc.id") + List findAllTenantRuleChainsByTenantId(@Param("tenantId") String tenantId, + @Param("nullTenantId") String nullTenantId, + @Param("searchText") String searchText, + @Param("idOffset") String idOffset, + Pageable pageable); } From 4956c0c573fbeba976f5fe5c6d9c138358439bed Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Mar 2018 13:12:00 +0200 Subject: [PATCH 006/171] Rule Chain service implementation. --- .../data/relation/RelationTypeGroup.java | 4 +- .../common/data/rule/RuleChainMetaData.java | 75 ++++++ .../server/dao/rule/BaseRuleChainService.java | 225 +++++++++++++++++- .../server/dao/rule/RuleChainService.java | 14 ++ .../server/dao/tenant/TenantServiceImpl.java | 5 + 5 files changed, 316 insertions(+), 7 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java index 90e0253370..55990552b0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java @@ -19,6 +19,8 @@ public enum RelationTypeGroup { COMMON, ALARM, - DASHBOARD + DASHBOARD, + RULE_CHAIN, + RULE_NODE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java new file mode 100644 index 0000000000..af141d6142 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java @@ -0,0 +1,75 @@ +/** + * 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.data.rule; + +import lombok.Data; +import org.thingsboard.server.common.data.id.RuleChainId; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by igor on 3/13/18. + */ +@Data +public class RuleChainMetaData { + + private RuleChainId ruleChainId; + + private Integer firstNodeIndex; + + private List nodes; + + private List connections; + + private List ruleChainConnections; + + public void addConnectionInfo(int fromIndex, int toIndex, String type) { + NodeConnectionInfo connectionInfo = new NodeConnectionInfo(); + connectionInfo.setFromIndex(fromIndex); + connectionInfo.setToIndex(toIndex); + connectionInfo.setType(type); + if (connections == null) { + connections = new ArrayList<>(); + } + connections.add(connectionInfo); + } + public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type) { + RuleChainConnectionInfo connectionInfo = new RuleChainConnectionInfo(); + connectionInfo.setFromIndex(fromIndex); + connectionInfo.setTargetRuleChainId(targetRuleChainId); + connectionInfo.setType(type); + if (ruleChainConnections == null) { + ruleChainConnections = new ArrayList<>(); + } + ruleChainConnections.add(connectionInfo); + } + + @Data + public class NodeConnectionInfo { + private int fromIndex; + private int toIndex; + private String type; + } + + @Data + public class RuleChainConnectionInfo { + private int fromIndex; + private RuleChainId targetRuleChainId; + private String type; + } + +} 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 eb6be43fcf..073ccb912e 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 @@ -20,19 +20,33 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.tenant.TenantDao; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; /** * Created by igor on 3/12/18. @@ -46,6 +60,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Autowired private RuleChainDao ruleChainDao; + @Autowired + private RuleNodeDao ruleNodeDao; + + @Autowired + private TenantDao tenantDao; + @Override public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain); @@ -53,7 +73,144 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC log.trace("Save system rule chain with predefined id {}", SYSTEM_TENANT); ruleChain.setTenantId(SYSTEM_TENANT); } - return ruleChainDao.save(ruleChain); + RuleChain savedRuleChain = ruleChainDao.save(ruleChain); + if (ruleChain.isRoot() && ruleChain.getTenantId() != null && ruleChain.getId() == null) { + try { + createRelation(new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create tenant to root rule chain relation. from: [{}], to: [{}]", + savedRuleChain.getTenantId(), savedRuleChain.getId()); + throw new RuntimeException(e); + } + } + return savedRuleChain; + } + + @Override + public RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) { + Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); + RuleChain ruleChain = findRuleChainById(ruleChainMetaData.getRuleChainId()); + if (ruleChain == null) { + return null; + } + + List nodes = ruleChainMetaData.getNodes(); + List toAdd = new ArrayList<>(); + List toUpdate = new ArrayList<>(); + List toDelete = new ArrayList<>(); + + Map ruleNodeIndexMap = new HashMap<>(); + if (nodes != null) { + for (RuleNode node : nodes) { + if (node.getId() != null) { + ruleNodeIndexMap.put(node.getId(), nodes.indexOf(node)); + } else { + toAdd.add(node); + } + } + } + + List existingRuleNodes = getRuleChainNodes(ruleChainMetaData.getRuleChainId()); + for (RuleNode existingNode : existingRuleNodes) { + deleteEntityRelations(existingNode.getId()); + Integer index = ruleNodeIndexMap.get(existingNode.getId()); + if (index != null) { + toUpdate.add(ruleChainMetaData.getNodes().get(index)); + } else { + toDelete.add(existingNode); + } + } + for (RuleNode node : toAdd) { + RuleNode savedNode = ruleNodeDao.save(node); + try { + createRelation(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create rule chain to rule node relation. from: [{}], to: [{}]", + ruleChainMetaData.getRuleChainId(), savedNode.getId()); + throw new RuntimeException(e); + } + int index = nodes.indexOf(node); + nodes.set(index, savedNode); + ruleNodeIndexMap.put(savedNode.getId(), index); + } + for (RuleNode node: toDelete) { + deleteRuleNode(node.getId()); + } + RuleNodeId firstRuleNodeId = null; + if (ruleChainMetaData.getFirstNodeIndex() != null) { + firstRuleNodeId = nodes.get(ruleChainMetaData.getFirstNodeIndex()).getId(); + } + if ((ruleChain.getFirstRuleNodeId() != null && !ruleChain.getFirstRuleNodeId().equals(firstRuleNodeId)) + || (ruleChain.getFirstRuleNodeId() == null && firstRuleNodeId != null)) { + ruleChain.setFirstRuleNodeId(firstRuleNodeId); + ruleChainDao.save(ruleChain); + } + if (ruleChainMetaData.getConnections() != null) { + for (RuleChainMetaData.NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) { + EntityId from = nodes.get(nodeConnection.getFromIndex()).getId(); + EntityId to = nodes.get(nodeConnection.getToIndex()).getId(); + String type = nodeConnection.getType(); + try { + createRelation(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create rule node relation. from: [{}], to: [{}]", from, to); + throw new RuntimeException(e); + } + } + } + if (ruleChainMetaData.getRuleChainConnections() != null) { + for (RuleChainMetaData.RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { + EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); + EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); + String type = nodeToRuleChainConnection.getType(); + try { + createRelation(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create rule node to rule chain relation. from: [{}], to: [{}]", from, to); + throw new RuntimeException(e); + } + } + } + + return loadRuleChainMetaData(ruleChainMetaData.getRuleChainId()); + } + + @Override + public RuleChainMetaData loadRuleChainMetaData(RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id."); + RuleChain ruleChain = findRuleChainById(ruleChainId); + if (ruleChain == null) { + return null; + } + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); + ruleChainMetaData.setRuleChainId(ruleChainId); + List ruleNodes = getRuleChainNodes(ruleChainId); + Map ruleNodeIndexMap = new HashMap<>(); + for (RuleNode node : ruleNodes) { + ruleNodeIndexMap.put(node.getId(), ruleNodes.indexOf(node)); + } + ruleChainMetaData.setNodes(ruleNodes); + if (ruleChain.getFirstRuleNodeId() != null) { + ruleChainMetaData.setFirstNodeIndex(ruleNodeIndexMap.get(ruleChain.getFirstRuleNodeId())); + } + for (RuleNode node : ruleNodes) { + int fromIndex = ruleNodeIndexMap.get(node.getId()); + List nodeRelations = getRuleNodeRelations(node.getId()); + for (EntityRelation nodeRelation : nodeRelations) { + String type = nodeRelation.getType(); + if (nodeRelation.getTo().getEntityType() == EntityType.RULE_NODE) { + RuleNodeId toNodeId = new RuleNodeId(nodeRelation.getTo().getId()); + int toIndex = ruleNodeIndexMap.get(toNodeId); + ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type); + } else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) { + RuleChainId targetRuleChainId = new RuleChainId(nodeRelation.getTo().getId()); + ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type); + } + } + } + return ruleChainMetaData; } @Override @@ -62,6 +219,33 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return ruleChainDao.findById(ruleChainId.getId()); } + @Override + public RuleChain getRootTenantRuleChain(TenantId tenantId) { + Validator.validateId(tenantId, "Incorrect tenant id for search request."); + List relations = relationService.findByFrom(tenantId, RelationTypeGroup.RULE_CHAIN); + if (relations != null && !relations.isEmpty()) { + EntityRelation relation = relations.get(0); + RuleChainId ruleChainId = new RuleChainId(relation.getTo().getId()); + return findRuleChainById(ruleChainId); + } else { + return null; + } + } + + @Override + public List getRuleChainNodes(RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id for search request."); + List relations = getRuleChainToNodeRelations(ruleChainId); + List ruleNodes = relations.stream().map(relation -> ruleNodeDao.findById(relation.getTo().getId())).collect(Collectors.toList()); + return ruleNodes; + } + + @Override + public List getRuleNodeRelations(RuleNodeId ruleNodeId) { + Validator.validateId(ruleNodeId, "Incorrect rule node id for search request."); + return relationService.findByFrom(ruleNodeId, RelationTypeGroup.RULE_NODE); + } + @Override public TextPageData findSystemRuleChains(TextPageLink pageLink) { Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system rule chain request."); @@ -92,16 +276,33 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC checkRuleNodesAndDelete(ruleChainId); } + @Override + public void deleteRuleChainsByTenantId(TenantId tenantId) { + Validator.validateId(tenantId, "Incorrect tenant id for delete rule chains request."); + tenantRuleChainsRemover.removeEntities(tenantId); + } + private void checkRuleNodesAndDelete(RuleChainId ruleChainId) { - //TODO: + List nodeRelations = getRuleChainToNodeRelations(ruleChainId); + for (EntityRelation relation : nodeRelations) { + deleteRuleNode(relation.getTo()); + } deleteEntityRelations(ruleChainId); ruleChainDao.removeById(ruleChainId.getId()); } - @Override - public void deleteRuleChainsByTenantId(TenantId tenantId) { - Validator.validateId(tenantId, "Incorrect tenant id for delete rule chains request."); - tenantRuleChainsRemover.removeEntities(tenantId); + private List getRuleChainToNodeRelations(RuleChainId ruleChainId) { + return relationService.findByFrom(ruleChainId, RelationTypeGroup.RULE_CHAIN); + } + + private void deleteRuleNode(EntityId entityId) { + deleteEntityRelations(entityId); + ruleNodeDao.removeById(entityId.getId()); + } + + private void createRelation(EntityRelation relation) throws ExecutionException, InterruptedException { + log.debug("Creating relation: {}", relation); + relationService.saveRelationAsync(relation).get(); } private DataValidator ruleChainValidator = @@ -111,6 +312,18 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (StringUtils.isEmpty(ruleChain.getName())) { throw new DataValidationException("Rule chain name should be specified!."); } + if (ruleChain.getTenantId() != null && !ruleChain.getTenantId().isNullUid()) { + Tenant tenant = tenantDao.findById(ruleChain.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Rule chain is referencing to non-existent tenant!"); + } + if (ruleChain.isRoot()) { + RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); + if (ruleChain.getId() == null || !ruleChain.getId().equals(rootRuleChain.getId())) { + throw new DataValidationException("Another root rule chain is present in scope of current tenant!"); + } + } + } } }; 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 00dc97315e..c6a2941938 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 @@ -18,11 +18,15 @@ package org.thingsboard.server.dao.rule; import org.thingsboard.server.common.data.id.PluginId; 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.page.TextPageData; 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.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; import java.util.List; @@ -33,8 +37,18 @@ public interface RuleChainService { RuleChain saveRuleChain(RuleChain ruleChain); + RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData); + + RuleChainMetaData loadRuleChainMetaData(RuleChainId ruleChainId); + RuleChain findRuleChainById(RuleChainId ruleChainId); + RuleChain getRootTenantRuleChain(TenantId tenantId); + + List getRuleChainNodes(RuleChainId ruleChainId); + + List getRuleNodeRelations(RuleNodeId ruleNodeId); + TextPageData findSystemRuleChains(TextPageLink pageLink); TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 24abe52802..3a607b623e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -31,6 +31,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.plugin.PluginService; +import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -76,6 +77,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private PluginService pluginService; + @Autowired + private RuleChainService ruleChainService; + @Override public Tenant findTenantById(TenantId tenantId) { log.trace("Executing findTenantById [{}]", tenantId); @@ -108,6 +112,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe assetService.deleteAssetsByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId); userService.deleteTenantAdmins(tenantId); + ruleChainService.deleteRuleChainsByTenantId(tenantId); ruleService.deleteRulesByTenantId(tenantId); pluginService.deletePluginsByTenantId(tenantId); tenantDao.removeById(tenantId.getId()); From 070e5b5b625c378427a2380994fd86d6174f3d8d Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 13 Mar 2018 18:03:00 +0200 Subject: [PATCH 007/171] Query refactoring and improvements --- .../main/data/upgrade/1.5.0/schema_update.cql | 20 ++-- .../src/main/resources/thingsboard.yml | 6 + common/message/pom.xml | 9 ++ .../thingsboard/server/common/msg/TbMsg.java | 80 +++++++++++++ .../server/common/msg}/TbMsgMetaData.java | 4 +- .../message/src/main/proto/tbmsg.proto | 6 +- dao/pom.xml | 15 ++- .../dao/nosql/CassandraAbstractDao.java | 9 ++ .../dao/nosql/CassandraAbstractModelDao.java | 3 + .../server/dao/queue}/MsqQueue.java | 11 +- .../server/dao/queue}/QueueBenchmark.java | 28 +++-- .../server/dao}/queue/jpa/SqlMsgQueue.java | 4 +- .../queue/cassandra/CassandraMsqQueue.java | 57 +++++---- .../dao/service}/queue/cassandra/MsgAck.java | 11 +- .../queue/cassandra/QueuePartitioner.java | 26 +++-- .../queue/cassandra/UnprocessedMsgFilter.java | 6 +- .../cassandra/repository/AckRepository.java | 8 +- .../cassandra/repository/MsgRepository.java | 10 +- .../ProcessedPartitionRepository.java | 4 +- .../impl/CassandraAckRepository.java | 44 +++---- .../impl/CassandraMsgRepository.java | 63 ++++++++++ ...CassandraProcessedPartitionRepository.java | 40 +++---- dao/src/main/resources/cassandra/schema.cql | 20 ++-- .../server/dao/NoSqlDaoServiceTestSuite.java | 4 +- .../queue/cassandra/QueuePartitionerTest.java | 14 +-- .../cassandra/UnprocessedMsgFilterTest.java | 6 +- .../impl/CassandraAckRepositoryTest.java | 18 +-- .../impl/CassandraMsgRepositoryTest.java | 30 +++-- ...andraProcessedPartitionRepositoryTest.java | 16 +-- .../test/resources/cassandra/system-test.cql | 27 ++++- dao/src/test/resources/nosql-test.properties | 7 +- pom.xml | 13 ++- rule-engine/pom.xml | 5 +- rule-engine/rule-engine-api/pom.xml | 9 +- .../rule/engine/api/TbContext.java | 3 +- .../thingsboard/rule/engine/api/TbMsg.java | 37 ------ .../thingsboard/rule/engine/api/TbNode.java | 4 +- .../rule/engine/api/TbNodeConfiguration.java | 2 +- .../rule/engine/api/TbNodeException.java | 2 +- .../rule/engine/api/TbNodeState.java | 2 +- rule-engine/rule-engine-components/pom.xml | 33 +----- .../thingsboard/rule/engine/TbNodeUtils.java | 2 +- .../engine/filter/TbMsgTypeFilterNode.java | 9 +- .../TbMsgTypeFilterNodeConfiguration.java | 2 +- .../engine/metadata/TbGetAttributesNode.java | 3 +- .../TbGetAttributesNodeConfiguration.java | 2 +- .../impl/CassandraMsgRepository.java | 109 ------------------ .../impl/SimpleAbstractCassandraDao.java | 77 ------------- .../engine/transform/TbTransformNode.java | 3 +- .../cassandra/CassandraMsqQueueTest.java | 48 -------- .../impl/SimpleAbstractCassandraDaoTest.java | 30 ----- .../test/resources/cassandra/system-test.cql | 75 ------------ 52 files changed, 454 insertions(+), 622 deletions(-) create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => common/message/src/main/java/org/thingsboard/server/common/msg}/TbMsgMetaData.java (91%) rename rule-engine/rule-engine-components/src/main/proto/msgqueue.proto => common/message/src/main/proto/tbmsg.proto (82%) rename {rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api => dao/src/main/java/org/thingsboard/server/dao/queue}/MsqQueue.java (76%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/tool => dao/src/main/java/org/thingsboard/server/dao/queue}/QueueBenchmark.java (84%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao}/queue/jpa/SqlMsgQueue.java (86%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/CassandraMsqQueue.java (52%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/MsgAck.java (70%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/QueuePartitioner.java (84%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/UnprocessedMsgFilter.java (87%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/repository/AckRepository.java (73%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/repository/MsgRepository.java (72%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/repository/ProcessedPartitionRepository.java (88%) rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/repository/impl/CassandraAckRepository.java (60%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepository.java rename {rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine => dao/src/main/java/org/thingsboard/server/dao/service}/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java (61%) rename {rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine => dao/src/test/java/org/thingsboard/server/dao/service}/queue/cassandra/QueuePartitionerTest.java (90%) rename {rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine => dao/src/test/java/org/thingsboard/server/dao/service}/queue/cassandra/UnprocessedMsgFilterTest.java (90%) rename {rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine => dao/src/test/java/org/thingsboard/server/dao/service}/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java (84%) rename {rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine => dao/src/test/java/org/thingsboard/server/dao/service}/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java (81%) rename {rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine => dao/src/test/java/org/thingsboard/server/dao/service}/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java (87%) delete mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsg.java delete mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepository.java delete mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDao.java delete mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueueTest.java delete mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDaoTest.java delete mode 100644 rule-engine/rule-engine-components/src/test/resources/cassandra/system-test.cql 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 826373d0ae..3e56de1bb3 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 @@ -16,11 +16,11 @@ CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( node_id timeuuid, - clustered_hash bigint, - partition bigint, + cluster_partition bigint, + ts_partition bigint, ts bigint, msg blob, - PRIMARY KEY ((node_id, clustered_hash, partition), ts)) + PRIMARY KEY ((node_id, cluster_partition, ts_partition), ts)) WITH CLUSTERING ORDER BY (ts DESC) AND compaction = { 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', @@ -33,10 +33,10 @@ AND compaction = { CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( node_id timeuuid, - clustered_hash bigint, - partition bigint, + cluster_partition bigint, + ts_partition bigint, msg_id timeuuid, - PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) + PRIMARY KEY ((node_id, cluster_partition, ts_partition), msg_id)) WITH CLUSTERING ORDER BY (msg_id DESC) AND compaction = { 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', @@ -49,10 +49,10 @@ AND compaction = { CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( node_id timeuuid, - clustered_hash bigint, - partition bigint, - PRIMARY KEY ((node_id, clustered_hash), partition)) -WITH CLUSTERING ORDER BY (partition DESC) + cluster_partition bigint, + ts_partition bigint, + PRIMARY KEY ((node_id, cluster_partition), ts_partition)) +WITH CLUSTERING ORDER BY (ts_partition DESC) AND compaction = { 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', 'min_threshold': '5', diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 8f862da57d..2369d01dc7 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -182,6 +182,12 @@ cassandra: # Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" + queue: + msg.ttl: 604800 # 7 days + ack.ttl: 604800 # 7 days + partitions.ttl: 604800 # 7 days + partitioning: "HOURS" + # SQL configuration parameters sql: # Specify executor service type used to perform timeseries insert tasks: SINGLE FIXED CACHED diff --git a/common/message/pom.xml b/common/message/pom.xml index 9e97d342c1..9290ffd463 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -56,6 +56,11 @@ ch.qos.logback logback-classic + + com.google.protobuf + protobuf-java + provided + junit junit @@ -70,6 +75,10 @@ + + org.xolstice.maven.plugins + protobuf-maven-plugin + 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 new file mode 100644 index 0000000000..5163b6c39d --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -0,0 +1,80 @@ +/** + * 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; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.msg.gen.MsgProtos; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * Created by ashvayka on 13.01.18. + */ +@Data +public final class TbMsg implements Serializable { + + private final UUID id; + private final String type; + private final EntityId originator; + private final TbMsgMetaData metaData; + + private final byte[] data; + + 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()); + } + + if (msg.getMetaData() != null) { + MsgProtos.TbMsgProto.TbMsgMetaDataProto.Builder metadataBuilder = MsgProtos.TbMsgProto.TbMsgMetaDataProto.newBuilder(); + metadataBuilder.putAllData(msg.getMetaData().getData()); + builder.addMetaData(metadataBuilder.build()); + } + + builder.setData(ByteString.copyFrom(msg.getData())); + byte[] bytes = builder.build().toByteArray(); + return ByteBuffer.wrap(bytes); + } + + public static TbMsg fromBytes(ByteBuffer buffer) { + try { + MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array()); + TbMsgMetaData metaData = new TbMsgMetaData(); + if (proto.getMetaDataCount() > 0) { + metaData.setData(proto.getMetaData(0).getDataMap()); + } + + EntityId entityId = null; + if (proto.getEntityId() != null) { + entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()); + } + + return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, proto.getData().toByteArray()); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException("Could not parse protobuf for TbMsg", e); + } + } +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java similarity index 91% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java index 8dd4840a84..1bbc7929e2 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbMsgMetaData.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api; +package org.thingsboard.server.common.msg; import lombok.Data; diff --git a/rule-engine/rule-engine-components/src/main/proto/msgqueue.proto b/common/message/src/main/proto/tbmsg.proto similarity index 82% rename from rule-engine/rule-engine-components/src/main/proto/msgqueue.proto rename to common/message/src/main/proto/tbmsg.proto index 1105aeb664..90fa2bdbae 100644 --- a/rule-engine/rule-engine-components/src/main/proto/msgqueue.proto +++ b/common/message/src/main/proto/tbmsg.proto @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -16,8 +16,8 @@ syntax = "proto3"; package msgqueue; -option java_package = "org.thingsboard.rule.engine.queue.cassandra.repository.gen"; -option java_outer_classname = "MsgQueueProtos"; +option java_package = "org.thingsboard.server.common.msg.gen"; +option java_outer_classname = "MsgProtos"; message TbMsgProto { diff --git a/dao/pom.xml b/dao/pom.xml index 1df74c0ba7..a987326930 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -38,7 +38,11 @@ org.thingsboard.common data - + + + org.thingsboard.common + message + org.slf4j slf4j-api @@ -140,6 +144,10 @@ test + + com.google.protobuf + protobuf-java + org.apache.curator curator-x-discovery @@ -198,6 +206,11 @@ org.elasticsearch.client rest + + org.junit.jupiter + junit-jupiter-api + RELEASE + diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java index 94299caeb4..c2f709f05b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java @@ -22,12 +22,21 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.model.type.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + @Slf4j public abstract class CassandraAbstractDao { @Autowired protected CassandraCluster cluster; + private ConcurrentMap preparedStatementMap = new ConcurrentHashMap<>(); + + protected PreparedStatement prepare(String query) { + return preparedStatementMap.computeIfAbsent(query, i -> getSession().prepare(i)); + } + private Session session; private ConsistencyLevel defaultReadLevel; diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java index bad7b9e9b9..7e87fa8fec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.nosql; +import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.ResultSetFuture; import com.datastax.driver.core.Statement; @@ -37,6 +38,8 @@ import javax.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; import static com.datastax.driver.core.querybuilder.QueryBuilder.select; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MsqQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/MsqQueue.java similarity index 76% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MsqQueue.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/MsqQueue.java index 33bd32d35b..345012892c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MsqQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/MsqQueue.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api; +package org.thingsboard.server.dao.queue; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.msg.TbMsg; import java.util.UUID; public interface MsqQueue { - ListenableFuture put(TbMsg msg, UUID nodeId, long clusteredHash); + ListenableFuture put(TbMsg msg, UUID nodeId, long clusterPartition); - ListenableFuture ack(TbMsg msg, UUID nodeId, long clusteredHash); + ListenableFuture ack(TbMsg msg, UUID nodeId, long clusterPartition); - Iterable findUnprocessed(UUID nodeId, long clusteredHash); + Iterable findUnprocessed(UUID nodeId, long clusterPartition); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/tool/QueueBenchmark.java b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java similarity index 84% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/tool/QueueBenchmark.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java index 58510d721c..da991fa97d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/tool/QueueBenchmark.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java @@ -1,4 +1,19 @@ -package org.thingsboard.rule.engine.tool; +/** + * 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.queue; import com.datastax.driver.core.Cluster; import com.datastax.driver.core.HostDistance; @@ -16,9 +31,8 @@ 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.rule.engine.api.MsqQueue; -import org.thingsboard.rule.engine.api.TbMsg; -import org.thingsboard.rule.engine.api.TbMsgMetaData; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.annotation.Nullable; import java.net.InetSocketAddress; @@ -29,9 +43,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -@SpringBootApplication -@EnableAutoConfiguration -@ComponentScan({"org.thingsboard.rule.engine"}) +//@SpringBootApplication +//@EnableAutoConfiguration +//@ComponentScan({"org.thingsboard.rule.engine"}) //@PropertySource("classpath:processing-pipeline.properties") @Slf4j public class QueueBenchmark implements CommandLineRunner { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/jpa/SqlMsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/jpa/SqlMsgQueue.java similarity index 86% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/jpa/SqlMsgQueue.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/jpa/SqlMsgQueue.java index d1bf73cbf4..eb3c9b95f4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/jpa/SqlMsgQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/jpa/SqlMsgQueue.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.jpa; +package org.thingsboard.server.dao.queue.jpa; //@todo-vp: implement public class SqlMsgQueue { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsqQueue.java similarity index 52% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsqQueue.java index 4839761c67..59e10d8f4c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/CassandraMsqQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsqQueue.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,60 +13,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra; +package org.thingsboard.server.dao.service.queue.cassandra; import com.datastax.driver.core.utils.UUIDs; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.api.MsqQueue; -import org.thingsboard.rule.engine.api.TbMsg; -import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; -import org.thingsboard.rule.engine.queue.cassandra.repository.MsgRepository; -import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.dao.queue.MsqQueue; +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; import java.util.List; import java.util.UUID; @Component @Slf4j +@NoSqlDao public class CassandraMsqQueue implements MsqQueue { - private final MsgRepository msgRepository; - private final AckRepository ackRepository; - private final UnprocessedMsgFilter unprocessedMsgFilter; - private final QueuePartitioner queuePartitioner; - - public CassandraMsqQueue(MsgRepository msgRepository, AckRepository ackRepository, - UnprocessedMsgFilter unprocessedMsgFilter, QueuePartitioner queuePartitioner) { - this.msgRepository = msgRepository; - this.ackRepository = ackRepository; - this.unprocessedMsgFilter = unprocessedMsgFilter; - this.queuePartitioner = queuePartitioner; - } - + @Autowired + private MsgRepository msgRepository; + @Autowired + private AckRepository ackRepository; + @Autowired + private UnprocessedMsgFilter unprocessedMsgFilter; + @Autowired + private QueuePartitioner queuePartitioner; @Override - public ListenableFuture put(TbMsg msg, UUID nodeId, long clusteredHash) { + public ListenableFuture put(TbMsg msg, UUID nodeId, long clusterPartition) { long msgTime = getMsgTime(msg); - long partition = queuePartitioner.getPartition(msgTime); - return msgRepository.save(msg, nodeId, clusteredHash, partition, msgTime); + long tsPartition = queuePartitioner.getPartition(msgTime); + return msgRepository.save(msg, nodeId, clusterPartition, tsPartition, msgTime); } @Override - public ListenableFuture ack(TbMsg msg, UUID nodeId, long clusteredHash) { - long partition = queuePartitioner.getPartition(getMsgTime(msg)); - MsgAck ack = new MsgAck(msg.getId(), nodeId, clusteredHash, partition); + public ListenableFuture ack(TbMsg msg, UUID nodeId, long clusterPartition) { + long tsPartition = queuePartitioner.getPartition(getMsgTime(msg)); + MsgAck ack = new MsgAck(msg.getId(), nodeId, clusterPartition, tsPartition); return ackRepository.ack(ack); } @Override - public Iterable findUnprocessed(UUID nodeId, long clusteredHash) { + public Iterable findUnprocessed(UUID nodeId, long clusterPartition) { List unprocessedMsgs = Lists.newArrayList(); - for (Long partition : queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash)) { - List msgs = msgRepository.findMsgs(nodeId, clusteredHash, partition); - List acks = ackRepository.findAcks(nodeId, clusteredHash, partition); + for (Long tsPartition : queuePartitioner.findUnprocessedPartitions(nodeId, clusterPartition)) { + List msgs = msgRepository.findMsgs(nodeId, clusterPartition, tsPartition); + List acks = ackRepository.findAcks(nodeId, clusterPartition, tsPartition); unprocessedMsgs.addAll(unprocessedMsgFilter.filter(msgs, acks)); } return unprocessedMsgs; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/MsgAck.java similarity index 70% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/MsgAck.java index c6885ad41b..fed6e856eb 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/MsgAck.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/MsgAck.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra; +package org.thingsboard.server.dao.service.queue.cassandra; -import com.datastax.driver.core.utils.UUIDs; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.rule.engine.api.TbMsg; -import org.thingsboard.server.common.data.UUIDConverter; import java.util.UUID; @@ -29,7 +26,7 @@ public class MsgAck { private final UUID msgId; private final UUID nodeId; - private final long clusteredHash; - private final long partition; + private final long clusteredPartition; + private final long tsPartition; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitioner.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java similarity index 84% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitioner.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java index 9a7886f54c..faad701d4c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitioner.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java @@ -1,26 +1,27 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors - *

+ * 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 - *

+ * + * 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.queue.cassandra; +package org.thingsboard.server.dao.service.queue.cassandra; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.dao.service.queue.cassandra.repository.ProcessedPartitionRepository; import org.thingsboard.server.dao.timeseries.TsPartitionDate; +import org.thingsboard.server.dao.util.NoSqlDao; import java.time.Clock; import java.time.Instant; @@ -32,26 +33,27 @@ import java.util.UUID; @Component @Slf4j +@NoSqlDao public class QueuePartitioner { - private ProcessedPartitionRepository processedPartitionRepository; - private final TsPartitionDate tsFormat; + private ProcessedPartitionRepository processedPartitionRepository; private Clock clock = Clock.systemUTC(); - public QueuePartitioner(@Value("${rule.queue.msg_partitioning}") String partitioning, + public QueuePartitioner(@Value("${cassandra.queue.partitioning}") String partitioning, ProcessedPartitionRepository processedPartitionRepository) { this.processedPartitionRepository = processedPartitionRepository; Optional partition = TsPartitionDate.parse(partitioning); if (partition.isPresent()) { tsFormat = partition.get(); } else { - log.warn("Incorrect configuration of partitioning {}", "MINUTES"); - throw new RuntimeException("Failed to parse partitioning property: " + "MINUTES" + "!"); + log.warn("Incorrect configuration of partitioning {}", partitioning); + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); } } public long getPartition(long ts) { + //TODO: use TsPartitionDate.truncateTo? LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilter.java similarity index 87% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilter.java index e114a8507d..4dcd351763 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilter.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra; +package org.thingsboard.server.dao.service.queue.cassandra; import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.api.TbMsg; +import org.thingsboard.server.common.msg.TbMsg; import java.util.Collection; import java.util.List; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/AckRepository.java similarity index 73% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/AckRepository.java index 40c0416957..d7cdb0cc06 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/AckRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/AckRepository.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra.repository; +package org.thingsboard.server.dao.service.queue.cassandra.repository; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.rule.engine.queue.cassandra.MsgAck; +import org.thingsboard.server.dao.service.queue.cassandra.MsgAck; import java.util.List; import java.util.UUID; @@ -25,5 +25,5 @@ public interface AckRepository { ListenableFuture ack(MsgAck msgAck); - List findAcks(UUID nodeId, long clusteredHash, long partition); + List findAcks(UUID nodeId, long clusterPartition, long tsPartition); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/MsgRepository.java similarity index 72% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/MsgRepository.java index 5d34d84c3b..d54f1af59f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/MsgRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/MsgRepository.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra.repository; +package org.thingsboard.server.dao.service.queue.cassandra.repository; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.rule.engine.api.TbMsg; +import org.thingsboard.server.common.msg.TbMsg; import java.util.List; import java.util.UUID; public interface MsgRepository { - ListenableFuture save(TbMsg msg, UUID nodeId, long clusteredHash, long partition, long msgTs); + ListenableFuture save(TbMsg msg, UUID nodeId, long clusterPartition, long tsPartition, long msgTs); - List findMsgs(UUID nodeId, long clusteredHash, long partition); + List findMsgs(UUID nodeId, long clusterPartition, long tsPartition); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/ProcessedPartitionRepository.java similarity index 88% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/ProcessedPartitionRepository.java index 807c0017c6..a50ab6143e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/ProcessedPartitionRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/ProcessedPartitionRepository.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra.repository; +package org.thingsboard.server.dao.service.queue.cassandra.repository; import com.google.common.util.concurrent.ListenableFuture; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepository.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepository.java similarity index 60% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepository.java index 57dc79c49b..1f62f2bf4e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepository.java @@ -1,62 +1,62 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors - *

+ * 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 - *

+ * + * 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.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; import com.datastax.driver.core.*; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.queue.cassandra.MsgAck; -import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; +import org.thingsboard.server.dao.nosql.CassandraAbstractDao; +import org.thingsboard.server.dao.service.queue.cassandra.MsgAck; +import org.thingsboard.server.dao.service.queue.cassandra.repository.AckRepository; +import org.thingsboard.server.dao.util.NoSqlDao; import java.util.ArrayList; import java.util.List; import java.util.UUID; @Component -public class CassandraAckRepository extends SimpleAbstractCassandraDao implements AckRepository { +@NoSqlDao +public class CassandraAckRepository extends CassandraAbstractDao implements AckRepository { - private final int ackQueueTtl; - - public CassandraAckRepository(Session session, int ackQueueTtl) { - super(session); - this.ackQueueTtl = ackQueueTtl; - } + @Value("${cassandra.queue.ack.ttl}") + private int ackQueueTtl; @Override public ListenableFuture ack(MsgAck msgAck) { - String insert = "INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) VALUES (?, ?, ?, ?) USING TTL ?"; + String insert = "INSERT INTO msg_ack_queue (node_id, cluster_partition, ts_partition, msg_id) VALUES (?, ?, ?, ?) USING TTL ?"; PreparedStatement statement = prepare(insert); - BoundStatement boundStatement = statement.bind(msgAck.getNodeId(), msgAck.getClusteredHash(), - msgAck.getPartition(), msgAck.getMsgId(), ackQueueTtl); + BoundStatement boundStatement = statement.bind(msgAck.getNodeId(), msgAck.getClusteredPartition(), + msgAck.getTsPartition(), msgAck.getMsgId(), ackQueueTtl); ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); return Futures.transform(resultSetFuture, (Function) input -> null); } @Override - public List findAcks(UUID nodeId, long clusteredHash, long partition) { + public List findAcks(UUID nodeId, long clusterPartition, long tsPartition) { String select = "SELECT msg_id FROM msg_ack_queue WHERE " + - "node_id = ? AND clustered_hash = ? AND partition = ?"; + "node_id = ? AND cluster_partition = ? AND ts_partition = ?"; PreparedStatement statement = prepare(select); - BoundStatement boundStatement = statement.bind(nodeId, clusteredHash, partition); + BoundStatement boundStatement = statement.bind(nodeId, clusterPartition, tsPartition); ResultSet rows = executeRead(boundStatement); List msgs = new ArrayList<>(); for (Row row : rows) { - msgs.add(new MsgAck(row.getUUID("msg_id"), nodeId, clusteredHash, partition)); + msgs.add(new MsgAck(row.getUUID("msg_id"), nodeId, clusterPartition, tsPartition)); } return msgs; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepository.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepository.java new file mode 100644 index 0000000000..2a70a8909a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepository.java @@ -0,0 +1,63 @@ +/** + * 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.service.queue.cassandra.repository.impl; + +import com.datastax.driver.core.*; +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.dao.nosql.CassandraAbstractDao; +import org.thingsboard.server.dao.service.queue.cassandra.repository.MsgRepository; +import org.thingsboard.server.dao.util.NoSqlDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Component +@NoSqlDao +public class CassandraMsgRepository extends CassandraAbstractDao implements MsgRepository { + + @Value("${cassandra.queue.msg.ttl}") + private int msqQueueTtl; + + @Override + public ListenableFuture save(TbMsg msg, UUID nodeId, long clusterPartition, long tsPartition, long msgTs) { + String insert = "INSERT INTO msg_queue (node_id, cluster_partition, ts_partition, ts, msg) VALUES (?, ?, ?, ?, ?) USING TTL ?"; + PreparedStatement statement = prepare(insert); + BoundStatement boundStatement = statement.bind(nodeId, clusterPartition, tsPartition, msgTs, TbMsg.toBytes(msg), msqQueueTtl); + ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); + return Futures.transform(resultSetFuture, (Function) input -> null); + } + + @Override + public List findMsgs(UUID nodeId, long clusterPartition, long tsPartition) { + String select = "SELECT node_id, cluster_partition, ts_partition, ts, msg FROM msg_queue WHERE " + + "node_id = ? AND cluster_partition = ? AND ts_partition = ?"; + PreparedStatement statement = prepare(select); + BoundStatement boundStatement = statement.bind(nodeId, clusterPartition, tsPartition); + ResultSet rows = executeRead(boundStatement); + List msgs = new ArrayList<>(); + for (Row row : rows) { + msgs.add(TbMsg.fromBytes(row.getBytes("msg"))); + } + return msgs; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java similarity index 61% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java index 7fc15d8207..b0eacfa1b3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepository.java @@ -1,53 +1,53 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors - *

+ * 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 - *

+ * + * 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.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; import com.datastax.driver.core.*; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.dao.nosql.CassandraAbstractDao; +import org.thingsboard.server.dao.service.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.dao.util.NoSqlDao; import java.util.Optional; import java.util.UUID; @Component -public class CassandraProcessedPartitionRepository extends SimpleAbstractCassandraDao implements ProcessedPartitionRepository { +@NoSqlDao +public class CassandraProcessedPartitionRepository extends CassandraAbstractDao implements ProcessedPartitionRepository { - private final int repositoryTtl; - - public CassandraProcessedPartitionRepository(Session session, int repositoryTtl) { - super(session); - this.repositoryTtl = repositoryTtl; - } + @Value("${cassandra.queue.partitions.ttl}") + private int partitionsTtl; @Override - public ListenableFuture partitionProcessed(UUID nodeId, long clusteredHash, long partition) { - String insert = "INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) VALUES (?, ?, ?) USING TTL ?"; + public ListenableFuture partitionProcessed(UUID nodeId, long clusterPartition, long tsPartition) { + String insert = "INSERT INTO processed_msg_partitions (node_id, cluster_partition, ts_partition) VALUES (?, ?, ?) USING TTL ?"; PreparedStatement prepared = prepare(insert); - BoundStatement boundStatement = prepared.bind(nodeId, clusteredHash, partition, repositoryTtl); + BoundStatement boundStatement = prepared.bind(nodeId, clusterPartition, tsPartition, partitionsTtl); ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); return Futures.transform(resultSetFuture, (Function) input -> null); } @Override public Optional findLastProcessedPartition(UUID nodeId, long clusteredHash) { - String select = "SELECT partition FROM processed_msg_partitions WHERE " + - "node_id = ? AND clustered_hash = ?"; + String select = "SELECT ts_partition FROM processed_msg_partitions WHERE " + + "node_id = ? AND cluster_partition = ?"; PreparedStatement prepared = prepare(select); BoundStatement boundStatement = prepared.bind(nodeId, clusteredHash); Row row = executeRead(boundStatement).one(); @@ -55,6 +55,6 @@ public class CassandraProcessedPartitionRepository extends SimpleAbstractCassand return Optional.empty(); } - return Optional.of(row.getLong("partition")); + return Optional.of(row.getLong("ts_partition")); } } diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index 6c62c89395..0ccf2badf8 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -616,11 +616,11 @@ AND compaction = { 'class' : 'LeveledCompactionStrategy' }; CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( node_id timeuuid, - clustered_hash bigint, - partition bigint, + cluster_partition bigint, + ts_partition bigint, ts bigint, msg blob, - PRIMARY KEY ((node_id, clustered_hash, partition), ts)) + PRIMARY KEY ((node_id, cluster_partition, ts_partition), ts)) WITH CLUSTERING ORDER BY (ts DESC) AND compaction = { 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', @@ -633,10 +633,10 @@ AND compaction = { CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( node_id timeuuid, - clustered_hash bigint, - partition bigint, + cluster_partition bigint, + ts_partition bigint, msg_id timeuuid, - PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) + PRIMARY KEY ((node_id, cluster_partition, ts_partition), msg_id)) WITH CLUSTERING ORDER BY (msg_id DESC) AND compaction = { 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', @@ -649,10 +649,10 @@ AND compaction = { CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( node_id timeuuid, - clustered_hash bigint, - partition bigint, - PRIMARY KEY ((node_id, clustered_hash), partition)) -WITH CLUSTERING ORDER BY (partition DESC) + cluster_partition bigint, + ts_partition bigint, + PRIMARY KEY ((node_id, cluster_partition), ts_partition)) +WITH CLUSTERING ORDER BY (ts_partition DESC) AND compaction = { 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', 'min_threshold': '5', diff --git a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java index 7e25baa45e..5e01249fb5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java @@ -26,7 +26,9 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.*ServiceNoSqlTest" + "org.thingsboard.server.dao.service.*ServiceNoSqlTest", + "org.thingsboard.server.dao.service.queue.cassandra.*.*.*Test", + "org.thingsboard.server.dao.service.queue.cassandra.*Test" }) public class NoSqlDaoServiceTestSuite { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitionerTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java similarity index 90% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitionerTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java index a71a737f74..a6ec464e6b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/QueuePartitionerTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java @@ -1,19 +1,19 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors - *

+ * 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 - *

+ * + * 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.queue.cassandra; +package org.thingsboard.server.dao.service.queue.cassandra; import org.junit.Before; @@ -21,7 +21,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; +import org.thingsboard.server.dao.service.queue.cassandra.repository.ProcessedPartitionRepository; import java.time.Clock; import java.time.Instant; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilterTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java similarity index 90% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilterTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java index dec8e35b2b..046b462064 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/UnprocessedMsgFilterTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra; +package org.thingsboard.server.dao.service.queue.cassandra; import com.google.common.collect.Lists; import org.junit.Test; -import org.thingsboard.rule.engine.api.TbMsg; +import org.thingsboard.server.common.msg.TbMsg; import java.util.Collection; import java.util.List; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java similarity index 84% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java index 38b7b9de8f..a86238e3b1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; import com.datastax.driver.core.utils.UUIDs; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; -import org.thingsboard.rule.engine.queue.cassandra.MsgAck; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.dao.service.AbstractServiceTest; +import org.thingsboard.server.dao.service.DaoNoSqlTest; +import org.thingsboard.server.dao.service.queue.cassandra.MsgAck; import java.util.List; import java.util.UUID; @@ -30,15 +33,12 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class CassandraAckRepositoryTest extends SimpleAbstractCassandraDaoTest { +@DaoNoSqlTest +public class CassandraAckRepositoryTest extends AbstractServiceTest { + @Autowired private CassandraAckRepository ackRepository; - @Before - public void init() { - ackRepository = new CassandraAckRepository(cassandraUnit.session, 1); - } - @Test public void acksInPartitionCouldBeFound() { UUID nodeId = UUID.fromString("055eee50-1883-11e8-b380-65b5d5335ba9"); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java similarity index 81% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java index a0827fc5ac..d17e1f2819 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java @@ -1,19 +1,19 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors - *

+ * 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 - *

+ * + * 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.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; //import static org.junit.jupiter.api.Assertions.*; @@ -21,9 +21,12 @@ import com.datastax.driver.core.utils.UUIDs; import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; -import org.thingsboard.rule.engine.api.TbMsg; -import org.thingsboard.rule.engine.api.TbMsgMetaData; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.service.AbstractServiceTest; +import org.thingsboard.server.dao.service.DaoNoSqlTest; import java.util.List; import java.util.UUID; @@ -33,15 +36,12 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class CassandraMsgRepositoryTest extends SimpleAbstractCassandraDaoTest { +@DaoNoSqlTest +public class CassandraMsgRepositoryTest extends AbstractServiceTest { + @Autowired private CassandraMsgRepository msgRepository; - @Before - public void init() { - msgRepository = new CassandraMsgRepository(cassandraUnit.session, 1); - } - @Test public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException { TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]); @@ -58,8 +58,6 @@ public class CassandraMsgRepositoryTest extends SimpleAbstractCassandraDaoTest { UUID nodeId = UUIDs.timeBased(); ListenableFuture future = msgRepository.save(msg, nodeId, 2L, 2L, 2L); future.get(); - List msgs = msgRepository.findMsgs(nodeId, 2L, 2L); - assertEquals(1, msgs.size()); TimeUnit.SECONDS.sleep(2); assertTrue(msgRepository.findMsgs(nodeId, 2L, 2L).isEmpty()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java similarity index 87% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java index a731452f24..1b487903b9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2017 The Thingsboard Authors + * 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. @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.queue.cassandra.repository.impl; +package org.thingsboard.server.dao.service.queue.cassandra.repository.impl; import com.datastax.driver.core.utils.UUIDs; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.dao.service.AbstractServiceTest; +import org.thingsboard.server.dao.service.DaoNoSqlTest; import java.util.List; import java.util.Optional; @@ -29,15 +32,12 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; -public class CassandraProcessedPartitionRepositoryTest extends SimpleAbstractCassandraDaoTest { +@DaoNoSqlTest +public class CassandraProcessedPartitionRepositoryTest extends AbstractServiceTest { + @Autowired private CassandraProcessedPartitionRepository partitionRepository; - @Before - public void init() { - partitionRepository = new CassandraProcessedPartitionRepository(cassandraUnit.session, 1); - } - @Test public void lastProcessedPartitionCouldBeFound() { UUID nodeId = UUID.fromString("055eee50-1883-11e8-b380-65b5d5335ba9"); diff --git a/dao/src/test/resources/cassandra/system-test.cql b/dao/src/test/resources/cassandra/system-test.cql index da5d1f1154..3b05fd71d1 100644 --- a/dao/src/test/resources/cassandra/system-test.cql +++ b/dao/src/test/resources/cassandra/system-test.cql @@ -1,2 +1,27 @@ TRUNCATE thingsboard.plugin; -TRUNCATE thingsboard.rule; \ No newline at end of file +TRUNCATE thingsboard.rule; + +-- msg_queue dataset + +INSERT INTO thingsboard.msg_queue (node_id, cluster_partition, ts_partition, ts, msg) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 201, null); +INSERT INTO thingsboard.msg_queue (node_id, cluster_partition, ts_partition, ts, msg) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 202, null); +INSERT INTO thingsboard.msg_queue (node_id, cluster_partition, ts_partition, ts, msg) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, 301, null); + +-- ack_queue dataset +INSERT INTO thingsboard.msg_ack_queue (node_id, cluster_partition, ts_partition, msg_id) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, bebaeb60-1888-11e8-bf21-65b5d5335ba9); +INSERT INTO thingsboard.msg_ack_queue (node_id, cluster_partition, ts_partition, msg_id) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, 12baeb60-1888-11e8-bf21-65b5d5335ba9); +INSERT INTO thingsboard.msg_ack_queue (node_id, cluster_partition, ts_partition, msg_id) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 32baeb60-1888-11e8-bf21-65b5d5335ba9); + +-- processed partition dataset +INSERT INTO thingsboard.processed_msg_partitions (node_id, cluster_partition, ts_partition) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 100); +INSERT INTO thingsboard.processed_msg_partitions (node_id, cluster_partition, ts_partition) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 777); +INSERT INTO thingsboard.processed_msg_partitions (node_id, cluster_partition, ts_partition) + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 202, 200); \ 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 482c6e7ed3..7c02666675 100644 --- a/dao/src/test/resources/nosql-test.properties +++ b/dao/src/test/resources/nosql-test.properties @@ -1 +1,6 @@ -database.type=cassandra \ No newline at end of file +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 diff --git a/pom.xml b/pom.xml index 121644a266..f331e32fb2 100755 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ 1.8.10.RELEASE 2.9.0 0.7.0 - 2.2.0 + 2.2.0 4.12 1.7.7 1.2.3 @@ -79,17 +79,19 @@ 2.5.3 1.2.1 9.4.1211 - org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* + org/thingsboard/server/gen/**/*, + org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* + 5.0.2 common + rule-engine dao extensions-api extensions-core extensions - rule-engine transport ui tools @@ -371,6 +373,11 @@ data ${project.version} + + org.thingsboard.rule-engine + rule-engine-api + ${project.version} + org.thingsboard.common message diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index e23f8714e3..29b5cdc4ef 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -1,6 +1,6 @@ + +

+ +
+

rulechain.add

+ +
+ + + +
+
+ + + +
+ +
+
+ + + + {{ 'action.add' | translate }} + + {{ 'action.cancel' | + translate }} + + +
+ diff --git a/ui/src/app/rulechain/index.js b/ui/src/app/rulechain/index.js new file mode 100644 index 0000000000..faecc03e03 --- /dev/null +++ b/ui/src/app/rulechain/index.js @@ -0,0 +1,25 @@ +/* + * 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. + */ + +import RuleChainRoutes from './rulechain.routes'; +import RuleChainController from './rulechain.controller'; +import RuleChainDirective from './rulechain.directive'; + +export default angular.module('thingsboard.ruleChain', []) + .config(RuleChainRoutes) + .controller('RuleChainController', RuleChainController) + .directive('tbRuleChain', RuleChainDirective) + .name; diff --git a/ui/src/app/rulechain/rulechain-card.tpl.html b/ui/src/app/rulechain/rulechain-card.tpl.html new file mode 100644 index 0000000000..48a572c8e0 --- /dev/null +++ b/ui/src/app/rulechain/rulechain-card.tpl.html @@ -0,0 +1,18 @@ + +
rulechain.system
diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html new file mode 100644 index 0000000000..ec68c574bf --- /dev/null +++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html @@ -0,0 +1,49 @@ + +{{ 'rulechain.export' | translate }} +{{ 'rulechain.delete' | translate }} + +
+ + + rulechain.copyId + +
+ + +
+ + + +
+
rulechain.name-required
+
+
+ + + + +
+
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js new file mode 100644 index 0000000000..475ef4e09a --- /dev/null +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -0,0 +1,172 @@ +/* + * 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. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import addRuleChainTemplate from './add-rulechain.tpl.html'; +import ruleChainCard from './rulechain-card.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function RuleChainController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) { + + var ruleChainActionsList = [ + { + onAction: function ($event, item) { + exportRuleChain($event, item); + }, + name: function() { $translate.instant('action.export') }, + details: function() { return $translate.instant('rulechain.export') }, + icon: "file_download" + }, + { + onAction: function ($event, item) { + vm.grid.deleteItem($event, item); + }, + name: function() { return $translate.instant('action.delete') }, + details: function() { return $translate.instant('rulechain.delete') }, + icon: "delete", + isEnabled: isRuleChainEditable + } + ]; + + var ruleChainAddItemActionsList = [ + { + onAction: function ($event) { + vm.grid.addItem($event); + }, + name: function() { return $translate.instant('action.create') }, + details: function() { return $translate.instant('rulechain.create-new-rulechain') }, + icon: "insert_drive_file" + }, + { + onAction: function ($event) { + importExport.importRuleChain($event).then( + function() { + vm.grid.refreshList(); + } + ); + }, + name: function() { return $translate.instant('action.import') }, + details: function() { return $translate.instant('rulechain.import') }, + icon: "file_upload" + } + ]; + + var vm = this; + + vm.types = types; + + vm.ruleChainGridConfig = { + + refreshParamsFunc: null, + + deleteItemTitleFunc: deleteRuleChainTitle, + deleteItemContentFunc: deleteRuleChainText, + deleteItemsTitleFunc: deleteRuleChainsTitle, + deleteItemsActionTitleFunc: deleteRuleChainsActionTitle, + deleteItemsContentFunc: deleteRuleChainsText, + + fetchItemsFunc: fetchRuleChains, + saveItemFunc: saveRuleChain, + deleteItemFunc: deleteRuleChain, + + getItemTitleFunc: getRuleChainTitle, + itemCardTemplateUrl: ruleChainCard, + parentCtl: vm, + + actionsList: ruleChainActionsList, + addItemActions: ruleChainAddItemActionsList, + + onGridInited: gridInited, + + addItemTemplateUrl: addRuleChainTemplate, + + addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') }, + noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') }, + itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') }, + isSelectionEnabled: isRuleChainEditable, + isDetailsReadOnly: function(ruleChain) { + return !isRuleChainEditable(ruleChain); + } + }; + + if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { + vm.ruleChainGridConfig.items = $stateParams.items; + } + + if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) { + vm.ruleChainGridConfig.topIndex = $stateParams.topIndex; + } + + vm.isRuleChainEditable = isRuleChainEditable; + + vm.exportRuleChain = exportRuleChain; + + function deleteRuleChainTitle(ruleChain) { + return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name}); + } + + function deleteRuleChainText() { + return $translate.instant('rulechain.delete-rulechain-text'); + } + + function deleteRuleChainsTitle(selectedCount) { + return $translate.instant('rulechain.delete-rulechains-title', {count: selectedCount}, 'messageformat'); + } + + function deleteRuleChainsActionTitle(selectedCount) { + return $translate.instant('rulechain.delete-rulechains-action-title', {count: selectedCount}, 'messageformat'); + } + + function deleteRuleChainsText() { + return $translate.instant('rulechain.delete-rulechains-text'); + } + + function gridInited(grid) { + vm.grid = grid; + } + + function fetchRuleChains(pageLink) { + return ruleChainService.getRuleChains(pageLink); + } + + function saveRuleChain(ruleChain) { + return ruleChainService.saveRuleChain(ruleChain); + } + + function deleteRuleChain(ruleChainId) { + return ruleChainService.deleteRuleChain(ruleChainId); + } + + function getRuleChainTitle(ruleChain) { + return ruleChain ? ruleChain.name : ''; + } + + function isRuleChainEditable(ruleChain) { + if (userService.getAuthority() === 'TENANT_ADMIN') { + return ruleChain && ruleChain.tenantId.id != types.id.nullUid; + } else { + return userService.getAuthority() === 'SYS_ADMIN'; + } + } + + function exportRuleChain($event, ruleChain) { + $event.stopPropagation(); + importExport.exportRuleChain(ruleChain.id.id); + } + +} diff --git a/ui/src/app/rulechain/rulechain.directive.js b/ui/src/app/rulechain/rulechain.directive.js new file mode 100644 index 0000000000..b23cd98136 --- /dev/null +++ b/ui/src/app/rulechain/rulechain.directive.js @@ -0,0 +1,47 @@ +/* + * 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. + */ + +/* eslint-disable import/no-unresolved, import/default */ + +import ruleChainFieldsetTemplate from './rulechain-fieldset.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function RuleChainDirective($compile, $templateCache, $mdDialog, $document, $q, $translate, types, toast) { + var linker = function (scope, element) { + var template = $templateCache.get(ruleChainFieldsetTemplate); + element.html(template); + + scope.onRuleChainIdCopied = function() { + toast.showSuccess($translate.instant('rulechain.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); + }; + + $compile(element.contents())(scope); + } + return { + restrict: "E", + link: linker, + scope: { + ruleChain: '=', + isEdit: '=', + isReadOnly: '=', + theForm: '=', + onExportRuleChain: '&', + onDeleteRuleChain: '&' + } + }; +} diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js new file mode 100644 index 0000000000..e8d6be49d2 --- /dev/null +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -0,0 +1,46 @@ +/* + * 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. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import ruleChainsTemplate from './rulechains.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function RuleChainRoutes($stateProvider) { + + $stateProvider + .state('home.ruleChains', { + url: '/ruleChains', + params: {'topIndex': 0}, + module: 'private', + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: ruleChainsTemplate, + controllerAs: 'vm', + controller: 'RuleChainController' + } + }, + data: { + searchEnabled: true, + pageTitle: 'rulechain.rulechains' + }, + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}' + } + }); +} diff --git a/ui/src/app/rulechain/rulechains.tpl.html b/ui/src/app/rulechain/rulechains.tpl.html new file mode 100644 index 0000000000..a4fbd79a91 --- /dev/null +++ b/ui/src/app/rulechain/rulechains.tpl.html @@ -0,0 +1,75 @@ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js index 9dbddd9485..5d97ea6113 100644 --- a/ui/src/app/services/menu.service.js +++ b/ui/src/app/services/menu.service.js @@ -78,6 +78,12 @@ function Menu(userService, $state, $rootScope) { state: 'home.rules', icon: 'settings_ethernet' }, + { + name: 'rulechain.rulechains', + type: 'link', + state: 'home.ruleChains', + icon: 'settings_ethernet' + }, { name: 'tenant.tenants', type: 'link', @@ -127,6 +133,16 @@ function Menu(userService, $state, $rootScope) { } ] }, + { + name: 'rulechain.management', + places: [ + { + name: 'rulechain.rulechains', + icon: 'settings_ethernet', + state: 'home.ruleChains' + } + ] + }, { name: 'tenant.management', places: [ @@ -182,6 +198,12 @@ function Menu(userService, $state, $rootScope) { state: 'home.rules', icon: 'settings_ethernet' }, + { + name: 'rulechain.rulechains', + type: 'link', + state: 'home.ruleChains', + icon: 'settings_ethernet' + }, { name: 'customer.customers', type: 'link', @@ -235,6 +257,16 @@ function Menu(userService, $state, $rootScope) { } ] }, + { + name: 'rulechain.management', + places: [ + { + name: 'rulechain.rulechains', + icon: 'settings_ethernet', + state: 'home.ruleChains' + } + ] + }, { name: 'customer.management', places: [ From 7bc74a1c7d0757c778c65070a5cf8b7128021500 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Thu, 15 Mar 2018 11:34:16 +0200 Subject: [PATCH 012/171] count white&black list host requests for logging correct statistic --- .../quota/inmemory/HostRequestIntervalRegistry.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistry.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistry.java index 8d254a08c6..3782ed22ed 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistry.java +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistry.java @@ -61,13 +61,14 @@ public class HostRequestIntervalRegistry { } public long tick(String clientHostId) { + IntervalCount intervalCount = hostCounts.computeIfAbsent(clientHostId, s -> new IntervalCount(intervalDurationMs)); + long currentCount = intervalCount.resetIfExpiredAndTick(); if (whiteList.contains(clientHostId)) { return 0; } else if (blackList.contains(clientHostId)) { return Long.MAX_VALUE; } - IntervalCount intervalCount = hostCounts.computeIfAbsent(clientHostId, s -> new IntervalCount(intervalDurationMs)); - return intervalCount.resetIfExpiredAndTick(); + return currentCount; } public void clean() { From 00352231b1c0e90250dc991a6b5c2c68c403078d Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 15 Mar 2018 15:17:32 +0200 Subject: [PATCH 013/171] LifeCycle Events for RuleChainActor --- .../server/actors/app/AppActor.java | 64 ++++--------- .../actors/ruleChain/RuleChainActor.java | 59 ++++++++++++ .../RuleChainActorMessageProcessor.java | 93 +++++++++++++++++++ .../ruleChain/RuleChainManagerActor.java | 43 +++++++++ .../server/actors/service/ActorService.java | 9 +- .../actors/service/DefaultActorService.java | 25 ++--- .../actors/shared/EntityActorsManager.java | 68 ++++++++++++++ .../actors/shared/plugin/PluginManager.java | 57 +++--------- .../shared/plugin/SystemPluginManager.java | 4 +- .../shared/plugin/TenantPluginManager.java | 5 +- .../shared/rulechain/RuleChainManager.java | 30 ++++++ .../rulechain/SystemRuleChainManager.java | 47 ++++++++++ .../rulechain/TenantRuleChainManager.java | 58 ++++++++++++ .../server/actors/tenant/TenantActor.java | 87 +++++------------ .../server/controller/PluginController.java | 8 +- .../controller/RuleChainController.java | 7 ++ .../server/controller/RuleController.java | 8 +- .../common/data/page/PageDataIterable.java | 1 + .../server/common/msg/MsgType.java | 7 ++ .../server/common/msg/RuleMsg.java | 38 -------- .../server/common/msg/TbActorMsg.java | 10 ++ .../msg/plugin/ComponentLifecycleMsg.java | 42 ++++----- 22 files changed, 513 insertions(+), 257 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java delete mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java 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 b475277630..549d71d984 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -22,18 +22,15 @@ import akka.event.LoggingAdapter; import akka.japi.Function; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.plugin.PluginTerminationMsg; -import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.DefaultActorService; -import org.thingsboard.server.actors.shared.plugin.PluginManager; import org.thingsboard.server.actors.shared.plugin.SystemPluginManager; -import org.thingsboard.server.actors.shared.rule.RuleManager; -import org.thingsboard.server.actors.shared.rule.SystemRuleManager; -import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; +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.RuleId; +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.cluster.ClusterEventMsg; @@ -43,27 +40,21 @@ import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; -import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg; import scala.concurrent.duration.Duration; import java.util.HashMap; import java.util.Map; -import java.util.Optional; -public class AppActor extends ContextAwareActor { +public class AppActor extends RuleChainManagerActor { private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); - private final RuleManager ruleManager; - private final PluginManager pluginManager; private final TenantService tenantService; private final Map tenantActors; private AppActor(ActorSystemContext systemContext) { - super(systemContext); - this.ruleManager = new SystemRuleManager(systemContext); - this.pluginManager = new SystemPluginManager(systemContext); + super(systemContext, new SystemRuleChainManager(systemContext), new SystemPluginManager(systemContext)); this.tenantService = systemContext.getTenantService(); this.tenantActors = new HashMap<>(); } @@ -77,8 +68,7 @@ public class AppActor extends ContextAwareActor { public void preStart() { logger.info("Starting main system actor."); try { - ruleManager.init(this.context()); - pluginManager.init(this.context()); + initRuleChains(); if (systemContext.isTenantComponentsInitEnabled()) { PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT); @@ -102,8 +92,6 @@ public class AppActor extends ContextAwareActor { processDeviceMsg((ToDeviceActorMsg) msg); } else if (msg instanceof ToPluginActorMsg) { onToPluginMsg((ToPluginActorMsg) msg); - } else if (msg instanceof ToRuleActorMsg) { - onToRuleMsg((ToRuleActorMsg) msg); } else if (msg instanceof ToDeviceActorNotificationMsg) { onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg); } else if (msg instanceof Terminated) { @@ -128,20 +116,10 @@ public class AppActor extends ContextAwareActor { tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); } - private void onToRuleMsg(ToRuleActorMsg msg) { - ActorRef target; - if (SYSTEM_TENANT.equals(msg.getTenantId())) { - target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId()); - } else { - target = getOrCreateTenantActor(msg.getTenantId()); - } - target.tell(msg, ActorRef.noSender()); - } - private void onToPluginMsg(ToPluginActorMsg msg) { ActorRef target; if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) { - target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId()); + target = pluginManager.getOrCreateActor(this.context(), msg.getPluginId()); } else { target = getOrCreateTenantActor(msg.getPluginTenantId()); } @@ -149,26 +127,16 @@ public class AppActor extends ContextAwareActor { } private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { - ActorRef target = null; + ActorRef target; if (SYSTEM_TENANT.equals(msg.getTenantId())) { - Optional pluginId = msg.getPluginId(); - Optional ruleId = msg.getRuleId(); - if (pluginId.isPresent()) { - target = pluginManager.getOrCreatePluginActor(this.context(), pluginId.get()); - } else if (ruleId.isPresent()) { - Optional ref = ruleManager.update(this.context(), ruleId.get(), msg.getEvent()); - if (ref.isPresent()) { - target = ref.get(); - } else { - logger.debug("Failed to find actor for rule: [{}]", ruleId); - return; - } - } + target = getEntityActorRef(msg.getEntityId()); } else { target = getOrCreateTenantActor(msg.getTenantId()); } if (target != null) { target.tell(msg, ActorRef.noSender()); + } else { + logger.debug("Invalid component lifecycle msg: {}", msg); } } @@ -180,7 +148,7 @@ public class AppActor extends ContextAwareActor { TenantId tenantId = toDeviceActorMsg.getTenantId(); ActorRef tenantActor = getOrCreateTenantActor(tenantId); if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) { - tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self()); +// tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self()); } else { tenantActor.tell(toDeviceActorMsg, context().self()); } 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 new file mode 100644 index 0000000000..83536f83da --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.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.server.actors.ruleChain; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ComponentActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; + +public class RuleChainActor extends ComponentActor { + + private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId) { + super(systemContext, tenantId, ruleChainId); + setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChainId, systemContext, + logger, context().parent(), context().self())); + } + + @Override + public void onReceive(Object msg) throws Exception { + logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName()); + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final TenantId tenantId; + private final RuleChainId ruleChainId; + + public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId pluginId) { + super(context); + this.tenantId = tenantId; + this.ruleChainId = pluginId; + } + + @Override + public RuleChainActor create() throws Exception { + return new RuleChainActor(context, tenantId, ruleChainId); + } + } + + @Override + protected long getErrorPersistFrequency() { + return systemContext.getPluginErrorPersistFrequency(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java new file mode 100644 index 0000000000..3075367863 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.actors.ruleChain; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.event.LoggingAdapter; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.plugin.*; +import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +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.plugin.ComponentLifecycleState; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.extensions.api.plugins.Plugin; +import org.thingsboard.server.extensions.api.plugins.PluginInitializationException; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; + +/** + * @author Andrew Shvayka + */ +public class RuleChainActorMessageProcessor extends ComponentMsgProcessor { + + private ComponentLifecycleState state; + + protected RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId pluginId, ActorSystemContext systemContext + , LoggingAdapter logger, ActorRef parent, ActorRef self) { + super(systemContext, logger, tenantId, pluginId); + } + + @Override + public void start() throws Exception { + + } + + @Override + public void stop() throws Exception { + + } + + @Override + public void onCreated(ActorContext context) throws Exception { + + } + + @Override + public void onUpdate(ActorContext context) throws Exception { + + } + + @Override + public void onActivate(ActorContext context) throws Exception { + + } + + @Override + public void onSuspend(ActorContext context) throws Exception { + + } + + @Override + public void onStop(ActorContext context) throws Exception { + + } + + @Override + public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { + + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java new file mode 100644 index 0000000000..2dca8ddb0d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java @@ -0,0 +1,43 @@ +package org.thingsboard.server.actors.ruleChain; + +import akka.actor.ActorRef; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.shared.plugin.PluginManager; +import org.thingsboard.server.actors.shared.rulechain.RuleChainManager; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.RuleChainId; + +/** + * Created by ashvayka on 15.03.18. + */ +public abstract class RuleChainManagerActor extends ContextAwareActor { + + protected final RuleChainManager ruleChainManager; + protected final PluginManager pluginManager; + + public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager, PluginManager pluginManager) { + super(systemContext); + this.ruleChainManager = ruleChainManager; + this.pluginManager = pluginManager; + } + + protected void initRuleChains() { + pluginManager.init(this.context()); + ruleChainManager.init(this.context()); + } + + protected ActorRef getEntityActorRef(EntityId entityId) { + ActorRef target = null; + switch (entityId.getEntityType()) { + case PLUGIN: + target = pluginManager.getOrCreateActor(this.context(), (PluginId) entityId); + break; + case RULE_CHAIN: + target = ruleChainManager.getOrCreateActor(this.context(), (RuleChainId) entityId); + break; + } + return target; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java index baae376c64..0f66d256ed 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java @@ -15,10 +15,7 @@ */ package org.thingsboard.server.actors.service; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.PluginId; -import org.thingsboard.server.common.data.id.RuleId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.transport.SessionMsgProcessor; import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener; @@ -26,9 +23,7 @@ import org.thingsboard.server.service.cluster.rpc.RpcMsgListener; public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor, RestMsgProcessor, RpcMsgListener, DiscoveryServiceListener { - void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state); - - void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state); + void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId); diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index bb84a30229..7b6268760b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -30,10 +30,7 @@ import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; import org.thingsboard.server.actors.session.SessionManagerActor; import org.thingsboard.server.actors.stats.StatsActor; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.PluginId; -import org.thingsboard.server.common.data.id.RuleId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.aware.SessionAwareMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; @@ -212,15 +209,9 @@ public class DefaultActorService implements ActorService { } @Override - public void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state) { - log.trace("[{}] Processing onPluginStateChange event: {}", pluginId, state); - broadcast(ComponentLifecycleMsg.forPlugin(tenantId, pluginId, state)); - } - - @Override - public void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state) { - log.trace("[{}] Processing onRuleStateChange event: {}", ruleId, state); - broadcast(ComponentLifecycleMsg.forRule(tenantId, ruleId, state)); + public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) { + log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state); + broadcast(new ComponentLifecycleMsg(tenantId, entityId, state)); } @Override diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java new file mode 100644 index 0000000000..a98609ceee --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java @@ -0,0 +1,68 @@ +package org.thingsboard.server.actors.shared; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.actor.UntypedActor; +import akka.japi.Creator; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.data.plugin.PluginMetaData; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by ashvayka on 15.03.18. + */ +@Slf4j +public abstract class EntityActorsManager> { + + protected final ActorSystemContext systemContext; + protected final Map actors; + + public EntityActorsManager(ActorSystemContext systemContext) { + this.systemContext = systemContext; + this.actors = new HashMap<>(); + } + + protected abstract TenantId getTenantId(); + + protected abstract String getDispatcherName(); + + protected abstract Creator creator(T entityId); + + protected abstract PageDataIterable.FetchFunction getFetchEntitiesFunction(); + + public void init(ActorContext context) { + for (M entity : new PageDataIterable<>(getFetchEntitiesFunction(), ContextAwareActor.ENTITY_PACK_LIMIT)) { + log.debug("[{}] Creating plugin actor", entity.getId()); + //TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa. + getOrCreateActor(context, (T) entity.getId()); + log.debug("[{}] Plugin actor created.", entity.getId()); + } + } + + + public ActorRef getOrCreateActor(ActorContext context, T entityId) { + return actors.computeIfAbsent(entityId, eId -> + context.actorOf(Props.create(creator(eId)) + .withDispatcher(getDispatcherName()), eId.toString())); + } + + public void broadcast(Object msg) { + actors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); + } + + public void remove(T id) { + actors.remove(id); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java index 4f5871f585..5142de2c15 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,63 +15,28 @@ */ package org.thingsboard.server.actors.shared.plugin; -import akka.actor.ActorContext; -import akka.actor.ActorRef; -import akka.actor.Props; +import akka.japi.Creator; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.plugin.PluginActor; -import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.shared.EntityActorsManager; import org.thingsboard.server.common.data.id.PluginId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageDataIterable; -import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.dao.plugin.PluginService; -import java.util.HashMap; -import java.util.Map; - @Slf4j -public abstract class PluginManager { +public abstract class PluginManager extends EntityActorsManager { - protected final ActorSystemContext systemContext; protected final PluginService pluginService; - protected final Map pluginActors; public PluginManager(ActorSystemContext systemContext) { - this.systemContext = systemContext; + super(systemContext); this.pluginService = systemContext.getPluginService(); - this.pluginActors = new HashMap<>(); } - public void init(ActorContext context) { - PageDataIterable pluginIterator = new PageDataIterable<>(getFetchPluginsFunction(), - ContextAwareActor.ENTITY_PACK_LIMIT); - for (PluginMetaData plugin : pluginIterator) { - log.debug("[{}] Creating plugin actor", plugin.getId()); - getOrCreatePluginActor(context, plugin.getId()); - log.debug("Plugin actor created."); - } + @Override + public Creator creator(PluginId entityId){ + return new PluginActor.ActorCreator(systemContext, getTenantId(), entityId); } - abstract FetchFunction getFetchPluginsFunction(); - - abstract TenantId getTenantId(); - - abstract String getDispatcherName(); - - public ActorRef getOrCreatePluginActor(ActorContext context, PluginId pluginId) { - return pluginActors.computeIfAbsent(pluginId, pId -> - context.actorOf(Props.create(new PluginActor.ActorCreator(systemContext, getTenantId(), pId)) - .withDispatcher(getDispatcherName()), pId.toString())); - } - - public void broadcast(Object msg) { - pluginActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); - } - - public void remove(PluginId id) { - pluginActors.remove(id); - } } diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java index 0888e23f25..88c52a6043 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java @@ -29,12 +29,12 @@ public class SystemPluginManager extends PluginManager { } @Override - FetchFunction getFetchPluginsFunction() { + protected FetchFunction getFetchEntitiesFunction() { return pluginService::findSystemPlugins; } @Override - TenantId getTenantId() { + protected TenantId getTenantId() { return BasePluginService.SYSTEM_TENANT; } diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java index 14ea2aa7f8..09115f0f7d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java @@ -19,6 +19,7 @@ import akka.actor.ActorContext; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; import org.thingsboard.server.common.data.plugin.PluginMetaData; @@ -39,12 +40,12 @@ public class TenantPluginManager extends PluginManager { } @Override - FetchFunction getFetchPluginsFunction() { + protected FetchFunction getFetchEntitiesFunction() { return link -> pluginService.findTenantPlugins(tenantId, link); } @Override - TenantId getTenantId() { + protected TenantId getTenantId() { return tenantId; } diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java new file mode 100644 index 0000000000..09396110b4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java @@ -0,0 +1,30 @@ +package org.thingsboard.server.actors.shared.rulechain; + +import akka.japi.Creator; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.ruleChain.RuleChainActor; +import org.thingsboard.server.actors.shared.EntityActorsManager; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.rule.RuleChainService; + +/** + * Created by ashvayka on 15.03.18. + */ +@Slf4j +public abstract class RuleChainManager extends EntityActorsManager { + + protected final RuleChainService service; + + public RuleChainManager(ActorSystemContext systemContext) { + super(systemContext); + this.service = systemContext.getRuleChainService(); + } + + @Override + public Creator creator(RuleChainId entityId) { + return new RuleChainActor.ActorCreator(systemContext, getTenantId(), entityId); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java new file mode 100644 index 0000000000..a8bb069685 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.actors.shared.rulechain; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.actors.shared.plugin.PluginManager; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.plugin.BasePluginService; + +public class SystemRuleChainManager extends RuleChainManager { + + public SystemRuleChainManager(ActorSystemContext systemContext) { + super(systemContext); + } + + @Override + protected FetchFunction getFetchEntitiesFunction() { + return service::findSystemRuleChains; + } + + @Override + protected TenantId getTenantId() { + return BasePluginService.SYSTEM_TENANT; + } + + @Override + protected String getDispatcherName() { + return DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java new file mode 100644 index 0000000000..aa2a63104a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.actors.shared.rulechain; + +import akka.actor.ActorContext; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.actors.shared.rule.RuleManager; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleMetaData; + +public class TenantRuleChainManager extends RuleChainManager { + + private final TenantId tenantId; + + public TenantRuleChainManager(ActorSystemContext systemContext, TenantId tenantId) { + super(systemContext); + this.tenantId = tenantId; + } + + @Override + public void init(ActorContext context) { + if (systemContext.isTenantComponentsInitEnabled()) { + super.init(context); + } + } + + @Override + protected TenantId getTenantId() { + return tenantId; + } + + @Override + protected String getDispatcherName() { + return DefaultActorService.TENANT_RULE_DISPATCHER_NAME; + } + + @Override + protected FetchFunction getFetchEntitiesFunction() { + return link -> service.findTenantRuleChains(tenantId, link); + } +} 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 b923fe15a3..86211794bf 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,52 +15,38 @@ */ package org.thingsboard.server.actors.tenant; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.event.Logging; +import akka.event.LoggingAdapter; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.device.DeviceActor; import org.thingsboard.server.actors.plugin.PluginTerminationMsg; -import org.thingsboard.server.actors.rule.ComplexRuleActorChain; -import org.thingsboard.server.actors.rule.RuleActorChain; -import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.DefaultActorService; -import org.thingsboard.server.actors.shared.plugin.PluginManager; import org.thingsboard.server.actors.shared.plugin.TenantPluginManager; -import org.thingsboard.server.actors.shared.rule.RuleManager; -import org.thingsboard.server.actors.shared.rule.TenantRuleManager; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.PluginId; -import org.thingsboard.server.common.data.id.RuleId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; - -import akka.actor.ActorRef; -import akka.actor.Props; -import akka.event.Logging; -import akka.event.LoggingAdapter; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; -import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg; -public class TenantActor extends ContextAwareActor { +import java.util.HashMap; +import java.util.Map; + +public class TenantActor extends RuleChainManagerActor { private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); private final TenantId tenantId; - private final RuleManager ruleManager; - private final PluginManager pluginManager; private final Map deviceActors; private TenantActor(ActorSystemContext systemContext, TenantId tenantId) { - super(systemContext); + super(systemContext, new TenantRuleChainManager(systemContext, tenantId), new TenantPluginManager(systemContext, tenantId)); this.tenantId = tenantId; - this.ruleManager = new TenantRuleManager(systemContext, tenantId); - this.pluginManager = new TenantPluginManager(systemContext, tenantId); this.deviceActors = new HashMap<>(); } @@ -68,8 +54,7 @@ public class TenantActor extends ContextAwareActor { public void preStart() { logger.info("[{}] Starting tenant actor.", tenantId); try { - ruleManager.init(this.context()); - pluginManager.init(this.context()); + initRuleChains(); logger.info("[{}] Tenant actor started.", tenantId); } catch (Exception e) { logger.error(e, "[{}] Unknown failure", tenantId); @@ -79,14 +64,10 @@ public class TenantActor extends ContextAwareActor { @Override public void onReceive(Object msg) throws Exception { logger.debug("[{}] Received message: {}", tenantId, msg); - if (msg instanceof RuleChainDeviceMsg) { - process((RuleChainDeviceMsg) msg); - } else if (msg instanceof ToDeviceActorMsg) { + if (msg instanceof ToDeviceActorMsg) { onToDeviceActorMsg((ToDeviceActorMsg) msg); } else if (msg instanceof ToPluginActorMsg) { onToPluginMsg((ToPluginActorMsg) msg); - } else if (msg instanceof ToRuleActorMsg) { - onToRuleMsg((ToRuleActorMsg) msg); } else if (msg instanceof ToDeviceActorNotificationMsg) { onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg); } else if (msg instanceof ClusterEventMsg) { @@ -113,14 +94,9 @@ public class TenantActor extends ContextAwareActor { getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender()); } - private void onToRuleMsg(ToRuleActorMsg msg) { - ActorRef target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId()); - target.tell(msg, ActorRef.noSender()); - } - private void onToPluginMsg(ToPluginActorMsg msg) { if (msg.getPluginTenantId().equals(tenantId)) { - ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId()); + ActorRef pluginActor = pluginManager.getOrCreateActor(this.context(), msg.getPluginId()); pluginActor.tell(msg, ActorRef.noSender()); } else { context().parent().tell(msg, ActorRef.noSender()); @@ -128,23 +104,11 @@ public class TenantActor extends ContextAwareActor { } private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { - Optional pluginId = msg.getPluginId(); - Optional ruleId = msg.getRuleId(); - if (pluginId.isPresent()) { - ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), pluginId.get()); - pluginActor.tell(msg, ActorRef.noSender()); - } else if (ruleId.isPresent()) { - ActorRef target; - Optional ref = ruleManager.update(this.context(), ruleId.get(), msg.getEvent()); - if (ref.isPresent()) { - target = ref.get(); - } else { - logger.debug("Failed to find actor for rule: [{}]", ruleId); - return; - } + ActorRef target = getEntityActorRef(msg.getEntityId()); + if (target != null) { target.tell(msg, ActorRef.noSender()); } else { - logger.debug("[{}] Invalid component lifecycle msg.", tenantId); + logger.debug("Invalid component lifecycle msg: {}", msg); } } @@ -152,13 +116,6 @@ public class TenantActor extends ContextAwareActor { pluginManager.remove(msg.getId()); } - private void process(RuleChainDeviceMsg msg) { - ToDeviceActorMsg toDeviceActorMsg = msg.getToDeviceActorMsg(); - ActorRef deviceActor = getOrCreateDeviceActor(toDeviceActorMsg.getDeviceId()); - RuleActorChain tenantChain = ruleManager.getRuleChain(this.context()); - RuleActorChain chain = new ComplexRuleActorChain(msg.getRuleChain(), tenantChain); - deviceActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, chain), context().self()); - } private ActorRef getOrCreateDeviceActor(DeviceId deviceId) { return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId)) diff --git a/application/src/main/java/org/thingsboard/server/controller/PluginController.java b/application/src/main/java/org/thingsboard/server/controller/PluginController.java index 2c69248faa..ed1760057b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/PluginController.java +++ b/application/src/main/java/org/thingsboard/server/controller/PluginController.java @@ -71,7 +71,7 @@ public class PluginController extends BaseController { boolean created = source.getId() == null; source.setTenantId(getCurrentUser().getTenantId()); PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source)); - actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), + actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); logEntityAction(plugin.getId(), plugin, @@ -97,7 +97,7 @@ public class PluginController extends BaseController { PluginId pluginId = new PluginId(toUUID(strPluginId)); PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); pluginService.activatePluginById(pluginId); - actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED); + actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED); logEntityAction(plugin.getId(), plugin, null, @@ -123,7 +123,7 @@ public class PluginController extends BaseController { PluginId pluginId = new PluginId(toUUID(strPluginId)); PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); pluginService.suspendPluginById(pluginId); - actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED); + actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED); logEntityAction(plugin.getId(), plugin, null, @@ -221,7 +221,7 @@ public class PluginController extends BaseController { PluginId pluginId = new PluginId(toUUID(strPluginId)); PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); pluginService.deletePluginById(pluginId); - actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED); + actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED); logEntityAction(pluginId, plugin, null, 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 f24646b56f..93891ac53a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -78,6 +78,9 @@ public class RuleChainController extends BaseController { ruleChain.setTenantId(getCurrentUser().getTenantId()); RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); + actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), + created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + logEntityAction(savedRuleChain.getId(), savedRuleChain, null, created ? ActionType.ADDED : ActionType.UPDATED, null); @@ -100,6 +103,8 @@ public class RuleChainController extends BaseController { RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId()); RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(ruleChainMetaData)); + actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); + logEntityAction(ruleChain.getId(), ruleChain, null, ActionType.UPDATED, null, ruleChainMetaData); @@ -183,6 +188,8 @@ public class RuleChainController extends BaseController { RuleChain ruleChain = checkRuleChain(ruleChainId); ruleChainService.deleteRuleChainById(ruleChainId); + actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED); + logEntityAction(ruleChainId, ruleChain, null, ActionType.DELETED, null, strRuleChainId); diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleController.java b/application/src/main/java/org/thingsboard/server/controller/RuleController.java index e498c8fffc..9a269029b5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleController.java @@ -73,7 +73,7 @@ public class RuleController extends BaseController { boolean created = source.getId() == null; source.setTenantId(getCurrentUser().getTenantId()); RuleMetaData rule = checkNotNull(ruleService.saveRule(source)); - actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), + actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); logEntityAction(rule.getId(), rule, @@ -99,7 +99,7 @@ public class RuleController extends BaseController { RuleId ruleId = new RuleId(toUUID(strRuleId)); RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); ruleService.activateRuleById(ruleId); - actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED); + actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED); logEntityAction(rule.getId(), rule, null, @@ -125,7 +125,7 @@ public class RuleController extends BaseController { RuleId ruleId = new RuleId(toUUID(strRuleId)); RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); ruleService.suspendRuleById(ruleId); - actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED); + actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED); logEntityAction(rule.getId(), rule, null, @@ -219,7 +219,7 @@ public class RuleController extends BaseController { RuleId ruleId = new RuleId(toUUID(strRuleId)); RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); ruleService.deleteRuleById(ruleId); - actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED); + actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED); logEntityAction(ruleId, rule, null, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java index 34f8c3aed6..ffd78225c4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.NoSuchElementException; import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UUIDBased; public class PageDataIterable> implements Iterable, Iterator { 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 new file mode 100644 index 0000000000..55c131d12d --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java @@ -0,0 +1,7 @@ +package org.thingsboard.server.common.msg; + +/** + * Created by ashvayka on 15.03.18. + */ +public enum MsgType { +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java deleted file mode 100644 index ee352ad722..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java +++ /dev/null @@ -1,38 +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.common.msg; - -import org.thingsboard.server.common.data.rule.Scope; -import org.thingsboard.server.common.data.rule.RuleType; -import org.thingsboard.server.common.msg.aware.RuleAwareMsg; - -/** - * Message that is used to deliver some data to the rule instance. - * For example: aggregated statistics or command decoded from http request. - * - * @author ashvayka - * - * @param - payload - */ -public interface RuleMsg extends RuleAwareMsg { - - Scope getRuleLevel(); - - RuleType getRuleType(); - - V getPayload(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java new file mode 100644 index 0000000000..e7668ff227 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java @@ -0,0 +1,10 @@ +package org.thingsboard.server.common.msg; + +/** + * Created by ashvayka on 15.03.18. + */ +public interface TbActorMsg { + + MsgType getMsgType(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java index d48c3feb10..b77722e826 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,14 +15,12 @@ */ package org.thingsboard.server.common.msg.plugin; -import lombok.Data; import lombok.Getter; import lombok.ToString; -import org.thingsboard.server.common.data.id.PluginId; -import org.thingsboard.server.common.data.id.RuleId; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; +import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.msg.aware.TenantAwareMsg; import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; @@ -35,31 +33,27 @@ import java.util.Optional; public class ComponentLifecycleMsg implements TenantAwareMsg, ToAllNodesMsg { @Getter private final TenantId tenantId; - private final PluginId pluginId; - private final RuleId ruleId; + @Getter + private final EntityId entityId; @Getter private final ComponentLifecycleEvent event; - public static ComponentLifecycleMsg forPlugin(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent event) { - return new ComponentLifecycleMsg(tenantId, pluginId, null, event); - } - - public static ComponentLifecycleMsg forRule(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent event) { - return new ComponentLifecycleMsg(tenantId, null, ruleId, event); - } - - private ComponentLifecycleMsg(TenantId tenantId, PluginId pluginId, RuleId ruleId, ComponentLifecycleEvent event) { + public ComponentLifecycleMsg(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent event) { this.tenantId = tenantId; - this.pluginId = pluginId; - this.ruleId = ruleId; + this.entityId = entityId; this.event = event; } public Optional getPluginId() { - return Optional.ofNullable(pluginId); + return entityId.getEntityType() == EntityType.PLUGIN ? Optional.of((PluginId) entityId) : Optional.empty(); } public Optional getRuleId() { - return Optional.ofNullable(ruleId); + return entityId.getEntityType() == EntityType.RULE ? Optional.of((RuleId) entityId) : Optional.empty(); + } + + public Optional getRuleChainId() { + return entityId.getEntityType() == EntityType.RULE_CHAIN ? Optional.of((RuleChainId) entityId) : Optional.empty(); } + } From edf24f5d6b3fd87257cfe3e35ad0da4fc2451c8a Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 16 Mar 2018 19:26:59 +0200 Subject: [PATCH 014/171] Enrich Rule Node with Tenant/Customer/Self attributes --- .../server/common/data/Customer.java | 2 +- .../server/common/data/Device.java | 2 +- .../server/common/data/HasCustomerId.java | 23 +++++++ .../server/common/data/HasTenantId.java | 23 +++++++ .../thingsboard/server/common/data/User.java | 2 +- .../server/common/data/alarm/Alarm.java | 3 +- .../server/common/data/asset/Asset.java | 7 +- .../common/data/plugin/PluginMetaData.java | 3 +- .../server/common/data/rule/RuleChain.java | 3 +- .../server/common/data/rule/RuleMetaData.java | 3 +- .../rule/engine/api/TbContext.java | 26 ++++++++ .../rule/engine/api/TbNodeException.java | 4 ++ .../thingsboard/rule/engine/DonAsynchron.java | 40 +++++++++++ .../engine/metadata/TbEntityGetAttrNode.java | 66 +++++++++++++++++++ .../engine/metadata/TbGetAttributesNode.java | 41 ++++++------ .../metadata/TbGetCustomerAttributeNode.java | 49 ++++++++++++++ .../TbGetEntityAttrNodeConfiguration.java | 27 ++++++++ .../metadata/TbGetTenantAttributeNode.java | 62 +++++++++++++++++ .../TbGetCustomerAttributeNodeTest.java | 44 +++++++++++++ 19 files changed, 399 insertions(+), 31 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasCustomerId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasTenantId.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java index 03115a9940..078c97b1c0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import com.fasterxml.jackson.databind.JsonNode; -public class Customer extends ContactBased implements HasName { +public class Customer extends ContactBased implements HasName, HasTenantId { private static final long serialVersionUID = -1599722990298929275L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index 13fa011d9d..95662c1292 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import com.fasterxml.jackson.databind.JsonNode; @EqualsAndHashCode(callSuper = true) -public class Device extends SearchTextBasedWithAdditionalInfo implements HasName { +public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId { private static final long serialVersionUID = 2807343040519543363L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasCustomerId.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasCustomerId.java new file mode 100644 index 0000000000..e89eba8bbb --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasCustomerId.java @@ -0,0 +1,23 @@ +/** + * 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.data; + +import org.thingsboard.server.common.data.id.CustomerId; + +public interface HasCustomerId { + + CustomerId getCustomerId(); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasTenantId.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasTenantId.java new file mode 100644 index 0000000000..ebba003cc8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasTenantId.java @@ -0,0 +1,23 @@ +/** + * 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.data; + +import org.thingsboard.server.common.data.id.TenantId; + +public interface HasTenantId { + + TenantId getTenantId(); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index c893d644d9..3b957764fc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.security.Authority; import com.fasterxml.jackson.databind.JsonNode; @EqualsAndHashCode(callSuper = true) -public class User extends SearchTextBasedWithAdditionalInfo implements HasName { +public class User extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId { private static final long serialVersionUID = 8250339805336035966L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index 70f5042a75..125406c4a1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -22,6 +22,7 @@ import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,7 +32,7 @@ import org.thingsboard.server.common.data.id.TenantId; @Data @Builder @AllArgsConstructor -public class Alarm extends BaseData implements HasName { +public class Alarm extends BaseData implements HasName, HasTenantId { private TenantId tenantId; private String type; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java index cc3c111724..c7b246c1ee 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java @@ -17,16 +17,13 @@ package org.thingsboard.server.common.data.asset; import com.fasterxml.jackson.databind.JsonNode; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.HasAdditionalInfo; -import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.SearchTextBased; -import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @EqualsAndHashCode(callSuper = true) -public class Asset extends SearchTextBasedWithAdditionalInfo implements HasName { +public class Asset extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId { private static final long serialVersionUID = 2807343040519543363L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java index 8576264d5b..4c33ffe65c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.TenantId; @@ -32,7 +33,7 @@ import java.io.IOException; @EqualsAndHashCode(callSuper = true) @Slf4j -public class PluginMetaData extends SearchTextBasedWithAdditionalInfo implements HasName { +public class PluginMetaData extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { private static final long serialVersionUID = 1L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java index e82c850b9d..f2ba0cc66e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java @@ -21,6 +21,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -29,7 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId; @Data @EqualsAndHashCode(callSuper = true) @Slf4j -public class RuleChain extends SearchTextBasedWithAdditionalInfo implements HasName { +public class RuleChain extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { private static final long serialVersionUID = -5656679015121935465L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java index 98adeb7742..953e5eb6bc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java @@ -23,6 +23,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.RuleId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; @Data @EqualsAndHashCode(callSuper = true) @Slf4j -public class RuleMetaData extends SearchTextBasedWithAdditionalInfo implements HasName { +public class RuleMetaData extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { private static final long serialVersionUID = -5656679015122935465L; 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 07cd72c001..e852500808 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 @@ -17,7 +17,15 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.plugin.PluginService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.rule.RuleService; +import org.thingsboard.server.dao.user.UserService; import java.util.UUID; @@ -40,6 +48,24 @@ public interface TbContext { void ack(UUID msg); + void tellError(TbMsg msg, Throwable th); + AttributesService getAttributesService(); + CustomerService getCustomerService(); + + UserService getUserService(); + + RuleService getRuleService(); + + PluginService getPluginService(); + + AssetService getAssetService(); + + DeviceService getDeviceService(); + + AlarmService getAlarmService(); + + RuleChainService getRuleChainService(); + } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java index 6766999d50..b42ec8ec13 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java @@ -22,6 +22,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; */ public class TbNodeException extends Exception { + public TbNodeException(String message) { + super(message); + } + public TbNodeException(Exception e) { super(e); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.java new file mode 100644 index 0000000000..18ad3949cf --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.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; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +import javax.annotation.Nullable; +import java.util.function.Consumer; + +public class DonAsynchron { + + public static void withCallback(ListenableFuture future, Consumer onSuccess, Consumer onFailure) { + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable T result) { + onSuccess.accept(result); + } + + @Override + public void onFailure(Throwable t) { + onFailure.accept(t); + } + }); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java new file mode 100644 index 0000000000..834daa1041 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java @@ -0,0 +1,66 @@ +/** + * 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.metadata; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.List; + +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; + +public abstract class TbEntityGetAttrNode implements TbNode { + + private TbGetEntityAttrNodeConfiguration config; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbGetEntityAttrNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + withCallback( + findEntityAsync(ctx, msg.getOriginator()), + entityId -> withCallback( + ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()), + attributes -> putAttributesAndTell(ctx, msg, attributes), + t -> ctx.tellError(msg, t) + ), + t -> ctx.tellError(msg, t)); + } + + private void putAttributesAndTell(TbContext ctx, TbMsg msg, List attributes) { + attributes.forEach(r -> { + String attrName = config.getAttrMapping().get(r.getKey()); + msg.getMetaData().putValue(attrName, r.getValueAsString()); + }); + ctx.tellNext(msg); + } + + @Override + public void destroy() { + + } + + protected abstract ListenableFuture findEntityAsync(TbContext ctx, EntityId originator); + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 11c644c429..34357ee3b2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -15,23 +15,27 @@ */ package org.thingsboard.rule.engine.metadata; +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.Futures; +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.DataConstants; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.dao.attributes.AttributesService; import java.util.List; +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; +import static org.thingsboard.server.common.data.DataConstants.*; + /** * Created by ashvayka on 19.01.18. */ @Slf4j public class TbGetAttributesNode implements TbNode { - TbGetAttributesNodeConfiguration config; + private TbGetAttributesNodeConfiguration config; @Override public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { @@ -40,26 +44,25 @@ public class TbGetAttributesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { - try { - //TODO: refactor this to work async and fetch attributes from cache. - AttributesService service = ctx.getAttributesService(); - fetchAttributes(msg, service, config.getClientAttributeNames(), DataConstants.CLIENT_SCOPE, "cs."); - fetchAttributes(msg, service, config.getServerAttributeNames(), DataConstants.SERVER_SCOPE, "ss."); - fetchAttributes(msg, service, config.getSharedAttributeNames(), DataConstants.SHARED_SCOPE, "shared."); - ctx.tellNext(msg); - } catch (Exception e) { - log.warn("[{}][{}] Failed to fetch attributes", msg.getOriginator(), msg.getId(), e); - throw new TbNodeException(e); - } + ListenableFuture> future = Futures.successfulAsList( + putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs."), + putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared."), + putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss.")); + + withCallback(future, i -> ctx.tellNext(msg), t -> ctx.tellError(msg, t)); } - private void fetchAttributes(TbMsg msg, AttributesService service, List attributeNames, String scope, String prefix) throws InterruptedException, java.util.concurrent.ExecutionException { - if (attributeNames != null && attributeNames.isEmpty()) { - List attributes = service.find(msg.getOriginator(), scope, attributeNames).get(); - attributes.forEach(attr -> msg.getMetaData().putValue(prefix + attr.getKey(), attr.getValueAsString())); - } + private ListenableFuture putAttributesAsync(TbMsg msg, List attributes, String prefix) { + attributes.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString())); + return Futures.immediateFuture(null); } + private ListenableFuture putAttrAsync(TbContext ctx, TbMsg msg, String scope, List attributes, String prefix) { + return Futures.transform(ctx.getAttributesService().find(msg.getOriginator(), scope, attributes), + (AsyncFunction, Void>) i -> putAttributesAsync(msg, i, prefix)); + } + + @Override public void destroy() { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java new file mode 100644 index 0000000000..a138886f9a --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -0,0 +1,49 @@ +/** + * 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.metadata; + +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.id.*; + +public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { + + @Override + protected ListenableFuture findEntityAsync(TbContext ctx, EntityId originator) { + + switch (originator.getEntityType()) { + case CUSTOMER: + return Futures.immediateFuture((CustomerId) originator); + case USER: + return getCustomerAsync(ctx.getUserService().findUserByIdAsync((UserId) originator)); + case ASSET: + return getCustomerAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) originator)); + case DEVICE: + return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) originator)); + default: + return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType " + originator)); + } + } + + private ListenableFuture getCustomerAsync(ListenableFuture future) { + return Futures.transform(future, (AsyncFunction) in -> Futures.immediateFuture(in.getCustomerId())); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java new file mode 100644 index 0000000000..bd53e83c07 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java @@ -0,0 +1,27 @@ +/** + * 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.metadata; + +import lombok.Data; + +import java.util.Map; +import java.util.Optional; + +@Data +public class TbGetEntityAttrNodeConfiguration { + + private Map attrMapping; +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java new file mode 100644 index 0000000000..eaa61eac04 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -0,0 +1,62 @@ +/** + * 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.metadata; + +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.alarm.AlarmId; +import org.thingsboard.server.common.data.id.*; + +@Slf4j +public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { + + @Override + protected ListenableFuture findEntityAsync(TbContext ctx, EntityId originator) { + + switch (originator.getEntityType()) { + case TENANT: + return Futures.immediateFuture((TenantId) originator); + case CUSTOMER: + return getTenantAsync(ctx.getCustomerService().findCustomerByIdAsync((CustomerId) originator)); + case USER: + return getTenantAsync(ctx.getUserService().findUserByIdAsync((UserId) originator)); + case RULE: + return getTenantAsync(ctx.getRuleService().findRuleByIdAsync((RuleId) originator)); + case PLUGIN: + return getTenantAsync(ctx.getPluginService().findPluginByIdAsync((PluginId) originator)); + case ASSET: + return getTenantAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) originator)); + case DEVICE: + return getTenantAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) originator)); + case ALARM: + return getTenantAsync(ctx.getAlarmService().findAlarmByIdAsync((AlarmId) originator)); + case RULE_CHAIN: + return getTenantAsync(ctx.getRuleChainService().findRuleChainByIdAsync((RuleChainId) originator)); + default: + return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType " + originator)); + } + } + + private ListenableFuture getTenantAsync(ListenableFuture future) { + return Futures.transform(future, (AsyncFunction) in -> Futures.immediateFuture(in.getTenantId())); + } + +} 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 new file mode 100644 index 0000000000..5daf619bba --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.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.rule.engine.metadata; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.msg.TbMsg; + +import static org.junit.Assert.*; + +@RunWith(MockitoJUnitRunner.class) +public class TbGetCustomerAttributeNodeTest { + + //todo-vp: return to this after fixing compilation problem with protobuff +// private TbGetCustomerAttributeNode node; +// +// @Mock +// private TbContext ctx; +// +// private TbMsg msg; +// +// @Test +// public void customerAttributeAddedInMetadata() { +// msg = new TbMsg(); +// node.onMsg(ctx, ); +// } + +} \ No newline at end of file From aec6b8c223c7a0878bd8bf01fc8f0a5f08df2651 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Mon, 19 Mar 2018 18:45:28 +0200 Subject: [PATCH 015/171] Rule node exception handling, add unit tests --- .../thingsboard/rule/engine/DonAsynchron.java | 7 +- .../engine/metadata/TbEntityGetAttrNode.java | 28 ++- .../engine/metadata/TbGetAttributesNode.java | 2 +- .../metadata/TbGetCustomerAttributeNode.java | 4 +- .../metadata/TbGetTenantAttributeNode.java | 4 +- .../TbGetCustomerAttributeNodeTest.java | 220 ++++++++++++++++-- 6 files changed, 231 insertions(+), 34 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.java index 18ad3949cf..4fed574638 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.java @@ -28,7 +28,12 @@ public class DonAsynchron { Futures.addCallback(future, new FutureCallback() { @Override public void onSuccess(@Nullable T result) { - onSuccess.accept(result); + try { + onSuccess.accept(result); + } catch (Throwable th) { + onFailure(th); + } + } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java index 834daa1041..7ba7dbccce 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -38,14 +38,18 @@ public abstract class TbEntityGetAttrNode implements TbNode @Override public void onMsg(TbContext ctx, TbMsg msg) { - withCallback( - findEntityAsync(ctx, msg.getOriginator()), - entityId -> withCallback( - ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()), - attributes -> putAttributesAndTell(ctx, msg, attributes), - t -> ctx.tellError(msg, t) - ), - t -> ctx.tellError(msg, t)); + try { + withCallback( + findEntityAsync(ctx, msg.getOriginator()), + entityId -> withCallback( + ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()), + attributes -> putAttributesAndTell(ctx, msg, attributes), + t -> ctx.tellError(msg, t) + ), + t -> ctx.tellError(msg, t)); + } catch (Throwable th) { + ctx.tellError(msg, th); + } } private void putAttributesAndTell(TbContext ctx, TbMsg msg, List attributes) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 34357ee3b2..4d41921740 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -44,7 +44,7 @@ public class TbGetAttributesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { - ListenableFuture> future = Futures.successfulAsList( + ListenableFuture> future = Futures.allAsList( putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs."), putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared."), putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss.")); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index a138886f9a..57f9b79e7b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -43,7 +43,9 @@ public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode } private ListenableFuture getCustomerAsync(ListenableFuture future) { - return Futures.transform(future, (AsyncFunction) in -> Futures.immediateFuture(in.getCustomerId())); + return Futures.transform(future, (AsyncFunction) in -> { + return in != null ? Futures.immediateFuture(in.getCustomerId()) + : Futures.immediateFailedFuture(new IllegalStateException("Customer not found"));}); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index eaa61eac04..2cf9a97d20 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -56,7 +56,9 @@ public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { } private ListenableFuture getTenantAsync(ListenableFuture future) { - return Futures.transform(future, (AsyncFunction) in -> Futures.immediateFuture(in.getTenantId())); + return Futures.transform(future, (AsyncFunction) in -> { + return in != null ? Futures.immediateFuture(in.getTenantId()) + : Futures.immediateFailedFuture(new IllegalStateException("Tenant not found"));}); } } 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 5daf619bba..ae41c00b20 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,30 +15,214 @@ */ package org.thingsboard.rule.engine.metadata; +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.User; +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.UserId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.user.UserService; -import static org.junit.Assert.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @RunWith(MockitoJUnitRunner.class) public class TbGetCustomerAttributeNodeTest { - //todo-vp: return to this after fixing compilation problem with protobuff -// private TbGetCustomerAttributeNode node; -// -// @Mock -// private TbContext ctx; -// -// private TbMsg msg; -// -// @Test -// public void customerAttributeAddedInMetadata() { -// msg = new TbMsg(); -// node.onMsg(ctx, ); -// } + private TbGetCustomerAttributeNode node; + + @Mock + private TbContext ctx; + + @Mock + private AttributesService attributesService; + @Mock + private UserService userService; + @Mock + private AssetService assetService; + @Mock + private DeviceService deviceService; + + private TbMsg msg; + + @Before + public void init() throws TbNodeException { + TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration(); + Map attrMapping = new HashMap<>(); + attrMapping.putIfAbsent("temperature", "tempo"); + config.setAttrMapping(attrMapping); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); + nodeConfiguration.setData(mapper.valueToTree(config)); + + node = new TbGetCustomerAttributeNode(); + node.init(nodeConfiguration, null); + } + + @Test + public void errorThrownIfCannotLoadAttributes() { + UserId userId = new UserId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + User user = new User(); + user.setCustomerId(customerId); + + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); + + when(ctx.getUserService()).thenReturn(userService); + when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); + + when(ctx.getAttributesService()).thenReturn(attributesService); + when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature"))) + .thenThrow(new IllegalStateException("something wrong")); + + node.onMsg(ctx, msg); + final ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellError(same(msg), captor.capture()); + + Throwable value = captor.getValue(); + assertEquals("something wrong", value.getMessage()); + assertTrue(msg.getMetaData().getData().isEmpty()); + } + + @Test + public void errorThrownIfCannotLoadAttributesAsync() { + UserId userId = new UserId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + User user = new User(); + user.setCustomerId(customerId); + + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); + + when(ctx.getUserService()).thenReturn(userService); + when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); + + when(ctx.getAttributesService()).thenReturn(attributesService); + when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFailedFuture(new IllegalStateException("something wrong"))); + + node.onMsg(ctx, msg); + final ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellError(same(msg), captor.capture()); + + Throwable value = captor.getValue(); + assertEquals("something wrong", value.getMessage()); + assertTrue(msg.getMetaData().getData().isEmpty()); + } + + @Test + public void errorThrownIfCustomerCannotBeFound() { + UserId userId = new UserId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + User user = new User(); + user.setCustomerId(customerId); + + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); + + when(ctx.getUserService()).thenReturn(userService); + when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null)); + + node.onMsg(ctx, msg); + final ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellError(same(msg), captor.capture()); + + Throwable value = captor.getValue(); + assertEquals(IllegalStateException.class, value.getClass()); + assertEquals("Customer not found", value.getMessage()); + assertTrue(msg.getMetaData().getData().isEmpty()); + } + + @Test + public void customerAttributeAddedInMetadata() { + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), new byte[4]); + entityAttributeFetched(customerId); + } + + @Test + public void usersCustomerAttributesFetched() { + UserId userId = new UserId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + User user = new User(); + user.setCustomerId(customerId); + + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); + + when(ctx.getUserService()).thenReturn(userService); + when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); + + entityAttributeFetched(customerId); + } + + @Test + public void assetsCustomerAttributesFetched() { + AssetId assetId = new AssetId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + Asset asset = new Asset(); + asset.setCustomerId(customerId); + + msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), new byte[4]); + + when(ctx.getAssetService()).thenReturn(assetService); + when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset)); + + entityAttributeFetched(customerId); + } + + @Test + public void deviceCustomerAttributesFetched() { + DeviceId deviceId = new DeviceId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + Device device = new Device(); + device.setCustomerId(customerId); + + msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), new byte[4]); + + when(ctx.getDeviceService()).thenReturn(deviceService); + when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device)); + + entityAttributeFetched(customerId); + } + + private void entityAttributeFetched(CustomerId customerId) { + List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); + + when(ctx.getAttributesService()).thenReturn(attributesService); + when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(attributes)); + node.onMsg(ctx, msg); + verify(ctx).tellNext(msg); + assertEquals(msg.getMetaData().getValue("tempo"), "high"); + } } \ No newline at end of file From e68ee8b299b1f143cb8249ff12cdf06926947f26 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Mon, 19 Mar 2018 19:33:04 +0200 Subject: [PATCH 016/171] add Timeseries GetAttribute node --- .../rule/engine/api/TbContext.java | 3 ++ .../engine/metadata/TbEntityGetAttrNode.java | 22 +++++++++- .../TbGetEntityAttrNodeConfiguration.java | 1 + .../TbGetCustomerAttributeNodeTest.java | 44 +++++++++++++++++-- 4 files changed, 65 insertions(+), 5 deletions(-) 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 e852500808..2908a4a39a 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 @@ -25,6 +25,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.plugin.PluginService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; import java.util.UUID; @@ -68,4 +69,6 @@ public interface TbContext { RuleChainService getRuleChainService(); + TimeseriesService getTimeseriesService(); + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java index 7ba7dbccce..2d6641472c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java @@ -15,14 +15,19 @@ */ package org.thingsboard.rule.engine.metadata; +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; import java.util.List; +import java.util.stream.Collectors; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @@ -42,7 +47,7 @@ public abstract class TbEntityGetAttrNode implements TbNode withCallback( findEntityAsync(ctx, msg.getOriginator()), entityId -> withCallback( - ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()), + config.isTelemetry() ? getLatestTelemetry(ctx, entityId) : getAttributesAsync(ctx, entityId), attributes -> putAttributesAndTell(ctx, msg, attributes), t -> ctx.tellError(msg, t) ), @@ -52,7 +57,20 @@ public abstract class TbEntityGetAttrNode implements TbNode } } - private void putAttributesAndTell(TbContext ctx, TbMsg msg, List attributes) { + private ListenableFuture> getAttributesAsync(TbContext ctx, EntityId entityId) { + ListenableFuture> latest = ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()); + return Futures.transform(latest, (Function, ? extends List>) l -> + l.stream().map(i -> (KvEntry) i).collect(Collectors.toList())); + } + + private ListenableFuture> getLatestTelemetry(TbContext ctx, EntityId entityId) { + ListenableFuture> latest = ctx.getTimeseriesService().findLatest(entityId, config.getAttrMapping().keySet()); + return Futures.transform(latest, (Function, ? extends List>) l -> + l.stream().map(i -> (KvEntry) i).collect(Collectors.toList())); + } + + + private void putAttributesAndTell(TbContext ctx, TbMsg msg, List attributes) { attributes.forEach(r -> { String attrName = config.getAttrMapping().get(r.getKey()); msg.getMetaData().putValue(attrName, r.getValueAsString()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java index bd53e83c07..a5e85c57d7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java @@ -24,4 +24,5 @@ import java.util.Optional; public class TbGetEntityAttrNodeConfiguration { private Map attrMapping; + private boolean isTelemetry = false; } 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 ae41c00b20..23c5d4b4ba 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 @@ -35,14 +35,13 @@ 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.UserId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; import java.util.Collections; @@ -68,6 +67,8 @@ public class TbGetCustomerAttributeNodeTest { @Mock private AttributesService attributesService; @Mock + private TimeseriesService timeseriesService; + @Mock private UserService userService; @Mock private AssetService assetService; @@ -82,6 +83,7 @@ public class TbGetCustomerAttributeNodeTest { Map attrMapping = new HashMap<>(); attrMapping.putIfAbsent("temperature", "tempo"); config.setAttrMapping(attrMapping); + config.setTelemetry(false); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); nodeConfiguration.setData(mapper.valueToTree(config)); @@ -214,6 +216,42 @@ public class TbGetCustomerAttributeNodeTest { entityAttributeFetched(customerId); } + @Test + public void deviceCustomerTelemetryFetched() throws TbNodeException { + TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration(); + Map attrMapping = new HashMap<>(); + attrMapping.putIfAbsent("temperature", "tempo"); + config.setAttrMapping(attrMapping); + config.setTelemetry(true); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); + nodeConfiguration.setData(mapper.valueToTree(config)); + + node = new TbGetCustomerAttributeNode(); + node.init(nodeConfiguration, null); + + + DeviceId deviceId = new DeviceId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + Device device = new Device(); + device.setCustomerId(customerId); + + msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), new byte[4]); + + when(ctx.getDeviceService()).thenReturn(deviceService); + when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device)); + + List timeseries = Lists.newArrayList(new BasicTsKvEntry(1L, new StringDataEntry("temperature", "highest"))); + + when(ctx.getTimeseriesService()).thenReturn(timeseriesService); + when(timeseriesService.findLatest(customerId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(timeseries)); + + node.onMsg(ctx, msg); + verify(ctx).tellNext(msg); + assertEquals(msg.getMetaData().getValue("tempo"), "highest"); + } + private void entityAttributeFetched(CustomerId customerId) { List attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); From 46e79bf66cef2eb42f476e26e6198eb5039f454e Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Mon, 19 Mar 2018 19:51:26 +0200 Subject: [PATCH 017/171] add Related GetAttribute node --- .../rule/engine/api/TbContext.java | 3 ++ .../TbGetRelatedAttrNodeConfiguration.java | 11 +++++ .../metadata/TbGetRelatedAttributeNode.java | 47 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java 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 2908a4a39a..541d35b899 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 @@ -23,6 +23,7 @@ import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.plugin.PluginService; +import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -71,4 +72,6 @@ public interface TbContext { TimeseriesService getTimeseriesService(); + RelationService getRelationService(); + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java new file mode 100644 index 0000000000..40987daeaa --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java @@ -0,0 +1,11 @@ +package org.thingsboard.rule.engine.metadata; + +import lombok.Data; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; + +@Data +public class TbGetRelatedAttrNodeConfiguration { + + private String relationType; + private EntitySearchDirection direction; +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java new file mode 100644 index 0000000000..596b1dfee5 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -0,0 +1,47 @@ +package org.thingsboard.rule.engine.metadata; + +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.apache.commons.collections.CollectionUtils; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeState; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.List; + +import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON; + +public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { + + private TbGetRelatedAttrNodeConfiguration config; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbGetRelatedAttrNodeConfiguration.class); + } + + @Override + protected ListenableFuture findEntityAsync(TbContext ctx, EntityId originator) { + RelationService relationService = ctx.getRelationService(); + if (config.getDirection() == EntitySearchDirection.FROM) { + ListenableFuture> asyncRelation = relationService.findByFromAndTypeAsync(originator, config.getRelationType(), COMMON); + return Futures.transform(asyncRelation, (AsyncFunction, EntityId>) + r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) + : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); + } else if (config.getDirection() == EntitySearchDirection.TO) { + ListenableFuture> asyncRelation = relationService.findByToAndTypeAsync(originator, config.getRelationType(), COMMON); + return Futures.transform(asyncRelation, (AsyncFunction, EntityId>) + r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) + : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); + } + + return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); + } +} From f8929de57861fedd199d0538af5ef823b385dbbc Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 20 Mar 2018 08:51:31 +0200 Subject: [PATCH 018/171] Adding RuleChain and RuleNode actors --- application/pom.xml | 4 + .../server/actors/ActorSystemContext.java | 6 + .../server/actors/app/AppActor.java | 57 ++++--- .../plugin/PluginActorMessageProcessor.java | 8 +- .../rule/RuleActorMessageProcessor.java | 4 +- .../actors/ruleChain/DefaultTbContext.java | 61 ++++++++ .../actors/ruleChain/RuleChainActor.java | 19 ++- .../RuleChainActorMessageProcessor.java | 140 ++++++++++++------ .../ruleChain/RuleChainManagerActor.java | 3 + .../ruleChain/RuleChainToRuleNodeMsg.java | 23 +++ .../actors/ruleChain/RuleNodeActor.java | 72 +++++++++ .../RuleNodeActorMessageProcessor.java | 78 ++++++++++ .../server/actors/ruleChain/RuleNodeCtx.java | 15 ++ .../actors/ruleChain/RuleNodeRelation.java | 17 +++ .../RuleNodeToRuleChainTellNextMsg.java | 24 +++ .../server/actors/service/ActorService.java | 4 + .../server/actors/service/ComponentActor.java | 4 +- .../actors/service/ContextAwareActor.java | 19 +++ .../actors/service/DefaultActorService.java | 6 + .../AbstractContextAwareMsgProcessor.java | 3 - .../actors/shared/ComponentMsgProcessor.java | 31 +++- .../actors/shared/EntityActorsManager.java | 9 +- .../shared/rulechain/RuleChainManager.java | 14 ++ .../server/actors/tenant/TenantActor.java | 57 ++++--- .../src/main/resources/thingsboard.yml | 6 + .../server/common/data/DataConstants.java | 2 + .../common/data/plugin/ComponentType.java | 2 +- .../server/common/msg/MsgType.java | 30 ++++ .../thingsboard/server/common/msg/TbMsg.java | 28 ++-- .../server/common/msg/TbMsgDataType.java | 11 ++ .../msg/plugin/ComponentLifecycleMsg.java | 8 +- .../msg/system/ServiceToRuleEngineMsg.java | 22 +++ common/message/src/main/proto/tbmsg.proto | 12 +- .../server/dao/rule/BaseRuleService.java | 63 +------- .../rule/engine/api/TbContext.java | 2 +- .../engine/transform/TbTransformNode.java | 1 + 36 files changed, 670 insertions(+), 195 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleNodeMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java diff --git a/application/pom.xml b/application/pom.xml index 8449246059..95322fa3a2 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -53,6 +53,10 @@ org.thingsboard extensions-api + + org.thingsboard.rule-engine + rule-engine-api + org.thingsboard extensions-core 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 f59ec63ed7..68d55475af 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -136,6 +136,12 @@ public class ActorSystemContext { @Value("${actors.plugin.error_persist_frequency}") @Getter private long pluginErrorPersistFrequency; + @Value("${actors.rule.chain.error_persist_frequency}") + @Getter private long ruleChainErrorPersistFrequency; + + @Value("${actors.rule.node.error_persist_frequency}") + @Getter private long ruleNodeErrorPersistFrequency; + @Value("${actors.rule.termination.delay}") @Getter private long ruleActorTerminationDelay; 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 549d71d984..5e05a60a14 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 @@ -33,9 +33,11 @@ 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.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; @@ -86,27 +88,48 @@ 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); + protected void process(TbActorMsg msg) { + switch (msg.getMsgType()) { + case COMPONENT_LIFE_CYCLE_MSG: + onComponentLifecycleMsg((ComponentLifecycleMsg) msg); + break; + case SERVICE_TO_RULE_ENGINE_MSG: + onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg); + break; + } + } + + private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { + if (SYSTEM_TENANT.equals(msg.getTenantId())) { + //TODO: ashvayka handle this. } else { - logger.warning("Unknown message: {}!", msg); + getOrCreateTenantActor(msg.getTenantId()).tell(msg, self()); } } + +// @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()); } diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java index 6e78e20521..09aaf80dee 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java @@ -57,7 +57,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor } @Override - public void start() throws Exception { + public void start(ActorContext context) throws Exception { logger.info("[{}] Going to start plugin actor.", entityId); pluginMd = systemContext.getPluginService().findPluginById(entityId); if (pluginMd == null) { @@ -76,7 +76,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor } @Override - public void stop() throws Exception { + public void stop(ActorContext context) throws Exception { onStop(); } @@ -191,7 +191,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor if (pluginImpl != null) { pluginImpl.stop(trustedCtx); } - start(); + start(context); } } @@ -217,7 +217,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor pluginImpl.resume(trustedCtx); logger.info("[{}] Plugin resumed.", entityId); } else { - start(); + start(context); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java index 2ebebfca33..236fca7e0f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java @@ -68,7 +68,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor { } @Override - public void start() throws Exception { + public void start(ActorContext context) throws Exception { logger.info("[{}][{}] Starting rule actor.", entityId, tenantId); ruleMd = systemContext.getRuleService().findRuleById(entityId); if (ruleMd == null) { @@ -86,7 +86,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor { } @Override - public void stop() throws Exception { + public void stop(ActorContext context) throws Exception { onStop(); } 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 new file mode 100644 index 0000000000..0cd7601653 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -0,0 +1,61 @@ +package org.thingsboard.server.actors.ruleChain; + +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.dao.attributes.AttributesService; + +/** + * Created by ashvayka on 19.03.18. + */ +class DefaultTbContext implements TbContext { + + private final ActorSystemContext mainCtx; + private final RuleNodeCtx nodeCtx; + + public DefaultTbContext(ActorSystemContext mainCtx, RuleNodeCtx nodeCtx) { + this.mainCtx = mainCtx; + this.nodeCtx = nodeCtx; + } + + @Override + public void tellNext(TbMsg msg) { + tellNext(msg, null); + } + + @Override + public void tellNext(TbMsg msg, String relationType) { + nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelfId(), relationType, msg), nodeCtx.getSelf()); + } + + @Override + public void tellSelf(TbMsg msg, long delayMs) { + + } + + @Override + public void tellOthers(TbMsg msg) { + + } + + @Override + public void tellSibling(TbMsg msg, ServerAddress address) { + + } + + @Override + public void spawn(TbMsg msg) { + + } + + @Override + public void ack(TbMsg msg) { + + } + + @Override + public AttributesService getAttributesService() { + return mainCtx.getAttributesService(); + } +} 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 83536f83da..20ea05deb4 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 @@ -20,6 +20,9 @@ import org.thingsboard.server.actors.service.ComponentActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; public class RuleChainActor extends ComponentActor { @@ -30,8 +33,18 @@ public class RuleChainActor extends ComponentActor { @@ -54,6 +67,6 @@ public class RuleChainActor extends ComponentActor * 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 - * + *

+ * 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. @@ -17,77 +17,127 @@ package org.thingsboard.server.actors.ruleChain; import akka.actor.ActorContext; import akka.actor.ActorRef; +import akka.actor.Props; import akka.event.LoggingAdapter; -import com.fasterxml.jackson.core.JsonProcessingException; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.plugin.*; +import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.ComponentMsgProcessor; -import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; -import org.thingsboard.server.common.data.plugin.ComponentType; -import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.extensions.api.plugins.Plugin; -import org.thingsboard.server.extensions.api.plugins.PluginInitializationException; -import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; -import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; -import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; -import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; -import org.thingsboard.server.extensions.api.rules.RuleException; +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; +import org.thingsboard.server.dao.rule.RuleChainService; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * @author Andrew Shvayka */ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor { - private ComponentLifecycleState state; + private final ActorRef parent; + private final ActorRef self; + private final Map nodeActors; + private final Map> nodeRoutes; + private final RuleChainService service; + + private RuleNodeId firstId; + private RuleNodeCtx firstNode; - protected RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId pluginId, ActorSystemContext systemContext + RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext , LoggingAdapter logger, ActorRef parent, ActorRef self) { - super(systemContext, logger, tenantId, pluginId); + super(systemContext, logger, tenantId, ruleChainId); + this.parent = parent; + this.self = self; + this.nodeActors = new HashMap<>(); + this.nodeRoutes = new HashMap<>(); + this.service = systemContext.getRuleChainService(); } @Override - public void start() throws Exception { - + public void start(ActorContext context) throws Exception { + RuleChain ruleChain = service.findRuleChainById(entityId); + List ruleNodeList = service.getRuleChainNodes(entityId); + // Creating and starting the actors; + for (RuleNode ruleNode : ruleNodeList) { + String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ? + DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME; + ActorRef ruleNodeActor = context.actorOf( + Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getId())) + .withDispatcher(dispatcherName), ruleNode.toString()); + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(self, ruleNodeActor, ruleNode.getId())); + } + // Populating the routes map; + for (RuleNode ruleNode : ruleNodeList) { + List 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() + "]"); + } + } + nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>()) + .add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType())); + } + } + + firstId = ruleChain.getFirstRuleNodeId(); + firstNode = nodeActors.get(ruleChain.getFirstRuleNodeId()); } @Override - public void stop() throws Exception { - + public void stop(ActorContext context) throws Exception { + nodeActors.values().stream().map(RuleNodeCtx::getSelf).forEach(context::stop); + nodeActors.clear(); + nodeRoutes.clear(); } @Override - public void onCreated(ActorContext context) throws Exception { + public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { } - @Override - public void onUpdate(ActorContext context) throws Exception { - + void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) { + TbMsg tbMsg = envelope.getTbMsg(); + //TODO: push to queue and act on ack in async way + pushMstToNode(firstNode, tbMsg); } - @Override - public void onActivate(ActorContext context) throws Exception { - + void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) { + RuleNodeId originator = envelope.getOriginator(); + String targetRelationType = envelope.getRelationType(); + //TODO: log debug output + List relations = nodeRoutes.get(originator); + for (RuleNodeRelation relation : relations) { + if (targetRelationType == null || targetRelationType.equalsIgnoreCase(relation.getType())) { + switch (relation.getOut().getEntityType()) { + case RULE_NODE: + RuleNodeId targetRuleNodeId = new RuleNodeId(relation.getOut().getId()); + RuleNodeCtx targetRuleNode = nodeActors.get(targetRuleNodeId); + pushMstToNode(targetRuleNode, envelope.getMsg()); + break; + case RULE_CHAIN: +// TODO: implement + break; + } + } + } } - @Override - public void onSuspend(ActorContext context) throws Exception { - - } - - @Override - public void onStop(ActorContext context) throws Exception { - + private void pushMstToNode(RuleNodeCtx nodeCtx, TbMsg msg) { + //TODO: log debug input + firstNode.getSelf().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg), self); } - @Override - public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { - - } } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java index 2dca8ddb0d..cb76f04306 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java @@ -8,6 +8,7 @@ import org.thingsboard.server.actors.shared.rulechain.RuleChainManager; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.dao.rule.RuleChainService; /** * Created by ashvayka on 15.03.18. @@ -16,11 +17,13 @@ public abstract class RuleChainManagerActor extends ContextAwareActor { protected final RuleChainManager ruleChainManager; protected final PluginManager pluginManager; + protected final RuleChainService ruleChainService; public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager, PluginManager pluginManager) { super(systemContext); this.ruleChainManager = ruleChainManager; this.pluginManager = pluginManager; + this.ruleChainService = systemContext.getRuleChainService(); } protected void initRuleChains() { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleNodeMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleNodeMsg.java new file mode 100644 index 0000000000..d6e82621ae --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleNodeMsg.java @@ -0,0 +1,23 @@ +package org.thingsboard.server.actors.ruleChain; + +import lombok.Data; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.TbMsg; + +/** + * Created by ashvayka on 19.03.18. + */ +@Data +final class RuleChainToRuleNodeMsg implements TbActorMsg { + + private final TbContext ctx; + private final TbMsg msg; + + @Override + public MsgType getMsgType() { + return MsgType.RULE_CHAIN_TO_RULE_MSG; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java new file mode 100644 index 0000000000..cba19d17bc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.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.server.actors.ruleChain; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ComponentActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbActorMsg; + +public class RuleNodeActor extends ComponentActor { + + private final RuleChainId ruleChainId; + + private RuleNodeActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + super(systemContext, tenantId, ruleNodeId); + this.ruleChainId = ruleChainId; + setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext, + logger, context().parent(), context().self())); + } + + @Override + protected void process(TbActorMsg msg) { + switch (msg.getMsgType()) { + case RULE_CHAIN_TO_RULE_MSG: + processor.onRuleChainToRuleNodeMsg((RuleChainToRuleNodeMsg) msg); + break; + } + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final TenantId tenantId; + private final RuleChainId ruleChainId; + private final RuleNodeId ruleNodeId; + + public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + super(context); + this.tenantId = tenantId; + this.ruleChainId = ruleChainId; + this.ruleNodeId = ruleNodeId; + + } + + @Override + public RuleNodeActor create() throws Exception { + return new RuleNodeActor(context, tenantId, ruleChainId, ruleNodeId); + } + } + + @Override + protected long getErrorPersistFrequency() { + return systemContext.getRuleNodeErrorPersistFrequency(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java new file mode 100644 index 0000000000..99e48d858d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.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.server.actors.ruleChain; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.event.LoggingAdapter; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; +import org.thingsboard.server.dao.rule.RuleChainService; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Andrew Shvayka + */ +public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor { + + private final ActorRef parent; + private final ActorRef self; + private final RuleChainService service; + + RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext + , LoggingAdapter logger, ActorRef parent, ActorRef self) { + super(systemContext, logger, tenantId, ruleNodeId); + this.parent = parent; + this.self = self; + this.service = systemContext.getRuleChainService(); + } + + @Override + public void start(ActorContext context) throws Exception { + + } + + @Override + public void stop(ActorContext context) throws Exception { + } + + @Override + public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { + + } + + void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) { + + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java new file mode 100644 index 0000000000..dd25f8bc85 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java @@ -0,0 +1,15 @@ +package org.thingsboard.server.actors.ruleChain; + +import akka.actor.ActorRef; +import lombok.Data; +import org.thingsboard.server.common.data.id.RuleNodeId; + +/** + * Created by ashvayka on 19.03.18. + */ +@Data +final class RuleNodeCtx { + private final ActorRef chainActor; + private final ActorRef self; + private final RuleNodeId selfId; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java new file mode 100644 index 0000000000..bd2d544eb3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.actors.ruleChain; + +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; + +/** + * Created by ashvayka on 19.03.18. + */ + +@Data +final class RuleNodeRelation { + + private final EntityId in; + private final EntityId out; + private final String type; + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java new file mode 100644 index 0000000000..95b362591d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java @@ -0,0 +1,24 @@ +package org.thingsboard.server.actors.ruleChain; + +import lombok.Data; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.TbMsg; + +/** + * Created by ashvayka on 19.03.18. + */ +@Data +final class RuleNodeToRuleChainTellNextMsg implements TbActorMsg { + + private final RuleNodeId originator; + private final String relationType; + private final TbMsg msg; + + @Override + public MsgType getMsgType() { + return MsgType.RULE_TO_RULE_CHAIN_TELL_NEXT_MSG; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java index 0f66d256ed..0be0385365 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java @@ -17,6 +17,8 @@ package org.thingsboard.server.actors.service; import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.common.transport.SessionMsgProcessor; import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener; import org.thingsboard.server.service.cluster.rpc.RpcMsgListener; @@ -25,6 +27,8 @@ public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); + void onMsg(ServiceToRuleEngineMsg msg); + void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId); void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType); diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java index 76b9be96b3..d0260dd974 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java @@ -54,7 +54,7 @@ public abstract class ComponentActor extends AbstractContextAwareMsgPr this.entityId = id; } - public abstract void start() throws Exception; + public abstract void start(ActorContext context) throws Exception; - public abstract void stop() throws Exception; + public abstract void stop(ActorContext context) throws Exception; - public abstract void onCreated(ActorContext context) throws Exception; + public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception; - public abstract void onUpdate(ActorContext context) throws Exception; + public void onCreated(ActorContext context) throws Exception { + start(context); + } - public abstract void onActivate(ActorContext context) throws Exception; + public void onUpdate(ActorContext context) throws Exception { + restart(context); + } - public abstract void onSuspend(ActorContext context) throws Exception; + public void onActivate(ActorContext context) throws Exception { + restart(context); + } - public abstract void onStop(ActorContext context) throws Exception; + public void onSuspend(ActorContext context) throws Exception { + stop(context); + } - public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception; + public void onStop(ActorContext context) throws Exception { + stop(context); + } + + private void restart(ActorContext context) throws Exception { + stop(context); + start(context); + } public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) { schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency); diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java index a98609ceee..917a6452c4 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java @@ -43,13 +43,16 @@ public abstract class EntityActorsManager(getFetchEntitiesFunction(), ContextAwareActor.ENTITY_PACK_LIMIT)) { - log.debug("[{}] Creating plugin actor", entity.getId()); + T entityId = (T) entity.getId(); + log.debug("[{}|{}] Creating entity actor", entityId.getEntityType(), entityId.getId()); //TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa. - getOrCreateActor(context, (T) entity.getId()); - log.debug("[{}] Plugin actor created.", entity.getId()); + ActorRef actorRef = getOrCreateActor(context, entityId); + visit(entity, actorRef); + log.debug("[{}|{}] Entity actor created.", entityId.getEntityType(), entityId.getId()); } } + protected void visit(M entity, ActorRef actorRef) {} public ActorRef getOrCreateActor(ActorContext context, T entityId) { return actors.computeIfAbsent(entityId, eId -> diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java index 09396110b4..97acb6cbfb 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java @@ -1,6 +1,8 @@ package org.thingsboard.server.actors.shared.rulechain; +import akka.actor.ActorRef; import akka.japi.Creator; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ruleChain.RuleChainActor; @@ -16,6 +18,10 @@ import org.thingsboard.server.dao.rule.RuleChainService; public abstract class RuleChainManager extends EntityActorsManager { protected final RuleChainService service; + @Getter + protected RuleChain rootChain; + @Getter + protected ActorRef rootChainActor; public RuleChainManager(ActorSystemContext systemContext) { super(systemContext); @@ -27,4 +33,12 @@ public abstract class RuleChainManager extends EntityActorsManager deviceActors; @@ -62,25 +62,42 @@ public class TenantActor extends RuleChainManagerActor { } @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); + protected void process(TbActorMsg msg) { + switch (msg.getMsgType()) { + case COMPONENT_LIFE_CYCLE_MSG: + onComponentLifecycleMsg((ComponentLifecycleMsg) msg); + break; + case SERVICE_TO_RULE_ENGINE_MSG: + onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg); + break; } } + 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())); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 1c842c8a1b..778c2a3ffb 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -215,6 +215,12 @@ actors: 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}" + chain: + # Errors for particular actor are persisted once per specified amount of milliseconds + error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}" + node: + # Errors for particular actor are persisted once per specified amount of milliseconds + error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}" statistics: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" 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 a776d7b6ce..5624ed801f 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 @@ -37,6 +37,8 @@ public class DataConstants { public static final String ERROR = "ERROR"; public static final String LC_EVENT = "LC_EVENT"; public static final String STATS = "STATS"; + public static final String RULE_CHAIN_DEBUG = "DEBUG_RULE_CHAIN"; + public static final String RULE_NODE_DEBUG = "DEBUG_RULE_NODE"; public static final String ONEWAY = "ONEWAY"; public static final String TWOWAY = "TWOWAY"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java index 45fb590ed6..ab6accaf11 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java @@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin; */ public enum ComponentType { - FILTER, PROCESSOR, ACTION, PLUGIN + ENRICHMENT, FILTER, PROCESSOR, TRANSFORMATION, ACTION, PLUGIN } 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 55c131d12d..22fc4285a9 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 @@ -4,4 +4,34 @@ package org.thingsboard.server.common.msg; * Created by ashvayka on 15.03.18. */ public enum MsgType { + + /** + * ADDED/UPDATED/DELETED events for main entities. + * + * @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} + */ + 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 RuleActor to RuleChainActor with command to process TbMsg by next nodes in chain. + */ + RULE_TO_RULE_CHAIN_TELL_NEXT_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 5163b6c39d..c689b24fe8 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -36,7 +36,7 @@ public final class TbMsg implements Serializable { private final String type; private final EntityId originator; private final TbMsgMetaData metaData; - + private final TbMsgDataType dataType; private final byte[] data; public static ByteBuffer toBytes(TbMsg msg) { @@ -49,11 +49,10 @@ public final class TbMsg implements Serializable { } if (msg.getMetaData() != null) { - MsgProtos.TbMsgProto.TbMsgMetaDataProto.Builder metadataBuilder = MsgProtos.TbMsgProto.TbMsgMetaDataProto.newBuilder(); - metadataBuilder.putAllData(msg.getMetaData().getData()); - builder.addMetaData(metadataBuilder.build()); + builder.setMetaData(MsgProtos.TbMsgMetaDataProto.newBuilder().putAllData(msg.getMetaData().getData()).build()); } + builder.setDataType(msg.getDataType().ordinal()); builder.setData(ByteString.copyFrom(msg.getData())); byte[] bytes = builder.build().toByteArray(); return ByteBuffer.wrap(bytes); @@ -63,16 +62,11 @@ public final class TbMsg implements Serializable { try { MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array()); TbMsgMetaData metaData = new TbMsgMetaData(); - if (proto.getMetaDataCount() > 0) { - metaData.setData(proto.getMetaData(0).getDataMap()); - } - - EntityId entityId = null; - if (proto.getEntityId() != null) { - entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()); - } + metaData.setData(proto.getMetaData().getDataMap()); - return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, proto.getData().toByteArray()); + EntityId entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()); + TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; + return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData().toByteArray()); } catch (InvalidProtocolBufferException e) { throw new IllegalStateException("Could not parse protobuf for TbMsg", e); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java new file mode 100644 index 0000000000..b6e2d5a5c5 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java @@ -0,0 +1,11 @@ +package org.thingsboard.server.common.msg; + +/** + * Created by ashvayka on 15.03.18. + */ +public enum TbMsgDataType { + + // Do not change ordering. We use ordinal to save some bytes on serialization + JSON, TEXT, BINARY; + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java index b77722e826..315eb86f6c 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java @@ -21,6 +21,8 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.TenantAwareMsg; import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; @@ -30,7 +32,7 @@ import java.util.Optional; * @author Andrew Shvayka */ @ToString -public class ComponentLifecycleMsg implements TenantAwareMsg, ToAllNodesMsg { +public class ComponentLifecycleMsg implements TbActorMsg, TenantAwareMsg, ToAllNodesMsg { @Getter private final TenantId tenantId; @Getter @@ -56,4 +58,8 @@ public class ComponentLifecycleMsg implements TenantAwareMsg, ToAllNodesMsg { return entityId.getEntityType() == EntityType.RULE_CHAIN ? Optional.of((RuleChainId) entityId) : Optional.empty(); } + @Override + public MsgType getMsgType() { + return MsgType.COMPONENT_LIFE_CYCLE_MSG; + } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java new file mode 100644 index 0000000000..3d82033b31 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java @@ -0,0 +1,22 @@ +package org.thingsboard.server.common.msg.system; + +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 ServiceToRuleEngineMsg implements TbActorMsg { + + private final TenantId tenantId; + private final TbMsg tbMsg; + + @Override + public MsgType getMsgType() { + return MsgType.SERVICE_TO_RULE_ENGINE_MSG; + } +} diff --git a/common/message/src/main/proto/tbmsg.proto b/common/message/src/main/proto/tbmsg.proto index 90fa2bdbae..62acff20ce 100644 --- a/common/message/src/main/proto/tbmsg.proto +++ b/common/message/src/main/proto/tbmsg.proto @@ -19,6 +19,9 @@ package msgqueue; option java_package = "org.thingsboard.server.common.msg.gen"; option java_outer_classname = "MsgProtos"; +message TbMsgMetaDataProto { + map data = 1; +} message TbMsgProto { string id = 1; @@ -26,11 +29,8 @@ message TbMsgProto { string entityType = 3; string entityId = 4; - message TbMsgMetaDataProto { - map data = 1; - } + TbMsgMetaDataProto metaData = 5; - repeated TbMsgMetaDataProto metaData = 5; - - bytes data = 6; + int32 dataType = 6; + bytes data = 7; } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java index f1df09edb9..fff3f6d907 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.rule; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -67,67 +66,7 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic @Override public RuleMetaData saveRule(RuleMetaData rule) { - ruleValidator.validate(rule); - if (rule.getTenantId() == null) { - log.trace("Save system rule metadata with predefined id {}", systemTenantId); - rule.setTenantId(systemTenantId); - } - if (rule.getId() != null) { - RuleMetaData oldVersion = ruleDao.findById(rule.getId()); - if (rule.getState() == null) { - rule.setState(oldVersion.getState()); - } else if (rule.getState() != oldVersion.getState()) { - throw new IncorrectParameterException("Use Activate/Suspend method to control state of the rule!"); - } - } else { - if (rule.getState() == null) { - rule.setState(ComponentLifecycleState.SUSPENDED); - } else if (rule.getState() != ComponentLifecycleState.SUSPENDED) { - throw new IncorrectParameterException("Use Activate/Suspend method to control state of the rule!"); - } - } - - validateFilters(rule.getFilters()); - if (rule.getProcessor() != null && !rule.getProcessor().isNull()) { - validateComponentJson(rule.getProcessor(), ComponentType.PROCESSOR); - } - if (rule.getAction() != null && !rule.getAction().isNull()) { - validateComponentJson(rule.getAction(), ComponentType.ACTION); - } - validateRuleAndPluginState(rule); - return ruleDao.save(rule); - } - - private void validateFilters(JsonNode filtersJson) { - if (filtersJson == null || filtersJson.isNull()) { - throw new IncorrectParameterException("Rule filters are required!"); - } - if (!filtersJson.isArray()) { - throw new IncorrectParameterException("Filters json is not an array!"); - } - ArrayNode filtersArray = (ArrayNode) filtersJson; - for (int i = 0; i < filtersArray.size(); i++) { - validateComponentJson(filtersArray.get(i), ComponentType.FILTER); - } - } - - private void validateComponentJson(JsonNode json, ComponentType type) { - if (json == null || json.isNull()) { - throw new IncorrectParameterException(type.name() + " is required!"); - } - String clazz = getIfValid(type.name(), json, "clazz", JsonNode::isTextual, JsonNode::asText); - String name = getIfValid(type.name(), json, "name", JsonNode::isTextual, JsonNode::asText); - JsonNode configuration = getIfValid(type.name(), json, "configuration", JsonNode::isObject, node -> node); - ComponentDescriptor descriptor = componentDescriptorService.findByClazz(clazz); - if (descriptor == null) { - throw new IncorrectParameterException(type.name() + " clazz " + clazz + " is not a valid component!"); - } - if (descriptor.getType() != type) { - throw new IncorrectParameterException("Clazz " + clazz + " is not a valid " + type.name() + " component!"); - } - if (!componentDescriptorService.validate(descriptor, configuration)) { - throw new IncorrectParameterException(type.name() + " configuration is not valid!"); - } + throw new RuntimeException("Not supported since v1.5!"); } private void validateRuleAndPluginState(RuleMetaData rule) { 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 07cd72c001..a1c85e2bfb 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 @@ -38,7 +38,7 @@ public interface TbContext { void spawn(TbMsg msg); - void ack(UUID msg); + void ack(TbMsg msg); AttributesService getAttributesService(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java index 9edd14c953..d1a4adc6b6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java @@ -44,6 +44,7 @@ public class TbTransformNode implements TbNode { try { //TODO: refactor this to work async and fetch attributes from cache. AttributesService service = ctx.getAttributesService(); + fetchAttributes(msg, service, config.getClientAttributeNames(), DataConstants.CLIENT_SCOPE, "cs."); fetchAttributes(msg, service, config.getServerAttributeNames(), DataConstants.SERVER_SCOPE, "ss."); fetchAttributes(msg, service, config.getSharedAttributeNames(), DataConstants.SHARED_SCOPE, "shared."); From 56248a2317630110be9822a508ef0e091962d6c3 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 20 Mar 2018 10:32:43 +0200 Subject: [PATCH 019/171] Adding License Header --- .../rule/engine/metadata/TbEntityGetAttrNode.java | 8 ++++---- .../TbGetRelatedAttrNodeConfiguration.java | 15 +++++++++++++++ .../metadata/TbGetRelatedAttributeNode.java | 15 +++++++++++++++ .../metadata/TbGetCustomerAttributeNodeTest.java | 8 ++++---- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java index 2d6641472c..52850be9a6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java index 40987daeaa..75b0a6524b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java @@ -1,3 +1,18 @@ +/** + * 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.metadata; import lombok.Data; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 596b1dfee5..5823c181de 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -1,3 +1,18 @@ +/** + * 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.metadata; import com.google.common.util.concurrent.AsyncFunction; 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 23c5d4b4ba..8e5ddb8069 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. From f51be6b2122bf2241e4577f7a18bd3a6a456dd38 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Tue, 20 Mar 2018 11:29:47 +0200 Subject: [PATCH 020/171] add filter & switch Nodes. add self get latest Telemetry. --- .../rule/engine/api/ListeningExecutor.java | 12 ++ .../rule/engine/api/TbContext.java | 2 + .../rule/engine/filter/TbJsFilterNode.java | 51 ++++++ .../filter/TbJsFilterNodeConfiguration.java | 9 + .../rule/engine/filter/TbJsSwitchNode.java | 56 +++++++ .../filter/TbJsSwitchNodeConfiguration.java | 12 ++ .../rule/engine/js/JsExecutorService.java | 30 ++++ .../rule/engine/js/NashornJsEngine.java | 79 +++++++++ .../engine/metadata/TbGetAttributesNode.java | 45 +++-- .../TbGetAttributesNodeConfiguration.java | 2 + .../engine/filter/TbJsFilterNodeTest.java | 154 ++++++++++++++++++ .../engine/filter/TbJsSwitchNodeTest.java | 118 ++++++++++++++ 12 files changed, 557 insertions(+), 13 deletions(-) create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java new file mode 100644 index 0000000000..a233cb36fc --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java @@ -0,0 +1,12 @@ +package org.thingsboard.rule.engine.api; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.concurrent.Callable; + +public interface ListeningExecutor { + + ListenableFuture executeAsync(Callable task); + + void onDestroy(); +} 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 541d35b899..c7e2bec28c 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 @@ -74,4 +74,6 @@ public interface TbContext { RelationService getRelationService(); + ListeningExecutor getJsExecutor(); + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java new file mode 100644 index 0000000000..476695eb20 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -0,0 +1,51 @@ +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.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.msg.TbMsg; + +import javax.script.Bindings; + +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; + +@Slf4j +public class TbJsFilterNode implements TbNode { + + private TbJsFilterNodeConfiguration config; + private NashornJsEngine jsEngine; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class); + this.jsEngine = new NashornJsEngine(config.getJsScript()); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + ListeningExecutor jsExecutor = ctx.getJsExecutor(); + withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(toBindings(msg))), + result -> processFilter(ctx, msg, result), + t -> ctx.tellError(msg, t)); + } + + private void processFilter(TbContext ctx, TbMsg msg, Boolean filterResult) { + if (filterResult) { + ctx.tellNext(msg); + } else { + log.debug("Msg filtered out {}", msg.getId()); + } + } + + private Bindings toBindings(TbMsg msg) { + return NashornJsEngine.bindMsg(msg); + } + + @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/TbJsFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java new file mode 100644 index 0000000000..488fa444d1 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java @@ -0,0 +1,9 @@ +package org.thingsboard.rule.engine.filter; + +import lombok.Data; + +@Data +public class TbJsFilterNodeConfiguration { + + private String jsScript; +} 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 new file mode 100644 index 0000000000..5d1514a325 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -0,0 +1,56 @@ +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.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.msg.TbMsg; + +import javax.script.Bindings; + +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; + +@Slf4j +public class TbJsSwitchNode implements TbNode { + + private TbJsSwitchNodeConfiguration config; + private NashornJsEngine jsEngine; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class); + this.jsEngine = new NashornJsEngine(config.getJsScript()); + if (config.getAllowedRelations().size() < 1) { + String message = "Switch node should have at least 1 relation"; + log.error(message); + throw new IllegalStateException(message); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + ListeningExecutor jsExecutor = ctx.getJsExecutor(); + withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))), + result -> processSwitch(ctx, msg, result), + t -> ctx.tellError(msg, t)); + } + + private void processSwitch(TbContext ctx, TbMsg msg, String nextRelation) { + if (config.getAllowedRelations().contains(nextRelation)) { + ctx.tellNext(msg, nextRelation); + } else { + ctx.tellError(msg, new IllegalStateException("Unsupported relation for switch " + nextRelation)); + } + } + + private Bindings toBindings(TbMsg msg) { + return NashornJsEngine.bindMsg(msg); + } + + @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/TbJsSwitchNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java new file mode 100644 index 0000000000..a179c96a1f --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java @@ -0,0 +1,12 @@ +package org.thingsboard.rule.engine.filter; + +import lombok.Data; + +import java.util.Set; + +@Data +public class TbJsSwitchNodeConfiguration { + + private String jsScript; + private Set allowedRelations; +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java new file mode 100644 index 0000000000..899188551f --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java @@ -0,0 +1,30 @@ +package org.thingsboard.rule.engine.js; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import org.thingsboard.rule.engine.api.ListeningExecutor; + +import javax.annotation.PreDestroy; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; + +public class JsExecutorService implements ListeningExecutor{ + + private final ListeningExecutorService service; + + public JsExecutorService(int threadCount) { + this.service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(threadCount)); + } + + @Override + public ListenableFuture executeAsync(Callable task) { + return service.submit(task); + } + + @PreDestroy + @Override + public void onDestroy() { + service.shutdown(); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java new file mode 100644 index 0000000000..c64cf7202f --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java @@ -0,0 +1,79 @@ +package org.thingsboard.rule.engine.js; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jdk.nashorn.api.scripting.NashornScriptEngineFactory; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.thingsboard.server.common.msg.TbMsg; + +import javax.script.*; +import java.util.Map; + + +@Slf4j +public class NashornJsEngine { + + public static final String METADATA = "meta"; + public static final String DATA = "msg"; + private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); + + private CompiledScript engine; + + public NashornJsEngine(String script) { + engine = compileScript(script); + } + + private static CompiledScript compileScript(String script) { + ScriptEngine engine = factory.getScriptEngine(new String[]{"--no-java"}); + Compilable compEngine = (Compilable) engine; + try { + return compEngine.compile(script); + } catch (ScriptException e) { + log.warn("Failed to compile JS script: {}", e.getMessage(), e); + throw new IllegalArgumentException("Can't compile script: " + e.getMessage()); + } + } + + public static Bindings bindMsg(TbMsg msg) { + try { + Bindings bindings = new SimpleBindings(); + bindings.put(METADATA, msg.getMetaData().getData()); + + if (ArrayUtils.isNotEmpty(msg.getData())) { + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(msg.getData()); + Map map = mapper.treeToValue(jsonNode, Map.class); + bindings.put(DATA, map); + } + + return bindings; + } catch (Throwable th) { + throw new IllegalArgumentException("Cannot bind js args", th); + } + } + + public boolean executeFilter(Bindings bindings) throws ScriptException { + Object eval = engine.eval(bindings); + if (eval instanceof Boolean) { + return (boolean) eval; + } else { + log.warn("Wrong result type: {}", eval); + throw new ScriptException("Wrong result type: " + eval); + } + } + + public String executeSwitch(Bindings bindings) throws ScriptException, NoSuchMethodException { + Object eval = this.engine.eval(bindings); + if (eval instanceof String) { + return (String) eval; + } else { + log.warn("Wrong result type: {}", eval); + throw new ScriptException("Wrong result type: " + eval); + } + } + + public void destroy() { + engine = null; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 4d41921740..bc6bc4a03c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,13 +15,16 @@ */ package org.thingsboard.rule.engine.metadata; -import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; import java.util.List; @@ -44,24 +47,40 @@ public class TbGetAttributesNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { - ListenableFuture> future = Futures.allAsList( - putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs."), - putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared."), - putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss.")); + if (CollectionUtils.isNotEmpty(config.getLatestTsKeyNames())) { + withCallback(getLatestTelemetry(ctx, msg, config.getLatestTsKeyNames()), + i -> ctx.tellNext(msg), + t -> ctx.tellError(msg, t)); + } else { + ListenableFuture> future = Futures.allAsList( + putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs."), + putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared."), + putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss.")); - withCallback(future, i -> ctx.tellNext(msg), t -> ctx.tellError(msg, t)); + withCallback(future, i -> ctx.tellNext(msg), t -> ctx.tellError(msg, t)); + } } - private ListenableFuture putAttributesAsync(TbMsg msg, List attributes, String prefix) { + private ListenableFuture putAttr(TbMsg msg, List attributes, String prefix) { attributes.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString())); return Futures.immediateFuture(null); } private ListenableFuture putAttrAsync(TbContext ctx, TbMsg msg, String scope, List attributes, String prefix) { - return Futures.transform(ctx.getAttributesService().find(msg.getOriginator(), scope, attributes), - (AsyncFunction, Void>) i -> putAttributesAsync(msg, i, prefix)); + ListenableFuture> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, attributes); + return Futures.transform(latest, (Function, Void>) l -> { + l.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString())); + return null; + }); } + private ListenableFuture getLatestTelemetry(TbContext ctx, TbMsg msg, List attributes) { + ListenableFuture> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), attributes); + return Futures.transform(latest, (Function, Void>) l -> { + l.forEach(r -> msg.getMetaData().putValue(r.getKey(), r.getValueAsString())); + return null; + }); + } @Override public void destroy() { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java index b54edef818..ad92314324 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java @@ -29,4 +29,6 @@ public class TbGetAttributesNodeConfiguration { private List sharedAttributeNames; private List serverAttributeNames; + private List latestTsKeyNames; + } 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 new file mode 100644 index 0000000000..6a4bec2d66 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -0,0 +1,154 @@ +package org.thingsboard.rule.engine.filter; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.thingsboard.rule.engine.api.ListeningExecutor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import javax.script.ScriptException; +import java.util.concurrent.Callable; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class TbJsFilterNodeTest { + + private TbJsFilterNode node; + + @Mock + private TbContext ctx; + @Mock + private ListeningExecutor executor; + + @Test + public void falseEvaluationDoNotSendMsg() throws TbNodeException { + initWithScript("10 > 15;"); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes()); + + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + verifyNoMoreInteractions(ctx); + } + + @Test + public void notValidMsgDataThrowsException() throws TbNodeException { + initWithScript("10 > 15;"); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), new byte[4]); + + when(ctx.getJsExecutor()).thenReturn(executor); + + mockJsExecutor(); + + node.onMsg(ctx, msg); + verifyError(msg, "Cannot bind js args", IllegalArgumentException.class); + } + + @Test + public void exceptionInJsThrowsException() throws TbNodeException { + initWithScript("meta.temp.curr < 15;"); + TbMsgMetaData metaData = new TbMsgMetaData(); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + String expectedMessage = "TypeError: Cannot get property \"curr\" of null in at line number 1"; + verifyError(msg, expectedMessage, ScriptException.class); + } + + @Test(expected = IllegalArgumentException.class) + public void notValidScriptThrowsException() throws TbNodeException { + initWithScript("10 > 15 asdq out"); + } + + @Test + public void metadataConditionCanBeFalse() throws TbNodeException { + initWithScript("meta.humidity < 15;"); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "10"); + metaData.putValue("humidity", "99"); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + verifyNoMoreInteractions(ctx); + } + + @Test + public void metadataConditionCanBeTrue() throws TbNodeException { + initWithScript("meta.temp < 15;"); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "10"); + metaData.putValue("humidity", "99"); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + verify(ctx).tellNext(msg); + } + + @Test + public void msgJsonParsedAndBinded() throws TbNodeException { + initWithScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;"); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "10"); + metaData.putValue("humidity", "99"); + String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + verify(ctx).tellNext(msg); + } + + private void initWithScript(String script) throws TbNodeException { + TbJsFilterNodeConfiguration config = new TbJsFilterNodeConfiguration(); + config.setJsScript(script); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); + nodeConfiguration.setData(mapper.valueToTree(config)); + + node = new TbJsFilterNode(); + node.init(nodeConfiguration, null); + } + + private void mockJsExecutor() { + when(ctx.getJsExecutor()).thenReturn(executor); + doAnswer((Answer>) invocationOnMock -> { + try { + Callable task = (Callable) (invocationOnMock.getArguments())[0]; + return Futures.immediateFuture((Boolean) task.call()); + } catch (Throwable th) { + return Futures.immediateFailedFuture(th); + } + }).when(executor).executeAsync(Matchers.any(Callable.class)); + } + + private void verifyError(TbMsg msg, String message, Class expectedClass) { + ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellError(same(msg), captor.capture()); + + Throwable value = captor.getValue(); + assertEquals(expectedClass, value.getClass()); + assertEquals(message, value.getMessage()); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..6ffa83bcc4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -0,0 +1,118 @@ +package org.thingsboard.rule.engine.filter; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.thingsboard.rule.engine.api.ListeningExecutor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.Set; +import java.util.concurrent.Callable; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class TbJsSwitchNodeTest { + + private TbJsSwitchNode node; + + @Mock + private TbContext ctx; + @Mock + private ListeningExecutor executor; + + @Test + public void allowedRelationPassed() throws TbNodeException { + String jsCode = "function nextRelation(meta, msg) {\n" + + " if(msg.passed == 5 && meta.temp == 10)\n" + + " return 'one'\n" + + " else\n" + + " return 'two';\n" + + "};\n" + + "\n" + + "nextRelation(meta, msg);"; + initWithScript(jsCode, Sets.newHashSet("one", "two")); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "10"); + metaData.putValue("humidity", "99"); + String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + verify(ctx).tellNext(msg, "one"); + } + + @Test + public void unknownRelationThrowsException() throws TbNodeException { + String jsCode = "function nextRelation(meta, msg) {\n" + + " return 'nine';" + + "};\n" + + "\n" + + "nextRelation(meta, msg);"; + initWithScript(jsCode, Sets.newHashSet("one", "two")); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "10"); + metaData.putValue("humidity", "99"); + String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + verifyError(msg, "Unsupported relation for switch nine", IllegalStateException.class); + } + + private void initWithScript(String script, Set relations) throws TbNodeException { + TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration(); + config.setJsScript(script); + config.setAllowedRelations(relations); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); + nodeConfiguration.setData(mapper.valueToTree(config)); + + node = new TbJsSwitchNode(); + node.init(nodeConfiguration, null); + } + + private void mockJsExecutor() { + when(ctx.getJsExecutor()).thenReturn(executor); + doAnswer((Answer>) invocationOnMock -> { + try { + Callable task = (Callable) (invocationOnMock.getArguments())[0]; + return Futures.immediateFuture((String) task.call()); + } catch (Throwable th) { + return Futures.immediateFailedFuture(th); + } + }).when(executor).executeAsync(Matchers.any(Callable.class)); + } + + private void verifyError(TbMsg msg, String message, Class expectedClass) { + ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellError(same(msg), captor.capture()); + + Throwable value = captor.getValue(); + assertEquals(expectedClass, value.getClass()); + assertEquals(message, value.getMessage()); + } +} \ No newline at end of file From 09c690634b1f03c818632fb6867ffda08e4688c8 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Tue, 20 Mar 2018 11:31:31 +0200 Subject: [PATCH 021/171] fix license header --- .../rule/engine/api/ListeningExecutor.java | 15 +++++++++++++++ .../rule/engine/filter/TbJsFilterNode.java | 15 +++++++++++++++ .../filter/TbJsFilterNodeConfiguration.java | 15 +++++++++++++++ .../rule/engine/filter/TbJsSwitchNode.java | 15 +++++++++++++++ .../filter/TbJsSwitchNodeConfiguration.java | 15 +++++++++++++++ .../rule/engine/js/JsExecutorService.java | 15 +++++++++++++++ .../rule/engine/js/NashornJsEngine.java | 15 +++++++++++++++ .../rule/engine/metadata/TbGetAttributesNode.java | 8 ++++---- .../rule/engine/filter/TbJsFilterNodeTest.java | 15 +++++++++++++++ .../rule/engine/filter/TbJsSwitchNodeTest.java | 15 +++++++++++++++ 10 files changed, 139 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java index a233cb36fc..fecf019425 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java @@ -1,3 +1,18 @@ +/** + * 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 com.google.common.util.concurrent.ListenableFuture; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 476695eb20..4972bc6452 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -1,3 +1,18 @@ +/** + * 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; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java index 488fa444d1..bf543e3276 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java @@ -1,3 +1,18 @@ +/** + * 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.Data; 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 5d1514a325..9dcb3325aa 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 @@ -1,3 +1,18 @@ +/** + * 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; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java index a179c96a1f..d0e2a51b2d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java @@ -1,3 +1,18 @@ +/** + * 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.Data; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java index 899188551f..19c2bb9405 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java @@ -1,3 +1,18 @@ +/** + * 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.js; import com.google.common.util.concurrent.ListenableFuture; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java index c64cf7202f..a40d4ecb0c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java @@ -1,3 +1,18 @@ +/** + * 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.js; import com.fasterxml.jackson.databind.JsonNode; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index bc6bc4a03c..887311cc9a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. 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 6a4bec2d66..6a9146de98 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 @@ -1,3 +1,18 @@ +/** + * 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 com.datastax.driver.core.utils.UUIDs; 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 6ffa83bcc4..1b4cce9fc9 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 @@ -1,3 +1,18 @@ +/** + * 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 com.datastax.driver.core.utils.UUIDs; From 1968b5d8423d6b5af57ae3603c2d77a06664ece2 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Tue, 20 Mar 2018 15:10:09 +0200 Subject: [PATCH 022/171] implement Transformation Nodes --- .../thingsboard/server/common/msg/TbMsg.java | 8 +- .../rule/engine/api/TbContext.java | 3 + .../rule/engine/filter/TbJsFilterNode.java | 10 +- .../rule/engine/filter/TbJsSwitchNode.java | 21 ++- .../filter/TbJsSwitchNodeConfiguration.java | 1 + .../rule/engine/js/NashornJsEngine.java | 55 ++++++- .../engine/metadata/TbEntityGetAttrNode.java | 6 +- .../metadata/TbGetCustomerAttributeNode.java | 28 +--- .../TbGetRelatedAttrNodeConfiguration.java | 2 +- .../metadata/TbGetRelatedAttributeNode.java | 27 +--- .../metadata/TbGetTenantAttributeNode.java | 39 +---- .../transform/TbAbstractTransformNode.java | 59 ++++++++ .../transform/TbChangeOriginatorNode.java | 93 ++++++++++++ .../TbChangeOriginatorNodeConfiguration.java | 27 ++++ .../engine/transform/TbTransformMsgNode.java | 56 +++++++ .../TbTransformMsgNodeConfiguration.java | 24 +++ .../engine/transform/TbTransformNode.java | 68 --------- .../TbTransformNodeConfiguration.java | 24 +++ .../util/EntitiesCustomerIdAsyncLoader.java | 50 +++++++ .../EntitiesRelatedEntityIdAsyncLoader.java | 51 +++++++ .../util/EntitiesTenantIdAsyncLoader.java | 60 ++++++++ .../engine/filter/TbJsFilterNodeTest.java | 6 +- .../engine/filter/TbJsSwitchNodeTest.java | 59 ++++++-- .../transform/TbChangeOriginatorNodeTest.java | 125 ++++++++++++++++ .../transform/TbTransformMsgNodeTest.java | 141 ++++++++++++++++++ 25 files changed, 856 insertions(+), 187 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java delete mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNodeConfiguration.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java 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 5163b6c39d..261dda7670 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 @@ -30,7 +30,7 @@ import java.util.UUID; * Created by ashvayka on 13.01.18. */ @Data -public final class TbMsg implements Serializable { +public final class TbMsg implements Serializable, Cloneable { private final UUID id; private final String type; @@ -39,6 +39,11 @@ public final class TbMsg implements Serializable { private final byte[] data; + @Override + public TbMsg clone() { + return fromBytes(toBytes(this)); + } + public static ByteBuffer toBytes(TbMsg msg) { MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder(); builder.setId(msg.getId().toString()); @@ -77,4 +82,5 @@ public final class TbMsg implements Serializable { throw new IllegalStateException("Could not parse protobuf for TbMsg", e); } } + } 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 c7e2bec28c..fdcf56aac9 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 @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; +import java.util.Set; import java.util.UUID; /** @@ -40,6 +41,8 @@ public interface TbContext { void tellNext(TbMsg msg, String relationType); + void tellNext(TbMsg msg, Set relationTypes); + void tellSelf(TbMsg msg, long delayMs); void tellOthers(TbMsg msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 4972bc6452..089c9b1919 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -41,18 +41,10 @@ public class TbJsFilterNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) { ListeningExecutor jsExecutor = ctx.getJsExecutor(); withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(toBindings(msg))), - result -> processFilter(ctx, msg, result), + filterResult -> ctx.tellNext(msg, Boolean.toString(filterResult)), t -> ctx.tellError(msg, t)); } - private void processFilter(TbContext ctx, TbMsg msg, Boolean filterResult) { - if (filterResult) { - ctx.tellNext(msg); - } else { - log.debug("Msg filtered out {}", msg.getId()); - } - } - private Bindings toBindings(TbMsg msg) { return NashornJsEngine.bindMsg(msg); } 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 9dcb3325aa..afd81636a0 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 @@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.js.NashornJsEngine; import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; +import java.util.Set; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -34,30 +35,40 @@ public class TbJsSwitchNode implements TbNode { @Override public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class); - this.jsEngine = new NashornJsEngine(config.getJsScript()); if (config.getAllowedRelations().size() < 1) { String message = "Switch node should have at least 1 relation"; log.error(message); throw new IllegalStateException(message); } + if (!config.isRouteToAllWithNoCheck()) { + this.jsEngine = new NashornJsEngine(config.getJsScript()); + } } @Override public void onMsg(TbContext ctx, TbMsg msg) { + if (config.isRouteToAllWithNoCheck()) { + ctx.tellNext(msg, config.getAllowedRelations()); + return; + } ListeningExecutor jsExecutor = ctx.getJsExecutor(); withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))), result -> processSwitch(ctx, msg, result), t -> ctx.tellError(msg, t)); } - private void processSwitch(TbContext ctx, TbMsg msg, String nextRelation) { - if (config.getAllowedRelations().contains(nextRelation)) { - ctx.tellNext(msg, nextRelation); + private void processSwitch(TbContext ctx, TbMsg msg, Set nextRelations) { + if (validateRelations(nextRelations)) { + ctx.tellNext(msg, nextRelations); } else { - ctx.tellError(msg, new IllegalStateException("Unsupported relation for switch " + nextRelation)); + ctx.tellError(msg, new IllegalStateException("Unsupported relation for switch " + nextRelations)); } } + private boolean validateRelations(Set nextRelations) { + return config.getAllowedRelations().containsAll(nextRelations); + } + private Bindings toBindings(TbMsg msg) { return NashornJsEngine.bindMsg(msg); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java index d0e2a51b2d..331302d542 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java @@ -24,4 +24,5 @@ public class TbJsSwitchNodeConfiguration { private String jsScript; private Set allowedRelations; + private boolean routeToAllWithNoCheck; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java index a40d4ecb0c..082535f0fe 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java @@ -15,15 +15,20 @@ */ package org.thingsboard.rule.engine.js; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Sets; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; +import jdk.nashorn.api.scripting.ScriptObjectMirror; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.thingsboard.server.common.msg.TbMsg; import javax.script.*; +import java.util.Collections; import java.util.Map; +import java.util.Set; @Slf4j @@ -68,6 +73,32 @@ public class NashornJsEngine { } } + private static TbMsg unbindMsg(Bindings bindings, TbMsg msg) throws JsonProcessingException { + for (Map.Entry entry : msg.getMetaData().getData().entrySet()) { + Object obj = entry.getValue(); + entry.setValue(obj.toString()); + } + + Object payload = bindings.get(DATA); + if (payload != null) { + ObjectMapper mapper = new ObjectMapper(); + byte[] bytes = mapper.writeValueAsBytes(payload); + return new TbMsg(msg.getId(), msg.getType(), msg.getOriginator(), msg.getMetaData(), bytes); + } + + return msg; + } + + public TbMsg executeUpdate(Bindings bindings, TbMsg msg) throws ScriptException { + try { + engine.eval(bindings); + return unbindMsg(bindings, msg); + } catch (Throwable th) { + th.printStackTrace(); + throw new IllegalArgumentException("Cannot unbind js args", th); + } + } + public boolean executeFilter(Bindings bindings) throws ScriptException { Object eval = engine.eval(bindings); if (eval instanceof Boolean) { @@ -78,14 +109,28 @@ public class NashornJsEngine { } } - public String executeSwitch(Bindings bindings) throws ScriptException, NoSuchMethodException { + public Set executeSwitch(Bindings bindings) throws ScriptException, NoSuchMethodException { Object eval = this.engine.eval(bindings); if (eval instanceof String) { - return (String) eval; - } else { - log.warn("Wrong result type: {}", eval); - throw new ScriptException("Wrong result type: " + eval); + return Collections.singleton((String) eval); + } else if (eval instanceof ScriptObjectMirror) { + ScriptObjectMirror mir = (ScriptObjectMirror) eval; + if (mir.isArray()) { + Set nextStates = Sets.newHashSet(); + for (Map.Entry entry : mir.entrySet()) { + if (entry.getValue() instanceof String) { + nextStates.add((String) entry.getValue()); + } else { + log.warn("Wrong result type: {}", eval); + throw new ScriptException("Wrong result type: " + eval); + } + } + return nextStates; + } } + + log.warn("Wrong result type: {}", eval); + throw new ScriptException("Wrong result type: " + eval); } public void destroy() { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java index 52850be9a6..269e40fc19 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java @@ -70,7 +70,7 @@ public abstract class TbEntityGetAttrNode implements TbNode } - private void putAttributesAndTell(TbContext ctx, TbMsg msg, List attributes) { + private void putAttributesAndTell(TbContext ctx, TbMsg msg, List attributes) { attributes.forEach(r -> { String attrName = config.getAttrMapping().get(r.getKey()); msg.getMetaData().putValue(attrName, r.getValueAsString()); @@ -85,4 +85,8 @@ public abstract class TbEntityGetAttrNode implements TbNode protected abstract ListenableFuture findEntityAsync(TbContext ctx, EntityId originator); + public void setConfig(TbGetEntityAttrNodeConfiguration config) { + this.config = config; + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index 57f9b79e7b..b7b1fd73a3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -15,37 +15,17 @@ */ package org.thingsboard.rule.engine.metadata; -import com.google.common.util.concurrent.AsyncFunction; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.HasCustomerId; -import org.thingsboard.server.common.data.id.*; +import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { @Override protected ListenableFuture findEntityAsync(TbContext ctx, EntityId originator) { - - switch (originator.getEntityType()) { - case CUSTOMER: - return Futures.immediateFuture((CustomerId) originator); - case USER: - return getCustomerAsync(ctx.getUserService().findUserByIdAsync((UserId) originator)); - case ASSET: - return getCustomerAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) originator)); - case DEVICE: - return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) originator)); - default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType " + originator)); - } - } - - private ListenableFuture getCustomerAsync(ListenableFuture future) { - return Futures.transform(future, (AsyncFunction) in -> { - return in != null ? Futures.immediateFuture(in.getCustomerId()) - : Futures.immediateFailedFuture(new IllegalStateException("Customer not found"));}); + return EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctx, originator); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java index 75b0a6524b..ae0b662d48 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java @@ -19,7 +19,7 @@ import lombok.Data; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @Data -public class TbGetRelatedAttrNodeConfiguration { +public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { private String relationType; private EntitySearchDirection direction; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 5823c181de..474fb5d6c0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -15,23 +15,14 @@ */ package org.thingsboard.rule.engine.metadata; -import com.google.common.util.concurrent.AsyncFunction; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeState; +import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.EntitySearchDirection; -import org.thingsboard.server.dao.relation.RelationService; - -import java.util.List; - -import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON; public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { @@ -40,23 +31,11 @@ public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { @Override public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbGetRelatedAttrNodeConfiguration.class); + setConfig(config); } @Override protected ListenableFuture findEntityAsync(TbContext ctx, EntityId originator) { - RelationService relationService = ctx.getRelationService(); - if (config.getDirection() == EntitySearchDirection.FROM) { - ListenableFuture> asyncRelation = relationService.findByFromAndTypeAsync(originator, config.getRelationType(), COMMON); - return Futures.transform(asyncRelation, (AsyncFunction, EntityId>) - r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) - : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); - } else if (config.getDirection() == EntitySearchDirection.TO) { - ListenableFuture> asyncRelation = relationService.findByToAndTypeAsync(originator, config.getRelationType(), COMMON); - return Futures.transform(asyncRelation, (AsyncFunction, EntityId>) - r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) - : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); - } - - return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getDirection(), config.getRelationType()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index 2cf9a97d20..b97e220c04 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -15,50 +15,19 @@ */ package org.thingsboard.rule.engine.metadata; -import com.google.common.util.concurrent.AsyncFunction; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.alarm.AlarmId; -import org.thingsboard.server.common.data.id.*; +import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; @Slf4j public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { @Override protected ListenableFuture findEntityAsync(TbContext ctx, EntityId originator) { - - switch (originator.getEntityType()) { - case TENANT: - return Futures.immediateFuture((TenantId) originator); - case CUSTOMER: - return getTenantAsync(ctx.getCustomerService().findCustomerByIdAsync((CustomerId) originator)); - case USER: - return getTenantAsync(ctx.getUserService().findUserByIdAsync((UserId) originator)); - case RULE: - return getTenantAsync(ctx.getRuleService().findRuleByIdAsync((RuleId) originator)); - case PLUGIN: - return getTenantAsync(ctx.getPluginService().findPluginByIdAsync((PluginId) originator)); - case ASSET: - return getTenantAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) originator)); - case DEVICE: - return getTenantAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) originator)); - case ALARM: - return getTenantAsync(ctx.getAlarmService().findAlarmByIdAsync((AlarmId) originator)); - case RULE_CHAIN: - return getTenantAsync(ctx.getRuleChainService().findRuleChainByIdAsync((RuleChainId) originator)); - default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType " + originator)); - } - } - - private ListenableFuture getTenantAsync(ListenableFuture future) { - return Futures.transform(future, (AsyncFunction) in -> { - return in != null ? Futures.immediateFuture(in.getTenantId()) - : Futures.immediateFailedFuture(new IllegalStateException("Tenant not found"));}); + return EntitiesTenantIdAsyncLoader.findEntityIdAsync(ctx, originator); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java new file mode 100644 index 0000000000..426a3276a9 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.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.transform; + +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.msg.TbMsg; + +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; + +/** + * Created by ashvayka on 19.01.18. + */ +@Slf4j +public abstract class TbAbstractTransformNode implements TbNode { + + private TbTransformNodeConfiguration config; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbTransformNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + withCallback(transform(ctx, msg), + m -> routeMsg(ctx, m), + t -> ctx.tellError(msg, t)); + } + + protected abstract ListenableFuture transform(TbContext ctx, TbMsg msg); + + private void routeMsg(TbContext ctx, TbMsg msg) { + if (config.isStartNewChain()) { + ctx.spawn(msg); + } else { + ctx.tellNext(msg); + } + } + + public void setConfig(TbTransformNodeConfiguration config) { + this.config = config; + } +} 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 new file mode 100644 index 0000000000..2592af253e --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -0,0 +1,93 @@ +/** + * 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.transform; + +import com.google.common.base.Function; +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.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeState; +import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; +import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; +import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.HashSet; + +@Slf4j +public class TbChangeOriginatorNode extends TbAbstractTransformNode { + + protected static final String CUSTOMER_SOURCE = "CUSTOMER"; + protected static final String TENANT_SOURCE = "TENANT"; + protected static final String RELATED_SOURCE = "RELATED"; + + private TbChangeOriginatorNodeConfiguration config; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbChangeOriginatorNodeConfiguration.class); + validateConfig(config); + setConfig(config); + } + + @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())); + } + + private ListenableFuture getNewOriginator(TbContext ctx, EntityId original) { + switch (config.getOriginatorSource()) { + case CUSTOMER_SOURCE: + return EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctx, original); + case TENANT_SOURCE: + return EntitiesTenantIdAsyncLoader.findEntityIdAsync(ctx, original); + case RELATED_SOURCE: + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, original, config.getDirection(), config.getRelationType()); + default: + return Futures.immediateFailedFuture(new IllegalStateException("Unexpected originator source " + config.getOriginatorSource())); + } + } + + private void validateConfig(TbChangeOriginatorNodeConfiguration conf) { + HashSet knownSources = Sets.newHashSet(CUSTOMER_SOURCE, TENANT_SOURCE, RELATED_SOURCE); + if (!knownSources.contains(conf.getOriginatorSource())) { + log.error("Unsupported source [{}] for TbChangeOriginatorNode", conf.getOriginatorSource()); + throw new IllegalArgumentException("Unsupported source TbChangeOriginatorNode" + conf.getOriginatorSource()); + } + + if (conf.getOriginatorSource().equals(RELATED_SOURCE)) { + if (conf.getDirection() == null || StringUtils.isBlank(conf.getRelationType())) { + log.error("Related source for TbChangeOriginatorNode should have direction and relationType. Actual [{}] [{}]", + conf.getDirection(), conf.getRelationType()); + throw new IllegalArgumentException("Wrong config for RElated Source in TbChangeOriginatorNode" + conf.getOriginatorSource()); + } + } + + } + + @Override + public void destroy() { + + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java new file mode 100644 index 0000000000..cf036810c4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java @@ -0,0 +1,27 @@ +/** + * 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.transform; + +import lombok.Data; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; + +@Data +public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration{ + + private String originatorSource; + private EntitySearchDirection direction; + private String relationType; +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java new file mode 100644 index 0000000000..241fbfbf7e --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -0,0 +1,56 @@ +/** + * 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.transform; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbNodeState; +import org.thingsboard.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.msg.TbMsg; + +import javax.script.Bindings; + +public class TbTransformMsgNode extends TbAbstractTransformNode { + + private TbTransformMsgNodeConfiguration config; + private NashornJsEngine jsEngine; + + @Override + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class); + this.jsEngine = new NashornJsEngine(config.getJsScript()); + setConfig(config); + } + + @Override + protected ListenableFuture transform(TbContext ctx, TbMsg msg) { + return ctx.getJsExecutor().executeAsync(() -> jsEngine.executeUpdate(toBindings(msg), msg)); + } + + private Bindings toBindings(TbMsg msg) { + return NashornJsEngine.bindMsg(msg); + } + + @Override + public void destroy() { + if (jsEngine != null) { + jsEngine.destroy(); + } + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java new file mode 100644 index 0000000000..9cc926b54d --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java @@ -0,0 +1,24 @@ +/** + * 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.transform; + +import lombok.Data; + +@Data +public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration { + + private String jsScript; +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java deleted file mode 100644 index 9edd14c953..0000000000 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java +++ /dev/null @@ -1,68 +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.rule.engine.transform; - -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.rule.engine.TbNodeUtils; -import org.thingsboard.rule.engine.api.*; -import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.dao.attributes.AttributesService; - -import java.util.List; - -/** - * Created by ashvayka on 19.01.18. - */ -@Slf4j -public class TbTransformNode implements TbNode { - - TbGetAttributesNodeConfiguration config; - - @Override - public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { - this.config = TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class); - } - - @Override - public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { - try { - //TODO: refactor this to work async and fetch attributes from cache. - AttributesService service = ctx.getAttributesService(); - fetchAttributes(msg, service, config.getClientAttributeNames(), DataConstants.CLIENT_SCOPE, "cs."); - fetchAttributes(msg, service, config.getServerAttributeNames(), DataConstants.SERVER_SCOPE, "ss."); - fetchAttributes(msg, service, config.getSharedAttributeNames(), DataConstants.SHARED_SCOPE, "shared."); - ctx.tellNext(msg); - } catch (Exception e) { - log.warn("[{}][{}] Failed to fetch attributes", msg.getOriginator(), msg.getId(), e); - throw new TbNodeException(e); - } - } - - private void fetchAttributes(TbMsg msg, AttributesService service, List attributeNames, String scope, String prefix) throws InterruptedException, java.util.concurrent.ExecutionException { - if (attributeNames != null && attributeNames.isEmpty()) { - List attributes = service.find(msg.getOriginator(), scope, attributeNames).get(); - attributes.forEach(attr -> msg.getMetaData().putValue(prefix + attr.getKey(), attr.getValueAsString())); - } - } - - @Override - public void destroy() { - - } -} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNodeConfiguration.java new file mode 100644 index 0000000000..d9f57806ae --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNodeConfiguration.java @@ -0,0 +1,24 @@ +/** + * 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.transform; + +import lombok.Data; + +@Data +public class TbTransformNodeConfiguration { + + private boolean startNewChain = false; +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java new file mode 100644 index 0000000000..67eb808795 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java @@ -0,0 +1,50 @@ +/** + * 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.util; + +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.id.*; + +public class EntitiesCustomerIdAsyncLoader { + + + public static ListenableFuture findEntityIdAsync(TbContext ctx, EntityId original) { + + switch (original.getEntityType()) { + case CUSTOMER: + return Futures.immediateFuture((CustomerId) original); + case USER: + return getCustomerAsync(ctx.getUserService().findUserByIdAsync((UserId) original)); + case ASSET: + return getCustomerAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) original)); + case DEVICE: + return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) original)); + default: + return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original)); + } + } + + private static ListenableFuture getCustomerAsync(ListenableFuture future) { + return Futures.transform(future, (AsyncFunction) in -> { + return in != null ? Futures.immediateFuture(in.getCustomerId()) + : Futures.immediateFailedFuture(new IllegalStateException("Customer not found"));}); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java new file mode 100644 index 0000000000..ac69c5dd59 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.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.util; + +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.apache.commons.collections.CollectionUtils; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.List; + +import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON; + +public class EntitiesRelatedEntityIdAsyncLoader { + + public static ListenableFuture findEntityAsync(TbContext ctx, EntityId originator, + EntitySearchDirection direction, String relationType) { + RelationService relationService = ctx.getRelationService(); + if (direction == EntitySearchDirection.FROM) { + ListenableFuture> asyncRelation = relationService.findByFromAndTypeAsync(originator, relationType, COMMON); + return Futures.transform(asyncRelation, (AsyncFunction, EntityId>) + r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) + : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); + } else if (direction == EntitySearchDirection.TO) { + ListenableFuture> asyncRelation = relationService.findByToAndTypeAsync(originator, relationType, COMMON); + return Futures.transform(asyncRelation, (AsyncFunction, EntityId>) + r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) + : Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); + } + + return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java new file mode 100644 index 0000000000..388881b84e --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.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.rule.engine.util; + +import com.google.common.util.concurrent.AsyncFunction; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.alarm.AlarmId; +import org.thingsboard.server.common.data.id.*; + +public class EntitiesTenantIdAsyncLoader { + + public static ListenableFuture findEntityIdAsync(TbContext ctx, EntityId original) { + + switch (original.getEntityType()) { + case TENANT: + return Futures.immediateFuture((TenantId) original); + case CUSTOMER: + return getTenantAsync(ctx.getCustomerService().findCustomerByIdAsync((CustomerId) original)); + case USER: + return getTenantAsync(ctx.getUserService().findUserByIdAsync((UserId) original)); + case RULE: + return getTenantAsync(ctx.getRuleService().findRuleByIdAsync((RuleId) original)); + case PLUGIN: + return getTenantAsync(ctx.getPluginService().findPluginByIdAsync((PluginId) original)); + case ASSET: + return getTenantAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) original)); + case DEVICE: + return getTenantAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) original)); + case ALARM: + return getTenantAsync(ctx.getAlarmService().findAlarmByIdAsync((AlarmId) original)); + case RULE_CHAIN: + return getTenantAsync(ctx.getRuleChainService().findRuleChainByIdAsync((RuleChainId) original)); + default: + return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original)); + } + } + + private static ListenableFuture getTenantAsync(ListenableFuture future) { + return Futures.transform(future, (AsyncFunction) in -> { + return in != null ? Futures.immediateFuture(in.getTenantId()) + : Futures.immediateFailedFuture(new IllegalStateException("Tenant not found"));}); + } +} 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 6a9146de98..2fd9f4e8ee 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 @@ -58,6 +58,7 @@ public class TbJsFilterNodeTest { node.onMsg(ctx, msg); verify(ctx).getJsExecutor(); + verify(ctx).tellNext(msg, "false"); verifyNoMoreInteractions(ctx); } @@ -102,6 +103,7 @@ public class TbJsFilterNodeTest { node.onMsg(ctx, msg); verify(ctx).getJsExecutor(); + verify(ctx).tellNext(msg, "false"); verifyNoMoreInteractions(ctx); } @@ -116,7 +118,7 @@ public class TbJsFilterNodeTest { node.onMsg(ctx, msg); verify(ctx).getJsExecutor(); - verify(ctx).tellNext(msg); + verify(ctx).tellNext(msg, "true"); } @Test @@ -132,7 +134,7 @@ public class TbJsFilterNodeTest { node.onMsg(ctx, msg); verify(ctx).getJsExecutor(); - verify(ctx).tellNext(msg); + verify(ctx).tellNext(msg, "true"); } private void initWithScript(String script) throws TbNodeException { 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 1b4cce9fc9..a2f5f7d6f2 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 @@ -34,14 +34,13 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.same; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class TbJsSwitchNodeTest { @@ -53,6 +52,41 @@ public class TbJsSwitchNodeTest { @Mock private ListeningExecutor executor; + @Test + public void routeToAllDoNotEvaluatesJs() throws TbNodeException { + HashSet relations = Sets.newHashSet("one", "two"); + initWithScript("test qwerty", relations, true); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes()); + + node.onMsg(ctx, msg); + verify(ctx).tellNext(msg, relations); + verifyNoMoreInteractions(ctx, executor); + } + + @Test + public void multipleRoutesAreAllowed() throws TbNodeException { + String jsCode = "function nextRelation(meta, msg) {\n" + + " if(msg.passed == 5 && meta.temp == 10)\n" + + " return ['three', 'one']\n" + + " else\n" + + " return 'two';\n" + + "};\n" + + "\n" + + "nextRelation(meta, msg);"; + initWithScript(jsCode, Sets.newHashSet("one", "two", "three"), false); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "10"); + metaData.putValue("humidity", "99"); + String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + verify(ctx).tellNext(msg, Sets.newHashSet("one", "three")); + } + @Test public void allowedRelationPassed() throws TbNodeException { String jsCode = "function nextRelation(meta, msg) {\n" + @@ -63,7 +97,7 @@ public class TbJsSwitchNodeTest { "};\n" + "\n" + "nextRelation(meta, msg);"; - initWithScript(jsCode, Sets.newHashSet("one", "two")); + initWithScript(jsCode, Sets.newHashSet("one", "two"), false); TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("temp", "10"); metaData.putValue("humidity", "99"); @@ -74,17 +108,17 @@ public class TbJsSwitchNodeTest { node.onMsg(ctx, msg); verify(ctx).getJsExecutor(); - verify(ctx).tellNext(msg, "one"); + verify(ctx).tellNext(msg, Sets.newHashSet("one")); } @Test public void unknownRelationThrowsException() throws TbNodeException { String jsCode = "function nextRelation(meta, msg) {\n" + - " return 'nine';" + + " return ['one','nine'];" + "};\n" + "\n" + "nextRelation(meta, msg);"; - initWithScript(jsCode, Sets.newHashSet("one", "two")); + initWithScript(jsCode, Sets.newHashSet("one", "two"), false); TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("temp", "10"); metaData.putValue("humidity", "99"); @@ -95,13 +129,14 @@ public class TbJsSwitchNodeTest { node.onMsg(ctx, msg); verify(ctx).getJsExecutor(); - verifyError(msg, "Unsupported relation for switch nine", IllegalStateException.class); + verifyError(msg, "Unsupported relation for switch [nine, one]", IllegalStateException.class); } - private void initWithScript(String script, Set relations) throws TbNodeException { + private void initWithScript(String script, Set relations, boolean routeToAll) throws TbNodeException { TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration(); config.setJsScript(script); config.setAllowedRelations(relations); + config.setRouteToAllWithNoCheck(routeToAll); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); nodeConfiguration.setData(mapper.valueToTree(config)); @@ -112,10 +147,10 @@ public class TbJsSwitchNodeTest { private void mockJsExecutor() { when(ctx.getJsExecutor()).thenReturn(executor); - doAnswer((Answer>) invocationOnMock -> { + doAnswer((Answer>>) invocationOnMock -> { try { Callable task = (Callable) (invocationOnMock.getArguments())[0]; - return Futures.immediateFuture((String) task.call()); + return Futures.immediateFuture((Set) task.call()); } catch (Throwable th) { return Futures.immediateFailedFuture(th); } 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 new file mode 100644 index 0000000000..77b00fb778 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java @@ -0,0 +1,125 @@ +/** + * 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.transform; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.Futures; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +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.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.asset.AssetService; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class TbChangeOriginatorNodeTest { + + private TbChangeOriginatorNode node; + + @Mock + private TbContext ctx; + @Mock + private AssetService assetService; + + + @Test + public void originatorCanBeChangedToCustomerId() throws TbNodeException { + init(false); + AssetId assetId = new AssetId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + Asset asset = new Asset(); + asset.setCustomerId(customerId); + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]); + + 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()); + } + + @Test + public void newChainCanBeStarted() throws TbNodeException { + init(true); + AssetId assetId = new AssetId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + Asset asset = new Asset(); + asset.setCustomerId(customerId); + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]); + + 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()); + } + + @Test + public void exceptionThrownIfCannotFindNewOriginator() throws TbNodeException { + init(true); + AssetId assetId = new AssetId(UUIDs.timeBased()); + CustomerId customerId = new CustomerId(UUIDs.timeBased()); + Asset asset = new Asset(); + asset.setCustomerId(customerId); + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]); + + when(ctx.getAssetService()).thenReturn(assetService); + when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("wrong"))); + + node.onMsg(ctx, msg); + ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellError(same(msg), captor.capture()); + Throwable value = captor.getValue(); + assertEquals("wrong", value.getMessage()); + } + + public void init(boolean startNewChain) throws TbNodeException { + TbChangeOriginatorNodeConfiguration config = new TbChangeOriginatorNodeConfiguration(); + config.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); + config.setStartNewChain(startNewChain); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); + nodeConfiguration.setData(mapper.valueToTree(config)); + + node = new TbChangeOriginatorNode(); + node.init(nodeConfiguration, null); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..876e70f9b8 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java @@ -0,0 +1,141 @@ +/** + * 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.transform; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.thingsboard.rule.engine.api.ListeningExecutor; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.concurrent.Callable; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class TbTransformMsgNodeTest { + + private TbTransformMsgNode node; + + @Mock + private TbContext ctx; + @Mock + private ListeningExecutor executor; + + @Test + public void metadataCanBeUpdated() throws TbNodeException { + initWithScript("meta.temp = meta.temp * 10;"); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "7"); + metaData.putValue("humidity", "99"); + String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx).tellNext(captor.capture()); + TbMsg actualMsg = captor.getValue(); + assertEquals("70.0", actualMsg.getMetaData().getValue("temp")); + } + + @Test + public void metadataCanBeAdded() throws TbNodeException { + initWithScript("meta.newAttr = meta.humidity - msg.passed;"); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "7"); + metaData.putValue("humidity", "99"); + String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx).tellNext(captor.capture()); + TbMsg actualMsg = captor.getValue(); + assertEquals("94.0", actualMsg.getMetaData().getValue("newAttr")); + } + + @Test + public void payloadCanBeUpdated() throws TbNodeException { + initWithScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' "); + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("temp", "7"); + metaData.putValue("humidity", "99"); + String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}"; + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); + mockJsExecutor(); + + node.onMsg(ctx, msg); + verify(ctx).getJsExecutor(); + ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx).tellNext(captor.capture()); + TbMsg actualMsg = captor.getValue(); + String expectedJson = "{\"name\":\"Vit\",\"passed\":35.0,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}"; + assertEquals(expectedJson, new String(actualMsg.getData())); + } + + private void initWithScript(String script) throws TbNodeException { + TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration(); + config.setJsScript(script); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); + nodeConfiguration.setData(mapper.valueToTree(config)); + + node = new TbTransformMsgNode(); + node.init(nodeConfiguration, null); + } + + private void mockJsExecutor() { + when(ctx.getJsExecutor()).thenReturn(executor); + doAnswer((Answer>) invocationOnMock -> { + try { + Callable task = (Callable) (invocationOnMock.getArguments())[0]; + return Futures.immediateFuture((TbMsg) task.call()); + } catch (Throwable th) { + return Futures.immediateFailedFuture(th); + } + }).when(executor).executeAsync(Matchers.any(Callable.class)); + } + + private void verifyError(TbMsg msg, String message, Class expectedClass) { + ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); + verify(ctx).tellError(same(msg), captor.capture()); + + Throwable value = captor.getValue(); + assertEquals(expectedClass, value.getClass()); + assertEquals(message, value.getMessage()); + } +} \ No newline at end of file From 64a8e5c152c6f0629d883608b87686edb9eace6a Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 20 Mar 2018 17:40:56 +0200 Subject: [PATCH 023/171] First integration test for the new rule engine. Fixed a lot of bugs --- application/pom.xml | 4 + .../main/data/upgrade/1.5.0/schema_update.cql | 2 + .../main/data/upgrade/1.5.0/schema_update.sql | 2 + .../server/actors/ActorSystemContext.java | 193 +++++++--- .../server/actors/app/AppActor.java | 13 +- .../server/actors/device/DeviceActor.java | 21 +- .../device/DeviceActorMessageProcessor.java | 63 ++-- .../server/actors/plugin/PluginActor.java | 7 + .../server/actors/rpc/RpcManagerActor.java | 7 + .../server/actors/rpc/RpcSessionActor.java | 7 + .../actors/rule/ChainProcessingContext.java | 117 ------ .../actors/rule/ChainProcessingMetaData.java | 41 --- .../actors/rule/ComplexRuleActorChain.java | 43 --- .../actors/rule/CompoundRuleActorChain.java | 20 - .../server/actors/rule/RuleActor.java | 90 ----- .../rule/RuleActorMessageProcessor.java | 345 ------------------ .../server/actors/rule/RuleActorMetaData.java | 107 ------ .../rule/RuleContextAwareMsgProcessor.java | 33 -- .../actors/rule/RuleProcessingContext.java | 115 ------ .../server/actors/rule/RuleProcessingMsg.java | 31 -- .../actors/rule/RuleTerminationMsg.java | 30 -- .../actors/rule/RuleToPluginTimeoutMsg.java | 36 -- .../actors/rule/SimpleRuleActorChain.java | 39 -- .../actors/ruleChain/DefaultTbContext.java | 90 ++++- .../actors/ruleChain/RuleChainActor.java | 15 +- .../RuleChainActorMessageProcessor.java | 33 +- .../ruleChain/RuleChainManagerActor.java | 15 + .../ruleChain/RuleChainToRuleNodeMsg.java | 15 + .../actors/ruleChain/RuleNodeActor.java | 26 +- .../RuleNodeActorMessageProcessor.java | 40 +- .../server/actors/ruleChain/RuleNodeCtx.java | 22 +- .../actors/ruleChain/RuleNodeRelation.java | 15 + .../RuleNodeToRuleChainTellNextMsg.java | 15 + .../RuleNodeToSelfErrorMsg.java} | 28 +- .../server/actors/service/ComponentActor.java | 1 - .../actors/service/ContextAwareActor.java | 9 +- .../actors/service/DefaultActorService.java | 8 +- .../server/actors/session/SessionActor.java | 7 + .../actors/session/SessionManagerActor.java | 7 + .../actors/shared/EntityActorsManager.java | 15 + .../actors/shared/plugin/PluginManager.java | 8 +- .../actors/shared/rule/RuleManager.java | 135 ------- .../actors/shared/rule/SystemRuleManager.java | 40 -- .../actors/shared/rule/TenantRuleManager.java | 48 --- .../shared/rulechain/RuleChainManager.java | 15 + .../rulechain/TenantRuleChainManager.java | 11 +- .../server/actors/stats/StatsActor.java | 7 + .../actors/tenant/RuleChainDeviceMsg.java | 40 -- .../server/actors/tenant/TenantActor.java | 13 +- .../controller/RuleChainController.java | 2 +- .../AnnotationComponentDiscoveryService.java | 49 ++- .../src/main/resources/thingsboard.yml | 14 +- .../controller/AbstractControllerTest.java | 2 + .../AbstractRuleEngineControllerTest.java | 41 +++ .../server/rules/RuleEngineSqlTestSuite.java | 35 ++ ...AbstractRuleEngineFlowIntegrationTest.java | 156 ++++++++ .../RuleEngineFlowSqlIntegrationTest.java} | 14 +- .../server/common/data/DataConstants.java | 7 +- .../common/data/plugin/ComponentType.java | 2 +- .../server/common/data/rule/RuleChain.java | 1 + .../server/common/data/rule/RuleNode.java | 1 + .../server/common/msg/MsgType.java | 20 + .../server/common/msg/TbActorMsg.java | 15 + .../thingsboard/server/common/msg/TbMsg.java | 30 +- .../server/common/msg/TbMsgDataType.java | 15 + .../server/common/msg/TbMsgMetaData.java | 19 +- .../msg/plugin/ComponentLifecycleMsg.java | 8 +- .../msg/system/ServiceToRuleEngineMsg.java | 15 + .../server/dao/model/ModelConstants.java | 2 + .../dao/model/nosql/RuleChainEntity.java | 16 +- .../dao/model/nosql/RuleNodeEntity.java | 9 + .../server/dao/model/sql/RuleChainEntity.java | 5 + .../server/dao/model/sql/RuleNodeEntity.java | 5 + .../server/dao/queue/QueueBenchmark.java | 3 +- .../server/dao/rule/BaseRuleChainService.java | 8 +- .../server/dao/rule/RuleChainService.java | 2 + dao/src/main/resources/cassandra/schema.cql | 2 + dao/src/main/resources/sql/schema.sql | 2 + .../dao/service/AbstractServiceTest.java | 6 +- .../cassandra/UnprocessedMsgFilterTest.java | 4 +- .../impl/CassandraMsgRepositoryTest.java | 7 +- pom.xml | 5 + .../rule/engine/api/ActionNode.java | 43 +++ .../rule/engine/api/EnrichmentNode.java | 42 +++ .../rule/engine/api/FilterNode.java | 43 +++ .../rule/engine/api/TbContext.java | 2 - .../rule/engine/api/TbNodeConfiguration.java | 4 +- .../rule/engine/api/TbNodeState.java | 2 +- .../rule/engine/api/TransformationNode.java | 43 +++ .../main/resources/EmptyNodeDescriptor.json | 2 + rule-engine/rule-engine-components/pom.xml | 5 - .../engine/metadata/TbGetAttributesNode.java | 26 +- .../metadata/TbGetCustomerAttributeNode.java | 2 + .../metadata/TbGetRelatedAttributeNode.java | 6 +- .../metadata/TbGetTenantAttributeNode.java | 4 +- .../TbGetCustomerAttributeNodeTest.java | 6 +- 96 files changed, 1238 insertions(+), 1558 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java rename application/src/main/java/org/thingsboard/server/actors/{rule/RulesProcessedMsg.java => ruleChain/RuleNodeToSelfErrorMsg.java} (54%) delete mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java delete mode 100644 application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java create mode 100644 application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java rename application/src/{main/java/org/thingsboard/server/actors/rule/RuleActorChain.java => test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java} (64%) create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java create mode 100644 rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json diff --git a/application/pom.xml b/application/pom.xml index 95322fa3a2..6da40a9143 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -57,6 +57,10 @@ org.thingsboard.rule-engine rule-engine-api + + org.thingsboard.rule-engine + rule-engine-components + org.thingsboard extensions-core 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 aa8b10bdfd..ab6884659d 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 @@ -69,6 +69,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_chain ( search_text text, first_rule_node_id uuid, root boolean, + debug_mode boolean, configuration text, additional_info text, PRIMARY KEY (id, tenant_id) @@ -85,6 +86,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( id uuid, type text, name text, + debug_mode boolean, search_text text, configuration text, additional_info text, 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 0043ed5bb0..2bed6ad9da 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 @@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS rule_chain ( name varchar(255), first_rule_node_id varchar(31), root boolean, + debug_mode boolean, search_text varchar(255), tenant_id varchar(31) ); @@ -31,5 +32,6 @@ CREATE TABLE IF NOT EXISTS rule_node ( configuration varchar(10000000), type varchar(255), name varchar(255), + debug_mode boolean, search_text varchar(255) ); \ No newline at end of file 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 68d55475af..bbe7771e61 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -28,12 +28,14 @@ import lombok.Setter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.springframework.util.Base64Utils; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; @@ -50,6 +52,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; 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.dao.user.UserService; import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; @@ -57,6 +60,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.util.Optional; @Component @@ -65,107 +69,154 @@ public class ActorSystemContext { protected final ObjectMapper mapper = new ObjectMapper(); - @Getter @Setter private ActorService actorService; + @Getter + @Setter + private ActorService actorService; @Autowired - @Getter private DiscoveryService discoveryService; + @Getter + private DiscoveryService discoveryService; @Autowired - @Getter @Setter private ComponentDiscoveryService componentService; + @Getter + @Setter + private ComponentDiscoveryService componentService; @Autowired - @Getter private ClusterRoutingService routingService; + @Getter + private ClusterRoutingService routingService; @Autowired - @Getter private ClusterRpcService rpcService; + @Getter + private ClusterRpcService rpcService; @Autowired - @Getter private DeviceAuthService deviceAuthService; + @Getter + private DeviceAuthService deviceAuthService; @Autowired - @Getter private DeviceService deviceService; + @Getter + private DeviceService deviceService; @Autowired - @Getter private AssetService assetService; + @Getter + private AssetService assetService; @Autowired - @Getter private TenantService tenantService; + @Getter + private TenantService tenantService; @Autowired - @Getter private CustomerService customerService; + @Getter + private CustomerService customerService; @Autowired - @Getter private RuleService ruleService; + @Getter + private UserService userService; @Autowired - @Getter private RuleChainService ruleChainService; + @Getter + private RuleService ruleService; @Autowired - @Getter private PluginService pluginService; + @Getter + private RuleChainService ruleChainService; @Autowired - @Getter private TimeseriesService tsService; + @Getter + private PluginService pluginService; @Autowired - @Getter private AttributesService attributesService; + @Getter + private TimeseriesService tsService; @Autowired - @Getter private EventService eventService; + @Getter + private AttributesService attributesService; @Autowired - @Getter private AlarmService alarmService; + @Getter + private EventService eventService; @Autowired - @Getter private RelationService relationService; + @Getter + private AlarmService alarmService; @Autowired - @Getter private AuditLogService auditLogService; + @Getter + private RelationService relationService; @Autowired - @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint; + @Getter + private AuditLogService auditLogService; + + @Autowired + @Getter + @Setter + private PluginWebSocketMsgEndpoint wsMsgEndpoint; @Value("${actors.session.sync.timeout}") - @Getter private long syncSessionTimeout; + @Getter + private long syncSessionTimeout; @Value("${actors.plugin.termination.delay}") - @Getter private long pluginActorTerminationDelay; + @Getter + private long pluginActorTerminationDelay; @Value("${actors.plugin.processing.timeout}") - @Getter private long pluginProcessingTimeout; + @Getter + private long pluginProcessingTimeout; @Value("${actors.plugin.error_persist_frequency}") - @Getter private long pluginErrorPersistFrequency; + @Getter + private long pluginErrorPersistFrequency; @Value("${actors.rule.chain.error_persist_frequency}") - @Getter private long ruleChainErrorPersistFrequency; + @Getter + private long ruleChainErrorPersistFrequency; @Value("${actors.rule.node.error_persist_frequency}") - @Getter private long ruleNodeErrorPersistFrequency; + @Getter + private long ruleNodeErrorPersistFrequency; @Value("${actors.rule.termination.delay}") - @Getter private long ruleActorTerminationDelay; + @Getter + private long ruleActorTerminationDelay; @Value("${actors.rule.error_persist_frequency}") - @Getter private long ruleErrorPersistFrequency; + @Getter + private long ruleErrorPersistFrequency; @Value("${actors.statistics.enabled}") - @Getter private boolean statisticsEnabled; + @Getter + private boolean statisticsEnabled; @Value("${actors.statistics.persist_frequency}") - @Getter private long statisticsPersistFrequency; + @Getter + private long statisticsPersistFrequency; @Value("${actors.tenant.create_components_on_init}") - @Getter private boolean tenantComponentsInitEnabled; + @Getter + private boolean tenantComponentsInitEnabled; - @Getter @Setter private ActorSystem actorSystem; + @Getter + @Setter + private ActorSystem actorSystem; - @Getter @Setter private ActorRef appActor; + @Getter + @Setter + private ActorRef appActor; - @Getter @Setter private ActorRef sessionManagerActor; + @Getter + @Setter + private ActorRef sessionManagerActor; - @Getter @Setter private ActorRef statsActor; + @Getter + @Setter + private ActorRef statsActor; - @Getter private final Config config; + @Getter + private final Config config; public ActorSystemContext() { config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load()); @@ -197,7 +248,7 @@ public class ActorSystemContext { eventService.save(event); } - private String toString(Exception e) { + private String toString(Throwable e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); return sw.toString(); @@ -217,4 +268,64 @@ public class ActorSystemContext { private JsonNode toBodyJson(ServerAddress server, String method, String body) { return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body); } + + public String getServerAddress() { + return discoveryService.getCurrentServer().getServerAddress().toString(); + } + + public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) { + persistDebug(tenantId, entityId, "IN", tbMsg, null); + } + + public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) { + persistDebug(tenantId, entityId, "IN", tbMsg, error); + } + + public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) { + persistDebug(tenantId, entityId, "OUT", tbMsg, error); + } + + public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) { + persistDebug(tenantId, entityId, "OUT", tbMsg, null); + } + + private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, Throwable error) { + Event event = new Event(); + event.setTenantId(tenantId); + event.setEntityId(entityId); + event.setType(DataConstants.DEBUG); + + ObjectNode node = mapper.createObjectNode() + .put("type", type) + .put("server", getServerAddress()) + .put("entityId", tbMsg.getOriginator().getId().toString()) + .put("entityName", tbMsg.getOriginator().getEntityType().name()) + .put("msgId", tbMsg.getId().toString()) + .put("msgType", tbMsg.getType()) + .put("dataType", tbMsg.getDataType().name()); + + ObjectNode mdNode = node.putObject("metadata"); + tbMsg.getMetaData().getData().forEach(mdNode::put); + + switch (tbMsg.getDataType()) { + case BINARY: + node.put("data", Base64Utils.encodeUrlSafe(tbMsg.getData())); + break; + default: + node.put("data", new String(tbMsg.getData(), StandardCharsets.UTF_8)); + break; + } + + if (error != null) { + node = node.put("error", toString(error)); + } + + event.setBody(node); + eventService.save(event); + } + + 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/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 5e05a60a14..a75158f2dd 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. @@ -88,7 +88,7 @@ public class AppActor extends RuleChainManagerActor { } @Override - protected void process(TbActorMsg msg) { + protected boolean process(TbActorMsg msg) { switch (msg.getMsgType()) { case COMPONENT_LIFE_CYCLE_MSG: onComponentLifecycleMsg((ComponentLifecycleMsg) msg); @@ -96,7 +96,10 @@ public class AppActor extends RuleChainManagerActor { case SERVICE_TO_RULE_ENGINE_MSG: onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg); break; + default: + return false; } + return true; } private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { 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 861c405e08..87bc9926d4 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 @@ -18,19 +18,19 @@ package org.thingsboard.server.actors.device; import akka.event.Logging; import akka.event.LoggingAdapter; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.rule.RulesProcessedMsg; import org.thingsboard.server.actors.service.ContextAwareActor; import org.thingsboard.server.actors.service.ContextBasedCreator; -import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; 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.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.*; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; public class DeviceActor extends ContextAwareActor { @@ -47,13 +47,18 @@ public class DeviceActor extends ContextAwareActor { this.processor = new DeviceActorMessageProcessor(systemContext, logger, 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); - } else if (msg instanceof ToDeviceActorMsg) { +// 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) { 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 21112bf4df..3644a491a4 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 @@ -19,9 +19,7 @@ import akka.actor.ActorContext; import akka.actor.ActorRef; import akka.event.LoggingAdapter; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.rule.*; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; -import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DeviceId; @@ -37,15 +35,10 @@ import org.thingsboard.server.common.msg.session.FromDeviceMsg; import org.thingsboard.server.common.msg.session.MsgType; import org.thingsboard.server.common.msg.session.SessionType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; -import org.thingsboard.server.extensions.api.device.*; -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.TimeoutIntMsg; -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.ToDeviceRpcRequestBody; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; -import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; +import org.thingsboard.server.extensions.api.device.DeviceAttributes; +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; @@ -230,18 +223,18 @@ 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 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) { SessionId sessionId = msg.getSessionId(); @@ -302,18 +295,18 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso ); } - 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()); - } +// 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) { SessionId sessionId = msg.getSessionId(); 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 265da386ff..88278f316d 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 @@ -23,6 +23,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.stats.StatsPersistTick; import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.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; @@ -40,6 +41,12 @@ public class PluginActor extends ComponentActor= this.md.chain.size()) { - throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!"); - } - } - - public ActorRef getDeviceActor() { - return md.originator; - } - - public ActorRef getCurrentActor() { - return md.chain.getRuleActorMd(index).getActorRef(); - } - - public boolean hasNext() { - return (getChainLength() - 1) > index; - } - - public boolean isFailure() { - return (error != null && error.isCritical()) || (response != null && !response.isSuccess()); - } - - public ChainProcessingContext getNext() { - return new ChainProcessingContext(this, 1, this.error); - } - - public ChainProcessingContext withError(RuleEngineError error) { - if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) { - return new ChainProcessingContext(this, 0, error); - } else { - return this; - } - } - - public int getChainLength() { - return md.chain.size(); - } - - public ToDeviceActorMsg getInMsg() { - return md.inMsg; - } - - public DeviceMetaData getDeviceMetaData() { - return md.deviceMetaData; - } - - public String getDeviceName() { - return md.deviceMetaData.getDeviceName(); - } - - public String getDeviceType() { - return md.deviceMetaData.getDeviceType(); - } - - public DeviceAttributes getAttributes() { - return md.deviceMetaData.getDeviceAttributes(); - } - - public ToDeviceMsg getResponse() { - return response; - } - - public void mergeResponse(ToDeviceMsg response) { - // TODO add merge logic - this.response = response; - } - - public RuleEngineErrorMsg getError() { - return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error); - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java b/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java deleted file mode 100644 index 8080d4ecc9..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import akka.actor.ActorRef; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; -import org.thingsboard.server.extensions.api.device.DeviceMetaData; - -/** - * Immutable part of chain processing data; - * - * @author ashvayka - */ -public final class ChainProcessingMetaData { - - final RuleActorChain chain; - final ToDeviceActorMsg inMsg; - final ActorRef originator; - final DeviceMetaData deviceMetaData; - - public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceMetaData deviceMetaData, ActorRef originator) { - super(); - this.chain = chain; - this.inMsg = inMsg; - this.originator = originator; - this.deviceMetaData = deviceMetaData; - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java b/application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java deleted file mode 100644 index 89d31bdc84..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -public class ComplexRuleActorChain implements RuleActorChain { - - private final RuleActorChain systemChain; - private final RuleActorChain tenantChain; - - public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) { - super(); - this.systemChain = systemChain; - this.tenantChain = tenantChain; - } - - @Override - public int size() { - return systemChain.size() + tenantChain.size(); - } - - @Override - public RuleActorMetaData getRuleActorMd(int index) { - if (index < systemChain.size()) { - return systemChain.getRuleActorMd(index); - } else { - return tenantChain.getRuleActorMd(index - systemChain.size()); - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java b/application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java deleted file mode 100644 index b2eb53fe3f..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -public class CompoundRuleActorChain { - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java deleted file mode 100644 index 062da2a3bb..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.service.ComponentActor; -import org.thingsboard.server.actors.service.ContextBasedCreator; -import org.thingsboard.server.actors.stats.StatsPersistTick; -import org.thingsboard.server.common.data.id.RuleId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; -import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; - -public class RuleActor extends ComponentActor { - - private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) { - super(systemContext, tenantId, ruleId); - setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger)); - } - - @Override - public void onReceive(Object msg) throws Exception { - logger.debug("[{}] Received message: {}", id, msg); - if (msg instanceof RuleProcessingMsg) { - try { - processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg); - increaseMessagesProcessedCount(); - } catch (Exception e) { - logAndPersist("onDeviceMsg", e); - } - } else if (msg instanceof PluginToRuleMsg) { - try { - processor.onPluginMsg(context(), (PluginToRuleMsg) msg); - } catch (Exception e) { - logAndPersist("onPluginMsg", e); - } - } else if (msg instanceof ComponentLifecycleMsg) { - onComponentLifecycleMsg((ComponentLifecycleMsg) msg); - } else if (msg instanceof ClusterEventMsg) { - onClusterEventMsg((ClusterEventMsg) msg); - } else if (msg instanceof RuleToPluginTimeoutMsg) { - try { - processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg); - } catch (Exception e) { - logAndPersist("onTimeoutMsg", e); - } - } else if (msg instanceof StatsPersistTick) { - onStatsPersistTick(id); - } else { - logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName()); - } - } - - public static class ActorCreator extends ContextBasedCreator { - private static final long serialVersionUID = 1L; - - private final TenantId tenantId; - private final RuleId ruleId; - - public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) { - super(context); - this.tenantId = tenantId; - this.ruleId = ruleId; - } - - @Override - public RuleActor create() throws Exception { - return new RuleActor(context, tenantId, ruleId); - } - } - - @Override - protected long getErrorPersistFrequency() { - return systemContext.getRuleErrorPersistFrequency(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java deleted file mode 100644 index 236fca7e0f..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java +++ /dev/null @@ -1,345 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import java.util.*; - -import com.fasterxml.jackson.core.JsonProcessingException; -import org.springframework.util.StringUtils; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper; -import org.thingsboard.server.actors.shared.ComponentMsgProcessor; -import org.thingsboard.server.common.data.id.PluginId; -import org.thingsboard.server.common.data.id.RuleId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; -import org.thingsboard.server.common.data.plugin.PluginMetaData; -import org.thingsboard.server.common.data.rule.RuleMetaData; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.core.BasicRequest; -import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; -import org.thingsboard.server.common.msg.core.RuleEngineError; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; -import org.thingsboard.server.common.msg.session.MsgType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; -import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException; -import org.thingsboard.server.extensions.api.rules.*; -import org.thingsboard.server.extensions.api.plugins.PluginAction; -import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; -import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; - -import com.fasterxml.jackson.databind.JsonNode; - -import akka.actor.ActorContext; -import akka.actor.ActorRef; -import akka.event.LoggingAdapter; - -class RuleActorMessageProcessor extends ComponentMsgProcessor { - - private final RuleProcessingContext ruleCtx; - private final Map pendingMsgMap; - - private RuleMetaData ruleMd; - private ComponentLifecycleState state; - private List filters; - private RuleProcessor processor; - private PluginAction action; - - private TenantId pluginTenantId; - private PluginId pluginId; - - protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) { - super(systemContext, logger, tenantId, ruleId); - this.pendingMsgMap = new HashMap<>(); - this.ruleCtx = new RuleProcessingContext(systemContext, ruleId); - } - - @Override - public void start(ActorContext context) throws Exception { - logger.info("[{}][{}] Starting rule actor.", entityId, tenantId); - ruleMd = systemContext.getRuleService().findRuleById(entityId); - if (ruleMd == null) { - throw new RuleInitializationException("Rule not found!"); - } - state = ruleMd.getState(); - if (state == ComponentLifecycleState.ACTIVE) { - logger.info("[{}] Rule is active. Going to initialize rule components.", entityId); - initComponent(); - } else { - logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId); - } - - logger.info("[{}][{}] Started rule actor.", entityId, tenantId); - } - - @Override - public void stop(ActorContext context) throws Exception { - onStop(); - } - - - private void initComponent() throws RuleException { - try { - if (!ruleMd.getFilters().isArray()) { - throw new RuntimeException("Filters are not array!"); - } - fetchPluginInfo(); - initFilters(); - initProcessor(); - initAction(); - } catch (RuntimeException e) { - throw new RuleInitializationException("Unknown runtime exception!", e); - } catch (InstantiationException e) { - throw new RuleInitializationException("No default constructor for rule implementation!", e); - } catch (IllegalAccessException e) { - throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e); - } catch (ClassNotFoundException e) { - throw new RuleInitializationException("Rule Class not found!", e); - } catch (Exception e) { - throw new RuleException(e.getMessage(), e); - } - } - - private void initAction() throws Exception { - if (ruleMd.getAction() != null && !ruleMd.getAction().isNull()) { - action = initComponent(ruleMd.getAction()); - } - } - - private void initProcessor() throws Exception { - if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) { - processor = initComponent(ruleMd.getProcessor()); - } - } - - private void initFilters() throws Exception { - filters = new ArrayList<>(ruleMd.getFilters().size()); - for (int i = 0; i < ruleMd.getFilters().size(); i++) { - filters.add(initComponent(ruleMd.getFilters().get(i))); - } - } - - private void fetchPluginInfo() { - if (!StringUtils.isEmpty(ruleMd.getPluginToken())) { - PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken()); - pluginTenantId = pluginMd.getTenantId(); - pluginId = pluginMd.getId(); - } - } - - protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException { - if (state != ComponentLifecycleState.ACTIVE) { - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES); - return; - } - ChainProcessingContext chainCtx = msg.getCtx(); - ToDeviceActorMsg inMsg = chainCtx.getInMsg(); - - ruleCtx.update(inMsg, chainCtx.getDeviceMetaData()); - - logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg); - for (RuleFilter filter : filters) { - if (!filter.filter(ruleCtx, inMsg)) { - logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg); - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED); - return; - } - } - RuleProcessingMetaData inMsgMd; - if (processor != null) { - logger.debug("[{}] Going to process in msg: {}", entityId, inMsg); - inMsgMd = processor.process(ruleCtx, inMsg); - } else { - inMsgMd = new RuleProcessingMetaData(); - } - logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg); - if (action != null) { - Optional> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); - if (ruleToPluginMsgOptional.isPresent()) { - RuleToPluginMsg ruleToPluginMsg = ruleToPluginMsgOptional.get(); - logger.debug("[{}] Device msg is converted to: {}", entityId, ruleToPluginMsg); - context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); - if (action.isOneWayAction()) { - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); - return; - } else { - pendingMsgMap.put(ruleToPluginMsg.getUid(), msg); - scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout()); - return; - } - } - } - logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId); - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); - } - - void onPluginMsg(ActorContext context, PluginToRuleMsg msg) { - RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid()); - if (pendingMsg != null) { - ChainProcessingContext ctx = pendingMsg.getCtx(); - Optional ruleResponseOptional = action.convert(msg); - if (ruleResponseOptional.isPresent()) { - ctx.mergeResponse(ruleResponseOptional.get()); - pushToNextRule(context, ctx, null); - } else { - pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS); - } - } else { - logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid()); - } - } - - void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) { - RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId()); - if (pendingMsg != null) { - logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg); - ChainProcessingContext ctx = pendingMsg.getCtx(); - pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT); - } - } - - private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) { - if (error != null) { - ctx = ctx.withError(error); - } - if (ctx.isFailure()) { - logger.debug("[{}][{}] Forwarding processing chain to device actor due to failure.", ruleMd.getId(), ctx.getInMsg().getDeviceId()); - ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); - } else if (!ctx.hasNext()) { - logger.debug("[{}][{}] Forwarding processing chain to device actor due to end of chain.", ruleMd.getId(), ctx.getInMsg().getDeviceId()); - ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); - } else { - logger.debug("[{}][{}] Forwarding processing chain to next rule actor.", ruleMd.getId(), ctx.getInMsg().getDeviceId()); - ChainProcessingContext nextTask = ctx.getNext(); - nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self()); - } - } - - @Override - public void onCreated(ActorContext context) { - logger.info("[{}] Going to process onCreated rule.", entityId); - } - - @Override - public void onUpdate(ActorContext context) throws RuleException { - RuleMetaData oldRuleMd = ruleMd; - ruleMd = systemContext.getRuleService().findRuleById(entityId); - logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd); - try { - fetchPluginInfo(); - if (filters == null || !Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) { - logger.info("[{}] Rule filters require restart due to json change from {} to {}.", - entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters())); - stopFilters(); - initFilters(); - } - if (processor == null || !Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) { - logger.info("[{}] Rule processor require restart due to configuration change.", entityId); - stopProcessor(); - initProcessor(); - } - if (action == null || !Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) { - logger.info("[{}] Rule action require restart due to configuration change.", entityId); - stopAction(); - initAction(); - } - } catch (RuntimeException e) { - throw new RuleInitializationException("Unknown runtime exception!", e); - } catch (InstantiationException e) { - throw new RuleInitializationException("No default constructor for rule implementation!", e); - } catch (IllegalAccessException e) { - throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e); - } catch (ClassNotFoundException e) { - throw new RuleInitializationException("Rule Class not found!", e); - } catch (JsonProcessingException e) { - throw new RuleInitializationException("Rule configuration is invalid!", e); - } catch (Exception e) { - throw new RuleInitializationException(e.getMessage(), e); - } - } - - @Override - public void onActivate(ActorContext context) throws Exception { - logger.info("[{}] Going to process onActivate rule.", entityId); - this.state = ComponentLifecycleState.ACTIVE; - if (filters != null) { - filters.forEach(RuleLifecycleComponent::resume); - if (processor != null) { - processor.resume(); - } else { - initProcessor(); - } - if (action != null) { - action.resume(); - } - logger.info("[{}] Rule resumed.", entityId); - } else { - start(); - } - } - - @Override - public void onSuspend(ActorContext context) { - logger.info("[{}] Going to process onSuspend rule.", entityId); - this.state = ComponentLifecycleState.SUSPENDED; - if (filters != null) { - filters.forEach(f -> f.suspend()); - } - if (processor != null) { - processor.suspend(); - } - if (action != null) { - action.suspend(); - } - } - - @Override - public void onStop(ActorContext context) { - logger.info("[{}] Going to process onStop rule.", entityId); - onStop(); - scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay()); - } - - private void onStop() { - this.state = ComponentLifecycleState.SUSPENDED; - stopFilters(); - stopProcessor(); - stopAction(); - } - - @Override - public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { - //Do nothing - } - - private void stopAction() { - if (action != null) { - action.stop(); - } - } - - private void stopProcessor() { - if (processor != null) { - processor.stop(); - } - } - - private void stopFilters() { - if (filters != null) { - filters.forEach(f -> f.stop()); - } - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java deleted file mode 100644 index df7adac396..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import java.util.Comparator; - -import org.thingsboard.server.common.data.id.RuleId; - -import akka.actor.ActorRef; - -public class RuleActorMetaData { - - private final RuleId ruleId; - private final boolean systemRule; - private final int weight; - private final ActorRef actorRef; - - public static final Comparator RULE_ACTOR_MD_COMPARATOR = new Comparator() { - - @Override - public int compare(RuleActorMetaData r1, RuleActorMetaData r2) { - if (r1.isSystemRule() && !r2.isSystemRule()) { - return 1; - } else if (!r1.isSystemRule() && r2.isSystemRule()) { - return -1; - } else { - return Integer.compare(r2.getWeight(), r1.getWeight()); - } - } - }; - - public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) { - return new RuleActorMetaData(ruleId, true, weight, actorRef); - } - - public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) { - return new RuleActorMetaData(ruleId, false, weight, actorRef); - } - - private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) { - super(); - this.ruleId = ruleId; - this.systemRule = systemRule; - this.weight = weight; - this.actorRef = actorRef; - } - - public RuleId getRuleId() { - return ruleId; - } - - public boolean isSystemRule() { - return systemRule; - } - - public int getWeight() { - return weight; - } - - public ActorRef getActorRef() { - return actorRef; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - RuleActorMetaData other = (RuleActorMetaData) obj; - if (ruleId == null) { - if (other.ruleId != null) - return false; - } else if (!ruleId.equals(other.ruleId)) - return false; - return true; - } - - @Override - public String toString() { - return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]"; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java deleted file mode 100644 index c0d0705c4d..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; -import org.thingsboard.server.common.data.id.RuleId; - -import akka.event.LoggingAdapter; - -public class RuleContextAwareMsgProcessor extends AbstractContextAwareMsgProcessor { - - private final RuleId ruleId; - - protected RuleContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, RuleId ruleId) { - super(systemContext, logger); - this.ruleId = ruleId; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java deleted file mode 100644 index 179307bb05..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.common.data.Event; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmId; -import org.thingsboard.server.common.data.id.*; -import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.dao.event.EventService; -import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; -import org.thingsboard.server.extensions.api.device.DeviceMetaData; -import org.thingsboard.server.extensions.api.rules.RuleContext; - -import java.util.Optional; -import java.util.concurrent.ExecutionException; - -public class RuleProcessingContext implements RuleContext { - - private final TimeseriesService tsService; - private final EventService eventService; - private final AlarmService alarmService; - private final RuleId ruleId; - private TenantId tenantId; - private CustomerId customerId; - private DeviceId deviceId; - private DeviceMetaData deviceMetaData; - - RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) { - this.tsService = systemContext.getTsService(); - this.eventService = systemContext.getEventService(); - this.alarmService = systemContext.getAlarmService(); - this.ruleId = ruleId; - } - - void update(ToDeviceActorMsg toDeviceActorMsg, DeviceMetaData deviceMetaData) { - this.tenantId = toDeviceActorMsg.getTenantId(); - this.customerId = toDeviceActorMsg.getCustomerId(); - this.deviceId = toDeviceActorMsg.getDeviceId(); - this.deviceMetaData = deviceMetaData; - } - - @Override - public RuleId getRuleId() { - return ruleId; - } - - @Override - public DeviceMetaData getDeviceMetaData() { - return deviceMetaData; - } - - @Override - public Event save(Event event) { - checkEvent(event); - return eventService.save(event); - } - - @Override - public Optional saveIfNotExists(Event event) { - checkEvent(event); - return eventService.saveIfNotExists(event); - } - - @Override - public Optional findEvent(String eventType, String eventUid) { - return eventService.findEvent(tenantId, deviceId, eventType, eventUid); - } - - @Override - public Alarm createOrUpdateAlarm(Alarm alarm) { - alarm.setTenantId(tenantId); - return alarmService.createOrUpdateAlarm(alarm); - } - - public Optional findLatestAlarm(EntityId originator, String alarmType) { - try { - return Optional.ofNullable(alarmService.findLatestByOriginatorAndType(tenantId, originator, alarmType).get()); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException("Failed to lookup alarm!", e); - } - } - - @Override - public ListenableFuture clearAlarm(AlarmId alarmId, long clearTs) { - return alarmService.clearAlarm(alarmId, clearTs); - } - - private void checkEvent(Event event) { - if (event.getTenantId() == null) { - event.setTenantId(tenantId); - } else if (!tenantId.equals(event.getTenantId())) { - throw new IllegalArgumentException("Invalid Tenant id!"); - } - if (event.getEntityId() == null) { - event.setEntityId(deviceId); - } - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java deleted file mode 100644 index 291a9a7cef..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -public class RuleProcessingMsg { - - private final ChainProcessingContext ctx; - - public RuleProcessingMsg(ChainProcessingContext ctx) { - super(); - this.ctx = ctx; - } - - public ChainProcessingContext getCtx() { - return ctx; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java deleted file mode 100644 index 4458e5217c..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import org.thingsboard.server.actors.shared.ActorTerminationMsg; -import org.thingsboard.server.common.data.id.PluginId; -import org.thingsboard.server.common.data.id.RuleId; - -/** - * @author Andrew Shvayka - */ -public class RuleTerminationMsg extends ActorTerminationMsg { - - public RuleTerminationMsg(RuleId id) { - super(id); - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java deleted file mode 100644 index 0258bce130..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import java.io.Serializable; -import java.util.UUID; - -public class RuleToPluginTimeoutMsg implements Serializable { - - private static final long serialVersionUID = 1L; - - private final UUID msgId; - - public RuleToPluginTimeoutMsg(UUID msgId) { - super(); - this.msgId = msgId; - } - - public UUID getMsgId() { - return msgId; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java b/application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java deleted file mode 100644 index 70a26e4430..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.rule; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -public class SimpleRuleActorChain implements RuleActorChain { - - private final List rules; - - public SimpleRuleActorChain(Set ruleSet) { - rules = new ArrayList<>(ruleSet); - rules.sort(RuleActorMetaData.RULE_ACTOR_MD_COMPARATOR); - } - - public int size() { - return rules.size(); - } - - public RuleActorMetaData getRuleActorMd(int index) { - return rules.get(index); - } - -} 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 0cd7601653..a1344aa8d4 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 @@ -1,10 +1,34 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.actors.ruleChain; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.plugin.PluginService; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.user.UserService; /** * Created by ashvayka on 19.03.18. @@ -26,27 +50,30 @@ class DefaultTbContext implements TbContext { @Override public void tellNext(TbMsg msg, String relationType) { - nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelfId(), relationType, msg), nodeCtx.getSelf()); + if (nodeCtx.getSelf().isDebugMode()) { + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg); + } + nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor()); } @Override public void tellSelf(TbMsg msg, long delayMs) { - + throw new RuntimeException("Not Implemented!"); } @Override public void tellOthers(TbMsg msg) { - + throw new RuntimeException("Not Implemented!"); } @Override public void tellSibling(TbMsg msg, ServerAddress address) { - + throw new RuntimeException("Not Implemented!"); } @Override public void spawn(TbMsg msg) { - + throw new RuntimeException("Not Implemented!"); } @Override @@ -54,8 +81,61 @@ class DefaultTbContext implements TbContext { } + @Override + public void tellError(TbMsg msg, Throwable th) { + if (nodeCtx.getSelf().isDebugMode()) { + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th); + } + nodeCtx.getSelfActor().tell(new RuleNodeToSelfErrorMsg(msg, th), nodeCtx.getSelfActor()); + } + @Override public AttributesService getAttributesService() { return mainCtx.getAttributesService(); } + + @Override + public CustomerService getCustomerService() { + return mainCtx.getCustomerService(); + } + + @Override + public UserService getUserService() { + return mainCtx.getUserService(); + } + + @Override + public PluginService getPluginService() { + return mainCtx.getPluginService(); + } + + @Override + public AssetService getAssetService() { + return mainCtx.getAssetService(); + } + + @Override + public DeviceService getDeviceService() { + return mainCtx.getDeviceService(); + } + + @Override + public AlarmService getAlarmService() { + return mainCtx.getAlarmService(); + } + + @Override + public RuleChainService getRuleChainService() { + return mainCtx.getRuleChainService(); + } + + @Override + public TimeseriesService getTimeseriesService() { + return mainCtx.getTsService(); + } + + @Override + public RelationService getRelationService() { + return mainCtx.getRelationService(); + } } 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 20ea05deb4..9c8de22376 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. @@ -33,7 +33,7 @@ public class RuleChainActor extends ComponentActor { @@ -69,4 +72,6 @@ public class RuleChainActor extends ComponentActor ruleNodeList = service.getRuleChainNodes(entityId); // Creating and starting the actors; @@ -74,8 +79,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor 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(); + } if (targetRelationType == null || targetRelationType.equalsIgnoreCase(relation.getType())) { switch (relation.getOut().getEntityType()) { case RULE_NODE: RuleNodeId targetRuleNodeId = new RuleNodeId(relation.getOut().getId()); RuleNodeCtx targetRuleNode = nodeActors.get(targetRuleNodeId); - pushMstToNode(targetRuleNode, envelope.getMsg()); + pushMstToNode(targetRuleNode, msg); break; case RULE_CHAIN: // TODO: implement @@ -137,7 +154,13 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor { private static final long serialVersionUID = 1L; @@ -69,4 +89,6 @@ public class RuleNodeActor extends ComponentActor componentClazz = Class.forName(ruleNode.getType()); + TbNode tbNode = (TbNode) (componentClazz.newInstance()); + tbNode.init(new TbNodeConfiguration(ruleNode.getConfiguration()), new TbNodeState()); + return tbNode; } } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java index dd25f8bc85..90c67c2a06 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java @@ -1,15 +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.ruleChain; import akka.actor.ActorRef; import lombok.Data; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleNode; /** * Created by ashvayka on 19.03.18. */ @Data final class RuleNodeCtx { + private final TenantId tenantId; private final ActorRef chainActor; - private final ActorRef self; - private final RuleNodeId selfId; + private final ActorRef selfActor; + private final RuleNode self; } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java index bd2d544eb3..7861e5473c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.actors.ruleChain; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java index 95b362591d..054284dc59 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.actors.ruleChain; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java similarity index 54% rename from application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java rename to application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java index 82b6a6784e..4fc03ac671 100644 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java @@ -13,22 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.actors.rule; +package org.thingsboard.server.actors.ruleChain; -public class RulesProcessedMsg { - private final ChainProcessingContext ctx; +import lombok.Data; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.TbMsg; - public RulesProcessedMsg(ChainProcessingContext ctx) { - super(); - this.ctx = ctx; - } +/** + * Created by ashvayka on 19.03.18. + */ +@Data +final class RuleNodeToSelfErrorMsg implements TbActorMsg { - public ChainProcessingContext getCtx() { - return ctx; - } + private final TbMsg msg; + private final Throwable error; @Override - public String toString() { - return "RulesProcessedMsg [ctx=" + ctx + "]"; + public MsgType getMsgType() { + return MsgType.RULE_TO_SELF_ERROR_MSG; } + } diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java index d0260dd974..6aa68d3c5b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java @@ -141,7 +141,6 @@ public abstract class ComponentActor + * * 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 - *

+ * + * 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. diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java b/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java index 37827d61fb..9d324c5b94 100644 --- a/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java @@ -23,6 +23,7 @@ import org.thingsboard.server.actors.service.ContextAwareActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.shared.SessionTimeoutMsg; import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg; @@ -60,6 +61,12 @@ public class SessionActor extends ContextAwareActor { }); } + @Override + protected boolean process(TbActorMsg msg) { + //TODO Move everything here, to work with TbActorMsg + return false; + } + @Override public void onReceive(Object msg) throws Exception { logger.debug("[{}] Processing: {}.", sessionId, msg); diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java index 9d67dabeb8..b5b17917b9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java @@ -26,6 +26,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.SessionTimeoutMsg; import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.SessionAwareMsg; import akka.event.Logging; @@ -48,6 +49,12 @@ public class SessionManagerActor extends ContextAwareActor { this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE); } + @Override + protected boolean process(TbActorMsg msg) { + //TODO Move everything here, to work with TbActorMsg + return false; + } + @Override public void onReceive(Object msg) throws Exception { if (msg instanceof SessionCtrlMsg) { diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java index 917a6452c4..d4a1f34006 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.actors.shared; import akka.actor.ActorContext; diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java index 5142de2c15..3345e5fff5 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java deleted file mode 100644 index 95d762afdf..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.shared.rule; - -import akka.actor.ActorContext; -import akka.actor.ActorRef; -import akka.actor.Props; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.rule.RuleActor; -import org.thingsboard.server.actors.rule.RuleActorChain; -import org.thingsboard.server.actors.rule.RuleActorMetaData; -import org.thingsboard.server.actors.rule.SimpleRuleActorChain; -import org.thingsboard.server.actors.service.ContextAwareActor; -import org.thingsboard.server.common.data.id.RuleId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageDataIterable; -import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; -import org.thingsboard.server.common.data.rule.RuleMetaData; -import org.thingsboard.server.dao.rule.RuleService; - -import java.util.*; - -@Slf4j -public abstract class RuleManager { - - protected final ActorSystemContext systemContext; - protected final RuleService ruleService; - protected final Map ruleActors; - protected final TenantId tenantId; - - private Map ruleMap; - private RuleActorChain ruleChain; - - public RuleManager(ActorSystemContext systemContext, TenantId tenantId) { - this.systemContext = systemContext; - this.ruleService = systemContext.getRuleService(); - this.ruleActors = new HashMap<>(); - this.tenantId = tenantId; - } - - public void init(ActorContext context) { - doInit(context); - } - - private void doInit(ActorContext context) { - PageDataIterable ruleIterator = new PageDataIterable<>(getFetchRulesFunction(), - ContextAwareActor.ENTITY_PACK_LIMIT); - ruleMap = new HashMap<>(); - - for (RuleMetaData rule : ruleIterator) { - log.debug("[{}] Creating rule actor {}", rule.getId(), rule); - ActorRef ref = getOrCreateRuleActor(context, rule.getId()); - ruleMap.put(rule, RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref)); - log.debug("[{}] Rule actor created.", rule.getId()); - } - - refreshRuleChain(); - } - - public Optional update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) { - if (ruleMap == null) { - doInit(context); - } - RuleMetaData rule; - if (event != ComponentLifecycleEvent.DELETED) { - rule = systemContext.getRuleService().findRuleById(ruleId); - } else { - rule = ruleMap.keySet().stream() - .filter(r -> r.getId().equals(ruleId)) - .peek(r -> r.setState(ComponentLifecycleState.SUSPENDED)) - .findFirst() - .orElse(null); - if (rule != null) { - ruleMap.remove(rule); - ruleActors.remove(ruleId); - } - } - if (rule != null) { - RuleActorMetaData actorMd = ruleMap.get(rule); - if (actorMd == null) { - ActorRef ref = getOrCreateRuleActor(context, rule.getId()); - actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref); - ruleMap.put(rule, actorMd); - } - refreshRuleChain(); - return Optional.of(actorMd.getActorRef()); - } else { - log.warn("[{}] Can't process unknown rule!", ruleId); - return Optional.empty(); - } - } - - abstract FetchFunction getFetchRulesFunction(); - - abstract String getDispatcherName(); - - public ActorRef getOrCreateRuleActor(ActorContext context, RuleId ruleId) { - return ruleActors.computeIfAbsent(ruleId, rId -> - context.actorOf(Props.create(new RuleActor.ActorCreator(systemContext, tenantId, rId)) - .withDispatcher(getDispatcherName()), rId.toString())); - } - - public RuleActorChain getRuleChain(ActorContext context) { - if (ruleChain == null) { - doInit(context); - } - return ruleChain; - } - - private void refreshRuleChain() { - Set activeRuleSet = new HashSet<>(); - for (Map.Entry rule : ruleMap.entrySet()) { - if (rule.getKey().getState() == ComponentLifecycleState.ACTIVE) { - activeRuleSet.add(rule.getValue()); - } - } - ruleChain = new SimpleRuleActorChain(activeRuleSet); - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java deleted file mode 100644 index d10731caed..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.shared.rule; - -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.service.DefaultActorService; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; -import org.thingsboard.server.common.data.rule.RuleMetaData; -import org.thingsboard.server.dao.model.ModelConstants; - -public class SystemRuleManager extends RuleManager { - - public SystemRuleManager(ActorSystemContext systemContext) { - super(systemContext, new TenantId(ModelConstants.NULL_UUID)); - } - - @Override - FetchFunction getFetchRulesFunction() { - return ruleService::findSystemRules; - } - - @Override - String getDispatcherName() { - return DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME; - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java deleted file mode 100644 index e4d023ca90..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.shared.rule; - -import akka.actor.ActorContext; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.service.DefaultActorService; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; -import org.thingsboard.server.common.data.rule.RuleMetaData; - -public class TenantRuleManager extends RuleManager { - - public TenantRuleManager(ActorSystemContext systemContext, TenantId tenantId) { - super(systemContext, tenantId); - } - - @Override - public void init(ActorContext context) { - if (systemContext.isTenantComponentsInitEnabled()) { - super.init(context); - } - } - - @Override - FetchFunction getFetchRulesFunction() { - return link -> ruleService.findTenantRules(tenantId, link); - } - - @Override - String getDispatcherName() { - return DefaultActorService.TENANT_RULE_DISPATCHER_NAME; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java index 97acb6cbfb..ff0c52ef45 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.actors.shared.rulechain; import akka.actor.ActorRef; diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java index aa2a63104a..731d8d8e6c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. @@ -18,12 +18,9 @@ package org.thingsboard.server.actors.shared.rulechain; import akka.actor.ActorContext; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.DefaultActorService; -import org.thingsboard.server.actors.shared.rule.RuleManager; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleMetaData; public class TenantRuleChainManager extends RuleChainManager { diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java index ccc31cca29..8623370896 100644 --- a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java @@ -24,6 +24,7 @@ import org.thingsboard.server.actors.service.ContextAwareActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; public class StatsActor extends ContextAwareActor { @@ -35,6 +36,12 @@ public class StatsActor extends ContextAwareActor { super(context); } + @Override + protected boolean process(TbActorMsg msg) { + //TODO Move everything here, to work with TbActorMsg\ + return false; + } + @Override public void onReceive(Object msg) throws Exception { logger.debug("Received message: {}", msg); diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java b/application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java deleted file mode 100644 index a84e0b5fd4..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.actors.tenant; - -import org.thingsboard.server.actors.rule.RuleActorChain; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; - -public class RuleChainDeviceMsg { - - private final ToDeviceActorMsg toDeviceActorMsg; - private final RuleActorChain ruleChain; - - public RuleChainDeviceMsg(ToDeviceActorMsg toDeviceActorMsg, RuleActorChain ruleChain) { - super(); - this.toDeviceActorMsg = toDeviceActorMsg; - this.ruleChain = ruleChain; - } - - public ToDeviceActorMsg getToDeviceActorMsg() { - return toDeviceActorMsg; - } - - public RuleActorChain getRuleChain() { - return ruleChain; - } - -} 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 a51e7a2187..d53c054414 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. @@ -62,7 +62,7 @@ public class TenantActor extends RuleChainManagerActor { } @Override - protected void process(TbActorMsg msg) { + protected boolean process(TbActorMsg msg) { switch (msg.getMsgType()) { case COMPONENT_LIFE_CYCLE_MSG: onComponentLifecycleMsg((ComponentLifecycleMsg) msg); @@ -70,7 +70,10 @@ public class TenantActor extends RuleChainManagerActor { case SERVICE_TO_RULE_ENGINE_MSG: onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg); break; + default: + return false; } + return true; } private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { 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 93891ac53a..c6befdd8ca 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -78,7 +78,7 @@ public class RuleChainController extends BaseController { ruleChain.setTenantId(getCurrentUser().getTenantId()); RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); - actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), + actorService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); logEntityAction(savedRuleChain.getId(), savedRuleChain, diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index 0a6081d8da..910b45954a 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -26,6 +26,10 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen import org.springframework.core.env.Environment; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.ActionNode; +import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.FilterNode; +import org.thingsboard.rule.engine.api.TransformationNode; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.dao.component.ComponentDescriptorService; @@ -79,8 +83,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe private List persist(Set filterDefs, ComponentType type) { List result = new ArrayList<>(); for (BeanDefinition def : filterDefs) { - ComponentDescriptor scannedComponent = scanAndPersistComponent(def, type); - result.add(scannedComponent); + result.add(scanAndPersistComponent(def, type)); } return result; } @@ -93,24 +96,36 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe Class clazz = Class.forName(clazzName); String descriptorResourceName; switch (type) { + case ENRICHMENT: + EnrichmentNode enrichmentAnnotation = clazz.getAnnotation(EnrichmentNode.class); + scannedComponent.setName(enrichmentAnnotation.name()); + scannedComponent.setScope(enrichmentAnnotation.scope()); + descriptorResourceName = enrichmentAnnotation.descriptor(); + break; case FILTER: - Filter filterAnnotation = clazz.getAnnotation(Filter.class); + FilterNode filterAnnotation = clazz.getAnnotation(FilterNode.class); scannedComponent.setName(filterAnnotation.name()); scannedComponent.setScope(filterAnnotation.scope()); descriptorResourceName = filterAnnotation.descriptor(); break; - case PROCESSOR: - Processor processorAnnotation = clazz.getAnnotation(Processor.class); - scannedComponent.setName(processorAnnotation.name()); - scannedComponent.setScope(processorAnnotation.scope()); - descriptorResourceName = processorAnnotation.descriptor(); + case TRANSFORMATION: + TransformationNode trAnnotation = clazz.getAnnotation(TransformationNode.class); + scannedComponent.setName(trAnnotation.name()); + scannedComponent.setScope(trAnnotation.scope()); + descriptorResourceName = trAnnotation.descriptor(); break; case ACTION: - Action actionAnnotation = clazz.getAnnotation(Action.class); + ActionNode actionAnnotation = clazz.getAnnotation(ActionNode.class); scannedComponent.setName(actionAnnotation.name()); scannedComponent.setScope(actionAnnotation.scope()); descriptorResourceName = actionAnnotation.descriptor(); break; + case OLD_ACTION: + Action oldActionAnnotation = clazz.getAnnotation(Action.class); + scannedComponent.setName(oldActionAnnotation.name()); + scannedComponent.setScope(oldActionAnnotation.scope()); + descriptorResourceName = oldActionAnnotation.descriptor(); + break; case PLUGIN: Plugin pluginAnnotation = clazz.getAnnotation(Plugin.class); scannedComponent.setName(pluginAnnotation.name()); @@ -122,12 +137,12 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe log.error("Can't initialize plugin {}, due to missing action {}!", def.getBeanClassName(), actionClazz.getName()); return new ClassNotFoundException("Action: " + actionClazz.getName() + "is missing!"); }); - if (actionComponent.getType() != ComponentType.ACTION) { + if (actionComponent.getType() != ComponentType.OLD_ACTION) { log.error("Plugin {} action {} has wrong component type!", def.getBeanClassName(), actionClazz.getName(), actionComponent.getType()); throw new RuntimeException("Plugin " + def.getBeanClassName() + "action " + actionClazz.getName() + " has wrong component type!"); } } - scannedComponent.setActions(Arrays.stream(pluginAnnotation.actions()).map(action -> action.getName()).collect(Collectors.joining(","))); + scannedComponent.setActions(Arrays.stream(pluginAnnotation.actions()).map(Class::getName).collect(Collectors.joining(","))); break; default: throw new RuntimeException(type + " is not supported yet!"); @@ -168,11 +183,15 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @Override public void discoverComponents() { - registerComponents(ComponentType.FILTER, Filter.class); + registerComponents(ComponentType.ENRICHMENT, EnrichmentNode.class); + + registerComponents(ComponentType.FILTER, FilterNode.class); + + registerComponents(ComponentType.TRANSFORMATION, TransformationNode.class); - registerComponents(ComponentType.PROCESSOR, Processor.class); + registerComponents(ComponentType.ACTION, ActionNode.class); - registerComponents(ComponentType.ACTION, Action.class); + registerComponents(ComponentType.OLD_ACTION, Action.class); registerComponents(ComponentType.PLUGIN, Plugin.class); @@ -199,7 +218,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe } List result = new ArrayList<>(); for (String action : plugin.getActions().split(",")) { - getComponent(action).ifPresent(v -> result.add(v)); + getComponent(action).ifPresent(result::add); } return result; } else { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 778c2a3ffb..27585212db 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -62,7 +62,7 @@ cluster: # Plugins configuration parameters plugins: # Comma seperated package list used during classpath scanning for plugins - scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions}" + scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}" # JWT Token parameters security.jwt: @@ -215,12 +215,12 @@ actors: 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}" - chain: - # Errors for particular actor are persisted once per specified amount of milliseconds - error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}" - node: - # Errors for particular actor are persisted once per specified amount of milliseconds - error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}" + chain: + # Errors for particular actor are persisted once per specified amount of milliseconds + error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}" + node: + # Errors for particular actor are persisted once per specified amount of milliseconds + error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}" statistics: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index b92e464a6e..3ec4dc815b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -96,6 +96,8 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC @Slf4j public abstract class AbstractControllerTest { + protected ObjectMapper mapper = new ObjectMapper(); + protected static final String TEST_TENANT_NAME = "TEST TENANT"; protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org"; diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java new file mode 100644 index 0000000000..9aa75745cf --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java @@ -0,0 +1,41 @@ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +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; + +/** + * Created by ashvayka on 20.03.18. + */ +public class AbstractRuleEngineControllerTest extends AbstractControllerTest{ + + protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception { + return doPost("/api/ruleChain", ruleChain, RuleChain.class); + } + + protected RuleChain getRuleChain(RuleChainId ruleChainId) throws Exception { + return doGet("/api/ruleChain/" + ruleChainId.getId().toString(), RuleChain.class); + } + + protected RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMD) throws Exception { + return doPost("/api/ruleChain/metadata", ruleChainMD, RuleChainMetaData.class); + } + + protected RuleChainMetaData getRuleChainMetaData(RuleChainId ruleChainId) throws Exception { + return doGet("/api/ruleChain/metadata/" + ruleChainId.getId().toString(), RuleChainMetaData.class); + } + + protected TimePageData getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception { + TimePageLink pageLink = new TimePageLink(limit); + return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&", + new TypeReference>() { + }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG, tenantId.getId()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java new file mode 100644 index 0000000000..136f76b496 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.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.rules; + +import org.junit.ClassRule; +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.runner.RunWith; +import org.thingsboard.server.dao.CustomSqlUnit; + +import java.util.Arrays; + +@RunWith(ClasspathSuite.class) +@ClasspathSuite.ClassnameFilters({ + "org.thingsboard.server.rules.flow.*Test", "org.thingsboard.server.rules.lifecycle.*Test"}) +public class RuleEngineSqlTestSuite { + + @ClassRule + public static CustomSqlUnit sqlUnit = new CustomSqlUnit( + Arrays.asList("sql/schema.sql", "sql/system-data.sql"), + "sql/drop-all-tables.sql", + "sql-test.properties"); +} 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 new file mode 100644 index 0000000000..1f2f70997c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -0,0 +1,156 @@ +/** + * 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.flow; + +import com.datastax.driver.core.utils.UUIDs; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; +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.TimePageData; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; +import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; +import org.thingsboard.server.dao.attributes.AttributesService; + +import java.util.Collections; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Valerii Sosliuk + */ +@Slf4j +public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRuleEngineControllerTest { + + private static final String MQTT_URL = "tcp://localhost:1883"; + private static final Long TIME_TO_HANDLE_REQUEST = 500L; + + private Tenant savedTenant; + private User tenantAdmin; + + @Autowired + private ActorService actorService; + + @Autowired + private AttributesService attributesService; + + @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"); + + createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + if (savedTenant != null) { + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk()); + } + } + + @Test + public void testSimpleRuleChainCreation() 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()); + + 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()); + + // 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(), + new byte[]{}); + actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)); + + 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", outEvent.getBody().get("metadata").get("ss.serverAttributeKey").asText()); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java b/application/src/test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java similarity index 64% rename from application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java rename to application/src/test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java index 3f3bd36f65..18a164ef33 100644 --- a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.actors.rule; +package org.thingsboard.server.rules.flow; -public interface RuleActorChain { - - int size(); - - RuleActorMetaData getRuleActorMd(int index); +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest; +/** + * Created by Valerii Sosliuk on 8/22/2017. + */ +@DaoSqlTest +public class RuleEngineFlowSqlIntegrationTest extends AbstractRuleEngineFlowIntegrationTest { } 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 5624ed801f..659a242882 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 @@ -37,9 +37,12 @@ public class DataConstants { public static final String ERROR = "ERROR"; public static final String LC_EVENT = "LC_EVENT"; public static final String STATS = "STATS"; - public static final String RULE_CHAIN_DEBUG = "DEBUG_RULE_CHAIN"; - public static final String RULE_NODE_DEBUG = "DEBUG_RULE_NODE"; + public static final String DEBUG = "DEBUG"; public static final String ONEWAY = "ONEWAY"; public static final String TWOWAY = "TWOWAY"; + + public static final String IN = "IN"; + public static final String OUT = "OUT"; + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java index ab6accaf11..a1030640cb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java @@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin; */ public enum ComponentType { - ENRICHMENT, FILTER, PROCESSOR, TRANSFORMATION, ACTION, PLUGIN + ENRICHMENT, FILTER, TRANSFORMATION, ACTION, OLD_ACTION, PLUGIN } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java index f2ba0cc66e..218061adb1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java @@ -38,6 +38,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo im private String name; private RuleNodeId firstRuleNodeId; private boolean root; + private boolean debugMode; private transient JsonNode configuration; @JsonIgnore private byte[] configurationBytes; 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 d044000117..fbc1103dee 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 @@ -34,6 +34,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo impl private String type; private String name; + private boolean debugMode; private transient JsonNode configuration; @JsonIgnore private byte[] configurationBytes; 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 22fc4285a9..7c00ee64ab 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 @@ -1,3 +1,18 @@ +/** + * 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; /** @@ -34,4 +49,9 @@ public enum MsgType { */ RULE_TO_RULE_CHAIN_TELL_NEXT_MSG, + /** + * Message that is sent by RuleActor implementation to RuleActor itself to log the error. + */ + RULE_TO_SELF_ERROR_MSG, + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java index e7668ff227..c361c11c0e 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java @@ -1,3 +1,18 @@ +/** + * 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; /** 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 c689b24fe8..524cc5fe60 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 @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. @@ -17,6 +17,7 @@ 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; @@ -30,6 +31,7 @@ import java.util.UUID; * Created by ashvayka on 13.01.18. */ @Data +@AllArgsConstructor public final class TbMsg implements Serializable { private final UUID id; @@ -39,6 +41,15 @@ public final class TbMsg implements Serializable { private final TbMsgDataType dataType; private final byte[] data; + public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, byte[] data) { + this.id = id; + this.type = type; + this.originator = originator; + this.metaData = metaData; + this.dataType = TbMsgDataType.JSON; + this.data = data; + } + public static ByteBuffer toBytes(TbMsg msg) { MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder(); builder.setId(msg.getId().toString()); @@ -61,9 +72,7 @@ public final class TbMsg implements Serializable { public static TbMsg fromBytes(ByteBuffer buffer) { try { MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array()); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.setData(proto.getMetaData().getDataMap()); - + TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap()); EntityId entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()); TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData().toByteArray()); @@ -71,4 +80,11 @@ public final class TbMsg implements Serializable { throw new IllegalStateException("Could not parse protobuf for TbMsg", e); } } + + public TbMsg copy() { + int dataSize = data.length; + byte[] dataCopy = new byte[dataSize]; + System.arraycopy( data, 0, dataCopy, 0, data.length ); + return new TbMsg(id, type, originator, metaData.copy(), dataType, dataCopy); + } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java index b6e2d5a5c5..2e367e9536 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java @@ -1,3 +1,18 @@ +/** + * 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; /** 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 1bbc7929e2..74a2d3f9c7 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 @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,9 +15,12 @@ */ package org.thingsboard.server.common.msg; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -25,10 +28,15 @@ import java.util.concurrent.ConcurrentHashMap; * Created by ashvayka on 13.01.18. */ @Data +@NoArgsConstructor public final class TbMsgMetaData implements Serializable { private Map data = new ConcurrentHashMap<>(); + TbMsgMetaData(Map data) { + this.data = data; + } + public String getValue(String key) { return data.get(key); } @@ -37,4 +45,7 @@ public final class TbMsgMetaData implements Serializable { data.put(key, value); } + public TbMsgMetaData copy() { + return new TbMsgMetaData(new ConcurrentHashMap<>(data)); + } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java index 315eb86f6c..c104281ffe 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java index 3d82033b31..0792b63c28 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java @@ -1,3 +1,18 @@ +/** + * 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.system; import lombok.Data; 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 a159b9e6ec..8c34cd3540 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 @@ -332,6 +332,8 @@ public class ModelConstants { public static final String EVENT_BY_TYPE_AND_ID_VIEW_NAME = "event_by_type_and_id"; public static final String EVENT_BY_ID_VIEW_NAME = "event_by_id"; + public static final String DEBUG_MODE = "debug_mode"; + /** * Cassandra rule chain constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java index 34659a83df..bfec3900a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -22,6 +22,8 @@ import com.datastax.driver.mapping.annotations.PartitionKey; import com.datastax.driver.mapping.annotations.Table; import com.fasterxml.jackson.databind.JsonNode; 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; @@ -54,6 +56,10 @@ public class RuleChainEntity implements SearchTextEntity { private UUID firstRuleNodeId; @Column(name = RULE_CHAIN_ROOT_PROPERTY) private boolean root; + @Getter + @Setter + @Column(name = DEBUG_MODE) + private boolean debugMode; @Column(name = RULE_CHAIN_CONFIGURATION_PROPERTY, codec = JsonCodec.class) private JsonNode configuration; @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) @@ -71,6 +77,7 @@ public class RuleChainEntity implements SearchTextEntity { this.searchText = ruleChain.getName(); this.firstRuleNodeId = DaoUtil.getId(ruleChain.getFirstRuleNodeId()); this.root = ruleChain.isRoot(); + this.debugMode = ruleChain.isDebugMode(); this.configuration = ruleChain.getConfiguration(); this.additionalInfo = ruleChain.getAdditionalInfo(); } @@ -157,6 +164,7 @@ public class RuleChainEntity implements SearchTextEntity { ruleChain.setFirstRuleNodeId(new RuleNodeId(this.firstRuleNodeId)); } ruleChain.setRoot(this.root); + ruleChain.setDebugMode(this.debugMode); ruleChain.setConfiguration(this.configuration); ruleChain.setAdditionalInfo(this.additionalInfo); return ruleChain; 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 ba96e4b98e..8d3f3c3eaf 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 @@ -21,6 +21,8 @@ import com.datastax.driver.mapping.annotations.PartitionKey; import com.datastax.driver.mapping.annotations.Table; import com.fasterxml.jackson.databind.JsonNode; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import lombok.ToString; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.rule.RuleNode; @@ -49,6 +51,11 @@ public class RuleNodeEntity implements SearchTextEntity { private JsonNode configuration; @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) private JsonNode additionalInfo; + @Getter + @Setter + @Column(name = DEBUG_MODE) + private boolean debugMode; + public RuleNodeEntity() { } @@ -59,6 +66,7 @@ public class RuleNodeEntity implements SearchTextEntity { } this.type = ruleNode.getType(); this.name = ruleNode.getName(); + this.debugMode = ruleNode.isDebugMode(); this.searchText = ruleNode.getName(); this.configuration = ruleNode.getConfiguration(); this.additionalInfo = ruleNode.getAdditionalInfo(); @@ -126,6 +134,7 @@ public class RuleNodeEntity implements SearchTextEntity { ruleNode.setCreatedTime(UUIDs.unixTimestamp(id)); ruleNode.setType(this.type); ruleNode.setName(this.name); + ruleNode.setDebugMode(this.debugMode); ruleNode.setConfiguration(this.configuration); ruleNode.setAdditionalInfo(this.additionalInfo); return ruleNode; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java index 471ec7b06b..a48421a96a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java @@ -58,6 +58,9 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT @Column(name = ModelConstants.RULE_CHAIN_ROOT_PROPERTY) private boolean root; + @Column(name = ModelConstants.DEBUG_MODE) + private boolean debugMode; + @Type(type = "json") @Column(name = ModelConstants.RULE_CHAIN_CONFIGURATION_PROPERTY) private JsonNode configuration; @@ -80,6 +83,7 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT this.firstRuleNodeId = UUIDConverter.fromTimeUUID(ruleChain.getFirstRuleNodeId().getId()); } this.root = ruleChain.isRoot(); + this.debugMode = ruleChain.isDebugMode(); this.configuration = ruleChain.getConfiguration(); this.additionalInfo = ruleChain.getAdditionalInfo(); } @@ -104,6 +108,7 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT ruleChain.setFirstRuleNodeId(new RuleNodeId(UUIDConverter.fromString(firstRuleNodeId))); } ruleChain.setRoot(root); + ruleChain.setDebugMode(debugMode); ruleChain.setConfiguration(configuration); ruleChain.setAdditionalInfo(additionalInfo); return ruleChain; 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 d96048756c..6a888c2d72 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 @@ -56,6 +56,9 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = ModelConstants.DEBUG_MODE) + private boolean debugMode; + public RuleNodeEntity() { } @@ -65,6 +68,7 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex } this.type = ruleNode.getType(); this.name = ruleNode.getName(); + this.debugMode = ruleNode.isDebugMode(); this.searchText = ruleNode.getName(); this.configuration = ruleNode.getConfiguration(); this.additionalInfo = ruleNode.getAdditionalInfo(); @@ -86,6 +90,7 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex ruleNode.setCreatedTime(UUIDs.unixTimestamp(getId())); ruleNode.setType(type); ruleNode.setName(name); + ruleNode.setDebugMode(debugMode); ruleNode.setConfiguration(configuration); ruleNode.setAdditionalInfo(additionalInfo); return ruleNode; 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 da991fa97d..bb76b97b18 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 @@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.annotation.Nullable; @@ -125,7 +126,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, dataStr.getBytes()); + return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr.getBytes()); } @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 04207ec2f5..3daed33045 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 @@ -219,6 +219,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return ruleChainDao.findById(ruleChainId.getId()); } + @Override + public RuleNode findRuleNodeById(RuleNodeId ruleNodeId) { + Validator.validateId(ruleNodeId, "Incorrect rule node id for search request."); + return ruleNodeDao.findById(ruleNodeId.getId()); + } + @Override public ListenableFuture findRuleChainByIdAsync(RuleChainId ruleChainId) { Validator.validateId(ruleChainId, "Incorrect rule chain id for search request."); @@ -325,7 +331,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } if (ruleChain.isRoot()) { RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); - if (ruleChain.getId() == null || !ruleChain.getId().equals(rootRuleChain.getId())) { + if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) { throw new DataValidationException("Another root rule chain is present in scope of current tenant!"); } } 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 e5f2840b8b..da7833d696 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 @@ -42,6 +42,8 @@ public interface RuleChainService { RuleChain findRuleChainById(RuleChainId ruleChainId); + RuleNode findRuleNodeById(RuleNodeId ruleNodeId); + ListenableFuture findRuleChainByIdAsync(RuleChainId ruleChainId); RuleChain getRootTenantRuleChain(TenantId tenantId); diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index 42c13f3ee6..d0e62b2827 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -669,6 +669,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_chain ( search_text text, first_rule_node_id uuid, root boolean, + debug_mode boolean, configuration text, additional_info text, PRIMARY KEY (id, tenant_id) @@ -685,6 +686,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( id uuid, type text, name text, + debug_mode boolean, search_text text, configuration text, additional_info text, diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql index 106204ab64..d7a0978140 100644 --- a/dao/src/main/resources/sql/schema.sql +++ b/dao/src/main/resources/sql/schema.sql @@ -263,6 +263,7 @@ CREATE TABLE IF NOT EXISTS rule_chain ( name varchar(255), first_rule_node_id varchar(31), root boolean, + debug_mode boolean, search_text varchar(255), tenant_id varchar(31) ); @@ -273,5 +274,6 @@ CREATE TABLE IF NOT EXISTS rule_node ( configuration varchar(10000000), type varchar(255), name varchar(255), + debug_mode boolean, search_text varchar(255) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index d083a90262..44a1a0927b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -217,10 +217,10 @@ public abstract class AbstractServiceTest { ruleMetaData.setWeight(weight); ruleMetaData.setPluginToken(pluginToken); - ruleMetaData.setAction(createNode(ComponentScope.TENANT, ComponentType.ACTION, + ruleMetaData.setAction(createNode(ComponentScope.TENANT, ComponentType.OLD_ACTION, "org.thingsboard.component.ActionTest", "TestJsonDescriptor.json", "TestJsonData.json")); - ruleMetaData.setProcessor(createNode(ComponentScope.TENANT, ComponentType.PROCESSOR, - "org.thingsboard.component.ProcessorTest", "TestJsonDescriptor.json", "TestJsonData.json")); +// ruleMetaData.setProcessor(createNode(ComponentScope.TENANT, ComponentType.PROCESSOR, +// "org.thingsboard.component.ProcessorTest", "TestJsonDescriptor.json", "TestJsonData.json")); ruleMetaData.setFilters(mapper.createArrayNode().add( createNode(ComponentScope.TENANT, ComponentType.FILTER, "org.thingsboard.component.FilterTest", "TestJsonDescriptor.json", "TestJsonData.json") 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 6302e63f89..3935c9b92e 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); - TbMsg msg2 = new TbMsg(id2, "T", null, null, null); + TbMsg msg1 = new TbMsg(id1, "T", null, null, null, null); + TbMsg msg2 = new TbMsg(id2, "T", null, null, null, null); 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/CassandraMsgRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java index d17e1f2819..99c6a91f84 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 @@ -24,6 +24,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.id.DeviceId; 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.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; @@ -44,7 +45,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest { @Test public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException { - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, new byte[4]); UUID nodeId = UUIDs.timeBased(); ListenableFuture future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); future.get(); @@ -54,7 +55,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest { @Test public void expiredMsgsAreNotReturned() throws ExecutionException, InterruptedException { - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, new byte[4]); UUID nodeId = UUIDs.timeBased(); ListenableFuture future = msgRepository.save(msg, nodeId, 2L, 2L, 2L); future.get(); @@ -67,7 +68,7 @@ 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, dataStr.getBytes()); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, TbMsgDataType.JSON, dataStr.getBytes()); UUID nodeId = UUIDs.timeBased(); ListenableFuture future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); future.get(); diff --git a/pom.xml b/pom.xml index f331e32fb2..f0c915a5b0 100755 --- a/pom.xml +++ b/pom.xml @@ -378,6 +378,11 @@ rule-engine-api ${project.version} + + org.thingsboard.rule-engine + rule-engine-components + ${project.version} + org.thingsboard.common message diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java new file mode 100644 index 0000000000..64e28f1bf2 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.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.api; + +import org.thingsboard.server.common.data.plugin.ComponentScope; +import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Andrew Shvayka + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ActionNode { + + String name(); + + ComponentScope scope() default ComponentScope.TENANT; + + String descriptor() default "EmptyNodeDescriptor.json"; + + String[] relationTypes() default {"Success","Failure"}; + + boolean customRelations() default false; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java new file mode 100644 index 0000000000..2267bdae67 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.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.rule.engine.api; + +import org.thingsboard.server.common.data.plugin.ComponentScope; +import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Andrew Shvayka + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface EnrichmentNode { + + String name(); + + ComponentScope scope() default ComponentScope.TENANT; + + String descriptor() default "EmptyNodeDescriptor.json"; + + String[] relationTypes() default {"Success","Failure"}; + + boolean customRelations() default false; +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java new file mode 100644 index 0000000000..5247e397dd --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.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.api; + +import org.thingsboard.server.common.data.plugin.ComponentScope; +import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Andrew Shvayka + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface FilterNode { + + String name(); + + ComponentScope scope() default ComponentScope.TENANT; + + String descriptor() default "EmptyNodeDescriptor.json"; + + String[] relationTypes() default {"Success","Failure"}; + + boolean customRelations() default false; + +} 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 ede076be4a..a105d20ada 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 @@ -58,8 +58,6 @@ public interface TbContext { UserService getUserService(); - RuleService getRuleService(); - PluginService getPluginService(); AssetService getAssetService(); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java index d06c0d277b..64053cd61c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeConfiguration.java @@ -22,8 +22,8 @@ import lombok.Data; * Created by ashvayka on 19.01.18. */ @Data -public class TbNodeConfiguration { +public final class TbNodeConfiguration { - private JsonNode data; + private final JsonNode data; } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java index c48b11d387..2c77a69ba3 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeState.java @@ -18,5 +18,5 @@ package org.thingsboard.rule.engine.api; /** * Created by ashvayka on 19.01.18. */ -public class TbNodeState { +public final class TbNodeState { } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java new file mode 100644 index 0000000000..bfe1dcaeda --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.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.api; + +import org.thingsboard.server.common.data.plugin.ComponentScope; +import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Andrew Shvayka + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TransformationNode { + + String name(); + + ComponentScope scope() default ComponentScope.TENANT; + + String descriptor() default "EmptyNodeDescriptor.json"; + + String[] relationTypes() default {"Success","Failure"}; + + boolean customRelations() default false; + +} diff --git a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json new file mode 100644 index 0000000000..7a73a41bfd --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 9b903b1b00..a97493bdd7 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -88,11 +88,6 @@ mockito-all test - - org.junit.jupiter - junit-jupiter-api - RELEASE - diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 4d41921740..ac12f95eab 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -1,12 +1,12 @@ /** * 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 - * + *

+ * 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. @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.metadata; +import com.google.common.base.Function; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -33,6 +34,7 @@ import static org.thingsboard.server.common.data.DataConstants.*; * Created by ashvayka on 19.01.18. */ @Slf4j +@EnrichmentNode(name = "Get Attributes Node") public class TbGetAttributesNode implements TbNode { private TbGetAttributesNodeConfiguration config; @@ -52,14 +54,20 @@ public class TbGetAttributesNode implements TbNode { withCallback(future, i -> ctx.tellNext(msg), t -> ctx.tellError(msg, t)); } - private ListenableFuture putAttributesAsync(TbMsg msg, List attributes, String prefix) { - attributes.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString())); - return Futures.immediateFuture(null); + private Void putAttributesAsync(TbMsg msg, List attributes, String prefix) { + if (attributes != null) { + attributes.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString())); + } + return null; } private ListenableFuture putAttrAsync(TbContext ctx, TbMsg msg, String scope, List attributes, String prefix) { - return Futures.transform(ctx.getAttributesService().find(msg.getOriginator(), scope, attributes), - (AsyncFunction, Void>) i -> putAttributesAsync(msg, i, prefix)); + if (attributes != null) { + return Futures.transform(ctx.getAttributesService().find(msg.getOriginator(), scope, attributes), + (Function, Void>) i -> putAttributesAsync(msg, i, prefix)); + } else { + return Futures.immediateFuture(null); + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index 57f9b79e7b..2e3d6172fa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -18,11 +18,13 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.rule.engine.api.EnrichmentNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.id.*; +@EnrichmentNode(name="Get Customer Attributes Node") public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 5823c181de..83aa4e2c17 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -20,10 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.TbNodeUtils; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeState; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -33,6 +30,7 @@ import java.util.List; import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON; +@EnrichmentNode(name="Get Related Entity Attributes Node") public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { private TbGetRelatedAttrNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index 2cf9a97d20..a1b05162dc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.EnrichmentNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.HasTenantId; @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.id.*; @Slf4j +@EnrichmentNode(name="Get Tenant Attributes Node") public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { @Override @@ -38,8 +40,6 @@ public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { return getTenantAsync(ctx.getCustomerService().findCustomerByIdAsync((CustomerId) originator)); case USER: return getTenantAsync(ctx.getUserService().findUserByIdAsync((UserId) originator)); - case RULE: - return getTenantAsync(ctx.getRuleService().findRuleByIdAsync((RuleId) originator)); case PLUGIN: return getTenantAsync(ctx.getPluginService().findPluginByIdAsync((PluginId) originator)); case ASSET: 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 8e5ddb8069..ad40f03006 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 @@ -85,8 +85,7 @@ public class TbGetCustomerAttributeNodeTest { config.setAttrMapping(attrMapping); config.setTelemetry(false); ObjectMapper mapper = new ObjectMapper(); - TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); - nodeConfiguration.setData(mapper.valueToTree(config)); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); node = new TbGetCustomerAttributeNode(); node.init(nodeConfiguration, null); @@ -224,8 +223,7 @@ public class TbGetCustomerAttributeNodeTest { config.setAttrMapping(attrMapping); config.setTelemetry(true); ObjectMapper mapper = new ObjectMapper(); - TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); - nodeConfiguration.setData(mapper.valueToTree(config)); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); node = new TbGetCustomerAttributeNode(); node.init(nodeConfiguration, null); From 5752ba81a43a75f542a405467081ff449c0575e4 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 21 Mar 2018 14:41:27 +0200 Subject: [PATCH 024/171] Implementation of Flows in Rule Engine --- .../actors/ruleChain/RuleChainActor.java | 13 +- .../RuleChainActorMessageProcessor.java | 87 ++++++---- .../ruleChain/RuleChainToRuleNodeMsg.java | 1 - .../actors/ruleChain/RuleNodeActor.java | 6 +- .../RuleNodeActorMessageProcessor.java | 27 +-- .../server/actors/ruleChain/RuleNodeCtx.java | 1 - .../ruleChain/RuleNodeToSelfErrorMsg.java | 1 - .../actors/shared/ComponentMsgProcessor.java | 8 + .../AbstractRuleEngineControllerTest.java | 2 +- .../server/rules/RuleEngineSqlTestSuite.java | 2 +- ...AbstractRuleEngineFlowIntegrationTest.java | 70 ++++++-- ...actRuleEngineLifecycleIntegrationTest.java | 158 +++++++++++++++++ ...RuleEngineLifecycleSqlIntegrationTest.java | 26 +++ .../common/data/rule/NodeConnectionInfo.java | 15 +- .../data/rule/RuleChainConnectionInfo.java | 16 +- .../common/data/rule/RuleChainMetaData.java | 14 -- .../server/dao/rule/BaseRuleChainService.java | 8 +- .../dao/service/rule/BaseRuleServiceTest.java | 163 ------------------ 18 files changed, 363 insertions(+), 255 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java rename dao/src/test/java/org/thingsboard/server/dao/service/rule/sql/RuleServiceSqlTest.java => common/data/src/main/java/org/thingsboard/server/common/data/rule/NodeConnectionInfo.java (71%) rename dao/src/test/java/org/thingsboard/server/dao/service/rule/nosql/RuleServiceNoSqlTest.java => common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainConnectionInfo.java (66%) delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/rule/BaseRuleServiceTest.java 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 9c8de22376..f539e32a3c 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 @@ -15,6 +15,8 @@ */ 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.service.ComponentActor; import org.thingsboard.server.actors.service.ContextBasedCreator; @@ -23,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; +import scala.concurrent.duration.Duration; public class RuleChainActor extends ComponentActor { @@ -73,5 +76,13 @@ public class RuleChainActor extends ComponentActor { + logAndPersist("Unknown Failure", ActorSystemContext.toException(t)); + return SupervisorStrategy.resume(); + }); } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index 7636152d2d..a853b1576b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -27,12 +27,14 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.dao.rule.RuleChainService; @@ -40,6 +42,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** * @author Andrew Shvayka @@ -54,7 +58,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList = service.getRuleChainNodes(entityId); // Creating and starting the actors; for (RuleNode ruleNode : ruleNodeList) { - String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ? - DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME; - ActorRef ruleNodeActor = context.actorOf( - Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getId())) - .withDispatcher(dispatcherName), ruleNode.getId().toString()); + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); } + initRoutes(ruleChain, ruleNodeList); + } + + @Override + public void onUpdate(ActorContext context) throws Exception { + RuleChain ruleChain = service.findRuleChainById(entityId); + List ruleNodeList = service.getRuleChainNodes(entityId); + + for (RuleNode ruleNode : ruleNodeList) { + RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); + if (existing == null) { + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); + } else { + existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); + } + } + + Set existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); + List removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); + removedRules.forEach(ruleNodeId -> { + RuleNodeCtx removed = nodeActors.remove(ruleNodeId); + removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); + }); + + initRoutes(ruleChain, ruleNodeList); + } + + @Override + public void stop(ActorContext context) throws Exception { + nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop); + nodeActors.clear(); + nodeRoutes.clear(); + context.stop(self); + } + + @Override + public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { + + } + + private ActorRef createRuleNodeActor(ActorContext context, RuleNode ruleNode) { + String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ? + DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME; + return context.actorOf( + Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getId())) + .withDispatcher(dispatcherName), ruleNode.getId().toString()); + } + + private void initRoutes(RuleChain ruleChain, List ruleNodeList) { + nodeRoutes.clear(); // Populating the routes map; for (RuleNode ruleNode : ruleNodeList) { List relations = service.getRuleNodeRelations(ruleNode.getId()); @@ -102,19 +149,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor relations = nodeRoutes.get(originator); if (relations == null) { return; @@ -153,12 +186,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor { @@ -37,6 +38,9 @@ public class RuleNodeActor extends ComponentActor extends AbstractContextAwareMsgProcessor { protected final TenantId tenantId; protected final T entityId; + protected ComponentLifecycleState state; protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) { super(systemContext, logger); @@ -67,4 +69,10 @@ public abstract class ComponentMsgProcessor extends AbstractContextAwareMsgPr public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) { schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency); } + + protected void checkActive() { + if (state != ComponentLifecycleState.ACTIVE) { + throw new IllegalStateException("Rule chain is not active!"); + } + } } 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 7997024e32..bbcb98ff48 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; /** * Created by ashvayka on 20.03.18. */ -public class AbstractRuleEngineControllerTest extends AbstractControllerTest{ +public class AbstractRuleEngineControllerTest extends AbstractControllerTest { protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception { return doPost("/api/ruleChain", ruleChain, RuleChain.class); 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 136f76b496..65b4293490 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,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.rules.flow.*Test", "org.thingsboard.server.rules.lifecycle.*Test"}) + "org.thingsboard.server.rules.flow.*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 5b8216b026..acbace6ae7 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 @@ -38,7 +38,9 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.rule.RuleChainService; +import java.util.Arrays; import java.util.Collections; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -49,17 +51,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @Slf4j public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRuleEngineControllerTest { - private static final String MQTT_URL = "tcp://localhost:1883"; - private static final Long TIME_TO_HANDLE_REQUEST = 500L; + protected Tenant savedTenant; + protected User tenantAdmin; - private Tenant savedTenant; - private User tenantAdmin; + @Autowired + protected ActorService actorService; @Autowired - private ActorService actorService; + protected AttributesService attributesService; @Autowired - private AttributesService attributesService; + protected RuleChainService ruleChainService; @Before public void beforeTest() throws Exception { @@ -89,7 +91,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule } @Test - public void testSimpleRuleChainCreation() throws Exception { + public void testRuleChainWithTwoRules() throws Exception { // Creating Rule Chain RuleChain ruleChain = new RuleChain(); ruleChain.setName("Simple Rule Chain"); @@ -102,17 +104,26 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule 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)); + 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)); - metaData.setNodes(Collections.singletonList(ruleNode)); - metaData.setFirstNodeIndex(0); + 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)); + + metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); + metaData.setFirstNodeIndex(0); + metaData.addConnectionInfo(0, 1, "Success"); metaData = saveRuleChainMetaData(metaData); Assert.assertNotNull(metaData); @@ -126,7 +137,12 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule device = doPost("/api/device", device, Device.class); attributesService.save(device.getId(), DataConstants.SERVER_SCOPE, - Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis()))); + 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(), @@ -150,7 +166,25 @@ 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("serverAttributeValue", outEvent.getBody().get("metadata").get("ss.serverAttributeKey").asText()); + Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText()); + + RuleChain finalRuleChain = ruleChain; + RuleNode lastRuleNode = metaData.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", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText()); + Assert.assertEquals("serverAttributeValue2", outEvent.getBody().get("metadata").get("ss.serverAttributeKey2").asText()); } } 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 new file mode 100644 index 0000000000..de690a745f --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -0,0 +1,158 @@ +/** + * 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.lifecycle; + +import com.datastax.driver.core.utils.UUIDs; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; +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.Event; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; +import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; +import org.thingsboard.server.dao.attributes.AttributesService; + +import java.util.Collections; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Valerii Sosliuk + */ +@Slf4j +public abstract class AbstractRuleEngineLifecycleIntegrationTest extends AbstractRuleEngineControllerTest { + + protected Tenant savedTenant; + protected User tenantAdmin; + + @Autowired + protected ActorService actorService; + + @Autowired + protected AttributesService attributesService; + + @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"); + + createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + if (savedTenant != null) { + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk()); + } + } + + @Test + public void testRuleChainWithOneRule() 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()); + + 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()); + + // 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()))); + + Thread.sleep(1000); + + // Pushing Message to the system + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), + "CUSTOM", + device.getId(), + new TbMsgMetaData(), + new byte[]{}); + actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)); + + 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", outEvent.getBody().get("metadata").get("ss.serverAttributeKey").asText()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java new file mode 100644 index 0000000000..004958b2ea --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java @@ -0,0 +1,26 @@ +/** + * 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.lifecycle; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest; + +/** + * Created by Valerii Sosliuk on 8/22/2017. + */ +@DaoSqlTest +public class RuleEngineLifecycleSqlIntegrationTest extends AbstractRuleEngineLifecycleIntegrationTest { +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/rule/sql/RuleServiceSqlTest.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/NodeConnectionInfo.java similarity index 71% rename from dao/src/test/java/org/thingsboard/server/dao/service/rule/sql/RuleServiceSqlTest.java rename to common/data/src/main/java/org/thingsboard/server/common/data/rule/NodeConnectionInfo.java index da3d4a0b7b..0c9fd5feb5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/rule/sql/RuleServiceSqlTest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/NodeConnectionInfo.java @@ -13,11 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.rule.sql; +package org.thingsboard.server.common.data.rule; -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.dao.service.rule.BaseRuleServiceTest; +import lombok.Data; -@DaoSqlTest -public class RuleServiceSqlTest extends BaseRuleServiceTest { +/** + * Created by ashvayka on 21.03.18. + */ +@Data +public class NodeConnectionInfo { + private int fromIndex; + private int toIndex; + private String type; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/rule/nosql/RuleServiceNoSqlTest.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainConnectionInfo.java similarity index 66% rename from dao/src/test/java/org/thingsboard/server/dao/service/rule/nosql/RuleServiceNoSqlTest.java rename to common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainConnectionInfo.java index 7ff9066499..35cf6aadad 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/rule/nosql/RuleServiceNoSqlTest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainConnectionInfo.java @@ -13,11 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service.rule.nosql; +package org.thingsboard.server.common.data.rule; -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.dao.service.rule.BaseRuleServiceTest; +import lombok.Data; +import org.thingsboard.server.common.data.id.RuleChainId; -@DaoNoSqlTest -public class RuleServiceNoSqlTest extends BaseRuleServiceTest { +/** + * Created by ashvayka on 21.03.18. + */ +@Data +public class RuleChainConnectionInfo { + private int fromIndex; + private RuleChainId targetRuleChainId; + private String type; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java index af141d6142..1be1518637 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java @@ -58,18 +58,4 @@ public class RuleChainMetaData { ruleChainConnections.add(connectionInfo); } - @Data - public class NodeConnectionInfo { - private int fromIndex; - private int toIndex; - private String type; - } - - @Data - public class RuleChainConnectionInfo { - private int fromIndex; - private RuleChainId targetRuleChainId; - private String type; - } - } 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 3daed33045..7d6cd0032e 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 @@ -31,7 +31,9 @@ import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractEntityService; @@ -148,7 +150,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ruleChainDao.save(ruleChain); } if (ruleChainMetaData.getConnections() != null) { - for (RuleChainMetaData.NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) { + for (NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) { EntityId from = nodes.get(nodeConnection.getFromIndex()).getId(); EntityId to = nodes.get(nodeConnection.getToIndex()).getId(); String type = nodeConnection.getType(); @@ -161,7 +163,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } } if (ruleChainMetaData.getRuleChainConnections() != null) { - for (RuleChainMetaData.RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { + for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); String type = nodeToRuleChainConnection.getType(); @@ -314,7 +316,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC private void createRelation(EntityRelation relation) throws ExecutionException, InterruptedException { log.debug("Creating relation: {}", relation); - relationService.saveRelationAsync(relation).get(); + relationService.saveRelation(relation); } private DataValidator ruleChainValidator = diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/rule/BaseRuleServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/rule/BaseRuleServiceTest.java deleted file mode 100644 index b6139df898..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/rule/BaseRuleServiceTest.java +++ /dev/null @@ -1,163 +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.dao.service.rule; - -import com.datastax.driver.core.utils.UUIDs; -import org.junit.Assert; -import org.junit.Test; -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.common.data.plugin.PluginMetaData; -import org.thingsboard.server.common.data.rule.RuleMetaData; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.service.AbstractServiceTest; - -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; - -public abstract class BaseRuleServiceTest extends AbstractServiceTest { - - @Test - public void saveRule() throws Exception { - PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - pluginService.savePlugin(plugin); - RuleMetaData ruleMetaData = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken())); - Assert.assertNotNull(ruleMetaData.getId()); - Assert.assertNotNull(ruleMetaData.getAdditionalInfo()); - ruleMetaData.setAdditionalInfo(mapper.readTree("{\"description\":\"test\"}")); - RuleMetaData newRuleMetaData = ruleService.saveRule(ruleMetaData); - Assert.assertEquals(ruleMetaData.getAdditionalInfo(), newRuleMetaData.getAdditionalInfo()); - } - - @Test - public void findRuleById() throws Exception { - PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - pluginService.savePlugin(plugin); - - RuleMetaData expected = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken())); - Assert.assertNotNull(expected.getId()); - RuleMetaData found = ruleService.findRuleById(expected.getId()); - Assert.assertEquals(expected, found); - } - - @Test - public void findPluginRules() throws Exception { - TenantId tenantIdA = new TenantId(UUIDs.timeBased()); - TenantId tenantIdB = new TenantId(UUIDs.timeBased()); - - PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - pluginService.savePlugin(pluginA); - pluginService.savePlugin(pluginB); - - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken())); - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken())); - - List foundA = ruleService.findPluginRules(pluginA.getApiToken()); - Assert.assertEquals(3, foundA.size()); - - List foundB = ruleService.findPluginRules(pluginB.getApiToken()); - Assert.assertEquals(2, foundB.size()); - } - - @Test - public void findSystemRules() throws Exception { - TenantId systemTenant = new TenantId(ModelConstants.NULL_UUID); // system tenant id - - PluginMetaData plugin = generatePlugin(systemTenant, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - pluginService.savePlugin(plugin); - ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken())); - ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken())); - ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken())); - TextPageData found = ruleService.findSystemRules(new TextPageLink(100)); - Assert.assertEquals(3, found.getData().size()); - } - - @Test - public void findTenantRules() throws Exception { - TenantId tenantIdA = new TenantId(UUIDs.timeBased()); - TenantId tenantIdB = new TenantId(UUIDs.timeBased()); - - PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - pluginService.savePlugin(pluginA); - pluginService.savePlugin(pluginB); - - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken())); - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken())); - - TextPageData foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100)); - Assert.assertEquals(3, foundA.getData().size()); - - TextPageData foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100)); - Assert.assertEquals(2, foundB.getData().size()); - } - - @Test - public void deleteRuleById() throws Exception { - PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - pluginService.savePlugin(plugin); - - RuleMetaData expected = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken())); - Assert.assertNotNull(expected.getId()); - RuleMetaData found = ruleService.findRuleById(expected.getId()); - Assert.assertEquals(expected, found); - ruleService.deleteRuleById(expected.getId()); - found = ruleService.findRuleById(expected.getId()); - Assert.assertNull(found); - } - - @Test - public void deleteRulesByTenantId() throws Exception { - TenantId tenantIdA = new TenantId(UUIDs.timeBased()); - TenantId tenantIdB = new TenantId(UUIDs.timeBased()); - - PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt()); - pluginService.savePlugin(pluginA); - pluginService.savePlugin(pluginB); - - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken())); - - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken())); - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken())); - - TextPageData foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100)); - Assert.assertEquals(3, foundA.getData().size()); - - TextPageData foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100)); - Assert.assertEquals(2, foundB.getData().size()); - - ruleService.deleteRulesByTenantId(tenantIdA); - - foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100)); - Assert.assertEquals(0, foundA.getData().size()); - - foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100)); - Assert.assertEquals(2, foundB.getData().size()); - } -} \ No newline at end of file From 6ece02f88f864f5a9551b870b3f40e8a33eb20c8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 21 Mar 2018 14:56:16 +0200 Subject: [PATCH 025/171] Rule chains UI --- .../common/data/rule/RuleChainMetaData.java | 9 +- .../server/dao/rule/BaseRuleChainService.java | 4 +- ui/package.json | 1 + ui/src/app/api/entity.service.js | 8 +- ui/src/app/api/rule-chain.service.js | 137 +++- ui/src/app/app.js | 3 + ui/src/app/common/types.constant.js | 38 ++ .../entity/entity-autocomplete.directive.js | 6 + ui/src/app/locale/locale.constant.js | 20 + ui/src/app/rulechain/add-link.tpl.html | 48 ++ ui/src/app/rulechain/add-rulenode.tpl.html | 48 ++ ui/src/app/rulechain/index.js | 10 +- ui/src/app/rulechain/link-fieldset.tpl.html | 39 ++ ui/src/app/rulechain/link.directive.js | 71 ++ .../app/rulechain/rulechain-fieldset.tpl.html | 2 +- ui/src/app/rulechain/rulechain.controller.js | 638 +++++++++++++++--- ui/src/app/rulechain/rulechain.routes.js | 41 +- ui/src/app/rulechain/rulechain.scss | 262 +++++++ ui/src/app/rulechain/rulechain.tpl.html | 130 ++++ ui/src/app/rulechain/rulechains.controller.js | 188 ++++++ .../app/rulechain/rulenode-fieldset.tpl.html | 55 ++ ui/src/app/rulechain/rulenode.directive.js | 79 +++ ui/src/app/rulechain/rulenode.tpl.html | 45 ++ 23 files changed, 1761 insertions(+), 121 deletions(-) create mode 100644 ui/src/app/rulechain/add-link.tpl.html create mode 100644 ui/src/app/rulechain/add-rulenode.tpl.html create mode 100644 ui/src/app/rulechain/link-fieldset.tpl.html create mode 100644 ui/src/app/rulechain/link.directive.js create mode 100644 ui/src/app/rulechain/rulechain.scss create mode 100644 ui/src/app/rulechain/rulechain.tpl.html create mode 100644 ui/src/app/rulechain/rulechains.controller.js create mode 100644 ui/src/app/rulechain/rulenode-fieldset.tpl.html create mode 100644 ui/src/app/rulechain/rulenode.directive.js create mode 100644 ui/src/app/rulechain/rulenode.tpl.html diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java index af141d6142..8deece2237 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.rule; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import org.thingsboard.server.common.data.id.RuleChainId; @@ -47,11 +48,12 @@ public class RuleChainMetaData { } connections.add(connectionInfo); } - public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type) { + public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type, JsonNode additionalInfo) { RuleChainConnectionInfo connectionInfo = new RuleChainConnectionInfo(); connectionInfo.setFromIndex(fromIndex); connectionInfo.setTargetRuleChainId(targetRuleChainId); connectionInfo.setType(type); + connectionInfo.setAdditionalInfo(additionalInfo); if (ruleChainConnections == null) { ruleChainConnections = new ArrayList<>(); } @@ -59,16 +61,17 @@ public class RuleChainMetaData { } @Data - public class NodeConnectionInfo { + public static class NodeConnectionInfo { private int fromIndex; private int toIndex; private String type; } @Data - public class RuleChainConnectionInfo { + public static class RuleChainConnectionInfo { private int fromIndex; private RuleChainId targetRuleChainId; + private JsonNode additionalInfo; private String type; } 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 04207ec2f5..c8c8df7641 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 @@ -166,7 +166,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); String type = nodeToRuleChainConnection.getType(); try { - createRelation(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); + createRelation(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); } catch (ExecutionException | InterruptedException e) { log.warn("[{}] Failed to create rule node to rule chain relation. from: [{}], to: [{}]", from, to); throw new RuntimeException(e); @@ -206,7 +206,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type); } else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) { RuleChainId targetRuleChainId = new RuleChainId(nodeRelation.getTo().getId()); - ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type); + ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type, nodeRelation.getAdditionalInfo()); } } } diff --git a/ui/package.json b/ui/package.json index ad95ef4a7a..b602720a48 100644 --- a/ui/package.json +++ b/ui/package.json @@ -69,6 +69,7 @@ "moment": "^2.15.0", "ngclipboard": "^1.1.1", "ngreact": "^0.3.0", + "ngFlowchart": "git://github.com/ikulikov/ngFlowchart.git#master", "objectpath": "^1.2.1", "oclazyload": "^1.0.9", "raphael": "^2.2.7", diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index e4c51a2ea9..ba1265f100 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes]) /*@ngInject*/ function EntityService($http, $q, $filter, $translate, $log, userService, deviceService, assetService, tenantService, customerService, - ruleService, pluginService, dashboardService, entityRelationService, attributeService, types, utils) { + ruleService, pluginService, ruleChainService, dashboardService, entityRelationService, attributeService, types, utils) { var service = { getEntity: getEntity, getEntities: getEntities, @@ -73,6 +73,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device case types.entityType.user: promise = userService.getUser(entityId, true, config); break; + case types.entityType.rulechain: + promise = ruleChainService.getRuleChain(entityId, config); + break; case types.entityType.alarm: $log.error('Get Alarm Entity is not implemented!'); break; @@ -271,6 +274,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device case types.entityType.plugin: promise = pluginService.getAllPlugins(pageLink, config); break; + case types.entityType.rulechain: + promise = ruleChainService.getRuleChains(pageLink, config); + break; case types.entityType.dashboard: if (user.authority === 'CUSTOMER_USER') { promise = dashboardService.getCustomerDashboards(customerId, pageLink, config); diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index 16afa747b8..be8e99db11 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -17,7 +17,9 @@ export default angular.module('thingsboard.api.ruleChain', []) .factory('ruleChainService', RuleChainService).name; /*@ngInject*/ -function RuleChainService($http, $q) { +function RuleChainService($http, $q, $filter, types) { + + var ruleNodeTypes = null; var service = { getSystemRuleChains: getSystemRuleChains, @@ -27,7 +29,11 @@ function RuleChainService($http, $q) { saveRuleChain: saveRuleChain, deleteRuleChain: deleteRuleChain, getRuleChainMetaData: getRuleChainMetaData, - saveRuleChainMetaData: saveRuleChainMetaData + saveRuleChainMetaData: saveRuleChainMetaData, + getRuleNodeTypes: getRuleNodeTypes, + getRuleNodeComponentType: getRuleNodeComponentType, + getRuleNodeSupportedLinks: getRuleNodeSupportedLinks, + resolveTargetRuleChains: resolveTargetRuleChains }; return service; @@ -147,4 +153,131 @@ function RuleChainService($http, $q) { return deferred.promise; } + function getRuleNodeSupportedLinks(nodeType) { //eslint-disable-line + //TODO: + var deferred = $q.defer(); + var linkLabels = [ + { name: 'Success', custom: false }, + { name: 'Fail', custom: false }, + { name: 'Custom', custom: true }, + ]; + deferred.resolve(linkLabels); + return deferred.promise; + } + + function getRuleNodeTypes() { + var deferred = $q.defer(); + if (ruleNodeTypes) { + deferred.resolve(ruleNodeTypes); + } else { + loadRuleNodeTypes().then( + (nodeTypes) => { + ruleNodeTypes = nodeTypes; + ruleNodeTypes.push( + { + nodeType: types.ruleNodeType.RULE_CHAIN.value, + type: 'Rule chain' + } + ); + deferred.resolve(ruleNodeTypes); + }, + () => { + deferred.reject(); + } + ); + } + return deferred.promise; + } + + function getRuleNodeComponentType(type) { + var res = $filter('filter')(ruleNodeTypes, {type: type}, true); + if (res && res.length) { + return res[0].nodeType; + } + return null; + } + + function resolveTargetRuleChains(ruleChainConnections) { + var deferred = $q.defer(); + if (ruleChainConnections && ruleChainConnections.length) { + var tasks = []; + for (var i = 0; i < ruleChainConnections.length; i++) { + tasks.push(getRuleChain(ruleChainConnections[i].targetRuleChainId.id)); + } + $q.all(tasks).then( + (ruleChains) => { + var ruleChainsMap = {}; + for (var i = 0; i < ruleChains.length; i++) { + ruleChainsMap[ruleChains[i].id.id] = ruleChains[i]; + } + deferred.resolve(ruleChainsMap); + }, + () => { + deferred.reject(); + } + ); + } else { + deferred.resolve({}); + } + return deferred.promise; + } + + function loadRuleNodeTypes() { + var deferred = $q.defer(); + deferred.resolve( + [ + { + nodeType: types.ruleNodeType.FILTER.value, + type: 'Filter' + }, + { + nodeType: types.ruleNodeType.FILTER.value, + type: 'Switch' + }, + { + nodeType: types.ruleNodeType.ENRICHMENT.value, + type: 'Self' + }, + { + nodeType: types.ruleNodeType.ENRICHMENT.value, + type: 'Tenant/Customer' + }, + { + nodeType: types.ruleNodeType.ENRICHMENT.value, + type: 'Related Entity' + }, + { + nodeType: types.ruleNodeType.ENRICHMENT.value, + type: 'Last Telemetry' + }, + { + nodeType: types.ruleNodeType.TRANSFORMATION.value, + type: 'Modify' + }, + { + nodeType: types.ruleNodeType.TRANSFORMATION.value, + type: 'New/Update' + }, + { + nodeType: types.ruleNodeType.ACTION.value, + type: 'Telemetry' + }, + { + nodeType: types.ruleNodeType.ACTION.value, + type: 'RPC call' + }, + { + nodeType: types.ruleNodeType.ACTION.value, + type: 'Send email' + }, + { + nodeType: types.ruleNodeType.ACTION.value, + type: 'Alarm' + } + ] + ); + return deferred.promise; + } + + } diff --git a/ui/src/app/app.js b/ui/src/app/app.js index 138b2308f3..31310135cd 100644 --- a/ui/src/app/app.js +++ b/ui/src/app/app.js @@ -49,6 +49,7 @@ import 'material-ui'; import 'react-schema-form'; import react from 'ngreact'; import '@flowjs/ng-flow/dist/ng-flow-standalone.min'; +import 'ngFlowchart/dist/ngFlowchart'; import thingsboardLocales from './locale/locale.constant'; import thingsboardLogin from './login'; @@ -86,6 +87,7 @@ import 'mdPickers/dist/mdPickers.min.css'; import 'angular-hotkeys/build/hotkeys.min.css'; import 'angular-carousel/dist/angular-carousel.min.css'; import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css'; +import 'ngFlowchart/dist/flowchart.css'; import '../scss/main.scss'; import AppConfig from './app.config'; @@ -113,6 +115,7 @@ angular.module('thingsboard', [ 'ngclipboard', react.name, 'flow', + 'flowchart', thingsboardLocales, thingsboardLogin, thingsboardDialogs, diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 77c21e1fd0..b134bf0575 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -457,6 +457,44 @@ export default angular.module('thingsboard.types', []) clientSide: false } }, + ruleNodeType: { + FILTER: { + value: "FILTER", + name: "rulenode.type-filter", + nodeClass: "tb-filter-type", + icon: "filter_list" + }, + ENRICHMENT: { + value: "ENRICHMENT", + name: "rulenode.type-enrichment", + nodeClass: "tb-enrichment-type", + icon: "playlist_add" + }, + TRANSFORMATION: { + value: "TRANSFORMATION", + name: "rulenode.type-transformation", + nodeClass: "tb-transformation-type", + icon: "transform" + }, + ACTION: { + value: "ACTION", + name: "rulenode.type-action", + nodeClass: "tb-action-type", + icon: "flash_on" + }, + RULE_CHAIN: { + value: "RULE_CHAIN", + name: "rulenode.type-rule-chain", + nodeClass: "tb-rule-chain-type", + icon: "settings_ethernet" + }, + INPUT: { + value: "INPUT", + nodeClass: "tb-input-type", + icon: "input", + special: true + } + }, valueType: { string: { value: "string", diff --git a/ui/src/app/entity/entity-autocomplete.directive.js b/ui/src/app/entity/entity-autocomplete.directive.js index c2053c0bcc..2dfc3be28f 100644 --- a/ui/src/app/entity/entity-autocomplete.directive.js +++ b/ui/src/app/entity/entity-autocomplete.directive.js @@ -143,6 +143,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter scope.noEntitiesMatchingText = 'plugin.no-plugins-matching'; scope.entityRequiredText = 'plugin.plugin-required'; break; + case types.entityType.rulechain: + scope.selectEntityText = 'rulechain.select-rulechain'; + scope.entityText = 'rulechain.rulechain'; + scope.noEntitiesMatchingText = 'rulechain.no-rulechains-matching'; + scope.entityRequiredText = 'rulechain.rulechain-required'; + break; case types.entityType.tenant: scope.selectEntityText = 'tenant.select-tenant'; scope.entityText = 'tenant.tenant'; diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index ea576ecf51..4c392ca84d 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1169,6 +1169,26 @@ export default angular.module('thingsboard.locale', []) "rulechain-required": "Rule chain is required", "management": "Rules management" }, + "rulenode": { + "add": "Add rule node", + "name": "Name", + "name-required": "Name is required.", + "type": "Type", + "description": "Description", + "delete": "Delete rule node", + "rulenode-details": "Rule node details", + "link-details": "Rule node link details", + "add-link": "Add link", + "link-label": "Link label", + "link-label-required": "Link label is required.", + "custom-link-label": "Custom link label", + "custom-link-label-required": "Custom link label is required.", + "type-filter": "Filter", + "type-enrichment": "Enrichment", + "type-transformation": "Transformation", + "type-action": "Action", + "type-rule-chain": "Rule Chain" + }, "rule-plugin": { "management": "Rules and plugins management" }, diff --git a/ui/src/app/rulechain/add-link.tpl.html b/ui/src/app/rulechain/add-link.tpl.html new file mode 100644 index 0000000000..42c07773bb --- /dev/null +++ b/ui/src/app/rulechain/add-link.tpl.html @@ -0,0 +1,48 @@ + + +

+ +
+

rulenode.add-link

+ +
+ + + +
+
+ + + +
+ +
+
+ + + + {{ 'action.add' | translate }} + + {{ 'action.cancel' | + translate }} + + + + diff --git a/ui/src/app/rulechain/add-rulenode.tpl.html b/ui/src/app/rulechain/add-rulenode.tpl.html new file mode 100644 index 0000000000..c36b43bc0f --- /dev/null +++ b/ui/src/app/rulechain/add-rulenode.tpl.html @@ -0,0 +1,48 @@ + + +
+ +
+

rulenode.add

+ +
+ + + +
+
+ + + +
+ +
+
+ + + + {{ 'action.add' | translate }} + + {{ 'action.cancel' | + translate }} + + +
+
diff --git a/ui/src/app/rulechain/index.js b/ui/src/app/rulechain/index.js index faecc03e03..7306762572 100644 --- a/ui/src/app/rulechain/index.js +++ b/ui/src/app/rulechain/index.js @@ -15,11 +15,19 @@ */ import RuleChainRoutes from './rulechain.routes'; -import RuleChainController from './rulechain.controller'; +import RuleChainsController from './rulechains.controller'; +import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller'; import RuleChainDirective from './rulechain.directive'; +import RuleNodeDirective from './rulenode.directive'; +import LinkDirective from './link.directive'; export default angular.module('thingsboard.ruleChain', []) .config(RuleChainRoutes) + .controller('RuleChainsController', RuleChainsController) .controller('RuleChainController', RuleChainController) + .controller('AddRuleNodeController', AddRuleNodeController) + .controller('AddRuleNodeLinkController', AddRuleNodeLinkController) .directive('tbRuleChain', RuleChainDirective) + .directive('tbRuleNode', RuleNodeDirective) + .directive('tbRuleNodeLink', LinkDirective) .name; diff --git a/ui/src/app/rulechain/link-fieldset.tpl.html b/ui/src/app/rulechain/link-fieldset.tpl.html new file mode 100644 index 0000000000..13ec6c3ce8 --- /dev/null +++ b/ui/src/app/rulechain/link-fieldset.tpl.html @@ -0,0 +1,39 @@ + + +
+ + + + + {{label.name}} + + +
+
rulenode.link-label-required
+
+
+ + + +
+
rulenode.custom-link-label-required
+
+
+
+
diff --git a/ui/src/app/rulechain/link.directive.js b/ui/src/app/rulechain/link.directive.js new file mode 100644 index 0000000000..b3565a3954 --- /dev/null +++ b/ui/src/app/rulechain/link.directive.js @@ -0,0 +1,71 @@ +/* + * 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. + */ + +/* eslint-disable import/no-unresolved, import/default */ + +import linkFieldsetTemplate from './link-fieldset.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function LinkDirective($compile, $templateCache, $filter) { + var linker = function (scope, element) { + var template = $templateCache.get(linkFieldsetTemplate); + element.html(template); + + scope.selectedLabel = null; + + scope.$watch('link', function() { + scope.selectedLabel = null; + if (scope.link && scope.labels) { + if (scope.link.label) { + var result = $filter('filter')(scope.labels, {name: scope.link.label}); + if (result && result.length) { + scope.selectedLabel = result[0]; + } else { + result = $filter('filter')(scope.labels, {custom: true}); + if (result && result.length && result[0].custom) { + scope.selectedLabel = result[0]; + } + } + } + } + }); + + scope.selectedLabelChanged = function() { + if (scope.link && scope.selectedLabel) { + if (!scope.selectedLabel.custom) { + scope.link.label = scope.selectedLabel.name; + } else { + scope.link.label = ""; + } + } + }; + + $compile(element.contents())(scope); + } + return { + restrict: "E", + link: linker, + scope: { + link: '=', + labels: '=', + isEdit: '=', + isReadOnly: '=', + theForm: '=' + } + }; +} diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html index ec68c574bf..a79ba35cf0 100644 --- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html @@ -32,7 +32,7 @@ - +
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 475ef4e09a..c854cfa36d 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -13,160 +13,582 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import './rulechain.scss'; + /* eslint-disable import/no-unresolved, import/default */ -import addRuleChainTemplate from './add-rulechain.tpl.html'; -import ruleChainCard from './rulechain-card.tpl.html'; +import addRuleNodeTemplate from './add-rulenode.tpl.html'; +import addRuleNodeLinkTemplate from './add-link.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ + +const deleteKeyCode = 46; +const ctrlKeyCode = 17; +const aKeyCode = 65; +const escKeyCode = 27; + /*@ngInject*/ -export default function RuleChainController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) { - - var ruleChainActionsList = [ - { - onAction: function ($event, item) { - exportRuleChain($event, item); - }, - name: function() { $translate.instant('action.export') }, - details: function() { return $translate.instant('rulechain.export') }, - icon: "file_download" - }, - { - onAction: function ($event, item) { - vm.grid.deleteItem($event, item); - }, - name: function() { return $translate.instant('action.delete') }, - details: function() { return $translate.instant('rulechain.delete') }, - icon: "delete", - isEnabled: isRuleChainEditable - } - ]; - - var ruleChainAddItemActionsList = [ - { - onAction: function ($event) { - vm.grid.addItem($event); - }, - name: function() { return $translate.instant('action.create') }, - details: function() { return $translate.instant('rulechain.create-new-rulechain') }, - icon: "insert_drive_file" - }, - { - onAction: function ($event) { - importExport.importRuleChain($event).then( - function() { - vm.grid.refreshList(); - } - ); - }, - name: function() { return $translate.instant('action.import') }, - details: function() { return $translate.instant('rulechain.import') }, - icon: "file_upload" - } - ]; +export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpansionPanel, $document, $mdDialog, $filter, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { var vm = this; + vm.$mdExpansionPanel = $mdExpansionPanel; vm.types = types; - vm.ruleChainGridConfig = { + vm.editingRuleNode = null; + vm.isEditingRuleNode = false; + + vm.editingRuleNodeLink = null; + vm.isEditingRuleNodeLink = false; - refreshParamsFunc: null, + vm.ruleChain = ruleChain; + vm.ruleChainMetaData = ruleChainMetaData; - deleteItemTitleFunc: deleteRuleChainTitle, - deleteItemContentFunc: deleteRuleChainText, - deleteItemsTitleFunc: deleteRuleChainsTitle, - deleteItemsActionTitleFunc: deleteRuleChainsActionTitle, - deleteItemsContentFunc: deleteRuleChainsText, + vm.canvasControl = {}; - fetchItemsFunc: fetchRuleChains, - saveItemFunc: saveRuleChain, - deleteItemFunc: deleteRuleChain, + vm.ruleChainModel = { + nodes: [], + edges: [] + }; + + vm.ruleNodeTypesModel = {}; + for (var type in types.ruleNodeType) { + if (!types.ruleNodeType[type].special) { + vm.ruleNodeTypesModel[type] = { + model: { + nodes: [], + edges: [] + }, + selectedObjects: [] + }; + } + } - getItemTitleFunc: getRuleChainTitle, - itemCardTemplateUrl: ruleChainCard, - parentCtl: vm, + vm.selectedObjects = []; - actionsList: ruleChainActionsList, - addItemActions: ruleChainAddItemActionsList, + vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects); - onGridInited: gridInited, + vm.ctrlDown = false; - addItemTemplateUrl: addRuleChainTemplate, + vm.saveRuleChain = saveRuleChain; + vm.revertRuleChain = revertRuleChain; - addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') }, - noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') }, - itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') }, - isSelectionEnabled: isRuleChainEditable, - isDetailsReadOnly: function(ruleChain) { - return !isRuleChainEditable(ruleChain); + vm.keyDown = function (evt) { + if (evt.keyCode === ctrlKeyCode) { + vm.ctrlDown = true; + evt.stopPropagation(); + evt.preventDefault(); } }; - if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { - vm.ruleChainGridConfig.items = $stateParams.items; - } + vm.keyUp = function (evt) { - if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) { - vm.ruleChainGridConfig.topIndex = $stateParams.topIndex; - } + if (evt.keyCode === deleteKeyCode) { + vm.modelservice.deleteSelected(); + } - vm.isRuleChainEditable = isRuleChainEditable; + if (evt.keyCode == aKeyCode && vm.ctrlDown) { + vm.modelservice.selectAll(); + } - vm.exportRuleChain = exportRuleChain; + if (evt.keyCode == escKeyCode) { + vm.modelservice.deselectAll(); + } - function deleteRuleChainTitle(ruleChain) { - return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name}); - } + if (evt.keyCode === ctrlKeyCode) { + vm.ctrlDown = false; + evt.stopPropagation(); + evt.preventDefault(); + } + }; - function deleteRuleChainText() { - return $translate.instant('rulechain.delete-rulechain-text'); - } + vm.onEditRuleNodeClosed = function() { + vm.editingRuleNode = null; + }; + + vm.onEditRuleNodeLinkClosed = function() { + vm.editingRuleNodeLink = null; + }; + + vm.saveRuleNode = function(theForm) { + theForm.$setPristine(); + vm.isEditingRuleNode = false; + vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; + vm.editingRuleNode = angular.copy(vm.editingRuleNode); + }; + + vm.saveRuleNodeLink = function(theForm) { + theForm.$setPristine(); + vm.isEditingRuleNodeLink = false; + vm.ruleChainModel.edges[vm.editingRuleNodeLinkIndex] = vm.editingRuleNodeLink; + vm.editingRuleNodeLink = angular.copy(vm.editingRuleNodeLink); + }; + + vm.onRevertRuleNodeEdit = function(theForm) { + theForm.$setPristine(); + var node = vm.ruleChainModel.nodes[vm.editingRuleNodeIndex]; + vm.editingRuleNode = angular.copy(node); + }; + + vm.onRevertRuleNodeLinkEdit = function(theForm) { + theForm.$setPristine(); + var edge = vm.ruleChainModel.edges[vm.editingRuleNodeLinkIndex]; + vm.editingRuleNodeLink = angular.copy(edge); + }; + + vm.editCallbacks = { + edgeDoubleClick: function (event, edge) { + var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); + if (sourceNode.nodeType != types.ruleNodeType.INPUT.value) { + ruleChainService.getRuleNodeSupportedLinks(sourceNode.type).then( + (labels) => { + vm.isEditingRuleNode = false; + vm.editingRuleNode = null; + vm.editingRuleNodeLinkLabels = labels; + vm.isEditingRuleNodeLink = true; + vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); + vm.editingRuleNodeLink = angular.copy(edge); + } + ); + } + }, + nodeCallbacks: { + 'doubleClick': function (event, node) { + if (node.nodeType != types.ruleNodeType.INPUT.value) { + vm.isEditingRuleNodeLink = false; + vm.editingRuleNodeLink = null; + vm.isEditingRuleNode = true; + vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node); + vm.editingRuleNode = angular.copy(node); + } + } + }, + isValidEdge: function (source, destination) { + return source.type === flowchartConstants.rightConnectorType && destination.type === flowchartConstants.leftConnectorType; + }, + createEdge: function (event, edge) { + var deferred = $q.defer(); + var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); + if (sourceNode.nodeType == types.ruleNodeType.INPUT.value) { + var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination); + if (destNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) { + deferred.reject(); + } else { + var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}); + if (res && res.length) { + vm.modelservice.edges.delete(res[0]); + } + deferred.resolve(edge); + } + } else { + ruleChainService.getRuleNodeSupportedLinks(sourceNode.type).then( + (labels) => { + addRuleNodeLink(event, edge, labels).then( + (link) => { + deferred.resolve(link); + }, + () => { + deferred.reject(); + } + ); + }, + () => { + deferred.reject(); + } + ); + } + return deferred.promise; + }, + dropNode: function (event, node) { + addRuleNode(event, node); + } + }; - function deleteRuleChainsTitle(selectedCount) { - return $translate.instant('rulechain.delete-rulechains-title', {count: selectedCount}, 'messageformat'); + loadRuleChainLibrary(); + + function loadRuleChainLibrary() { + ruleChainService.getRuleNodeTypes().then( + (ruleNodeTypes) => { + for (var i=0;i { + createRuleChainModel(ruleChainsMap); + } + ); } - function deleteRuleChainsText() { - return $translate.instant('rulechain.delete-rulechains-text'); + function createRuleChainModel(ruleChainsMap) { + var nodes = []; + for (var i=0;i -1) { + var destNode = nodes[vm.ruleChainMetaData.firstNodeIndex]; + if (destNode) { + var connectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType); + if (connectors && connectors.length) { + var edge = { + source: vm.inputConnectorId, + destination: connectors[0].id + }; + vm.ruleChainModel.edges.push(edge); + } + } + } + + if (vm.ruleChainMetaData.connections) { + for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) { + var connection = vm.ruleChainMetaData.connections[0]; + var sourceNode = nodes[connection.fromIndex]; + destNode = nodes[connection.toIndex]; + if (sourceNode && destNode) { + var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType); + var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType); + if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) { + edge = { + source: sourceConnectors[0].id, + destination: destConnectors[0].id, + label: connection.type + }; + vm.ruleChainModel.edges.push(edge); + } + } + } + } + + if (vm.ruleChainMetaData.ruleChainConnections) { + var ruleChainNodesMap = {}; + for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) { + var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i]; + var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id]; + if (ruleChainConnection.additionalInfo && ruleChainConnection.additionalInfo.ruleChainNodeId) { + var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId]; + if (!ruleChainNode) { + ruleChainNode = { + id: vm.nextNodeID++, + additionalInfo: ruleChainConnection.additionalInfo, + targetRuleChainId: ruleChainConnection.targetRuleChainId.id, + x: ruleChainConnection.additionalInfo.layoutX, + y: ruleChainConnection.additionalInfo.layoutY, + type: 'Rule chain', + name: ruleChain.name, + nodeType: vm.types.ruleNodeType.RULE_CHAIN.value, + nodeClass: vm.types.ruleNodeType.RULE_CHAIN.nodeClass, + icon: vm.types.ruleNodeType.RULE_CHAIN.icon, + connectors: [ + { + type: flowchartConstants.leftConnectorType, + id: vm.nextConnectorID++ + } + ] + }; + ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode; + vm.ruleChainModel.nodes.push(ruleChainNode); + } + sourceNode = nodes[ruleChainConnection.fromIndex]; + if (sourceNode) { + connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType); + if (connectors && connectors.length) { + var ruleChainEdge = { + source: connectors[0].id, + destination: ruleChainNode.connectors[0].id, + label: ruleChainConnection.type + }; + vm.ruleChainModel.edges.push(ruleChainEdge); + } + } + } + } + } + + vm.canvasControl.adjustCanvasSize(); + + vm.isDirty = false; + + $mdUtil.nextTick(() => { + vm.ruleChainWatch = $scope.$watch('vm.ruleChainModel', + function (newVal, oldVal) { + if (!vm.isDirty && !angular.equals(newVal, oldVal)) { + vm.isDirty = true; + } + }, true + ); + }); } - function gridInited(grid) { - vm.grid = grid; + function saveRuleChain() { + var ruleChainMetaData = { + ruleChainId: vm.ruleChain.id, + nodes: [], + connections: [], + ruleChainConnections: [] + }; + + var nodes = []; + + for (var i=0;i { + vm.ruleChainMetaData = ruleChainMetaData; + prepareRuleChain(); + } + ); } - function fetchRuleChains(pageLink) { - return ruleChainService.getRuleChains(pageLink); + function revertRuleChain() { + prepareRuleChain(); } - function saveRuleChain(ruleChain) { - return ruleChainService.saveRuleChain(ruleChain); + function addRuleNode($event, ruleNode) { + $mdDialog.show({ + controller: 'AddRuleNodeController', + controllerAs: 'vm', + templateUrl: addRuleNodeTemplate, + parent: angular.element($document[0].body), + locals: {ruleNode: ruleNode, ruleChainId: vm.ruleChain.id.id}, + fullscreen: true, + targetEvent: $event + }).then(function (ruleNode) { + ruleNode.id = vm.nextNodeID++; + ruleNode.connectors = []; + ruleNode.connectors.push( + { + id: vm.nextConnectorID++, + type: flowchartConstants.leftConnectorType + } + ); + if (ruleNode.nodeType != types.ruleNodeType.RULE_CHAIN.value) { + ruleNode.connectors.push( + { + id: vm.nextConnectorID++, + type: flowchartConstants.rightConnectorType + } + ); + } + vm.ruleChainModel.nodes.push(ruleNode); + }, function () { + }); } - function deleteRuleChain(ruleChainId) { - return ruleChainService.deleteRuleChain(ruleChainId); + function addRuleNodeLink($event, link, labels) { + return $mdDialog.show({ + controller: 'AddRuleNodeLinkController', + controllerAs: 'vm', + templateUrl: addRuleNodeLinkTemplate, + parent: angular.element($document[0].body), + locals: {link: link, labels: labels}, + fullscreen: true, + targetEvent: $event + }); } - function getRuleChainTitle(ruleChain) { - return ruleChain ? ruleChain.name : ''; +} + +/*@ngInject*/ +export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, helpLinks) { + + var vm = this; + + vm.helpLinks = helpLinks; + vm.ruleNode = ruleNode; + vm.ruleChainId = ruleChainId; + + vm.add = add; + vm.cancel = cancel; + + function cancel() { + $mdDialog.cancel(); } - function isRuleChainEditable(ruleChain) { - if (userService.getAuthority() === 'TENANT_ADMIN') { - return ruleChain && ruleChain.tenantId.id != types.id.nullUid; - } else { - return userService.getAuthority() === 'SYS_ADMIN'; - } + function add() { + $scope.theForm.$setPristine(); + $mdDialog.hide(vm.ruleNode); } +} + +/*@ngInject*/ +export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, helpLinks) { - function exportRuleChain($event, ruleChain) { - $event.stopPropagation(); - importExport.exportRuleChain(ruleChain.id.id); + var vm = this; + + vm.helpLinks = helpLinks; + vm.link = link; + vm.labels = labels; + + vm.add = add; + vm.cancel = cancel; + + function cancel() { + $mdDialog.cancel(); } + function add() { + $scope.theForm.$setPristine(); + $mdDialog.hide(vm.link); + } } diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index e8d6be49d2..808661a65a 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -15,12 +15,16 @@ */ /* eslint-disable import/no-unresolved, import/default */ +import ruleNodeTemplate from './rulenode.tpl.html'; import ruleChainsTemplate from './rulechains.tpl.html'; +import ruleChainTemplate from './rulechain.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function RuleChainRoutes($stateProvider) { +export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider) { + + NodeTemplatePathProvider.setTemplatePath(ruleNodeTemplate); $stateProvider .state('home.ruleChains', { @@ -32,7 +36,7 @@ export default function RuleChainRoutes($stateProvider) { "content@home": { templateUrl: ruleChainsTemplate, controllerAs: 'vm', - controller: 'RuleChainController' + controller: 'RuleChainsController' } }, data: { @@ -42,5 +46,36 @@ export default function RuleChainRoutes($stateProvider) { ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "rulechain.rulechains"}' } - }); + }).state('home.ruleChains.ruleChain', { + url: '/:ruleChainId', + reloadOnSearch: false, + module: 'private', + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: ruleChainTemplate, + controller: 'RuleChainController', + controllerAs: 'vm' + } + }, + resolve: { + ruleChain: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleChain($stateParams.ruleChainId); + }, + ruleChainMetaData: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId); + } + }, + data: { + searchEnabled: false, + pageTitle: 'rulechain.rulechain' + }, + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' + } + }); } diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss new file mode 100644 index 0000000000..258e232ea4 --- /dev/null +++ b/ui/src/app/rulechain/rulechain.scss @@ -0,0 +1,262 @@ +/** + * 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. + */ + +.tb-rulechain { + .tb-fullscreen-button-style { + z-index: 1; + } + .tb-rulechain-library { + width: 250px; + min-width: 250px; + overflow-y: auto; + overflow-x: hidden; + + .tb-rulechain-library-panel-group { + .tb-panel-title { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + min-width: 180px; + } + .fc-canvas { + background: none; + } + md-icon.md-expansion-panel-icon { + margin-right: 0px; + } + md-expansion-panel-collapsed, .md-expansion-panel-header-container { + background: #e6e6e6; + border-color: #909090; + position: static; + } + md-expansion-panel { + &.md-open { + margin-top: 0; + margin-bottom: 0; + } + } + md-expansion-panel-content { + padding: 0px; + } + } + } + .tb-rulechain-graph { + overflow: auto; + } +} + +.fc-canvas { + min-width: 100%; + min-height: 100%; + outline: none; +} + +.tb-rule-node { + display: flex; + flex-direction: row; + min-width: 150px; + max-width: 150px; + min-height: 28px; + max-height: 28px; + padding: 5px 10px; + border-radius: 5px; + background-color: #F15B26; + color: #333; + border: solid 1px #777; + font-size: 12px; + &.tb-input-type { + background-color: #a3eaa9; + user-select: none; + } + &.tb-filter-type { + background-color: #f1e861; + } + &.tb-enrichment-type { + background-color: #cdf14e; + } + &.tb-transformation-type { + background-color: #79cef1; + } + &.tb-action-type { + background-color: #f1928f; + } + &.tb-rule-chain-type { + background-color: #d6c4f1; + } + md-icon { + font-size: 20px; + width: 20px; + height: 20px; + min-height: 20px; + min-width: 20px; + padding-right: 4px; + } + .tb-node-type { + + } + .tb-node-title { + font-weight: 600; + } + .tb-node-type, .tb-node-title { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } +} + +.fc-node { + z-index: 1; + outline: none; + &.fc-hover, &.fc-selected { + -webkit-filter: brightness(70%); + filter: brightness(70%); + } + &.fc-dragging { + z-index: 10; + } + p { + padding: 0 15px; + text-align: center; + } +} + +.fc-leftConnectors, .fc-rightConnectors { + position: absolute; + top: 0; + height: 100%; + + display: flex; + flex-direction: column; + + z-index: 0; + .fc-magnet { + align-items: center; + } +} + +.fc-leftConnectors { + left: -20px; +} + +.fc-rightConnectors { + right: -20px; +} + +.fc-magnet { + display: flex; + flex-grow: 1; + height: 60px; + justify-content: center; +} + +.fc-connector { + width: 14px; + height: 14px; + border: 1px solid #333; + margin: 10px; + border-radius: 5px; + background-color: #ccc; +} + +.fc-connector.fc-hover { + background-color: #000; +} + +.fc-edge { + outline: none; + stroke: gray; + stroke-width: 4; + fill: transparent; + &.fc-selected { + stroke: red; + stroke-width: 4; + fill: transparent; + } + &.fc-active { + animation: dash 3s linear infinite; + stroke-dasharray: 20; + } + &.fc-hover { + stroke: gray; + stroke-width: 6; + fill: transparent; + } + &.fc-dragging { + pointer-events: none; + } +} + +.edge-endpoint { + fill: gray; +} + +.fc-nodedelete { + display: none; +} + +.fc-selected .fc-nodedelete { + outline: none; + display: block; + position: absolute; + right: -13px; + top: -16px; + border: solid 2px white; + border-radius: 50%; + font-weight: 600; + font-size: 18px; + line-height: 18px; + height: 20px; + padding-top: 2px; + width: 22px; + background: #494949; + color: #fff; + text-align: center; + vertical-align: bottom; + cursor: pointer; +} + +.fc-edge-label { + position: absolute; + user-select: none; + pointer-events: none; + opacity: 0.8; +} + +.fc-edge-label-text { + position: absolute; + left: 50%; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + white-space: nowrap; + text-align: center; + font-size: 14px; + font-weight: 600; + top: 5px; + span { + border: solid 2px #003a79; + border-radius: 10px; + color: #003a79; + background-color: #fff; + padding: 3px 5px; + } +} + +@keyframes dash { + from { + stroke-dashoffset: 500; + } +} diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html new file mode 100644 index 0000000000..9f22ebceb9 --- /dev/null +++ b/ui/src/app/rulechain/rulechain.tpl.html @@ -0,0 +1,130 @@ + + + +
+
+
+ + + +
{{vm.types.ruleNodeType[typeId].name}}
+ +
+ + +
{{vm.types.ruleNodeType[typeId].name}}
+ +
+ + + +
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + + + +
+ + +
+
+
+ +
diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js new file mode 100644 index 0000000000..2a30b0c98f --- /dev/null +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -0,0 +1,188 @@ +/* + * 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. + */ +/* eslint-disable import/no-unresolved, import/default */ + +import addRuleChainTemplate from './add-rulechain.tpl.html'; +import ruleChainCard from './rulechain-card.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function RuleChainsController(ruleChainService, userService, importExport, $state, $stateParams, $filter, $translate, types) { + + var ruleChainActionsList = [ + { + onAction: function ($event, item) { + vm.grid.openItem($event, item); + }, + name: function() { return $translate.instant('rulechain.details') }, + details: function() { return $translate.instant('rulechain.rulechain-details') }, + icon: "edit" + }, + { + onAction: function ($event, item) { + exportRuleChain($event, item); + }, + name: function() { $translate.instant('action.export') }, + details: function() { return $translate.instant('rulechain.export') }, + icon: "file_download" + }, + { + onAction: function ($event, item) { + vm.grid.deleteItem($event, item); + }, + name: function() { return $translate.instant('action.delete') }, + details: function() { return $translate.instant('rulechain.delete') }, + icon: "delete", + isEnabled: isRuleChainEditable + } + ]; + + var ruleChainAddItemActionsList = [ + { + onAction: function ($event) { + vm.grid.addItem($event); + }, + name: function() { return $translate.instant('action.create') }, + details: function() { return $translate.instant('rulechain.create-new-rulechain') }, + icon: "insert_drive_file" + }, + { + onAction: function ($event) { + importExport.importRuleChain($event).then( + function() { + vm.grid.refreshList(); + } + ); + }, + name: function() { return $translate.instant('action.import') }, + details: function() { return $translate.instant('rulechain.import') }, + icon: "file_upload" + } + ]; + + var vm = this; + + vm.types = types; + + vm.ruleChainGridConfig = { + + refreshParamsFunc: null, + + deleteItemTitleFunc: deleteRuleChainTitle, + deleteItemContentFunc: deleteRuleChainText, + deleteItemsTitleFunc: deleteRuleChainsTitle, + deleteItemsActionTitleFunc: deleteRuleChainsActionTitle, + deleteItemsContentFunc: deleteRuleChainsText, + + fetchItemsFunc: fetchRuleChains, + saveItemFunc: saveRuleChain, + clickItemFunc: openRuleChain, + deleteItemFunc: deleteRuleChain, + + getItemTitleFunc: getRuleChainTitle, + itemCardTemplateUrl: ruleChainCard, + parentCtl: vm, + + actionsList: ruleChainActionsList, + addItemActions: ruleChainAddItemActionsList, + + onGridInited: gridInited, + + addItemTemplateUrl: addRuleChainTemplate, + + addItemText: function() { return $translate.instant('rulechain.add-rulechain-text') }, + noItemsText: function() { return $translate.instant('rulechain.no-rulechains-text') }, + itemDetailsText: function() { return $translate.instant('rulechain.rulechain-details') }, + isSelectionEnabled: isRuleChainEditable, + isDetailsReadOnly: function(ruleChain) { + return !isRuleChainEditable(ruleChain); + } + }; + + if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { + vm.ruleChainGridConfig.items = $stateParams.items; + } + + if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) { + vm.ruleChainGridConfig.topIndex = $stateParams.topIndex; + } + + vm.isRuleChainEditable = isRuleChainEditable; + + vm.exportRuleChain = exportRuleChain; + + function deleteRuleChainTitle(ruleChain) { + return $translate.instant('rulechain.delete-rulechain-title', {ruleChainName: ruleChain.name}); + } + + function deleteRuleChainText() { + return $translate.instant('rulechain.delete-rulechain-text'); + } + + function deleteRuleChainsTitle(selectedCount) { + return $translate.instant('rulechain.delete-rulechains-title', {count: selectedCount}, 'messageformat'); + } + + function deleteRuleChainsActionTitle(selectedCount) { + return $translate.instant('rulechain.delete-rulechains-action-title', {count: selectedCount}, 'messageformat'); + } + + function deleteRuleChainsText() { + return $translate.instant('rulechain.delete-rulechains-text'); + } + + function gridInited(grid) { + vm.grid = grid; + } + + function fetchRuleChains(pageLink) { + return ruleChainService.getRuleChains(pageLink); + } + + function saveRuleChain(ruleChain) { + return ruleChainService.saveRuleChain(ruleChain); + } + + function openRuleChain($event, ruleChain) { + if ($event) { + $event.stopPropagation(); + } + $state.go('home.ruleChains.ruleChain', {ruleChainId: ruleChain.id.id}); + } + + function deleteRuleChain(ruleChainId) { + return ruleChainService.deleteRuleChain(ruleChainId); + } + + function getRuleChainTitle(ruleChain) { + return ruleChain ? ruleChain.name : ''; + } + + function isRuleChainEditable(ruleChain) { + if (userService.getAuthority() === 'TENANT_ADMIN') { + return ruleChain && ruleChain.tenantId.id != types.id.nullUid; + } else { + return userService.getAuthority() === 'SYS_ADMIN'; + } + } + + function exportRuleChain($event, ruleChain) { + $event.stopPropagation(); + importExport.exportRuleChain(ruleChain.id.id); + } + +} diff --git a/ui/src/app/rulechain/rulenode-fieldset.tpl.html b/ui/src/app/rulechain/rulenode-fieldset.tpl.html new file mode 100644 index 0000000000..2b2faf8257 --- /dev/null +++ b/ui/src/app/rulechain/rulenode-fieldset.tpl.html @@ -0,0 +1,55 @@ + +{{ 'rulenode.delete' | translate }} + + +
+ + + + +
+ + + +
+
rulenode.name-required
+
+
+ + + + +
+
+ + + + + + +
+
+
diff --git a/ui/src/app/rulechain/rulenode.directive.js b/ui/src/app/rulechain/rulenode.directive.js new file mode 100644 index 0000000000..7fa4a18ca3 --- /dev/null +++ b/ui/src/app/rulechain/rulenode.directive.js @@ -0,0 +1,79 @@ +/* + * 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. + */ + +/* eslint-disable import/no-unresolved, import/default */ + +import ruleNodeFieldsetTemplate from './rulenode-fieldset.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function RuleNodeDirective($compile, $templateCache, ruleChainService, types) { + var linker = function (scope, element) { + var template = $templateCache.get(ruleNodeFieldsetTemplate); + element.html(template); + + scope.types = types; + + scope.params = { + targetRuleChainId: null + }; + + scope.$watch('ruleNode', function() { + if (scope.ruleNode && scope.ruleNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) { + scope.params.targetRuleChainId = scope.ruleNode.targetRuleChainId; + watchTargetRuleChain(); + } else { + if (scope.targetRuleChainWatch) { + scope.targetRuleChainWatch(); + scope.targetRuleChainWatch = null; + } + } + }); + + function watchTargetRuleChain() { + scope.targetRuleChainWatch = scope.$watch('params.targetRuleChainId', + function(targetRuleChainId) { + if (scope.ruleNode.targetRuleChainId != targetRuleChainId) { + scope.ruleNode.targetRuleChainId = targetRuleChainId; + if (targetRuleChainId) { + ruleChainService.getRuleChain(targetRuleChainId).then( + (ruleChain) => { + scope.ruleNode.name = ruleChain.name; + } + ); + } else { + scope.ruleNode.name = ""; + } + } + } + ); + } + $compile(element.contents())(scope); + } + return { + restrict: "E", + link: linker, + scope: { + ruleChainId: '=', + ruleNode: '=', + isEdit: '=', + isReadOnly: '=', + theForm: '=', + onDeleteRuleNode: '&' + } + }; +} diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html new file mode 100644 index 0000000000..f1d3619ebc --- /dev/null +++ b/ui/src/app/rulechain/rulenode.tpl.html @@ -0,0 +1,45 @@ + +
+
+ {{node.icon}} +
+ {{ node.type }} + {{ node.name }} +
+
+
+
+
+
+
+
+
+
+
+
+
+ × +
+
From a5c59e5c88b2ed17c8eec24cf5946401c098713d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 21 Mar 2018 15:03:21 +0200 Subject: [PATCH 026/171] Update ngFlowchart git repository --- ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/package.json b/ui/package.json index b602720a48..e68aa34223 100644 --- a/ui/package.json +++ b/ui/package.json @@ -69,7 +69,7 @@ "moment": "^2.15.0", "ngclipboard": "^1.1.1", "ngreact": "^0.3.0", - "ngFlowchart": "git://github.com/ikulikov/ngFlowchart.git#master", + "ngFlowchart": "git://github.com/thingsboard/ngFlowchart.git#master", "objectpath": "^1.2.1", "oclazyload": "^1.0.9", "raphael": "^2.2.7", From cb20bcc23a3c8da5871907bc76a67942d132f89d Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Wed, 21 Mar 2018 16:31:25 +0200 Subject: [PATCH 027/171] add Annotation description to Nodes --- .../org/thingsboard/rule/engine/filter/TbJsFilterNode.java | 1 + .../org/thingsboard/rule/engine/filter/TbJsSwitchNode.java | 1 + .../thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java | 1 + .../rule/engine/transform/TbChangeOriginatorNode.java | 6 ++---- .../rule/engine/transform/TbTransformMsgNode.java | 6 ++---- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 089c9b1919..d3fee6859e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -26,6 +26,7 @@ import javax.script.Bindings; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j +@FilterNode(name = "Filter Node", relationTypes = {"True", "False", "Failure"}) public class TbJsFilterNode implements TbNode { private TbJsFilterNodeConfiguration config; 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 afd81636a0..5c7d1d10f4 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 @@ -27,6 +27,7 @@ import java.util.Set; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j +@FilterNode(name = "Switch Node", customRelations = true) public class TbJsSwitchNode implements TbNode { private TbJsSwitchNodeConfiguration config; 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 026da1ba83..381512a806 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 @@ -24,6 +24,7 @@ import org.thingsboard.server.common.msg.TbMsg; * Created by ashvayka on 19.01.18. */ @Slf4j +@FilterNode(name = "Message Type Filter Node") public class TbMsgTypeFilterNode implements TbNode { TbMsgTypeFilterNodeConfiguration config; 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 2592af253e..36063b7fc2 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 @@ -22,10 +22,7 @@ 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.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeState; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; @@ -35,6 +32,7 @@ import org.thingsboard.server.common.msg.TbMsg; import java.util.HashSet; @Slf4j +@EnrichmentNode(name = "Change Originator Node") public class TbChangeOriginatorNode extends TbAbstractTransformNode { protected static final String CUSTOMER_SOURCE = "CUSTOMER"; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 241fbfbf7e..c0d20f43c4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -17,15 +17,13 @@ package org.thingsboard.rule.engine.transform; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.TbNodeUtils; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeState; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.js.NashornJsEngine; import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; +@EnrichmentNode(name = "Transformation Node") public class TbTransformMsgNode extends TbAbstractTransformNode { private TbTransformMsgNodeConfiguration config; From 344f6045a64dd2f3ff9aaeade9629868fd63754d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 21 Mar 2018 17:34:07 +0200 Subject: [PATCH 028/171] Use ComponentDescriptors in RuleChain UI --- .../server/controller/BaseController.java | 10 ++ .../ComponentDescriptorController.java | 18 ++++ .../AnnotationComponentDiscoveryService.java | 9 ++ .../component/ComponentDiscoveryService.java | 3 + .../app/api/component-descriptor.service.js | 38 +++++++- ui/src/app/api/rule-chain.service.js | 92 ++++--------------- ui/src/app/common/types.constant.js | 11 +++ ui/src/app/locale/locale.constant.js | 4 +- .../app/rulechain/rulechain-fieldset.tpl.html | 5 + ui/src/app/rulechain/rulechain.controller.js | 70 +++++++------- ui/src/app/rulechain/rulechain.tpl.html | 2 +- .../app/rulechain/rulenode-fieldset.tpl.html | 11 ++- ui/src/app/rulechain/rulenode.directive.js | 2 +- ui/src/app/rulechain/rulenode.tpl.html | 2 +- 14 files changed, 161 insertions(+), 116 deletions(-) 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 2a4ba719af..d1c282508b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -71,6 +71,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -480,6 +481,15 @@ public abstract class BaseController { } } + List checkComponentDescriptorsByTypes(Set types) throws ThingsboardException { + try { + log.debug("[{}] Lookup component descriptors", types); + return componentDescriptorService.getComponents(types); + } catch (Exception e) { + throw handleException(e, false); + } + } + List checkPluginActionsByPluginClazz(String pluginClazz) throws ThingsboardException { try { checkComponentDescriptorByClazz(pluginClazz); diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java index e63a443769..7a43cd35a2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java @@ -21,7 +21,9 @@ import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.exception.ThingsboardException; +import java.util.HashSet; import java.util.List; +import java.util.Set; @RestController @RequestMapping("/api") @@ -51,6 +53,22 @@ public class ComponentDescriptorController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") + @RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET) + @ResponseBody + public List getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes) throws ThingsboardException { + checkArrayParameter("componentTypes", strComponentTypes); + try { + Set componentTypes = new HashSet<>(); + for (String strComponentType : strComponentTypes) { + componentTypes.add(ComponentType.valueOf(strComponentType)); + } + return checkComponentDescriptorsByTypes(componentTypes); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @RequestMapping(value = "/components/actions/{pluginClazz:.+}", method = RequestMethod.GET) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index 910b45954a..d9416d7226 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -203,6 +203,15 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return Collections.unmodifiableList(componentsMap.get(type)); } + @Override + public List getComponents(Set types) { + List result = new ArrayList<>(); + for (ComponentType type : types) { + result.addAll(componentsMap.get(type)); + } + return Collections.unmodifiableList(result); + } + @Override public Optional getComponent(String clazz) { return Optional.ofNullable(components.get(clazz)); diff --git a/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java index ea27e60646..7a15a1b253 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import java.util.List; import java.util.Optional; +import java.util.Set; /** * @author Andrew Shvayka @@ -30,6 +31,8 @@ public interface ComponentDiscoveryService { List getComponents(ComponentType type); + List getComponents(Set types); + Optional getComponent(String clazz); List getPluginActions(String pluginClazz); diff --git a/ui/src/app/api/component-descriptor.service.js b/ui/src/app/api/component-descriptor.service.js index 4478d71b95..cc3f710629 100644 --- a/ui/src/app/api/component-descriptor.service.js +++ b/ui/src/app/api/component-descriptor.service.js @@ -26,7 +26,8 @@ function ComponentDescriptorService($http, $q) { var service = { getComponentDescriptorsByType: getComponentDescriptorsByType, getComponentDescriptorByClazz: getComponentDescriptorByClazz, - getPluginActionsByPluginClazz: getPluginActionsByPluginClazz + getPluginActionsByPluginClazz: getPluginActionsByPluginClazz, + getComponentDescriptorsByTypes: getComponentDescriptorsByTypes } return service; @@ -52,6 +53,41 @@ function ComponentDescriptorService($http, $q) { return deferred.promise; } + function getComponentDescriptorsByTypes(componentTypes) { + var deferred = $q.defer(); + var result = []; + for (var i=componentTypes.length-1;i>=0;i--) { + var componentType = componentTypes[i]; + if (componentsByType[componentType]) { + result = result.concat(componentsByType[componentType]); + componentTypes.splice(i, 1); + } + } + if (!componentTypes.length) { + deferred.resolve(result); + } else { + var url = '/api/components?componentTypes=' + componentTypes.join(','); + $http.get(url, null).then(function success(response) { + var components = response.data; + for (var i = 0; i < components.length; i++) { + var component = components[i]; + var componentsList = componentsByType[component.type]; + if (!componentsList) { + componentsList = []; + componentsByType[component.type] = componentsList; + } + componentsList.push(component); + componentsByClazz[component.clazz] = component; + } + result = result.concat(components); + deferred.resolve(components); + }, function fail() { + deferred.reject(); + }); + } + return deferred.promise; + } + function getComponentDescriptorByClazz(componentDescriptorClazz) { var deferred = $q.defer(); if (componentsByClazz[componentDescriptorClazz]) { diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index be8e99db11..f17553545e 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -17,9 +17,9 @@ export default angular.module('thingsboard.api.ruleChain', []) .factory('ruleChainService', RuleChainService).name; /*@ngInject*/ -function RuleChainService($http, $q, $filter, types) { +function RuleChainService($http, $q, $filter, types, componentDescriptorService) { - var ruleNodeTypes = null; + var ruleNodeComponents = null; var service = { getSystemRuleChains: getSystemRuleChains, @@ -30,8 +30,8 @@ function RuleChainService($http, $q, $filter, types) { deleteRuleChain: deleteRuleChain, getRuleChainMetaData: getRuleChainMetaData, saveRuleChainMetaData: saveRuleChainMetaData, - getRuleNodeTypes: getRuleNodeTypes, - getRuleNodeComponentType: getRuleNodeComponentType, + getRuleNodeComponents: getRuleNodeComponents, + getRuleNodeComponentByClazz: getRuleNodeComponentByClazz, getRuleNodeSupportedLinks: getRuleNodeSupportedLinks, resolveTargetRuleChains: resolveTargetRuleChains }; @@ -165,21 +165,18 @@ function RuleChainService($http, $q, $filter, types) { return deferred.promise; } - function getRuleNodeTypes() { + function getRuleNodeComponents() { var deferred = $q.defer(); - if (ruleNodeTypes) { - deferred.resolve(ruleNodeTypes); + if (ruleNodeComponents) { + deferred.resolve(ruleNodeComponents); } else { - loadRuleNodeTypes().then( - (nodeTypes) => { - ruleNodeTypes = nodeTypes; - ruleNodeTypes.push( - { - nodeType: types.ruleNodeType.RULE_CHAIN.value, - type: 'Rule chain' - } + loadRuleNodeComponents().then( + (components) => { + ruleNodeComponents = components; + ruleNodeComponents.push( + types.ruleChainNodeComponent ); - deferred.resolve(ruleNodeTypes); + deferred.resolve(ruleNodeComponents); }, () => { deferred.reject(); @@ -189,10 +186,10 @@ function RuleChainService($http, $q, $filter, types) { return deferred.promise; } - function getRuleNodeComponentType(type) { - var res = $filter('filter')(ruleNodeTypes, {type: type}, true); + function getRuleNodeComponentByClazz(clazz) { + var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true); if (res && res.length) { - return res[0].nodeType; + return res[0]; } return null; } @@ -222,61 +219,8 @@ function RuleChainService($http, $q, $filter, types) { return deferred.promise; } - function loadRuleNodeTypes() { - var deferred = $q.defer(); - deferred.resolve( - [ - { - nodeType: types.ruleNodeType.FILTER.value, - type: 'Filter' - }, - { - nodeType: types.ruleNodeType.FILTER.value, - type: 'Switch' - }, - { - nodeType: types.ruleNodeType.ENRICHMENT.value, - type: 'Self' - }, - { - nodeType: types.ruleNodeType.ENRICHMENT.value, - type: 'Tenant/Customer' - }, - { - nodeType: types.ruleNodeType.ENRICHMENT.value, - type: 'Related Entity' - }, - { - nodeType: types.ruleNodeType.ENRICHMENT.value, - type: 'Last Telemetry' - }, - { - nodeType: types.ruleNodeType.TRANSFORMATION.value, - type: 'Modify' - }, - { - nodeType: types.ruleNodeType.TRANSFORMATION.value, - type: 'New/Update' - }, - { - nodeType: types.ruleNodeType.ACTION.value, - type: 'Telemetry' - }, - { - nodeType: types.ruleNodeType.ACTION.value, - type: 'RPC call' - }, - { - nodeType: types.ruleNodeType.ACTION.value, - type: 'Send email' - }, - { - nodeType: types.ruleNodeType.ACTION.value, - type: 'Alarm' - } - ] - ); - return deferred.promise; + function loadRuleNodeComponents() { + return componentDescriptorService.getComponentDescriptorsByTypes(types.ruleNodeTypeComponentTypes); } diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index b134bf0575..2f8d152029 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -457,6 +457,17 @@ export default angular.module('thingsboard.types', []) clientSide: false } }, + ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"], + ruleChainNodeComponent: { + type: 'RULE_CHAIN', + name: 'Rule chain', + clazz: 'tb.internal.RuleChain' + }, + inputNodeComponent: { + type: 'INPUT', + name: 'Input', + clazz: 'tb.internal.Input' + }, ruleNodeType: { FILTER: { value: "FILTER", diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 4c392ca84d..18c9330165 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1167,7 +1167,8 @@ export default angular.module('thingsboard.locale', []) "select-rulechain": "Select rule chain", "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.", "rulechain-required": "Rule chain is required", - "management": "Rules management" + "management": "Rules management", + "debug-mode": "Debug mode" }, "rulenode": { "add": "Add rule node", @@ -1177,6 +1178,7 @@ export default angular.module('thingsboard.locale', []) "description": "Description", "delete": "Delete rule node", "rulenode-details": "Rule node details", + "debug-mode": "Debug mode", "link-details": "Rule node link details", "add-link": "Add link", "link-label": "Link label", diff --git a/ui/src/app/rulechain/rulechain-fieldset.tpl.html b/ui/src/app/rulechain/rulechain-fieldset.tpl.html index a79ba35cf0..2189daaeee 100644 --- a/ui/src/app/rulechain/rulechain-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulechain-fieldset.tpl.html @@ -41,6 +41,11 @@
rulechain.name-required
+ + {{ 'rulechain.debug-mode' | translate }} + + diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index c854cfa36d..31886b8a28 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -54,6 +54,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans }; vm.ruleNodeTypesModel = {}; + vm.ruleChainLibraryLoaded = false; for (var type in types.ruleNodeType) { if (!types.ruleNodeType[type].special) { vm.ruleNodeTypesModel[type] = { @@ -141,8 +142,8 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans vm.editCallbacks = { edgeDoubleClick: function (event, edge) { var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); - if (sourceNode.nodeType != types.ruleNodeType.INPUT.value) { - ruleChainService.getRuleNodeSupportedLinks(sourceNode.type).then( + if (sourceNode.component.type != types.ruleNodeType.INPUT.value) { + ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then( (labels) => { vm.isEditingRuleNode = false; vm.editingRuleNode = null; @@ -156,7 +157,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans }, nodeCallbacks: { 'doubleClick': function (event, node) { - if (node.nodeType != types.ruleNodeType.INPUT.value) { + if (node.component.type != types.ruleNodeType.INPUT.value) { vm.isEditingRuleNodeLink = false; vm.editingRuleNodeLink = null; vm.isEditingRuleNode = true; @@ -171,9 +172,9 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans createEdge: function (event, edge) { var deferred = $q.defer(); var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); - if (sourceNode.nodeType == types.ruleNodeType.INPUT.value) { + if (sourceNode.component.type == types.ruleNodeType.INPUT.value) { var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination); - if (destNode.nodeType == types.ruleNodeType.RULE_CHAIN.value) { + if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) { deferred.reject(); } else { var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}); @@ -183,7 +184,7 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans deferred.resolve(edge); } } else { - ruleChainService.getRuleNodeSupportedLinks(sourceNode.type).then( + ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then( (labels) => { addRuleNodeLink(event, edge, labels).then( (link) => { @@ -209,24 +210,23 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans loadRuleChainLibrary(); function loadRuleChainLibrary() { - ruleChainService.getRuleNodeTypes().then( - (ruleNodeTypes) => { - for (var i=0;i { + for (var i=0;i
- +
{{vm.types.ruleNodeType[typeId].name}}
diff --git a/ui/src/app/rulechain/rulenode-fieldset.tpl.html b/ui/src/app/rulechain/rulenode-fieldset.tpl.html index 2b2faf8257..0d16e45405 100644 --- a/ui/src/app/rulechain/rulenode-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulenode-fieldset.tpl.html @@ -23,9 +23,9 @@
- + -
+
@@ -33,12 +33,17 @@
rulenode.name-required
+ + {{ 'rulenode.debug-mode' | translate }} + + -
+
{{node.icon}}
- {{ node.type }} + {{ node.component.name }} {{ node.name }}
From f8ec713d4a36446ebb383af81ba800f065d1f7d4 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Thu, 22 Mar 2018 10:53:50 +0200 Subject: [PATCH 029/171] update description in Node Annotations --- .../rule/engine/api/EnrichmentNode.java | 11 +++++++++-- .../thingsboard/rule/engine/api/FilterNode.java | 11 +++++++++-- .../rule/engine/api/TransformationNode.java | 8 ++++++++ .../rule/engine/filter/TbJsFilterNode.java | 7 ++++++- .../rule/engine/filter/TbJsSwitchNode.java | 7 ++++++- .../rule/engine/filter/TbMsgTypeFilterNode.java | 5 ++++- .../rule/engine/metadata/TbGetAttributesNode.java | 14 +++++++------- .../metadata/TbGetCustomerAttributeNode.java | 6 +++++- .../engine/metadata/TbGetRelatedAttributeNode.java | 8 +++++++- .../engine/metadata/TbGetTenantAttributeNode.java | 6 +++++- .../engine/transform/TbChangeOriginatorNode.java | 5 ++++- .../rule/engine/transform/TbTransformMsgNode.java | 6 +++++- 12 files changed, 75 insertions(+), 19 deletions(-) diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java index 2267bdae67..b3ff2559eb 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -32,11 +31,19 @@ public @interface EnrichmentNode { String name(); + String nodeDescription(); + + String nodeDetails(); + + boolean inEnabled() default true; + + boolean outEnabled() default true; + ComponentScope scope() default ComponentScope.TENANT; String descriptor() default "EmptyNodeDescriptor.json"; - String[] relationTypes() default {"Success","Failure"}; + String[] relationTypes() default {"Success", "Failure"}; boolean customRelations() default false; } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java index 5247e397dd..d34755dc2c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -32,11 +31,19 @@ public @interface FilterNode { String name(); + String nodeDescription(); + + String nodeDetails(); + + boolean inEnabled() default true; + + boolean outEnabled() default true; + ComponentScope scope() default ComponentScope.TENANT; String descriptor() default "EmptyNodeDescriptor.json"; - String[] relationTypes() default {"Success","Failure"}; + String[] relationTypes() default {"Success", "Failure"}; boolean customRelations() default false; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java index bfe1dcaeda..76b0dda01e 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java @@ -32,6 +32,14 @@ public @interface TransformationNode { String name(); + String nodeDescription(); + + String nodeDetails(); + + boolean inEnabled() default true; + + boolean outEnabled() default true; + ComponentScope scope() default ComponentScope.TENANT; String descriptor() default "EmptyNodeDescriptor.json"; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index d3fee6859e..393496a41e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -26,7 +26,12 @@ import javax.script.Bindings; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j -@FilterNode(name = "Filter Node", relationTypes = {"True", "False", "Failure"}) +@FilterNode(name = "script", relationTypes = {"True", "False", "Failure"}, + nodeDescription = "Filter incoming messages using JS script", + nodeDetails = "Evaluate incoming Message with configured JS condition. " + + "If 'True' - send Message via 'True' chain, otherwise 'False' chain is used." + + "Message payload can be accessed via 'msg' property. For example 'msg.temperature < 10;'" + + "Message metadata can be accessed via 'meta' property. For example 'meta.customerName === 'John';'") public class TbJsFilterNode implements TbNode { private TbJsFilterNodeConfiguration config; 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 5c7d1d10f4..dff214567d 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 @@ -27,7 +27,12 @@ import java.util.Set; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j -@FilterNode(name = "Switch Node", customRelations = true) +@FilterNode(name = "switch", customRelations = true, + nodeDescription = "Route incoming Message to one or multiple output chains", + nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + + "If Array is empty - message not routed to next Node. " + + "Message payload can be accessed via 'msg' property. For example 'msg.temperature < 10;' " + + "Message metadata can be accessed via 'meta' property. For example 'meta.customerName === 'John';' ") public class TbJsSwitchNode implements TbNode { private TbJsSwitchNodeConfiguration config; 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 381512a806..c08c3e0bf5 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 @@ -24,7 +24,10 @@ import org.thingsboard.server.common.msg.TbMsg; * Created by ashvayka on 19.01.18. */ @Slf4j -@FilterNode(name = "Message Type Filter Node") +@FilterNode(name = "message type", + 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.") public class TbMsgTypeFilterNode implements TbNode { TbMsgTypeFilterNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 90eadcb2c7..216cf28015 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -21,12 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.TbNodeUtils; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeState; -import org.thingsboard.rule.engine.api.TbNode; -import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; @@ -40,7 +35,12 @@ import static org.thingsboard.server.common.data.DataConstants.*; * Created by ashvayka on 19.01.18. */ @Slf4j -@EnrichmentNode(name = "Get Attributes Node") +@EnrichmentNode(name = "originator attributes", + nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + + "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + + "'meta.cs.temperature' or 'meta.shared.limit' " + + "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.") public class TbGetAttributesNode implements TbNode { private TbGetAttributesNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index 18ddfcf6b9..c9cabaaabc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -22,7 +22,11 @@ import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; -@EnrichmentNode(name="Get Customer Attributes Node") +@EnrichmentNode(name="customer attributes", + nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + + "To access those attributes in other nodes this template can be used " + + "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 3a0dce85a2..f85d019b04 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -26,7 +26,13 @@ import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; -@EnrichmentNode(name="Get Related Entity Attributes Node") +@EnrichmentNode(name="related attributes", + nodeDescription = "Add Originators Related Entity Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + + "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + + "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + + "To access those attributes in other nodes this template can be used " + + "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { private TbGetRelatedAttrNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index e51c053ca2..2603bc5cc1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -24,7 +24,11 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @Slf4j -@EnrichmentNode(name="Get Tenant Attributes Node") +@EnrichmentNode(name="tenant attributes", + nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + + "To access those attributes in other nodes this template can be used " + + "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { @Override 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 36063b7fc2..8ab8e18abc 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 @@ -32,7 +32,10 @@ import org.thingsboard.server.common.msg.TbMsg; import java.util.HashSet; @Slf4j -@EnrichmentNode(name = "Change Originator Node") +@TransformationNode(name="change originator", + nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", + nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + + "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ") public class TbChangeOriginatorNode extends TbAbstractTransformNode { protected static final String CUSTOMER_SOURCE = "CUSTOMER"; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index c0d20f43c4..3cd7cd1374 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -23,7 +23,11 @@ import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; -@EnrichmentNode(name = "Transformation Node") +@TransformationNode(name = "script", + nodeDescription = "Change Message payload and Metadata using JavaScript", + nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside. " + + "'meta' - is a Message metadata. " + + "'msg' - is a Message payload. Any properties can be changed/removed/added in those objects.") public class TbTransformMsgNode extends TbAbstractTransformNode { private TbTransformMsgNodeConfiguration config; From dff2ca0cb18fe5e03e95fa76dcb9f2ec465fd96f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 22 Mar 2018 11:15:22 +0200 Subject: [PATCH 030/171] Rule Node tooltips --- ui/src/app/rulechain/rulechain.controller.js | 82 +++++++++++++++++++- ui/src/app/rulechain/rulechain.tpl.html | 8 +- ui/src/app/rulechain/rulenode.tpl.html | 5 +- 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 31886b8a28..76d0fec0c5 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -16,6 +16,10 @@ import './rulechain.scss'; +import 'tooltipster/dist/css/tooltipster.bundle.min.css'; +import 'tooltipster/dist/js/tooltipster.bundle.min.js'; +import 'tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css'; + /* eslint-disable import/no-unresolved, import/default */ import addRuleNodeTemplate from './add-rulenode.tpl.html'; @@ -30,7 +34,7 @@ const aKeyCode = 65; const escKeyCode = 27; /*@ngInject*/ -export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpansionPanel, $document, $mdDialog, $filter, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { +export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog, $filter, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { var vm = this; @@ -139,6 +143,82 @@ export function RuleChainController($stateParams, $scope, $q, $mdUtil, $mdExpans vm.editingRuleNodeLink = angular.copy(edge); }; + vm.nodeLibCallbacks = { + nodeCallbacks: { + 'mouseEnter': function (event, node) { + displayNodeDescriptionTooltip(event, node); + }, + 'mouseLeave': function () { + destroyTooltips(); + } + } + }; + + vm.typeHeaderMouseEnter = function(event, typeId) { + displayTooltip(event, + '
' + + '
' + + '
' + typeId + '
' + + '
' + 'Some description of component type' + '
' + + '
' + + '
' + ); + }; + + vm.destroyTooltips = destroyTooltips; + + function destroyTooltips() { + if (vm.tooltipTimeout) { + $timeout.cancel(vm.tooltipTimeout); + vm.tooltipTimeout = null; + } + var instances = angular.element.tooltipster.instances(); + instances.forEach((instance) => { + instance.destroy(); + }); + } + + function displayNodeDescriptionTooltip(event, node) { + displayTooltip(event, + '
' + + '
' + + '
' + node.component.name + '
' + + '
' + 'Some description of node' + '
' + + '
' + + '
' + ); + } + + function displayTooltip(event, content) { + destroyTooltips(); + vm.tooltipTimeout = $timeout(() => { + var element = angular.element(event.target); + element.tooltipster( + { + theme: 'tooltipster-shadow', + delay: 100, + trigger: 'custom', + triggerOpen: { + click: false, + tap: false + }, + triggerClose: { + click: true, + tap: true, + scroll: true + }, + side: 'right', + trackOrigin: true + } + ); + var contentElement = angular.element(content); + $compile(contentElement)($scope); + var tooltip = element.tooltipster('instance'); + tooltip.content(contentElement); + tooltip.open(); + }, 500); + } + vm.editCallbacks = { edgeDoubleClick: function (event, edge) { var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html index 30f2caa91f..9f1141e887 100644 --- a/ui/src/app/rulechain/rulechain.tpl.html +++ b/ui/src/app/rulechain/rulechain.tpl.html @@ -23,12 +23,15 @@
- +
{{vm.types.ruleNodeType[typeId].name}}
- +
{{vm.types.ruleNodeType[typeId].name}}
@@ -36,6 +39,7 @@ diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html index f8e08e0d2c..5a521a8333 100644 --- a/ui/src/app/rulechain/rulenode.tpl.html +++ b/ui/src/app/rulechain/rulenode.tpl.html @@ -18,7 +18,10 @@
+ ng-dblclick="callbacks.doubleClick($event, node)" + ng-mouseover="callbacks.mouseOver($event, node)" + ng-mouseenter="callbacks.mouseEnter($event, node)" + ng-mouseleave="callbacks.mouseLeave($event, node)">
{{node.icon}} From 8f3fbc945dd7c33c1eb1305e499e07725d2c0570 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 22 Mar 2018 13:16:28 +0200 Subject: [PATCH 031/171] RuleNode annotation refactoring --- .../AnnotationComponentDiscoveryService.java | 90 ++++++++++++------- .../rule/engine/api/ActionNode.java | 43 --------- .../rule/engine/api/EnrichmentNode.java | 49 ---------- .../rule/engine/api/NodeDefinition.java | 33 +++++++ .../api/{FilterNode.java => RuleNode.java} | 11 ++- .../rule/engine/api/TransformationNode.java | 51 ----------- .../src/main/resources/EmptyNodeConfig.json | 2 + .../rule/engine/filter/TbJsFilterNode.java | 11 ++- .../rule/engine/filter/TbJsSwitchNode.java | 9 +- .../engine/filter/TbMsgTypeFilterNode.java | 7 +- .../engine/metadata/TbGetAttributesNode.java | 12 +-- .../metadata/TbGetCustomerAttributeNode.java | 9 +- .../metadata/TbGetRelatedAttributeNode.java | 13 ++- .../metadata/TbGetTenantAttributeNode.java | 9 +- .../transform/TbChangeOriginatorNode.java | 5 +- .../engine/transform/TbTransformMsgNode.java | 11 ++- ui/src/app/common/types.constant.js | 15 +++- ui/src/app/rulechain/rulechain.controller.js | 3 +- ui/src/app/rulechain/rulechain.scss | 29 ++++++ 19 files changed, 194 insertions(+), 218 deletions(-) delete mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java delete mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java rename rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/{FilterNode.java => RuleNode.java} (87%) delete mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java create mode 100644 rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index d9416d7226..52d7d7d80c 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -16,6 +16,8 @@ package org.thingsboard.server.service.component; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Charsets; import com.google.common.io.Resources; import lombok.extern.slf4j.Slf4j; @@ -26,16 +28,14 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen import org.springframework.core.env.Environment; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.ActionNode; -import org.thingsboard.rule.engine.api.EnrichmentNode; -import org.thingsboard.rule.engine.api.FilterNode; -import org.thingsboard.rule.engine.api.TransformationNode; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.dao.component.ComponentDescriptorService; import org.thingsboard.server.extensions.api.component.*; import javax.annotation.PostConstruct; +import java.io.IOException; import java.lang.annotation.Annotation; import java.util.*; import java.util.stream.Collectors; @@ -70,6 +70,24 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe } } + private void registerRuleNodeComponents() { + Set ruleNodeBeanDefinitions = getBeanDefinitions(RuleNode.class); + for (BeanDefinition def : ruleNodeBeanDefinitions) { + try { + String clazzName = def.getBeanClassName(); + Class clazz = Class.forName(clazzName); + RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); + ComponentType type = ruleNodeAnnotation.type(); + ComponentDescriptor component = scanAndPersistComponent(def, type); + components.put(component.getClazz(), component); + componentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component); + } catch (Exception e) { + log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e); + throw new RuntimeException(e); + } + } + } + private void registerComponents(ComponentType type, Class annotation) { List components = persist(getBeanDefinitions(annotation), type); componentsMap.put(type, components); @@ -97,34 +115,25 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe String descriptorResourceName; switch (type) { case ENRICHMENT: - EnrichmentNode enrichmentAnnotation = clazz.getAnnotation(EnrichmentNode.class); - scannedComponent.setName(enrichmentAnnotation.name()); - scannedComponent.setScope(enrichmentAnnotation.scope()); - descriptorResourceName = enrichmentAnnotation.descriptor(); - break; case FILTER: - FilterNode filterAnnotation = clazz.getAnnotation(FilterNode.class); - scannedComponent.setName(filterAnnotation.name()); - scannedComponent.setScope(filterAnnotation.scope()); - descriptorResourceName = filterAnnotation.descriptor(); - break; case TRANSFORMATION: - TransformationNode trAnnotation = clazz.getAnnotation(TransformationNode.class); - scannedComponent.setName(trAnnotation.name()); - scannedComponent.setScope(trAnnotation.scope()); - descriptorResourceName = trAnnotation.descriptor(); - break; case ACTION: - ActionNode actionAnnotation = clazz.getAnnotation(ActionNode.class); - scannedComponent.setName(actionAnnotation.name()); - scannedComponent.setScope(actionAnnotation.scope()); - descriptorResourceName = actionAnnotation.descriptor(); + RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); + scannedComponent.setName(ruleNodeAnnotation.name()); + scannedComponent.setScope(ruleNodeAnnotation.scope()); + NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation); + ObjectNode configurationDescriptor = mapper.createObjectNode(); + JsonNode node = mapper.valueToTree(nodeDefinition); + configurationDescriptor.set("nodeDefinition", node); + scannedComponent.setConfigurationDescriptor(configurationDescriptor); break; case OLD_ACTION: Action oldActionAnnotation = clazz.getAnnotation(Action.class); scannedComponent.setName(oldActionAnnotation.name()); scannedComponent.setScope(oldActionAnnotation.scope()); descriptorResourceName = oldActionAnnotation.descriptor(); + scannedComponent.setConfigurationDescriptor(mapper.readTree( + Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8))); break; case PLUGIN: Plugin pluginAnnotation = clazz.getAnnotation(Plugin.class); @@ -143,12 +152,12 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe } } scannedComponent.setActions(Arrays.stream(pluginAnnotation.actions()).map(Class::getName).collect(Collectors.joining(","))); + scannedComponent.setConfigurationDescriptor(mapper.readTree( + Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8))); break; default: throw new RuntimeException(type + " is not supported yet!"); } - scannedComponent.setConfigurationDescriptor(mapper.readTree( - Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8))); scannedComponent.setClazz(clazzName); log.info("Processing scanned component: {}", scannedComponent); } catch (Exception e) { @@ -171,6 +180,20 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return scannedComponent; } + private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws IOException { + NodeDefinition nodeDefinition = new NodeDefinition(); + nodeDefinition.setDetails(nodeAnnotation.nodeDetails()); + nodeDefinition.setDescription(nodeAnnotation.nodeDescription()); + nodeDefinition.setInEnabled(nodeAnnotation.inEnabled()); + nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); + nodeDefinition.setRelationTypes(nodeAnnotation.relationTypes()); + nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); + String defaultConfigResourceName = nodeAnnotation.defaultConfigResource(); + nodeDefinition.setDefaultConfiguration(mapper.readTree( + Resources.toString(Resources.getResource(defaultConfigResourceName), Charsets.UTF_8))); + return nodeDefinition; + } + private Set getBeanDefinitions(Class componentType) { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(componentType)); @@ -183,13 +206,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @Override public void discoverComponents() { - registerComponents(ComponentType.ENRICHMENT, EnrichmentNode.class); - - registerComponents(ComponentType.FILTER, FilterNode.class); - - registerComponents(ComponentType.TRANSFORMATION, TransformationNode.class); - registerComponents(ComponentType.ACTION, ActionNode.class); + registerRuleNodeComponents(); registerComponents(ComponentType.OLD_ACTION, Action.class); @@ -200,15 +218,19 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @Override public List getComponents(ComponentType type) { - return Collections.unmodifiableList(componentsMap.get(type)); + if (componentsMap.containsKey(type)) { + return Collections.unmodifiableList(componentsMap.get(type)); + } else { + return Collections.emptyList(); + } } @Override public List getComponents(Set types) { List result = new ArrayList<>(); - for (ComponentType type : types) { + types.stream().filter(type -> componentsMap.containsKey(type)).forEach(type -> { result.addAll(componentsMap.get(type)); - } + }); return Collections.unmodifiableList(result); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java deleted file mode 100644 index 64e28f1bf2..0000000000 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ActionNode.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.rule.engine.api; - -import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Andrew Shvayka - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface ActionNode { - - String name(); - - ComponentScope scope() default ComponentScope.TENANT; - - String descriptor() default "EmptyNodeDescriptor.json"; - - String[] relationTypes() default {"Success","Failure"}; - - boolean customRelations() default false; - -} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java deleted file mode 100644 index b3ff2559eb..0000000000 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EnrichmentNode.java +++ /dev/null @@ -1,49 +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.rule.engine.api; - -import org.thingsboard.server.common.data.plugin.ComponentScope; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Andrew Shvayka - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface EnrichmentNode { - - String name(); - - String nodeDescription(); - - String nodeDetails(); - - boolean inEnabled() default true; - - boolean outEnabled() default true; - - ComponentScope scope() default ComponentScope.TENANT; - - String descriptor() default "EmptyNodeDescriptor.json"; - - String[] relationTypes() default {"Success", "Failure"}; - - boolean customRelations() default false; -} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java new file mode 100644 index 0000000000..6c57d92a54 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.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.api; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; + +@Data +public class NodeDefinition { + + private String details; + private String description; + private boolean inEnabled; + private boolean outEnabled; + String[] relationTypes; + boolean customRelations; + JsonNode defaultConfiguration; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java similarity index 87% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java index d34755dc2c..f8e0fa2e29 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/FilterNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java @@ -16,18 +16,18 @@ package org.thingsboard.rule.engine.api; import org.thingsboard.server.common.data.plugin.ComponentScope; +import org.thingsboard.server.common.data.plugin.ComponentType; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * @author Andrew Shvayka - */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -public @interface FilterNode { +public @interface RuleNode { + + ComponentType type(); String name(); @@ -41,10 +41,9 @@ public @interface FilterNode { ComponentScope scope() default ComponentScope.TENANT; - String descriptor() default "EmptyNodeDescriptor.json"; + String defaultConfigResource() default "EmptyNodeConfig.json"; String[] relationTypes() default {"Success", "Failure"}; boolean customRelations() default false; - } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java deleted file mode 100644 index 76b0dda01e..0000000000 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TransformationNode.java +++ /dev/null @@ -1,51 +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.rule.engine.api; - -import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Andrew Shvayka - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface TransformationNode { - - String name(); - - String nodeDescription(); - - String nodeDetails(); - - boolean inEnabled() default true; - - boolean outEnabled() default true; - - ComponentScope scope() default ComponentScope.TENANT; - - String descriptor() default "EmptyNodeDescriptor.json"; - - String[] relationTypes() default {"Success","Failure"}; - - boolean customRelations() default false; - -} diff --git a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json new file mode 100644 index 0000000000..7a73a41bfd --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index 393496a41e..c85d4807e0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; @@ -26,12 +27,14 @@ import javax.script.Bindings; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j -@FilterNode(name = "script", relationTypes = {"True", "False", "Failure"}, +@RuleNode( + type = ComponentType.FILTER, + name = "script", relationTypes = {"True", "False", "Failure"}, nodeDescription = "Filter incoming messages using JS script", nodeDetails = "Evaluate incoming Message with configured JS condition. " + - "If 'True' - send Message via 'True' chain, otherwise 'False' chain is used." + - "Message payload can be accessed via 'msg' property. For example 'msg.temperature < 10;'" + - "Message metadata can be accessed via 'meta' property. For example 'meta.customerName === 'John';'") + "If True - send Message via True chain, otherwise False chain is used." + + "Message payload can be accessed via msg property. For example msg.temperature < 10;" + + "Message metadata can be accessed via meta property. For example meta.customerName === 'John';") public class TbJsFilterNode implements TbNode { private TbJsFilterNodeConfiguration config; 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 dff214567d..faf97b4e50 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 @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; @@ -27,12 +28,14 @@ import java.util.Set; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j -@FilterNode(name = "switch", customRelations = true, +@RuleNode( + type = ComponentType.FILTER, + name = "switch", customRelations = true, nodeDescription = "Route incoming Message to one or multiple output chains", nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + "If Array is empty - message not routed to next Node. " + - "Message payload can be accessed via 'msg' property. For example 'msg.temperature < 10;' " + - "Message metadata can be accessed via 'meta' property. For example 'meta.customerName === 'John';' ") + "Message payload can be accessed via msg property. For example msg.temperature < 10; " + + "Message metadata can be accessed via meta property. For example meta.customerName === 'John';") public class TbJsSwitchNode implements TbNode { private TbJsSwitchNodeConfiguration config; 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 c08c3e0bf5..7a6f9fdbee 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 @@ -18,16 +18,19 @@ 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.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; /** * Created by ashvayka on 19.01.18. */ @Slf4j -@FilterNode(name = "message type", +@RuleNode( + type = ComponentType.FILTER, + name = "message type", 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.") + "If incoming MessageType is expected - send Message via Success chain, otherwise Failure chain is used.") public class TbMsgTypeFilterNode implements TbNode { TbMsgTypeFilterNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 216cf28015..6228206da8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -24,6 +24,7 @@ import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import java.util.List; @@ -35,11 +36,12 @@ import static org.thingsboard.server.common.data.DataConstants.*; * Created by ashvayka on 19.01.18. */ @Slf4j -@EnrichmentNode(name = "originator attributes", - nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", - nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + - "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + - "'meta.cs.temperature' or 'meta.shared.limit' " + +@RuleNode(type = ComponentType.ENRICHMENT, + name = "originator attributes", + nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", + nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + + "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + + "meta.cs.temperature or meta.shared.limit " + "If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.") public class TbGetAttributesNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index c9cabaaabc..d85fb56de0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -16,17 +16,20 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.plugin.ComponentType; -@EnrichmentNode(name="customer attributes", +@RuleNode( + type = ComponentType.ENRICHMENT, + name="customer attributes", nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "meta.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index f85d019b04..26b756176c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -17,22 +17,21 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.TbNodeUtils; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbNodeState; -import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.plugin.ComponentType; -@EnrichmentNode(name="related attributes", +@RuleNode( + type = ComponentType.ENRICHMENT, + name="related attributes", nodeDescription = "Add Originators Related Entity Attributes or Latest Telemetry into Message Metadata", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "meta.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { private TbGetRelatedAttrNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index 2603bc5cc1..7d9c50bfc6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -17,18 +17,21 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.rule.engine.api.EnrichmentNode; +import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentType; @Slf4j -@EnrichmentNode(name="tenant attributes", +@RuleNode( + type = ComponentType.ENRICHMENT, + name="tenant attributes", nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + - "'meta.temperature'. If Latest Telemetry enrichment configured, latest telemetry added into metadata") + "meta.temperature. If Latest Telemetry enrichment configured, latest telemetry added into metadata") public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { @Override 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 8ab8e18abc..d237df83ce 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 @@ -27,12 +27,15 @@ import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import java.util.HashSet; @Slf4j -@TransformationNode(name="change originator", +@RuleNode( + type = ComponentType.TRANSFORMATION, + name="change originator", nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ") diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 3cd7cd1374..babdbc3085 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -19,15 +19,18 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.js.NashornJsEngine; +import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.script.Bindings; -@TransformationNode(name = "script", +@RuleNode( + type = ComponentType.TRANSFORMATION, + name = "script", nodeDescription = "Change Message payload and Metadata using JavaScript", - nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside. " + - "'meta' - is a Message metadata. " + - "'msg' - is a Message payload. Any properties can be changed/removed/added in those objects.") + nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.
" + + "meta - is a Message metadata.
" + + "msg - is a Message payload.
Any properties can be changed/removed/added in those objects.") public class TbTransformMsgNode extends TbAbstractTransformNode { private TbTransformMsgNodeConfiguration config; diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 2f8d152029..4dd961a11f 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -460,8 +460,19 @@ export default angular.module('thingsboard.types', []) ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"], ruleChainNodeComponent: { type: 'RULE_CHAIN', - name: 'Rule chain', - clazz: 'tb.internal.RuleChain' + name: 'rule chain', + clazz: 'tb.internal.RuleChain', + configurationDescriptor: { + nodeDefinition: { + description: "Forwards incoming messages to specified Rule Chain", + details: "Forwards incoming messages to specified Rule Chain", + inEnabled: true, + outEnabled: false, + relationTypes: [], + customRelations: false, + defaultConfiguration: {} + } + } }, inputNodeComponent: { type: 'INPUT', diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 76d0fec0c5..973618de6b 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -183,7 +183,8 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, '
' + '
' + '
' + node.component.name + '
' + - '
' + 'Some description of node' + '
' + + '
' + node.component.configurationDescriptor.nodeDefinition.description + '
' + + '
' + node.component.configurationDescriptor.nodeDefinition.details + '
' + '
' + '
' ); diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index 258e232ea4..26a5225a68 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -260,3 +260,32 @@ stroke-dashoffset: 500; } } + +.tb-rule-node-tooltip { + font-size: 14px; + width: 300px; + color: #333; + #tooltip-content { + .tb-node-title { + font-weight: 600; + } + .tb-node-description { + font-style: italic; + color: #555; + } + .tb-node-details { + padding-top: 10px; + padding-bottom: 10px; + } + code { + padding: 0px 3px 2px 3px; + margin: 1px; + color: #AD1625; + white-space: nowrap; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 2px; + font-size: 12px; + } + } +} \ No newline at end of file From 73473e3bebf1b62a26558ce30ba3ab4f9801a3a4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 22 Mar 2018 13:48:46 +0200 Subject: [PATCH 032/171] Rule Chain: Update tooltip messages --- ui/src/app/common/types.constant.js | 7 ++++++- ui/src/app/locale/locale.constant.js | 7 ++++++- ui/src/app/rulechain/rulechain.controller.js | 8 +++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 4dd961a11f..8026115b35 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -464,7 +464,7 @@ export default angular.module('thingsboard.types', []) clazz: 'tb.internal.RuleChain', configurationDescriptor: { nodeDefinition: { - description: "Forwards incoming messages to specified Rule Chain", + description: "", details: "Forwards incoming messages to specified Rule Chain", inEnabled: true, outEnabled: false, @@ -483,30 +483,35 @@ export default angular.module('thingsboard.types', []) FILTER: { value: "FILTER", name: "rulenode.type-filter", + details: "rulenode.type-filter-details", nodeClass: "tb-filter-type", icon: "filter_list" }, ENRICHMENT: { value: "ENRICHMENT", name: "rulenode.type-enrichment", + details: "rulenode.type-enrichment-details", nodeClass: "tb-enrichment-type", icon: "playlist_add" }, TRANSFORMATION: { value: "TRANSFORMATION", name: "rulenode.type-transformation", + details: "rulenode.type-transformation-details", nodeClass: "tb-transformation-type", icon: "transform" }, ACTION: { value: "ACTION", name: "rulenode.type-action", + details: "rulenode.type-action-details", nodeClass: "tb-action-type", icon: "flash_on" }, RULE_CHAIN: { value: "RULE_CHAIN", name: "rulenode.type-rule-chain", + details: "rulenode.type-rule-chain-details", nodeClass: "tb-rule-chain-type", icon: "settings_ethernet" }, diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 18c9330165..360c8282ac 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1186,10 +1186,15 @@ export default angular.module('thingsboard.locale', []) "custom-link-label": "Custom link label", "custom-link-label-required": "Custom link label is required.", "type-filter": "Filter", + "type-filter-details": "Filter incoming messages with configured conditions", "type-enrichment": "Enrichment", + "type-enrichment-details": "Add additional information into Message Metadata", "type-transformation": "Transformation", + "type-transformation-details": "Change Message payload and Metadata", "type-action": "Action", - "type-rule-chain": "Rule Chain" + "type-action-details": "Perform special action", + "type-rule-chain": "Rule Chain", + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain" }, "rule-plugin": { "management": "Rules and plugins management" diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index 973618de6b..d9bbf2fd8d 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -34,7 +34,8 @@ const aKeyCode = 65; const escKeyCode = 27; /*@ngInject*/ -export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog, $filter, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { +export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog, + $filter, $translate, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { var vm = this; @@ -155,11 +156,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }; vm.typeHeaderMouseEnter = function(event, typeId) { + var ruleNodeType = types.ruleNodeType[typeId]; displayTooltip(event, '
' + '
' + - '
' + typeId + '
' + - '
' + 'Some description of component type' + '
' + + '
' + $translate.instant(ruleNodeType.name) + '
' + + '
' + $translate.instant(ruleNodeType.details) + '
' + '
' + '
' ); From adb71ecfe7b2a87e372d207837c769017f49d18a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 23 Mar 2018 10:41:29 +0200 Subject: [PATCH 033/171] Rule Node Configuration --- .../AnnotationComponentDiscoveryService.java | 9 +- .../src/main/resources/thingsboard.yml | 2 +- .../rule/engine/api/NodeConfiguration.java | 22 +++ .../thingsboard/rule/engine/api/RuleNode.java | 5 +- .../src/main/resources/EmptyNodeConfig.json | 2 - .../main/resources/EmptyNodeDescriptor.json | 2 - .../rule/engine/filter/TbJsFilterNode.java | 1 + .../filter/TbJsFilterNodeConfiguration.java | 10 +- .../rule/engine/filter/TbJsSwitchNode.java | 1 + .../filter/TbJsSwitchNodeConfiguration.java | 17 +- .../engine/filter/TbMsgTypeFilterNode.java | 1 + .../TbMsgTypeFilterNodeConfiguration.java | 11 +- .../engine/metadata/TbGetAttributesNode.java | 1 + .../TbGetAttributesNodeConfiguration.java | 13 +- .../metadata/TbGetCustomerAttributeNode.java | 1 + .../TbGetEntityAttrNodeConfiguration.java | 14 +- .../TbGetRelatedAttrNodeConfiguration.java | 19 +- .../metadata/TbGetRelatedAttributeNode.java | 1 + .../metadata/TbGetTenantAttributeNode.java | 1 + .../transform/TbChangeOriginatorNode.java | 1 + .../TbChangeOriginatorNodeConfiguration.java | 14 +- .../engine/transform/TbTransformMsgNode.java | 1 + .../TbTransformMsgNodeConfiguration.java | 11 +- ui/src/app/api/rule-chain.service.js | 25 +-- .../components/json-object-edit.directive.js | 165 ++++++++++++++++++ ui/src/app/components/json-object-edit.scss | 35 ++++ .../app/components/json-object-edit.tpl.html | 34 ++++ ui/src/app/layout/index.js | 4 +- ui/src/app/locale/locale.constant.js | 1 + ui/src/app/rulechain/rulechain.controller.js | 82 ++++----- .../app/rulechain/rulenode-fieldset.tpl.html | 5 + ui/src/app/rulechain/rulenode.directive.js | 2 + ui/src/app/rulechain/rulenode.scss | 22 +++ ui/src/app/rulechain/rulenode.tpl.html | 2 +- ui/src/scss/main.scss | 18 ++ 35 files changed, 483 insertions(+), 72 deletions(-) create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java delete mode 100644 rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json delete mode 100644 rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json create mode 100644 ui/src/app/components/json-object-edit.directive.js create mode 100644 ui/src/app/components/json-object-edit.scss create mode 100644 ui/src/app/components/json-object-edit.tpl.html create mode 100644 ui/src/app/rulechain/rulenode.scss diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index 52d7d7d80c..479f424e93 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -180,7 +180,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe return scannedComponent; } - private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws IOException { + private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws Exception { NodeDefinition nodeDefinition = new NodeDefinition(); nodeDefinition.setDetails(nodeAnnotation.nodeDetails()); nodeDefinition.setDescription(nodeAnnotation.nodeDescription()); @@ -188,9 +188,10 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); nodeDefinition.setRelationTypes(nodeAnnotation.relationTypes()); nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); - String defaultConfigResourceName = nodeAnnotation.defaultConfigResource(); - nodeDefinition.setDefaultConfiguration(mapper.readTree( - Resources.toString(Resources.getResource(defaultConfigResourceName), Charsets.UTF_8))); + Class configClazz = nodeAnnotation.configClazz(); + NodeConfiguration config = configClazz.newInstance(); + NodeConfiguration defaultConfiguration = config.defaultConfiguration(); + nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); return nodeDefinition; } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 27585212db..1df2f1cf31 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -234,7 +234,7 @@ caffeine: specs: relations: timeToLiveInMinutes: 1440 - maxSize: 100000 + maxSize: 0 deviceCredentials: timeToLiveInMinutes: 1440 maxSize: 100000 diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.java new file mode 100644 index 0000000000..5e4c4b5399 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeConfiguration.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.rule.engine.api; + +public interface NodeConfiguration { + + NodeConfiguration defaultConfiguration(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java index f8e0fa2e29..16170347a7 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java @@ -35,15 +35,16 @@ public @interface RuleNode { String nodeDetails(); + Class configClazz(); + boolean inEnabled() default true; boolean outEnabled() default true; ComponentScope scope() default ComponentScope.TENANT; - String defaultConfigResource() default "EmptyNodeConfig.json"; - String[] relationTypes() default {"Success", "Failure"}; boolean customRelations() default false; + } diff --git a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json deleted file mode 100644 index 7a73a41bfd..0000000000 --- a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeConfig.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json b/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json deleted file mode 100644 index 7a73a41bfd..0000000000 --- a/rule-engine/rule-engine-api/src/main/resources/EmptyNodeDescriptor.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index c85d4807e0..07b166db68 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -30,6 +30,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @RuleNode( type = ComponentType.FILTER, name = "script", relationTypes = {"True", "False", "Failure"}, + configClazz = TbJsFilterNodeConfiguration.class, nodeDescription = "Filter incoming messages using JS script", nodeDetails = "Evaluate incoming Message with configured JS condition. " + "If True - send Message via True chain, otherwise False chain is used." + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java index bf543e3276..3b19c7c641 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java @@ -16,9 +16,17 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; @Data -public class TbJsFilterNodeConfiguration { +public class TbJsFilterNodeConfiguration implements NodeConfiguration { private String jsScript; + + @Override + public TbJsFilterNodeConfiguration defaultConfiguration() { + TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); + configuration.setJsScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;"); + return configuration; + } } 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 faf97b4e50..c1236a4653 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 @@ -31,6 +31,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @RuleNode( type = ComponentType.FILTER, name = "switch", customRelations = true, + configClazz = TbJsSwitchNodeConfiguration.class, nodeDescription = "Route incoming Message to one or multiple output chains", nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + "If Array is empty - message not routed to next Node. " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java index 331302d542..b354c7199f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java @@ -15,14 +15,29 @@ */ package org.thingsboard.rule.engine.filter; +import com.google.common.collect.Sets; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; import java.util.Set; @Data -public class TbJsSwitchNodeConfiguration { +public class TbJsSwitchNodeConfiguration implements NodeConfiguration { private String jsScript; private Set allowedRelations; private boolean routeToAllWithNoCheck; + + @Override + public TbJsSwitchNodeConfiguration defaultConfiguration() { + TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); + configuration.setJsScript("function nextRelation(meta, msg) {\n" + + " return ['one','nine'];" + + "};\n" + + "\n" + + "nextRelation(meta, msg);"); + configuration.setAllowedRelations(Sets.newHashSet("one", "two")); + configuration.setRouteToAllWithNoCheck(false); + return configuration; + } } 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 7a6f9fdbee..3a86c25afb 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 @@ -28,6 +28,7 @@ import org.thingsboard.server.common.msg.TbMsg; @RuleNode( type = ComponentType.FILTER, name = "message type", + configClazz = TbMsgTypeFilterNodeConfiguration.class, 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.") diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java index 3b7ba9055e..a2e1b179f0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java @@ -16,15 +16,24 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import java.util.Arrays; +import java.util.Collections; import java.util.List; /** * Created by ashvayka on 19.01.18. */ @Data -public class TbMsgTypeFilterNodeConfiguration { +public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration { private List messageTypes; + @Override + public TbMsgTypeFilterNodeConfiguration defaultConfiguration() { + TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration(); + configuration.setMessageTypes(Arrays.asList("GET_ATTRIBUTES","POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST")); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 6228206da8..69ee9d7c79 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -38,6 +38,7 @@ import static org.thingsboard.server.common.data.DataConstants.*; @Slf4j @RuleNode(type = ComponentType.ENRICHMENT, name = "originator attributes", + configClazz = TbGetAttributesNodeConfiguration.class, nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message metadata " + "with specific prefix: cs/shared/ss. To access those attributes in other nodes this template can be used " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java index ad92314324..103b4de956 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java @@ -16,14 +16,16 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import java.util.Collections; import java.util.List; /** * Created by ashvayka on 19.01.18. */ @Data -public class TbGetAttributesNodeConfiguration { +public class TbGetAttributesNodeConfiguration implements NodeConfiguration { private List clientAttributeNames; private List sharedAttributeNames; @@ -31,4 +33,13 @@ public class TbGetAttributesNodeConfiguration { private List latestTsKeyNames; + @Override + public TbGetAttributesNodeConfiguration defaultConfiguration() { + TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration(); + configuration.setClientAttributeNames(Collections.emptyList()); + configuration.setSharedAttributeNames(Collections.emptyList()); + configuration.setServerAttributeNames(Collections.emptyList()); + configuration.setLatestTsKeyNames(Collections.emptyList()); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index d85fb56de0..cc6d6a1197 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @RuleNode( type = ComponentType.ENRICHMENT, name="customer attributes", + configClazz = TbGetEntityAttrNodeConfiguration.class, nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java index a5e85c57d7..51951150af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java @@ -16,13 +16,25 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @Data -public class TbGetEntityAttrNodeConfiguration { +public class TbGetEntityAttrNodeConfiguration implements NodeConfiguration { private Map attrMapping; private boolean isTelemetry = false; + + @Override + public TbGetEntityAttrNodeConfiguration defaultConfiguration() { + TbGetEntityAttrNodeConfiguration configuration = new TbGetEntityAttrNodeConfiguration(); + Map attrMapping = new HashMap<>(); + attrMapping.putIfAbsent("temperature", "tempo"); + configuration.setAttrMapping(attrMapping); + configuration.setTelemetry(true); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java index ae0b662d48..82119926af 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttrNodeConfiguration.java @@ -16,11 +16,28 @@ package org.thingsboard.rule.engine.metadata; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import java.util.HashMap; +import java.util.Map; + @Data -public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { +public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration { private String relationType; private EntitySearchDirection direction; + + @Override + public TbGetRelatedAttrNodeConfiguration defaultConfiguration() { + TbGetRelatedAttrNodeConfiguration configuration = new TbGetRelatedAttrNodeConfiguration(); + Map attrMapping = new HashMap<>(); + attrMapping.putIfAbsent("temperature", "tempo"); + configuration.setAttrMapping(attrMapping); + configuration.setTelemetry(true); + configuration.setRelationType(EntityRelation.CONTAINS_TYPE); + configuration.setDirection(EntitySearchDirection.FROM); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java index 26b756176c..22c0b9f0a8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @RuleNode( type = ComponentType.ENRICHMENT, name="related attributes", + configClazz = TbGetRelatedAttrNodeConfiguration.class, nodeDescription = "Add Originators Related Entity Attributes or Latest Telemetry into Message Metadata", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java index 7d9c50bfc6..b5f5e02dbe 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @RuleNode( type = ComponentType.ENRICHMENT, name="tenant attributes", + configClazz = TbGetEntityAttrNodeConfiguration.class, nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "To access those attributes in other nodes this template can be used " + 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 d237df83ce..40a647a822 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 @@ -36,6 +36,7 @@ import java.util.HashSet; @RuleNode( type = ComponentType.TRANSFORMATION, name="change originator", + configClazz = TbChangeOriginatorNodeConfiguration.class, nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ") diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java index cf036810c4..3370408231 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java @@ -16,12 +16,24 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @Data -public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration{ +public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { private String originatorSource; private EntitySearchDirection direction; private String relationType; + + @Override + public TbChangeOriginatorNodeConfiguration defaultConfiguration() { + TbChangeOriginatorNodeConfiguration configuration = new TbChangeOriginatorNodeConfiguration(); + configuration.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); + configuration.setDirection(EntitySearchDirection.FROM); + configuration.setRelationType(EntityRelation.CONTAINS_TYPE); + configuration.setStartNewChain(false); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index babdbc3085..626790fcf8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -27,6 +27,7 @@ import javax.script.Bindings; @RuleNode( type = ComponentType.TRANSFORMATION, name = "script", + configClazz = TbTransformMsgNodeConfiguration.class, nodeDescription = "Change Message payload and Metadata using JavaScript", nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.
" + "meta - is a Message metadata.
" + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java index 9cc926b54d..4f9e9eb681 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java @@ -16,9 +16,18 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; @Data -public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration { +public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { private String jsScript; + + @Override + public TbTransformMsgNodeConfiguration defaultConfiguration() { + TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); + configuration.setStartNewChain(false); + configuration.setJsScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' "); + return configuration; + } } diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index f17553545e..ebc48fad61 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -153,16 +153,21 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService) return deferred.promise; } - function getRuleNodeSupportedLinks(nodeType) { //eslint-disable-line - //TODO: - var deferred = $q.defer(); - var linkLabels = [ - { name: 'Success', custom: false }, - { name: 'Fail', custom: false }, - { name: 'Custom', custom: true }, - ]; - deferred.resolve(linkLabels); - return deferred.promise; + function getRuleNodeSupportedLinks(component) { + var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes; + var customRelations = component.configurationDescriptor.nodeDefinition.customRelations; + var linkLabels = []; + for (var i=0;i +
+
+ + + +
+
+
+
+
+
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js index e90334bf52..d397d142c8 100644 --- a/ui/src/app/layout/index.js +++ b/ui/src/app/layout/index.js @@ -29,6 +29,7 @@ import thingsboardNoAnimate from '../components/no-animate.directive'; import thingsboardOnFinishRender from '../components/finish-render.directive'; import thingsboardSideMenu from '../components/side-menu.directive'; import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; +import thingsboardJsonObjectEdit from '../components/json-object-edit.directive'; import thingsboardUserMenu from './user-menu.directive'; @@ -90,7 +91,8 @@ export default angular.module('thingsboard.home', [ thingsboardNoAnimate, thingsboardOnFinishRender, thingsboardSideMenu, - thingsboardDashboardAutocomplete + thingsboardDashboardAutocomplete, + thingsboardJsonObjectEdit ]) .config(HomeRoutes) .controller('HomeController', HomeController) diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 360c8282ac..cca2a11648 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1179,6 +1179,7 @@ export default angular.module('thingsboard.locale', []) "delete": "Delete rule node", "rulenode-details": "Rule node details", "debug-mode": "Debug mode", + "configuration": "Configuration", "link-details": "Rule node link details", "add-link": "Add link", "link-label": "Link label", diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index d9bbf2fd8d..b792f138f8 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -151,6 +151,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }, 'mouseLeave': function () { destroyTooltips(); + }, + 'mouseDown': function () { + destroyTooltips(); } } }; @@ -226,16 +229,12 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, edgeDoubleClick: function (event, edge) { var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); if (sourceNode.component.type != types.ruleNodeType.INPUT.value) { - ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then( - (labels) => { - vm.isEditingRuleNode = false; - vm.editingRuleNode = null; - vm.editingRuleNodeLinkLabels = labels; - vm.isEditingRuleNodeLink = true; - vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); - vm.editingRuleNodeLink = angular.copy(edge); - } - ); + vm.isEditingRuleNode = false; + vm.editingRuleNode = null; + vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); + vm.isEditingRuleNodeLink = true; + vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); + vm.editingRuleNodeLink = angular.copy(edge); } }, nodeCallbacks: { @@ -267,16 +266,10 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, deferred.resolve(edge); } } else { - ruleChainService.getRuleNodeSupportedLinks(sourceNode.component.clazz).then( - (labels) => { - addRuleNodeLink(event, edge, labels).then( - (link) => { - deferred.resolve(link); - }, - () => { - deferred.reject(); - } - ); + var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); + addRuleNodeLink(event, edge, labels).then( + (link) => { + deferred.resolve(link); }, () => { deferred.reject(); @@ -309,24 +302,19 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, y: 10+50*model.nodes.length, connectors: [] }; - if (componentType == types.ruleNodeType.RULE_CHAIN.value) { - node.connectors.push( - { - type: flowchartConstants.leftConnectorType, - id: model.nodes.length - } - ); - } else { + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) { node.connectors.push( { type: flowchartConstants.leftConnectorType, - id: model.nodes.length*2 + id: model.nodes.length * 2 } ); + } + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) { node.connectors.push( { type: flowchartConstants.rightConnectorType, - id: model.nodes.length*2+1 + id: model.nodes.length * 2 + 1 } ); } @@ -398,17 +386,24 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, name: ruleNode.name, nodeClass: vm.types.ruleNodeType[component.type].nodeClass, icon: vm.types.ruleNodeType[component.type].icon, - connectors: [ + connectors: [] + }; + if (component.configurationDescriptor.nodeDefinition.inEnabled) { + node.connectors.push( { type: flowchartConstants.leftConnectorType, id: vm.nextConnectorID++ - }, + } + ); + } + if (component.configurationDescriptor.nodeDefinition.outEnabled) { + node.connectors.push( { type: flowchartConstants.rightConnectorType, id: vm.nextConnectorID++ } - ] - }; + ); + } nodes.push(node); vm.ruleChainModel.nodes.push(node); } @@ -590,6 +585,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, } function addRuleNode($event, ruleNode) { + + ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); + $mdDialog.show({ controller: 'AddRuleNodeController', controllerAs: 'vm', @@ -601,13 +599,15 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }).then(function (ruleNode) { ruleNode.id = vm.nextNodeID++; ruleNode.connectors = []; - ruleNode.connectors.push( - { - id: vm.nextConnectorID++, - type: flowchartConstants.leftConnectorType - } - ); - if (ruleNode.component.type != types.ruleNodeType.RULE_CHAIN.value) { + if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) { + ruleNode.connectors.push( + { + id: vm.nextConnectorID++, + type: flowchartConstants.leftConnectorType + } + ); + } + if (ruleNode.component.configurationDescriptor.nodeDefinition.outEnabled) { ruleNode.connectors.push( { id: vm.nextConnectorID++, diff --git a/ui/src/app/rulechain/rulenode-fieldset.tpl.html b/ui/src/app/rulechain/rulenode-fieldset.tpl.html index 0d16e45405..30cf0752cd 100644 --- a/ui/src/app/rulechain/rulenode-fieldset.tpl.html +++ b/ui/src/app/rulechain/rulenode-fieldset.tpl.html @@ -38,6 +38,11 @@ ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }} + + diff --git a/ui/src/app/rulechain/rulenode.directive.js b/ui/src/app/rulechain/rulenode.directive.js index 998e9981c6..be3e9c36b5 100644 --- a/ui/src/app/rulechain/rulenode.directive.js +++ b/ui/src/app/rulechain/rulenode.directive.js @@ -14,6 +14,8 @@ * limitations under the License. */ +import './rulenode.scss'; + /* eslint-disable import/no-unresolved, import/default */ import ruleNodeFieldsetTemplate from './rulenode-fieldset.tpl.html'; diff --git a/ui/src/app/rulechain/rulenode.scss b/ui/src/app/rulechain/rulenode.scss new file mode 100644 index 0000000000..febc637a37 --- /dev/null +++ b/ui/src/app/rulechain/rulenode.scss @@ -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. + */ + +.tb-rulenode { + tb-json-object-edit.tb-rule-node-configuration-json { + height: 300px; + display: block; + } +} \ No newline at end of file diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html index 5a521a8333..ffc8a0f20d 100644 --- a/ui/src/app/rulechain/rulenode.tpl.html +++ b/ui/src/app/rulechain/rulenode.tpl.html @@ -19,7 +19,7 @@ id="{{node.id}}" ng-attr-style="position: absolute; top: {{ node.y }}px; left: {{ node.x }}px;" ng-dblclick="callbacks.doubleClick($event, node)" - ng-mouseover="callbacks.mouseOver($event, node)" + ng-mousedown="callbacks.mouseDown($event, node)" ng-mouseenter="callbacks.mouseEnter($event, node)" ng-mouseleave="callbacks.mouseLeave($event, node)">
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index 93ff3205c0..6aa662c58c 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -203,6 +203,12 @@ md-sidenav { * THINGSBOARD SPECIFIC ***********************/ +$swift-ease-out-duration: 0.4s !default; +$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; + +$input-label-float-offset: 6px !default; +$input-label-float-scale: 0.75 !default; + label { &.tb-title { pointer-events: none; @@ -213,6 +219,18 @@ label { &.no-padding { padding-bottom: 0px; } + &.tb-required:after { + content: ' *'; + font-size: 13px; + vertical-align: top; + color: rgba(0,0,0,0.54); + } + &.tb-error { + color: rgb(221,44,0); + &.tb-required:after { + color: rgb(221,44,0); + } + } } } From f49d480d30ac5b5ac187b6a7676f19c129616d9c Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 23 Mar 2018 13:35:04 +0200 Subject: [PATCH 034/171] No More Plugins --- .../controller/TelemetryController.java | 350 ++++++++++++++++++ .../server/controller/ValidationCallback.java | 73 ++++ .../service/security/model/SecurityUser.java | 1 + .../thingsboard/server/common/data/User.java | 12 + .../api/plugins/PluginConstants.java | 2 + 5 files changed, 438 insertions(+) create mode 100644 application/src/main/java/org/thingsboard/server/controller/TelemetryController.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/ValidationCallback.java diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java new file mode 100644 index 0000000000..dcc4a5cf54 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -0,0 +1,350 @@ +package org.thingsboard.server.controller; + +import com.google.common.base.Function; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.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.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.actors.plugin.ValidationResult; +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.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.exception.ThingsboardException; +import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity; +import org.thingsboard.server.extensions.api.plugins.PluginConstants; +import org.thingsboard.server.service.security.model.SecurityUser; + +import javax.annotation.Nullable; +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +/** + * Created by ashvayka on 22.03.18. + */ +@RestController +@RequestMapping(PluginConstants.TELEMETRY_URL_PREFIX) +@Slf4j +public class TelemetryController extends BaseController { + + public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!"; + public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!"; + public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!"; + + @Autowired + private AttributesService attributesService; + + @Autowired + private TimeseriesService tsService; + + private ExecutorService executor; + + 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 = "/{entityType}/{entityId}/keys/ATTRIBUTES", method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult getAttributeKeys( + @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException { + DeferredResult response = new DeferredResult(); + EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); + + validate(getCurrentUser(), entityId, new ValidationCallback(response, + new FutureCallback>() { + @Override + public void onSuccess(@Nullable DeferredResult result) { + List>> futures = new ArrayList<>(); + for (String scope : DataConstants.allScopes()) { + futures.add(attributesService.findAll(entityId, scope)); + } + + ListenableFuture> future = Futures.transform(Futures.successfulAsList(futures), + (Function>, ? extends List>) input -> { + List tmp = new ArrayList<>(); + if (input != null) { + input.forEach(tmp::addAll); + } + return tmp; + }, executor); + + Futures.addCallback(future, getAttributeKeysPluginCallback(result)); + } + + @Override + public void onFailure(Throwable t) { + handleError(t, response, HttpStatus.INTERNAL_SERVER_ERROR); + } + })); + + return response; + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/{entityType}/{entityId}/keys/ATTRIBUTES/{scope}", method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult getAttributeKeysByScope() { + return null; + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/{entityType}/{entityId}/values/ATTRIBUTES", method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult getAttributeValues() { + return null; + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/{entityType}/{entityId}/values/ATTRIBUTES", method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult getAttributeValuesByScope() { + return null; + } + + private FutureCallback> getAttributeKeysPluginCallback(final DeferredResult response) { + return new FutureCallback>() { + + @Override + public void onSuccess(List attributes) { + List keys = attributes.stream().map(KvEntry::getKey).collect(Collectors.toList()); + response.setResult(new ResponseEntity<>(keys, HttpStatus.OK)); + } + + @Override + public void onFailure(Throwable e) { + log.error("Failed to fetch attributes", e); + handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR); + } + }; + } + + private void handleError(Throwable e, final DeferredResult response, HttpStatus defaultErrorStatus) { + ResponseEntity responseEntity; + if (e != null && e instanceof ToErrorResponseEntity) { + responseEntity = ((ToErrorResponseEntity) e).toErrorResponseEntity(); + } else if (e != null && e instanceof IllegalArgumentException) { + responseEntity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); + } else { + responseEntity = new ResponseEntity<>(defaultErrorStatus); + } + response.setResult(responseEntity); + } + + private void validate(SecurityUser currentUser, EntityId entityId, ValidationCallback callback) { + switch (entityId.getEntityType()) { + case DEVICE: + validateDevice(currentUser, entityId, callback); + return; +// case ASSET: +// validateAsset(ctx, entityId, callback); +// return; +// case RULE: +// validateRule(ctx, entityId, callback); +// return; +// case RULE_CHAIN: +// validateRuleChain(ctx, entityId, callback); +// return; +// case PLUGIN: +// validatePlugin(ctx, entityId, callback); +// return; +// case CUSTOMER: +// validateCustomer(ctx, entityId, callback); +// return; +// case TENANT: +// validateTenant(ctx, entityId, callback); +// return; + default: + //TODO: add support of other entities + throw new IllegalStateException("Not Implemented!"); + } + } + + private void validateDevice(final SecurityUser currentUser, EntityId entityId, ValidationCallback callback) { + if (currentUser.isSystemAdmin()) { + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); + } else { + ListenableFuture deviceFuture = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId())); + Futures.addCallback(deviceFuture, getCallback(callback, device -> { + if (device == null) { + return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND); + } else { + if (!device.getTenantId().equals(currentUser.getTenantId())) { + return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!"); + } else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) { + return ValidationResult.accessDenied("Device doesn't belong to the current Customer!"); + } else { + return ValidationResult.ok(); + } + } + })); + } + } + + private FutureCallback getCallback(ValidationCallback callback, Function transformer) { + return new FutureCallback() { + @Override + public void onSuccess(@Nullable T result) { + callback.onSuccess(transformer.apply(result)); + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }; + } +// +// private void validateAsset(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isSystemAdmin()) { +// callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture assetFuture = pluginCtx.assetService.findAssetByIdAsync(new AssetId(entityId.getId())); +// Futures.addCallback(assetFuture, getCallback(callback, asset -> { +// if (asset == null) { +// return ValidationResult.entityNotFound("Asset with requested id wasn't found!"); +// } else { +// if (!asset.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!"); +// } else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) { +// return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// private void validateRule(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isCustomerUser()) { +// callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture ruleFuture = pluginCtx.ruleService.findRuleByIdAsync(new RuleId(entityId.getId())); +// Futures.addCallback(ruleFuture, getCallback(callback, rule -> { +// if (rule == null) { +// return ValidationResult.entityNotFound("Rule with requested id wasn't found!"); +// } else { +// if (ctx.isTenantAdmin() && !rule.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Rule doesn't belong to the current Tenant!"); +// } else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) { +// return ValidationResult.accessDenied("Rule is not in system scope!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// private void validateRuleChain(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isCustomerUser()) { +// callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture ruleChainFuture = pluginCtx.ruleChainService.findRuleChainByIdAsync(new RuleChainId(entityId.getId())); +// Futures.addCallback(ruleChainFuture, getCallback(callback, ruleChain -> { +// if (ruleChain == null) { +// return ValidationResult.entityNotFound("Rule chain with requested id wasn't found!"); +// } else { +// if (ctx.isTenantAdmin() && !ruleChain.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!"); +// } else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) { +// return ValidationResult.accessDenied("Rule chain is not in system scope!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// +// private void validatePlugin(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isCustomerUser()) { +// callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture pluginFuture = pluginCtx.pluginService.findPluginByIdAsync(new PluginId(entityId.getId())); +// Futures.addCallback(pluginFuture, getCallback(callback, plugin -> { +// if (plugin == null) { +// return ValidationResult.entityNotFound("Plugin with requested id wasn't found!"); +// } else { +// if (ctx.isTenantAdmin() && !plugin.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Plugin doesn't belong to the current Tenant!"); +// } else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) { +// return ValidationResult.accessDenied("Plugin is not in system scope!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// private void validateCustomer(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isSystemAdmin()) { +// callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else { +// ListenableFuture customerFuture = pluginCtx.customerService.findCustomerByIdAsync(new CustomerId(entityId.getId())); +// Futures.addCallback(customerFuture, getCallback(callback, customer -> { +// if (customer == null) { +// return ValidationResult.entityNotFound("Customer with requested id wasn't found!"); +// } else { +// if (!customer.getTenantId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!"); +// } else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) { +// return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!"); +// } else { +// return ValidationResult.ok(); +// } +// } +// })); +// } +// } +// +// private void validateTenant(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { +// if (ctx.isCustomerUser()) { +// callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); +// } else if (ctx.isSystemAdmin()) { +// callback.onSuccess(this, ValidationResult.ok()); +// } else { +// ListenableFuture tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId())); +// Futures.addCallback(tenantFuture, getCallback(callback, tenant -> { +// if (tenant == null) { +// return ValidationResult.entityNotFound("Tenant with requested id wasn't found!"); +// } else if (!tenant.getId().equals(ctx.getTenantId())) { +// return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!"); +// } else { +// return ValidationResult.ok(); +// } +// })); +// } +// } + + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/ValidationCallback.java b/application/src/main/java/org/thingsboard/server/controller/ValidationCallback.java new file mode 100644 index 0000000000..ead90ea2f2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/ValidationCallback.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.google.common.util.concurrent.FutureCallback; +import org.springframework.http.ResponseEntity; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.actors.plugin.ValidationResult; +import org.thingsboard.server.actors.plugin.ValidationResultCode; +import org.thingsboard.server.extensions.api.exception.AccessDeniedException; +import org.thingsboard.server.extensions.api.exception.EntityNotFoundException; +import org.thingsboard.server.extensions.api.exception.InternalErrorException; +import org.thingsboard.server.extensions.api.exception.UnauthorizedException; + +/** + * Created by ashvayka on 21.02.17. + */ +public class ValidationCallback implements FutureCallback { + + private final DeferredResult response; + private final FutureCallback> action; + + public ValidationCallback(DeferredResult response, FutureCallback> action) { + this.response = response; + this.action = action; + } + + @Override + public void onSuccess(ValidationResult result) { + ValidationResultCode resultCode = result.getResultCode(); + if (resultCode == ValidationResultCode.OK) { + action.onSuccess(response); + } else { + Exception e; + switch (resultCode) { + case ENTITY_NOT_FOUND: + e = new EntityNotFoundException(result.getMessage()); + break; + case UNAUTHORIZED: + e = new UnauthorizedException(result.getMessage()); + break; + case ACCESS_DENIED: + e = new AccessDeniedException(result.getMessage()); + break; + case INTERNAL_ERROR: + e = new InternalErrorException(result.getMessage()); + break; + default: + e = new UnauthorizedException("Permission denied."); + break; + } + onFailure(e); + } + } + + @Override + public void onFailure(Throwable e) { + action.onFailure(e); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java index f4a28a03a6..91ee7bf6d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.model; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UserId; import java.util.Collection; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index 3b957764fc..7b016f540e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; @@ -138,4 +139,15 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H return builder.toString(); } + public boolean isSystemAdmin() { + return tenantId == null || EntityId.NULL_UUID.equals(tenantId.getId()); + } + + public boolean isTenantAdmin() { + return !isSystemAdmin() && (customerId == null || EntityId.NULL_UUID.equals(customerId.getId())); + } + + public boolean isCustomerUser() { + return !isSystemAdmin() && !isTenantAdmin(); + } } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java index ac9600a6a5..d7438f4fbc 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java @@ -19,5 +19,7 @@ package org.thingsboard.server.extensions.api.plugins; * @author Andrew Shvayka */ public class PluginConstants { + public static final String TELEMETRY_URL_PREFIX = "/api/plugins/telemetry"; + public static final String RPC_URL_PREFIX = "/api/plugins/rpc"; public static final String PLUGIN_URL_PREFIX = "/api/plugins"; } From 68a943e2677053358bea9566e4e42a26d4eed217 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 23 Mar 2018 16:40:27 +0200 Subject: [PATCH 035/171] Fix ace editors. --- ui/src/app/components/ace-editor-fix.js | 45 +++++++++++++++++++ ui/src/app/components/js-func.directive.js | 3 ++ .../components/json-object-edit.directive.js | 5 ++- .../components/react/json-form-ace-editor.jsx | 8 ++++ .../widget/widget-config.directive.js | 5 +++ 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 ui/src/app/components/ace-editor-fix.js diff --git a/ui/src/app/components/ace-editor-fix.js b/ui/src/app/components/ace-editor-fix.js new file mode 100644 index 0000000000..f68767e6e3 --- /dev/null +++ b/ui/src/app/components/ace-editor-fix.js @@ -0,0 +1,45 @@ +/* + * 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. + */ + +export default function fixAceEditor(aceEditor) { + aceEditor.$blockScrolling = Infinity; + aceEditor.on("showGutterTooltip", function (tooltip) { + if (!tooltip.isAttachedToBody) { + document.body.appendChild(tooltip.$element); //eslint-disable-line + tooltip.isAttachedToBody = true; + onElementRemoved(tooltip.$parentNode, () => { + if (tooltip.$element.parentNode != null) { + tooltip.$element.parentNode.removeChild(tooltip.$element); + } + }); + } + }); +} + +function onElementRemoved(element, callback) { + if (!document.body.contains(element)) { //eslint-disable-line + callback(); + } else { + var observer; + observer = new MutationObserver(function(mutations) { //eslint-disable-line + if (!document.body.contains(element)) { //eslint-disable-line + callback(); + observer.disconnect(); + } + }); + observer.observe(document.body, {childList: true}); //eslint-disable-line + } +} diff --git a/ui/src/app/components/js-func.directive.js b/ui/src/app/components/js-func.directive.js index f95d003e8a..33cebdebf0 100644 --- a/ui/src/app/components/js-func.directive.js +++ b/ui/src/app/components/js-func.directive.js @@ -22,6 +22,8 @@ import thingsboardToast from '../services/toast'; import thingsboardUtils from '../common/utils.service'; import thingsboardExpandFullscreen from './expand-fullscreen.directive'; +import fixAceEditor from './ace-editor-fix'; + /* eslint-disable import/no-unresolved, import/default */ import jsFuncTemplate from './js-func.tpl.html'; @@ -83,6 +85,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { scope.js_editor.session.on("change", function () { scope.cleanupJsErrors(); }); + fixAceEditor(_ace); } }; diff --git a/ui/src/app/components/json-object-edit.directive.js b/ui/src/app/components/json-object-edit.directive.js index 03ddc07db7..db0aa60bb5 100644 --- a/ui/src/app/components/json-object-edit.directive.js +++ b/ui/src/app/components/json-object-edit.directive.js @@ -19,6 +19,8 @@ import 'brace/ext/language_tools'; import 'brace/mode/json'; import 'ace-builds/src-min-noconflict/snippets/json'; +import fixAceEditor from './ace-editor-fix'; + /* eslint-disable import/no-unresolved, import/default */ import jsonObjectEditTemplate from './json-object-edit.tpl.html'; @@ -30,7 +32,7 @@ export default angular.module('thingsboard.directives.jsonObjectEdit', []) .name; /*@ngInject*/ -function JsonObjectEdit($compile, $templateCache, toast, utils) { +function JsonObjectEdit($compile, $templateCache, $document, toast, utils) { var linker = function (scope, element, attrs, ngModelCtrl) { var template = $templateCache.get(jsonObjectEditTemplate); @@ -67,6 +69,7 @@ function JsonObjectEdit($compile, $templateCache, toast, utils) { scope.json_editor.session.on("change", function () { scope.cleanupJsonErrors(); }); + fixAceEditor(_ace); } }; diff --git a/ui/src/app/components/react/json-form-ace-editor.jsx b/ui/src/app/components/react/json-form-ace-editor.jsx index 1c4c02e16f..5afd3d18f1 100644 --- a/ui/src/app/components/react/json-form-ace-editor.jsx +++ b/ui/src/app/components/react/json-form-ace-editor.jsx @@ -23,6 +23,8 @@ import FlatButton from 'material-ui/FlatButton'; import 'brace/ext/language_tools'; import 'brace/theme/github'; +import fixAceEditor from './../ace-editor-fix'; + class ThingsboardAceEditor extends React.Component { constructor(props) { @@ -31,6 +33,7 @@ class ThingsboardAceEditor extends React.Component { this.onBlur = this.onBlur.bind(this); this.onFocus = this.onFocus.bind(this); this.onTidy = this.onTidy.bind(this); + this.onLoad = this.onLoad.bind(this); var value = props.value ? props.value + '' : ''; this.state = { value: value, @@ -72,6 +75,10 @@ class ThingsboardAceEditor extends React.Component { } } + onLoad(editor) { + fixAceEditor(editor); + } + render() { const styles = reactCSS({ @@ -117,6 +124,7 @@ class ThingsboardAceEditor extends React.Component { onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur} + onLoad={this.onLoad} name={this.props.form.title} value={this.state.value} readOnly={this.props.form.readonly} diff --git a/ui/src/app/components/widget/widget-config.directive.js b/ui/src/app/components/widget/widget-config.directive.js index d0ee6a3d04..4d4d958427 100644 --- a/ui/src/app/components/widget/widget-config.directive.js +++ b/ui/src/app/components/widget/widget-config.directive.js @@ -23,6 +23,8 @@ import thingsboardJsonForm from '../json-form.directive'; import thingsboardManageWidgetActions from './action/manage-widget-actions.directive'; import 'angular-ui-ace'; +import fixAceEditor from './../ace-editor-fix'; + import './widget-config.scss'; /* eslint-disable import/no-unresolved, import/default */ @@ -72,6 +74,9 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout enableSnippets: true, enableBasicAutocompletion: true, enableLiveAutocompletion: true + }, + onLoad: function (_ace) { + fixAceEditor(_ace); } }; From 94039f943b46caa0dc86ab722fbf0006b9c4acab Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 23 Mar 2018 16:56:37 +0200 Subject: [PATCH 036/171] Squashed commit of the following: commit 8b637c9e9456963e1e24ec0e6306903d2e765c1f Author: vparomskiy Date: Fri Mar 23 16:54:48 2018 +0200 add cassandra properties in test suite --- .../server/actors/plugin/PluginActorMessageProcessor.java | 1 + application/src/main/resources/thingsboard.yml | 4 ++-- .../server/dao/timeseries/CassandraBaseTimeseriesDao.java | 3 --- .../thingsboard/server/dao/util/BufferedRateLimiter.java | 8 ++++++-- dao/src/test/resources/cassandra-test.properties | 5 +++++ 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java index 51ca652d6f..f6bf54d349 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java @@ -106,6 +106,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor try { pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg()); } catch (Exception ex) { + logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex); RuleToPluginMsg ruleMsg = msg.getMsg(); MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR; Integer requestId = 0; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ff5824dbfd..fc04a193e1 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -133,7 +133,7 @@ quota: intervalMin: 2 database: - type: "${DATABASE_TYPE:cassandra}" # cassandra OR sql + type: "${DATABASE_TYPE:sql}" # cassandra OR sql # Cassandra driver configuration parameters cassandra: @@ -238,7 +238,7 @@ caffeine: specs: relations: timeToLiveInMinutes: 1440 - maxSize: 0 + maxSize: 100000 deviceCredentials: timeToLiveInMinutes: 1440 maxSize: 100000 diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index cf141711a5..cda4b1669b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -439,8 +439,6 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem private PreparedStatement getLatestStmt() { if (latestInsertStmt == null) { -// latestInsertStmt = new PreparedStatement[DataType.values().length]; -// for (DataType type : DataType.values()) { latestInsertStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_LATEST_CF + "(" + ModelConstants.ENTITY_TYPE_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN + @@ -451,7 +449,6 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem "," + ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.DOUBLE_VALUE_COLUMN + ")" + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); -// } } return latestInsertStmt; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java index de07dbfa47..2acd623a37 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java @@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger; @Component @Slf4j +@NoSqlDao public class BufferedRateLimiter implements AsyncRateLimiter { private final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); @@ -113,6 +114,9 @@ public class BufferedRateLimiter implements AsyncRateLimiter { lockedFuture.cancelFuture(); return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Buffer is full. Reject")); } + if(permits.get() < permitsLimit) { + reprocessQueue(); + } return lockedFuture.future; } catch (InterruptedException e) { return Futures.immediateFailedFuture(new IllegalStateException("Rate Limit Task interrupted. Reject")); @@ -130,8 +134,8 @@ public class BufferedRateLimiter implements AsyncRateLimiter { expiredCount++; } } - log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}]", maxQueueSize.getAndSet(0), - maxGrantedPermissions.getAndSet(0), expiredCount); + log.info("Permits maxBuffer is [{}] max concurrent [{}] expired [{}] current granted [{}]", maxQueueSize.getAndSet(0), + maxGrantedPermissions.getAndSet(0), expiredCount, permits.get()); } private class LockedFuture { diff --git a/dao/src/test/resources/cassandra-test.properties b/dao/src/test/resources/cassandra-test.properties index 82fcbe1949..737687f053 100644 --- a/dao/src/test/resources/cassandra-test.properties +++ b/dao/src/test/resources/cassandra-test.properties @@ -47,3 +47,8 @@ cassandra.query.default_fetch_size=2000 cassandra.query.ts_key_value_partitioning=HOURS cassandra.query.max_limit_per_request=1000 +cassandra.query.buffer_size=100000 +cassandra.query.concurrent_limit=1000 +cassandra.query.permit_max_wait_time=20000 +cassandra.query.rate_limit_print_interval_ms=30000 + From 05f92cbe656478626a83db0cbdb9da2698f91ac1 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 23 Mar 2018 19:29:48 +0200 Subject: [PATCH 037/171] Fixed installation script --- .../service/install/DefaultSystemDataLoaderService.java | 4 ++-- application/src/main/resources/thingsboard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 1ef805f3c9..908faf2315 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 @@ -187,7 +187,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Override public void loadSystemRules() throws Exception { - loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null); +// loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null); } @Override @@ -228,7 +228,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { "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()); +// 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); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ff5824dbfd..c08de4f1b8 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -133,7 +133,7 @@ quota: intervalMin: 2 database: - type: "${DATABASE_TYPE:cassandra}" # cassandra OR sql + type: "${DATABASE_TYPE:sql}" # cassandra OR sql # Cassandra driver configuration parameters cassandra: From 7b1a39fb68f153daf2f48c7aa7198d4deaab95d3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 23 Mar 2018 20:59:26 +0200 Subject: [PATCH 038/171] Rule Chain hover and select improvements. --- ui/src/app/locale/locale.constant.js | 1 + ui/src/app/rulechain/rulechain.controller.js | 11 +++++ ui/src/app/rulechain/rulechain.scss | 51 ++++++++++++++++---- ui/src/app/rulechain/rulechain.tpl.html | 7 +++ ui/src/app/rulechain/rulenode.tpl.html | 1 + 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index cca2a11648..a7b2b443da 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1177,6 +1177,7 @@ export default angular.module('thingsboard.locale', []) "type": "Type", "description": "Description", "delete": "Delete rule node", + "delete-selected-objects": "Delete selected nodes and connections", "rulenode-details": "Rule node details", "debug-mode": "Debug mode", "configuration": "Configuration", diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index b792f138f8..565e72b444 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -81,6 +81,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, vm.saveRuleChain = saveRuleChain; vm.revertRuleChain = revertRuleChain; + vm.objectsSelected = objectsSelected; + vm.deleteSelected = deleteSelected; + vm.keyDown = function (evt) { if (evt.keyCode === ctrlKeyCode) { vm.ctrlDown = true; @@ -632,6 +635,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }); } + function objectsSelected() { + return vm.modelservice.nodes.getSelectedNodes().length > 0 || + vm.modelservice.edges.getSelectedEdges().length > 0 + } + + function deleteSelected() { + vm.modelservice.deleteSelected(); + } } /*@ngInject*/ diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index 26a5225a68..c1d1376d50 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -121,10 +121,6 @@ .fc-node { z-index: 1; outline: none; - &.fc-hover, &.fc-selected { - -webkit-filter: brightness(70%); - filter: brightness(70%); - } &.fc-dragging { z-index: 10; } @@ -132,6 +128,26 @@ padding: 0 15px; text-align: center; } + .fc-node-overlay { + position: absolute; + pointer-events: none; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: #000; + opacity: 0; + } + &.fc-hover { + .fc-node-overlay { + opacity: 0.25; + } + } + &.fc-selected { + .fc-node-overlay { + opacity: 0.25; + } + } } .fc-leftConnectors, .fc-rightConnectors { @@ -181,6 +197,7 @@ stroke: gray; stroke-width: 4; fill: transparent; + transition: stroke-width .2s; &.fc-selected { stroke: red; stroke-width: 4; @@ -232,20 +249,29 @@ .fc-edge-label { position: absolute; user-select: none; - pointer-events: none; + transition: transform .2s; opacity: 0.8; + &.fc-hover { + transform: scale(1.25); + } + &.fc-selected { + .fc-edge-label-text { + span { + border: solid red; + color: red; + } + } + } } .fc-edge-label-text { position: absolute; - left: 50%; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); white-space: nowrap; text-align: center; font-size: 14px; font-weight: 600; - top: 5px; span { border: solid 2px #003a79; border-radius: 10px; @@ -255,6 +281,13 @@ } } +.fc-select-rectangle { + border: 2px dashed #5262ff; + position: absolute; + background: rgba(20,125,255,0.1); + z-index: 2; +} + @keyframes dash { from { stroke-dashoffset: 500; diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html index 9f1141e887..eec376ea42 100644 --- a/ui/src/app/rulechain/rulechain.tpl.html +++ b/ui/src/app/rulechain/rulechain.tpl.html @@ -112,6 +112,13 @@