Commit 102586bed517377f1a7e068185bde05aacce93c6

Authored by Igor Kulikov
2 parents 6ece02f8 c31be45c

Merge with develop/1.5

Showing 137 changed files with 4353 additions and 1744 deletions
... ... @@ -54,6 +54,14 @@
54 54 <artifactId>extensions-api</artifactId>
55 55 </dependency>
56 56 <dependency>
  57 + <groupId>org.thingsboard.rule-engine</groupId>
  58 + <artifactId>rule-engine-api</artifactId>
  59 + </dependency>
  60 + <dependency>
  61 + <groupId>org.thingsboard.rule-engine</groupId>
  62 + <artifactId>rule-engine-components</artifactId>
  63 + </dependency>
  64 + <dependency>
57 65 <groupId>org.thingsboard</groupId>
58 66 <artifactId>extensions-core</artifactId>
59 67 </dependency>
... ...
... ... @@ -69,6 +69,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_chain (
69 69 search_text text,
70 70 first_rule_node_id uuid,
71 71 root boolean,
  72 + debug_mode boolean,
72 73 configuration text,
73 74 additional_info text,
74 75 PRIMARY KEY (id, tenant_id)
... ... @@ -85,6 +86,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
85 86 id uuid,
86 87 type text,
87 88 name text,
  89 + debug_mode boolean,
88 90 search_text text,
89 91 configuration text,
90 92 additional_info text,
... ...
... ... @@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS rule_chain (
21 21 name varchar(255),
22 22 first_rule_node_id varchar(31),
23 23 root boolean,
  24 + debug_mode boolean,
24 25 search_text varchar(255),
25 26 tenant_id varchar(31)
26 27 );
... ... @@ -31,5 +32,6 @@ CREATE TABLE IF NOT EXISTS rule_node (
31 32 configuration varchar(10000000),
32 33 type varchar(255),
33 34 name varchar(255),
  35 + debug_mode boolean,
34 36 search_text varchar(255)
35 37 );
\ No newline at end of file
... ...
... ... @@ -28,12 +28,16 @@ import lombok.Setter;
28 28 import org.springframework.beans.factory.annotation.Autowired;
29 29 import org.springframework.beans.factory.annotation.Value;
30 30 import org.springframework.stereotype.Component;
  31 +import org.springframework.util.Base64Utils;
  32 +import org.thingsboard.rule.engine.api.ListeningExecutor;
  33 +import org.thingsboard.rule.engine.js.JsExecutorService;
31 34 import org.thingsboard.server.actors.service.ActorService;
32 35 import org.thingsboard.server.common.data.DataConstants;
33 36 import org.thingsboard.server.common.data.Event;
34 37 import org.thingsboard.server.common.data.id.EntityId;
35 38 import org.thingsboard.server.common.data.id.TenantId;
36 39 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  40 +import org.thingsboard.server.common.msg.TbMsg;
37 41 import org.thingsboard.server.common.msg.cluster.ServerAddress;
38 42 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
39 43 import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
... ... @@ -50,6 +54,7 @@ import org.thingsboard.server.dao.rule.RuleChainService;
50 54 import org.thingsboard.server.dao.rule.RuleService;
51 55 import org.thingsboard.server.dao.tenant.TenantService;
52 56 import org.thingsboard.server.dao.timeseries.TimeseriesService;
  57 +import org.thingsboard.server.dao.user.UserService;
53 58 import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
54 59 import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
55 60 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
... ... @@ -57,6 +62,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService;
57 62
58 63 import java.io.PrintWriter;
59 64 import java.io.StringWriter;
  65 +import java.nio.charset.StandardCharsets;
60 66 import java.util.Optional;
61 67
62 68 @Component
... ... @@ -65,101 +71,154 @@ public class ActorSystemContext {
65 71
66 72 protected final ObjectMapper mapper = new ObjectMapper();
67 73
68   - @Getter @Setter private ActorService actorService;
  74 + @Getter
  75 + @Setter
  76 + private ActorService actorService;
69 77
70 78 @Autowired
71   - @Getter private DiscoveryService discoveryService;
  79 + @Getter
  80 + private DiscoveryService discoveryService;
72 81
73 82 @Autowired
74   - @Getter @Setter private ComponentDiscoveryService componentService;
  83 + @Getter
  84 + @Setter
  85 + private ComponentDiscoveryService componentService;
75 86
76 87 @Autowired
77   - @Getter private ClusterRoutingService routingService;
  88 + @Getter
  89 + private ClusterRoutingService routingService;
78 90
79 91 @Autowired
80   - @Getter private ClusterRpcService rpcService;
  92 + @Getter
  93 + private ClusterRpcService rpcService;
81 94
82 95 @Autowired
83   - @Getter private DeviceAuthService deviceAuthService;
  96 + @Getter
  97 + private DeviceAuthService deviceAuthService;
84 98
85 99 @Autowired
86   - @Getter private DeviceService deviceService;
  100 + @Getter
  101 + private DeviceService deviceService;
87 102
88 103 @Autowired
89   - @Getter private AssetService assetService;
  104 + @Getter
  105 + private AssetService assetService;
90 106
91 107 @Autowired
92   - @Getter private TenantService tenantService;
  108 + @Getter
  109 + private TenantService tenantService;
93 110
94 111 @Autowired
95   - @Getter private CustomerService customerService;
  112 + @Getter
  113 + private CustomerService customerService;
96 114
97 115 @Autowired
98   - @Getter private RuleService ruleService;
  116 + @Getter
  117 + private UserService userService;
99 118
100 119 @Autowired
101   - @Getter private RuleChainService ruleChainService;
  120 + @Getter
  121 + private RuleService ruleService;
102 122
103 123 @Autowired
104   - @Getter private PluginService pluginService;
  124 + @Getter
  125 + private RuleChainService ruleChainService;
105 126
106 127 @Autowired
107   - @Getter private TimeseriesService tsService;
  128 + @Getter
  129 + private PluginService pluginService;
108 130
109 131 @Autowired
110   - @Getter private AttributesService attributesService;
  132 + @Getter
  133 + private TimeseriesService tsService;
111 134
112 135 @Autowired
113   - @Getter private EventService eventService;
  136 + @Getter
  137 + private AttributesService attributesService;
114 138
115 139 @Autowired
116   - @Getter private AlarmService alarmService;
  140 + @Getter
  141 + private EventService eventService;
117 142
118 143 @Autowired
119   - @Getter private RelationService relationService;
  144 + @Getter
  145 + private AlarmService alarmService;
120 146
121 147 @Autowired
122   - @Getter private AuditLogService auditLogService;
  148 + @Getter
  149 + private RelationService relationService;
123 150
124 151 @Autowired
125   - @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint;
  152 + @Getter
  153 + private AuditLogService auditLogService;
  154 +
  155 + @Autowired
  156 + @Getter
  157 + @Setter
  158 + private PluginWebSocketMsgEndpoint wsMsgEndpoint;
126 159
127 160 @Value("${actors.session.sync.timeout}")
128   - @Getter private long syncSessionTimeout;
  161 + @Getter
  162 + private long syncSessionTimeout;
129 163
130 164 @Value("${actors.plugin.termination.delay}")
131   - @Getter private long pluginActorTerminationDelay;
  165 + @Getter
  166 + private long pluginActorTerminationDelay;
132 167
133 168 @Value("${actors.plugin.processing.timeout}")
134   - @Getter private long pluginProcessingTimeout;
  169 + @Getter
  170 + private long pluginProcessingTimeout;
135 171
136 172 @Value("${actors.plugin.error_persist_frequency}")
137   - @Getter private long pluginErrorPersistFrequency;
  173 + @Getter
  174 + private long pluginErrorPersistFrequency;
  175 +
  176 + @Value("${actors.rule.chain.error_persist_frequency}")
  177 + @Getter
  178 + private long ruleChainErrorPersistFrequency;
  179 +
  180 + @Value("${actors.rule.node.error_persist_frequency}")
  181 + @Getter
  182 + private long ruleNodeErrorPersistFrequency;
138 183
139 184 @Value("${actors.rule.termination.delay}")
140   - @Getter private long ruleActorTerminationDelay;
  185 + @Getter
  186 + private long ruleActorTerminationDelay;
141 187
142 188 @Value("${actors.rule.error_persist_frequency}")
143   - @Getter private long ruleErrorPersistFrequency;
  189 + @Getter
  190 + private long ruleErrorPersistFrequency;
144 191
145 192 @Value("${actors.statistics.enabled}")
146   - @Getter private boolean statisticsEnabled;
  193 + @Getter
  194 + private boolean statisticsEnabled;
147 195
148 196 @Value("${actors.statistics.persist_frequency}")
149   - @Getter private long statisticsPersistFrequency;
  197 + @Getter
  198 + private long statisticsPersistFrequency;
150 199
151 200 @Value("${actors.tenant.create_components_on_init}")
152   - @Getter private boolean tenantComponentsInitEnabled;
  201 + @Getter
  202 + private boolean tenantComponentsInitEnabled;
153 203
154   - @Getter @Setter private ActorSystem actorSystem;
  204 + @Getter
  205 + @Setter
  206 + private ActorSystem actorSystem;
155 207
156   - @Getter @Setter private ActorRef appActor;
  208 + @Getter
  209 + @Setter
  210 + private ActorRef appActor;
157 211
158   - @Getter @Setter private ActorRef sessionManagerActor;
  212 + @Getter
  213 + @Setter
  214 + private ActorRef sessionManagerActor;
159 215
160   - @Getter @Setter private ActorRef statsActor;
  216 + @Getter
  217 + @Setter
  218 + private ActorRef statsActor;
161 219
162   - @Getter private final Config config;
  220 + @Getter
  221 + private final Config config;
163 222
164 223 public ActorSystemContext() {
165 224 config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load());
... ... @@ -191,7 +250,7 @@ public class ActorSystemContext {
191 250 eventService.save(event);
192 251 }
193 252
194   - private String toString(Exception e) {
  253 + private String toString(Throwable e) {
195 254 StringWriter sw = new StringWriter();
196 255 e.printStackTrace(new PrintWriter(sw));
197 256 return sw.toString();
... ... @@ -211,4 +270,69 @@ public class ActorSystemContext {
211 270 private JsonNode toBodyJson(ServerAddress server, String method, String body) {
212 271 return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body);
213 272 }
  273 +
  274 + public String getServerAddress() {
  275 + return discoveryService.getCurrentServer().getServerAddress().toString();
  276 + }
  277 +
  278 + public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) {
  279 + persistDebug(tenantId, entityId, "IN", tbMsg, null);
  280 + }
  281 +
  282 + public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) {
  283 + persistDebug(tenantId, entityId, "IN", tbMsg, error);
  284 + }
  285 +
  286 + public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) {
  287 + persistDebug(tenantId, entityId, "OUT", tbMsg, error);
  288 + }
  289 +
  290 + public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) {
  291 + persistDebug(tenantId, entityId, "OUT", tbMsg, null);
  292 + }
  293 +
  294 + private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, Throwable error) {
  295 + Event event = new Event();
  296 + event.setTenantId(tenantId);
  297 + event.setEntityId(entityId);
  298 + event.setType(DataConstants.DEBUG);
  299 +
  300 + ObjectNode node = mapper.createObjectNode()
  301 + .put("type", type)
  302 + .put("server", getServerAddress())
  303 + .put("entityId", tbMsg.getOriginator().getId().toString())
  304 + .put("entityName", tbMsg.getOriginator().getEntityType().name())
  305 + .put("msgId", tbMsg.getId().toString())
  306 + .put("msgType", tbMsg.getType())
  307 + .put("dataType", tbMsg.getDataType().name());
  308 +
  309 + ObjectNode mdNode = node.putObject("metadata");
  310 + tbMsg.getMetaData().getData().forEach(mdNode::put);
  311 +
  312 + switch (tbMsg.getDataType()) {
  313 + case BINARY:
  314 + node.put("data", Base64Utils.encodeUrlSafe(tbMsg.getData()));
  315 + break;
  316 + default:
  317 + node.put("data", new String(tbMsg.getData(), StandardCharsets.UTF_8));
  318 + break;
  319 + }
  320 +
  321 + if (error != null) {
  322 + node = node.put("error", toString(error));
  323 + }
  324 +
  325 + event.setBody(node);
  326 + eventService.save(event);
  327 + }
  328 +
  329 + public static Exception toException(Throwable error) {
  330 + return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
  331 + }
  332 +
  333 + public ListeningExecutor getExecutor() {
  334 + //TODO: take thread count from yml.
  335 + return new JsExecutorService(1);
  336 + }
  337 +
214 338 }
... ...
... ... @@ -22,48 +22,41 @@ import akka.event.LoggingAdapter;
22 22 import akka.japi.Function;
23 23 import org.thingsboard.server.actors.ActorSystemContext;
24 24 import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
25   -import org.thingsboard.server.actors.service.ContextAwareActor;
  25 +import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
26 26 import org.thingsboard.server.actors.service.ContextBasedCreator;
27 27 import org.thingsboard.server.actors.service.DefaultActorService;
28   -import org.thingsboard.server.actors.shared.plugin.PluginManager;
29 28 import org.thingsboard.server.actors.shared.plugin.SystemPluginManager;
30   -import org.thingsboard.server.actors.shared.rule.RuleManager;
31   -import org.thingsboard.server.actors.shared.rule.SystemRuleManager;
32   -import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
  29 +import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager;
33 30 import org.thingsboard.server.actors.tenant.TenantActor;
34 31 import org.thingsboard.server.common.data.Tenant;
35 32 import org.thingsboard.server.common.data.id.PluginId;
36   -import org.thingsboard.server.common.data.id.RuleId;
  33 +import org.thingsboard.server.common.data.id.RuleChainId;
37 34 import org.thingsboard.server.common.data.id.TenantId;
38 35 import org.thingsboard.server.common.data.page.PageDataIterable;
  36 +import org.thingsboard.server.common.msg.TbActorMsg;
39 37 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
40 38 import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
41 39 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  40 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
42 41 import org.thingsboard.server.dao.model.ModelConstants;
43 42 import org.thingsboard.server.dao.tenant.TenantService;
44 43 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
45 44 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
46   -import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
47 45 import scala.concurrent.duration.Duration;
48 46
49 47 import java.util.HashMap;
50 48 import java.util.Map;
51   -import java.util.Optional;
52 49
53   -public class AppActor extends ContextAwareActor {
  50 +public class AppActor extends RuleChainManagerActor {
54 51
55 52 private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
56 53
57 54 public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
58   - private final RuleManager ruleManager;
59   - private final PluginManager pluginManager;
60 55 private final TenantService tenantService;
61 56 private final Map<TenantId, ActorRef> tenantActors;
62 57
63 58 private AppActor(ActorSystemContext systemContext) {
64   - super(systemContext);
65   - this.ruleManager = new SystemRuleManager(systemContext);
66   - this.pluginManager = new SystemPluginManager(systemContext);
  59 + super(systemContext, new SystemRuleChainManager(systemContext), new SystemPluginManager(systemContext));
67 60 this.tenantService = systemContext.getTenantService();
68 61 this.tenantActors = new HashMap<>();
69 62 }
... ... @@ -77,8 +70,7 @@ public class AppActor extends ContextAwareActor {
77 70 public void preStart() {
78 71 logger.info("Starting main system actor.");
79 72 try {
80   - ruleManager.init(this.context());
81   - pluginManager.init(this.context());
  73 + initRuleChains();
82 74
83 75 if (systemContext.isTenantComponentsInitEnabled()) {
84 76 PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT);
... ... @@ -96,29 +88,51 @@ public class AppActor extends ContextAwareActor {
96 88 }
97 89
98 90 @Override
99   - public void onReceive(Object msg) throws Exception {
100   - logger.debug("Received message: {}", msg);
101   - if (msg instanceof ToDeviceActorMsg) {
102   - processDeviceMsg((ToDeviceActorMsg) msg);
103   - } else if (msg instanceof ToPluginActorMsg) {
104   - onToPluginMsg((ToPluginActorMsg) msg);
105   - } else if (msg instanceof ToRuleActorMsg) {
106   - onToRuleMsg((ToRuleActorMsg) msg);
107   - } else if (msg instanceof ToDeviceActorNotificationMsg) {
108   - onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
109   - } else if (msg instanceof Terminated) {
110   - processTermination((Terminated) msg);
111   - } else if (msg instanceof ClusterEventMsg) {
112   - broadcast(msg);
113   - } else if (msg instanceof ComponentLifecycleMsg) {
114   - onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
115   - } else if (msg instanceof PluginTerminationMsg) {
116   - onPluginTerminated((PluginTerminationMsg) msg);
  91 + protected boolean process(TbActorMsg msg) {
  92 + switch (msg.getMsgType()) {
  93 + case COMPONENT_LIFE_CYCLE_MSG:
  94 + onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  95 + break;
  96 + case SERVICE_TO_RULE_ENGINE_MSG:
  97 + onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
  98 + break;
  99 + default:
  100 + return false;
  101 + }
  102 + return true;
  103 + }
  104 +
  105 + private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
  106 + if (SYSTEM_TENANT.equals(msg.getTenantId())) {
  107 + //TODO: ashvayka handle this.
117 108 } else {
118   - logger.warning("Unknown message: {}!", msg);
  109 + getOrCreateTenantActor(msg.getTenantId()).tell(msg, self());
119 110 }
120 111 }
121 112
  113 +
  114 +// @Override
  115 +// public void onReceive(Object msg) throws Exception {
  116 +// logger.debug("Received message: {}", msg);
  117 +// if (msg instanceof ToDeviceActorMsg) {
  118 +// processDeviceMsg((ToDeviceActorMsg) msg);
  119 +// } else if (msg instanceof ToPluginActorMsg) {
  120 +// onToPluginMsg((ToPluginActorMsg) msg);
  121 +// } else if (msg instanceof ToDeviceActorNotificationMsg) {
  122 +// onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
  123 +// } else if (msg instanceof Terminated) {
  124 +// processTermination((Terminated) msg);
  125 +// } else if (msg instanceof ClusterEventMsg) {
  126 +// broadcast(msg);
  127 +// } else if (msg instanceof ComponentLifecycleMsg) {
  128 +// onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  129 +// } else if (msg instanceof PluginTerminationMsg) {
  130 +// onPluginTerminated((PluginTerminationMsg) msg);
  131 +// } else {
  132 +// logger.warning("Unknown message: {}!", msg);
  133 +// }
  134 +// }
  135 +
122 136 private void onPluginTerminated(PluginTerminationMsg msg) {
123 137 pluginManager.remove(msg.getId());
124 138 }
... ... @@ -128,20 +142,10 @@ public class AppActor extends ContextAwareActor {
128 142 tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
129 143 }
130 144
131   - private void onToRuleMsg(ToRuleActorMsg msg) {
132   - ActorRef target;
133   - if (SYSTEM_TENANT.equals(msg.getTenantId())) {
134   - target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
135   - } else {
136   - target = getOrCreateTenantActor(msg.getTenantId());
137   - }
138   - target.tell(msg, ActorRef.noSender());
139   - }
140   -
141 145 private void onToPluginMsg(ToPluginActorMsg msg) {
142 146 ActorRef target;
143 147 if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) {
144   - target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
  148 + target = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
145 149 } else {
146 150 target = getOrCreateTenantActor(msg.getPluginTenantId());
147 151 }
... ... @@ -149,26 +153,16 @@ public class AppActor extends ContextAwareActor {
149 153 }
150 154
151 155 private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
152   - ActorRef target = null;
  156 + ActorRef target;
153 157 if (SYSTEM_TENANT.equals(msg.getTenantId())) {
154   - Optional<PluginId> pluginId = msg.getPluginId();
155   - Optional<RuleId> ruleId = msg.getRuleId();
156   - if (pluginId.isPresent()) {
157   - target = pluginManager.getOrCreatePluginActor(this.context(), pluginId.get());
158   - } else if (ruleId.isPresent()) {
159   - Optional<ActorRef> ref = ruleManager.update(this.context(), ruleId.get(), msg.getEvent());
160   - if (ref.isPresent()) {
161   - target = ref.get();
162   - } else {
163   - logger.debug("Failed to find actor for rule: [{}]", ruleId);
164   - return;
165   - }
166   - }
  158 + target = getEntityActorRef(msg.getEntityId());
167 159 } else {
168 160 target = getOrCreateTenantActor(msg.getTenantId());
169 161 }
170 162 if (target != null) {
171 163 target.tell(msg, ActorRef.noSender());
  164 + } else {
  165 + logger.debug("Invalid component lifecycle msg: {}", msg);
172 166 }
173 167 }
174 168
... ... @@ -180,7 +174,7 @@ public class AppActor extends ContextAwareActor {
180 174 TenantId tenantId = toDeviceActorMsg.getTenantId();
181 175 ActorRef tenantActor = getOrCreateTenantActor(tenantId);
182 176 if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
183   - tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
  177 +// tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
184 178 } else {
185 179 tenantActor.tell(toDeviceActorMsg, context().self());
186 180 }
... ...
... ... @@ -18,19 +18,19 @@ package org.thingsboard.server.actors.device;
18 18 import akka.event.Logging;
19 19 import akka.event.LoggingAdapter;
20 20 import org.thingsboard.server.actors.ActorSystemContext;
21   -import org.thingsboard.server.actors.rule.RulesProcessedMsg;
22 21 import org.thingsboard.server.actors.service.ContextAwareActor;
23 22 import org.thingsboard.server.actors.service.ContextBasedCreator;
24   -import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
25 23 import org.thingsboard.server.common.data.id.DeviceId;
26 24 import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.msg.TbActorMsg;
27 26 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
28 27 import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
29 28 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
30 29 import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
31 30 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
32 31 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
33   -import org.thingsboard.server.extensions.api.plugins.msg.*;
  32 +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
  33 +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
34 34
35 35 public class DeviceActor extends ContextAwareActor {
36 36
... ... @@ -48,12 +48,17 @@ public class DeviceActor extends ContextAwareActor {
48 48 }
49 49
50 50 @Override
  51 + protected boolean process(TbActorMsg msg) {
  52 + return false;
  53 + }
  54 +
  55 + @Override
51 56 public void onReceive(Object msg) throws Exception {
52   - if (msg instanceof RuleChainDeviceMsg) {
53   - processor.process(context(), (RuleChainDeviceMsg) msg);
54   - } else if (msg instanceof RulesProcessedMsg) {
55   - processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
56   - } else if (msg instanceof ToDeviceActorMsg) {
  57 +// if (msg instanceof RuleChainDeviceMsg) {
  58 +// processor.process(context(), (RuleChainDeviceMsg) msg);
  59 +// } else if (msg instanceof RulesProcessedMsg) {
  60 +// processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
  61 + if (msg instanceof ToDeviceActorMsg) {
57 62 processor.process(context(), (ToDeviceActorMsg) msg);
58 63 } else if (msg instanceof ToDeviceActorNotificationMsg) {
59 64 if (msg instanceof DeviceAttributesEventNotificationMsg) {
... ...
... ... @@ -19,9 +19,7 @@ import akka.actor.ActorContext;
19 19 import akka.actor.ActorRef;
20 20 import akka.event.LoggingAdapter;
21 21 import org.thingsboard.server.actors.ActorSystemContext;
22   -import org.thingsboard.server.actors.rule.*;
23 22 import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
24   -import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
25 23 import org.thingsboard.server.common.data.DataConstants;
26 24 import org.thingsboard.server.common.data.Device;
27 25 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -37,15 +35,10 @@ import org.thingsboard.server.common.msg.session.FromDeviceMsg;
37 35 import org.thingsboard.server.common.msg.session.MsgType;
38 36 import org.thingsboard.server.common.msg.session.SessionType;
39 37 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
40   -import org.thingsboard.server.extensions.api.device.*;
41   -import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
42   -import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
43   -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg;
44   -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
45   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
46   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
47   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
48   -import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
  38 +import org.thingsboard.server.extensions.api.device.DeviceAttributes;
  39 +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
  40 +import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
  41 +import org.thingsboard.server.extensions.api.plugins.msg.*;
49 42
50 43 import java.util.*;
51 44 import java.util.concurrent.ExecutionException;
... ... @@ -230,18 +223,18 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
230 223 }
231 224 }
232 225
233   - void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
234   - ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
235   - srcMsg.getToDeviceActorMsg(), new DeviceMetaData(deviceId, deviceName, deviceType, deviceAttributes), context.self());
236   - ChainProcessingContext ctx = new ChainProcessingContext(md);
237   - if (ctx.getChainLength() > 0) {
238   - RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
239   - ActorRef ruleActorRef = ctx.getCurrentActor();
240   - ruleActorRef.tell(msg, ActorRef.noSender());
241   - } else {
242   - context.self().tell(new RulesProcessedMsg(ctx), context.self());
243   - }
244   - }
  226 +// void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
  227 +// ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
  228 +// srcMsg.getToDeviceActorMsg(), new DeviceMetaData(deviceId, deviceName, deviceType, deviceAttributes), context.self());
  229 +// ChainProcessingContext ctx = new ChainProcessingContext(md);
  230 +// if (ctx.getChainLength() > 0) {
  231 +// RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
  232 +// ActorRef ruleActorRef = ctx.getCurrentActor();
  233 +// ruleActorRef.tell(msg, ActorRef.noSender());
  234 +// } else {
  235 +// context.self().tell(new RulesProcessedMsg(ctx), context.self());
  236 +// }
  237 +// }
245 238
246 239 void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) {
247 240 SessionId sessionId = msg.getSessionId();
... ... @@ -302,18 +295,18 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
302 295 );
303 296 }
304 297
305   - void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
306   - ChainProcessingContext ctx = msg.getCtx();
307   - ToDeviceActorMsg inMsg = ctx.getInMsg();
308   - SessionId sid = inMsg.getSessionId();
309   - ToDeviceSessionActorMsg response;
310   - if (ctx.getResponse() != null) {
311   - response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
312   - } else {
313   - response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
314   - }
315   - sendMsgToSessionActor(response, inMsg.getServerAddress());
316   - }
  298 +// void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
  299 +// ChainProcessingContext ctx = msg.getCtx();
  300 +// ToDeviceActorMsg inMsg = ctx.getInMsg();
  301 +// SessionId sid = inMsg.getSessionId();
  302 +// ToDeviceSessionActorMsg response;
  303 +// if (ctx.getResponse() != null) {
  304 +// response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
  305 +// } else {
  306 +// response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
  307 +// }
  308 +// sendMsgToSessionActor(response, inMsg.getServerAddress());
  309 +// }
317 310
318 311 private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) {
319 312 SessionId sessionId = msg.getSessionId();
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
23 23 import org.thingsboard.server.actors.stats.StatsPersistTick;
24 24 import org.thingsboard.server.common.data.id.PluginId;
25 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.msg.TbActorMsg;
26 27 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
27 28 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
28 29 import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
... ... @@ -41,6 +42,12 @@ public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProc
41 42 }
42 43
43 44 @Override
  45 + protected boolean process(TbActorMsg msg) {
  46 + //TODO Move everything here, to work with TbActorMsg
  47 + return false;
  48 + }
  49 +
  50 + @Override
44 51 public void onReceive(Object msg) throws Exception {
45 52 if (msg instanceof PluginWebsocketMsg) {
46 53 onWebsocketMsg((PluginWebsocketMsg<?>) msg);
... ...
... ... @@ -57,7 +57,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
57 57 }
58 58
59 59 @Override
60   - public void start() throws Exception {
  60 + public void start(ActorContext context) throws Exception {
61 61 logger.info("[{}] Going to start plugin actor.", entityId);
62 62 pluginMd = systemContext.getPluginService().findPluginById(entityId);
63 63 if (pluginMd == null) {
... ... @@ -76,7 +76,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
76 76 }
77 77
78 78 @Override
79   - public void stop() throws Exception {
  79 + public void stop(ActorContext context) throws Exception {
80 80 onStop();
81 81 }
82 82
... ... @@ -191,7 +191,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
191 191 if (pluginImpl != null) {
192 192 pluginImpl.stop(trustedCtx);
193 193 }
194   - start();
  194 + start(context);
195 195 }
196 196 }
197 197
... ... @@ -217,7 +217,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
217 217 pluginImpl.resume(trustedCtx);
218 218 logger.info("[{}] Plugin resumed.", entityId);
219 219 } else {
220   - start();
  220 + start(context);
221 221 }
222 222 }
223 223
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.actors.ActorSystemContext;
23 23 import org.thingsboard.server.actors.service.ContextAwareActor;
24 24 import org.thingsboard.server.actors.service.ContextBasedCreator;
25 25 import org.thingsboard.server.actors.service.DefaultActorService;
  26 +import org.thingsboard.server.common.msg.TbActorMsg;
26 27 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
27 28 import org.thingsboard.server.common.msg.cluster.ServerAddress;
28 29 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
... ... @@ -57,6 +58,12 @@ public class RpcManagerActor extends ContextAwareActor {
57 58 }
58 59
59 60 @Override
  61 + protected boolean process(TbActorMsg msg) {
  62 + //TODO Move everything here, to work with TbActorMsg
  63 + return false;
  64 + }
  65 +
  66 + @Override
60 67 public void onReceive(Object msg) throws Exception {
61 68 if (msg instanceof RpcSessionTellMsg) {
62 69 onMsg((RpcSessionTellMsg) msg);
... ...
... ... @@ -23,6 +23,7 @@ import io.grpc.stub.StreamObserver;
23 23 import org.thingsboard.server.actors.ActorSystemContext;
24 24 import org.thingsboard.server.actors.service.ContextAwareActor;
25 25 import org.thingsboard.server.actors.service.ContextBasedCreator;
  26 +import org.thingsboard.server.common.msg.TbActorMsg;
26 27 import org.thingsboard.server.common.msg.cluster.ServerAddress;
27 28 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
28 29 import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
... ... @@ -48,6 +49,12 @@ public class RpcSessionActor extends ContextAwareActor {
48 49 }
49 50
50 51 @Override
  52 + protected boolean process(TbActorMsg msg) {
  53 + //TODO Move everything here, to work with TbActorMsg
  54 + return false;
  55 + }
  56 +
  57 + @Override
51 58 public void onReceive(Object msg) throws Exception {
52 59 if (msg instanceof RpcSessionTellMsg) {
53 60 tell((RpcSessionTellMsg) msg);
... ...
1   -/**
2   - * Copyright © 2016-2018 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package org.thingsboard.server.actors.rule;
17   -
18   -import akka.actor.ActorRef;
19   -import org.thingsboard.server.common.msg.core.RuleEngineError;
20   -import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg;
21   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
22   -import org.thingsboard.server.common.msg.session.ToDeviceMsg;
23   -import org.thingsboard.server.extensions.api.device.DeviceAttributes;
24   -import org.thingsboard.server.extensions.api.device.DeviceMetaData;
25   -
26   -public class ChainProcessingContext {
27   -
28   - private final ChainProcessingMetaData md;
29   - private final int index;
30   - private final RuleEngineError error;
31   - private ToDeviceMsg response;
32   -
33   -
34   - public ChainProcessingContext(ChainProcessingMetaData md) {
35   - super();
36   - this.md = md;
37   - this.index = 0;
38   - this.error = RuleEngineError.NO_RULES;
39   - }
40   -
41   - private ChainProcessingContext(ChainProcessingContext other, int indexOffset, RuleEngineError error) {
42   - super();
43   - this.md = other.md;
44   - this.index = other.index + indexOffset;
45   - this.error = error;
46   - this.response = other.response;
47   -
48   - if (this.index < 0 || this.index >= this.md.chain.size()) {
49   - throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!");
50   - }
51   - }
52   -
53   - public ActorRef getDeviceActor() {
54   - return md.originator;
55   - }
56   -
57   - public ActorRef getCurrentActor() {
58   - return md.chain.getRuleActorMd(index).getActorRef();
59   - }
60   -
61   - public boolean hasNext() {
62   - return (getChainLength() - 1) > index;
63   - }
64   -
65   - public boolean isFailure() {
66   - return (error != null && error.isCritical()) || (response != null && !response.isSuccess());
67   - }
68   -
69   - public ChainProcessingContext getNext() {
70   - return new ChainProcessingContext(this, 1, this.error);
71   - }
72   -
73   - public ChainProcessingContext withError(RuleEngineError error) {
74   - if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) {
75   - return new ChainProcessingContext(this, 0, error);
76   - } else {
77   - return this;
78   - }
79   - }
80   -
81   - public int getChainLength() {
82   - return md.chain.size();
83   - }
84   -
85   - public ToDeviceActorMsg getInMsg() {
86   - return md.inMsg;
87   - }
88   -
89   - public DeviceMetaData getDeviceMetaData() {
90   - return md.deviceMetaData;
91   - }
92   -
93   - public String getDeviceName() {
94   - return md.deviceMetaData.getDeviceName();
95   - }
96   -
97   - public String getDeviceType() {
98   - return md.deviceMetaData.getDeviceType();
99   - }
100   -
101   - public DeviceAttributes getAttributes() {
102   - return md.deviceMetaData.getDeviceAttributes();
103   - }
104   -
105   - public ToDeviceMsg getResponse() {
106   - return response;
107   - }
108   -
109   - public void mergeResponse(ToDeviceMsg response) {
110   - // TODO add merge logic
111   - this.response = response;
112   - }
113   -
114   - public RuleEngineErrorMsg getError() {
115   - return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error);
116   - }
117   -}
1   -/**
2   - * Copyright © 2016-2018 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package org.thingsboard.server.actors.rule;
17   -
18   -import java.util.*;
19   -
20   -import com.fasterxml.jackson.core.JsonProcessingException;
21   -import org.springframework.util.StringUtils;
22   -import org.thingsboard.server.actors.ActorSystemContext;
23   -import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper;
24   -import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
25   -import org.thingsboard.server.common.data.id.PluginId;
26   -import org.thingsboard.server.common.data.id.RuleId;
27   -import org.thingsboard.server.common.data.id.TenantId;
28   -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
29   -import org.thingsboard.server.common.data.plugin.PluginMetaData;
30   -import org.thingsboard.server.common.data.rule.RuleMetaData;
31   -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
32   -import org.thingsboard.server.common.msg.core.BasicRequest;
33   -import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
34   -import org.thingsboard.server.common.msg.core.RuleEngineError;
35   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
36   -import org.thingsboard.server.common.msg.session.MsgType;
37   -import org.thingsboard.server.common.msg.session.ToDeviceMsg;
38   -import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException;
39   -import org.thingsboard.server.extensions.api.rules.*;
40   -import org.thingsboard.server.extensions.api.plugins.PluginAction;
41   -import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
42   -import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
43   -
44   -import com.fasterxml.jackson.databind.JsonNode;
45   -
46   -import akka.actor.ActorContext;
47   -import akka.actor.ActorRef;
48   -import akka.event.LoggingAdapter;
49   -
50   -class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
51   -
52   - private final RuleProcessingContext ruleCtx;
53   - private final Map<UUID, RuleProcessingMsg> pendingMsgMap;
54   -
55   - private RuleMetaData ruleMd;
56   - private ComponentLifecycleState state;
57   - private List<RuleFilter> filters;
58   - private RuleProcessor processor;
59   - private PluginAction action;
60   -
61   - private TenantId pluginTenantId;
62   - private PluginId pluginId;
63   -
64   - protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) {
65   - super(systemContext, logger, tenantId, ruleId);
66   - this.pendingMsgMap = new HashMap<>();
67   - this.ruleCtx = new RuleProcessingContext(systemContext, ruleId);
68   - }
69   -
70   - @Override
71   - public void start() throws Exception {
72   - logger.info("[{}][{}] Starting rule actor.", entityId, tenantId);
73   - ruleMd = systemContext.getRuleService().findRuleById(entityId);
74   - if (ruleMd == null) {
75   - throw new RuleInitializationException("Rule not found!");
76   - }
77   - state = ruleMd.getState();
78   - if (state == ComponentLifecycleState.ACTIVE) {
79   - logger.info("[{}] Rule is active. Going to initialize rule components.", entityId);
80   - initComponent();
81   - } else {
82   - logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId);
83   - }
84   -
85   - logger.info("[{}][{}] Started rule actor.", entityId, tenantId);
86   - }
87   -
88   - @Override
89   - public void stop() throws Exception {
90   - onStop();
91   - }
92   -
93   -
94   - private void initComponent() throws RuleException {
95   - try {
96   - if (!ruleMd.getFilters().isArray()) {
97   - throw new RuntimeException("Filters are not array!");
98   - }
99   - fetchPluginInfo();
100   - initFilters();
101   - initProcessor();
102   - initAction();
103   - } catch (RuntimeException e) {
104   - throw new RuleInitializationException("Unknown runtime exception!", e);
105   - } catch (InstantiationException e) {
106   - throw new RuleInitializationException("No default constructor for rule implementation!", e);
107   - } catch (IllegalAccessException e) {
108   - throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
109   - } catch (ClassNotFoundException e) {
110   - throw new RuleInitializationException("Rule Class not found!", e);
111   - } catch (Exception e) {
112   - throw new RuleException(e.getMessage(), e);
113   - }
114   - }
115   -
116   - private void initAction() throws Exception {
117   - if (ruleMd.getAction() != null && !ruleMd.getAction().isNull()) {
118   - action = initComponent(ruleMd.getAction());
119   - }
120   - }
121   -
122   - private void initProcessor() throws Exception {
123   - if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) {
124   - processor = initComponent(ruleMd.getProcessor());
125   - }
126   - }
127   -
128   - private void initFilters() throws Exception {
129   - filters = new ArrayList<>(ruleMd.getFilters().size());
130   - for (int i = 0; i < ruleMd.getFilters().size(); i++) {
131   - filters.add(initComponent(ruleMd.getFilters().get(i)));
132   - }
133   - }
134   -
135   - private void fetchPluginInfo() {
136   - if (!StringUtils.isEmpty(ruleMd.getPluginToken())) {
137   - PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
138   - pluginTenantId = pluginMd.getTenantId();
139   - pluginId = pluginMd.getId();
140   - }
141   - }
142   -
143   - protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
144   - if (state != ComponentLifecycleState.ACTIVE) {
145   - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES);
146   - return;
147   - }
148   - ChainProcessingContext chainCtx = msg.getCtx();
149   - ToDeviceActorMsg inMsg = chainCtx.getInMsg();
150   -
151   - ruleCtx.update(inMsg, chainCtx.getDeviceMetaData());
152   -
153   - logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg);
154   - for (RuleFilter filter : filters) {
155   - if (!filter.filter(ruleCtx, inMsg)) {
156   - logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg);
157   - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED);
158   - return;
159   - }
160   - }
161   - RuleProcessingMetaData inMsgMd;
162   - if (processor != null) {
163   - logger.debug("[{}] Going to process in msg: {}", entityId, inMsg);
164   - inMsgMd = processor.process(ruleCtx, inMsg);
165   - } else {
166   - inMsgMd = new RuleProcessingMetaData();
167   - }
168   - logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg);
169   - if (action != null) {
170   - Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);
171   - if (ruleToPluginMsgOptional.isPresent()) {
172   - RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();
173   - logger.debug("[{}] Device msg is converted to: {}", entityId, ruleToPluginMsg);
174   - context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());
175   - if (action.isOneWayAction()) {
176   - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
177   - return;
178   - } else {
179   - pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);
180   - scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout());
181   - return;
182   - }
183   - }
184   - }
185   - logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
186   - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
187   - }
188   -
189   - void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) {
190   - RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid());
191   - if (pendingMsg != null) {
192   - ChainProcessingContext ctx = pendingMsg.getCtx();
193   - Optional<ToDeviceMsg> ruleResponseOptional = action.convert(msg);
194   - if (ruleResponseOptional.isPresent()) {
195   - ctx.mergeResponse(ruleResponseOptional.get());
196   - pushToNextRule(context, ctx, null);
197   - } else {
198   - pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS);
199   - }
200   - } else {
201   - logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid());
202   - }
203   - }
204   -
205   - void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) {
206   - RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId());
207   - if (pendingMsg != null) {
208   - logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg);
209   - ChainProcessingContext ctx = pendingMsg.getCtx();
210   - pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT);
211   - }
212   - }
213   -
214   - private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) {
215   - if (error != null) {
216   - ctx = ctx.withError(error);
217   - }
218   - if (ctx.isFailure()) {
219   - logger.debug("[{}][{}] Forwarding processing chain to device actor due to failure.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
220   - ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
221   - } else if (!ctx.hasNext()) {
222   - logger.debug("[{}][{}] Forwarding processing chain to device actor due to end of chain.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
223   - ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
224   - } else {
225   - logger.debug("[{}][{}] Forwarding processing chain to next rule actor.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
226   - ChainProcessingContext nextTask = ctx.getNext();
227   - nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self());
228   - }
229   - }
230   -
231   - @Override
232   - public void onCreated(ActorContext context) {
233   - logger.info("[{}] Going to process onCreated rule.", entityId);
234   - }
235   -
236   - @Override
237   - public void onUpdate(ActorContext context) throws RuleException {
238   - RuleMetaData oldRuleMd = ruleMd;
239   - ruleMd = systemContext.getRuleService().findRuleById(entityId);
240   - logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd);
241   - try {
242   - fetchPluginInfo();
243   - if (filters == null || !Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) {
244   - logger.info("[{}] Rule filters require restart due to json change from {} to {}.",
245   - entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters()));
246   - stopFilters();
247   - initFilters();
248   - }
249   - if (processor == null || !Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) {
250   - logger.info("[{}] Rule processor require restart due to configuration change.", entityId);
251   - stopProcessor();
252   - initProcessor();
253   - }
254   - if (action == null || !Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) {
255   - logger.info("[{}] Rule action require restart due to configuration change.", entityId);
256   - stopAction();
257   - initAction();
258   - }
259   - } catch (RuntimeException e) {
260   - throw new RuleInitializationException("Unknown runtime exception!", e);
261   - } catch (InstantiationException e) {
262   - throw new RuleInitializationException("No default constructor for rule implementation!", e);
263   - } catch (IllegalAccessException e) {
264   - throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
265   - } catch (ClassNotFoundException e) {
266   - throw new RuleInitializationException("Rule Class not found!", e);
267   - } catch (JsonProcessingException e) {
268   - throw new RuleInitializationException("Rule configuration is invalid!", e);
269   - } catch (Exception e) {
270   - throw new RuleInitializationException(e.getMessage(), e);
271   - }
272   - }
273   -
274   - @Override
275   - public void onActivate(ActorContext context) throws Exception {
276   - logger.info("[{}] Going to process onActivate rule.", entityId);
277   - this.state = ComponentLifecycleState.ACTIVE;
278   - if (filters != null) {
279   - filters.forEach(RuleLifecycleComponent::resume);
280   - if (processor != null) {
281   - processor.resume();
282   - } else {
283   - initProcessor();
284   - }
285   - if (action != null) {
286   - action.resume();
287   - }
288   - logger.info("[{}] Rule resumed.", entityId);
289   - } else {
290   - start();
291   - }
292   - }
293   -
294   - @Override
295   - public void onSuspend(ActorContext context) {
296   - logger.info("[{}] Going to process onSuspend rule.", entityId);
297   - this.state = ComponentLifecycleState.SUSPENDED;
298   - if (filters != null) {
299   - filters.forEach(f -> f.suspend());
300   - }
301   - if (processor != null) {
302   - processor.suspend();
303   - }
304   - if (action != null) {
305   - action.suspend();
306   - }
307   - }
308   -
309   - @Override
310   - public void onStop(ActorContext context) {
311   - logger.info("[{}] Going to process onStop rule.", entityId);
312   - onStop();
313   - scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay());
314   - }
315   -
316   - private void onStop() {
317   - this.state = ComponentLifecycleState.SUSPENDED;
318   - stopFilters();
319   - stopProcessor();
320   - stopAction();
321   - }
322   -
323   - @Override
324   - public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
325   - //Do nothing
326   - }
327   -
328   - private void stopAction() {
329   - if (action != null) {
330   - action.stop();
331   - }
332   - }
333   -
334   - private void stopProcessor() {
335   - if (processor != null) {
336   - processor.stop();
337   - }
338   - }
339   -
340   - private void stopFilters() {
341   - if (filters != null) {
342   - filters.forEach(f -> f.stop());
343   - }
344   - }
345   -}
1   -/**
2   - * Copyright © 2016-2018 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package org.thingsboard.server.actors.rule;
17   -
18   -import java.util.Comparator;
19   -
20   -import org.thingsboard.server.common.data.id.RuleId;
21   -
22   -import akka.actor.ActorRef;
23   -
24   -public class RuleActorMetaData {
25   -
26   - private final RuleId ruleId;
27   - private final boolean systemRule;
28   - private final int weight;
29   - private final ActorRef actorRef;
30   -
31   - public static final Comparator<RuleActorMetaData> RULE_ACTOR_MD_COMPARATOR = new Comparator<RuleActorMetaData>() {
32   -
33   - @Override
34   - public int compare(RuleActorMetaData r1, RuleActorMetaData r2) {
35   - if (r1.isSystemRule() && !r2.isSystemRule()) {
36   - return 1;
37   - } else if (!r1.isSystemRule() && r2.isSystemRule()) {
38   - return -1;
39   - } else {
40   - return Integer.compare(r2.getWeight(), r1.getWeight());
41   - }
42   - }
43   - };
44   -
45   - public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) {
46   - return new RuleActorMetaData(ruleId, true, weight, actorRef);
47   - }
48   -
49   - public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) {
50   - return new RuleActorMetaData(ruleId, false, weight, actorRef);
51   - }
52   -
53   - private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) {
54   - super();
55   - this.ruleId = ruleId;
56   - this.systemRule = systemRule;
57   - this.weight = weight;
58   - this.actorRef = actorRef;
59   - }
60   -
61   - public RuleId getRuleId() {
62   - return ruleId;
63   - }
64   -
65   - public boolean isSystemRule() {
66   - return systemRule;
67   - }
68   -
69   - public int getWeight() {
70   - return weight;
71   - }
72   -
73   - public ActorRef getActorRef() {
74   - return actorRef;
75   - }
76   -
77   - @Override
78   - public int hashCode() {
79   - final int prime = 31;
80   - int result = 1;
81   - result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode());
82   - return result;
83   - }
84   -
85   - @Override
86   - public boolean equals(Object obj) {
87   - if (this == obj)
88   - return true;
89   - if (obj == null)
90   - return false;
91   - if (getClass() != obj.getClass())
92   - return false;
93   - RuleActorMetaData other = (RuleActorMetaData) obj;
94   - if (ruleId == null) {
95   - if (other.ruleId != null)
96   - return false;
97   - } else if (!ruleId.equals(other.ruleId))
98   - return false;
99   - return true;
100   - }
101   -
102   - @Override
103   - public String toString() {
104   - return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]";
105   - }
106   -
107   -}
1   -/**
2   - * Copyright © 2016-2018 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package org.thingsboard.server.actors.rule;
17   -
18   -import com.google.common.util.concurrent.ListenableFuture;
19   -import org.thingsboard.server.actors.ActorSystemContext;
20   -import org.thingsboard.server.common.data.Event;
21   -import org.thingsboard.server.common.data.alarm.Alarm;
22   -import org.thingsboard.server.common.data.alarm.AlarmId;
23   -import org.thingsboard.server.common.data.id.*;
24   -import org.thingsboard.server.dao.alarm.AlarmService;
25   -import org.thingsboard.server.dao.event.EventService;
26   -import org.thingsboard.server.dao.timeseries.TimeseriesService;
27   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
28   -import org.thingsboard.server.extensions.api.device.DeviceMetaData;
29   -import org.thingsboard.server.extensions.api.rules.RuleContext;
30   -
31   -import java.util.Optional;
32   -import java.util.concurrent.ExecutionException;
33   -
34   -public class RuleProcessingContext implements RuleContext {
35   -
36   - private final TimeseriesService tsService;
37   - private final EventService eventService;
38   - private final AlarmService alarmService;
39   - private final RuleId ruleId;
40   - private TenantId tenantId;
41   - private CustomerId customerId;
42   - private DeviceId deviceId;
43   - private DeviceMetaData deviceMetaData;
44   -
45   - RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) {
46   - this.tsService = systemContext.getTsService();
47   - this.eventService = systemContext.getEventService();
48   - this.alarmService = systemContext.getAlarmService();
49   - this.ruleId = ruleId;
50   - }
51   -
52   - void update(ToDeviceActorMsg toDeviceActorMsg, DeviceMetaData deviceMetaData) {
53   - this.tenantId = toDeviceActorMsg.getTenantId();
54   - this.customerId = toDeviceActorMsg.getCustomerId();
55   - this.deviceId = toDeviceActorMsg.getDeviceId();
56   - this.deviceMetaData = deviceMetaData;
57   - }
58   -
59   - @Override
60   - public RuleId getRuleId() {
61   - return ruleId;
62   - }
63   -
64   - @Override
65   - public DeviceMetaData getDeviceMetaData() {
66   - return deviceMetaData;
67   - }
68   -
69   - @Override
70   - public Event save(Event event) {
71   - checkEvent(event);
72   - return eventService.save(event);
73   - }
74   -
75   - @Override
76   - public Optional<Event> saveIfNotExists(Event event) {
77   - checkEvent(event);
78   - return eventService.saveIfNotExists(event);
79   - }
80   -
81   - @Override
82   - public Optional<Event> findEvent(String eventType, String eventUid) {
83   - return eventService.findEvent(tenantId, deviceId, eventType, eventUid);
84   - }
85   -
86   - @Override
87   - public Alarm createOrUpdateAlarm(Alarm alarm) {
88   - alarm.setTenantId(tenantId);
89   - return alarmService.createOrUpdateAlarm(alarm);
90   - }
91   -
92   - public Optional<Alarm> findLatestAlarm(EntityId originator, String alarmType) {
93   - try {
94   - return Optional.ofNullable(alarmService.findLatestByOriginatorAndType(tenantId, originator, alarmType).get());
95   - } catch (InterruptedException | ExecutionException e) {
96   - throw new RuntimeException("Failed to lookup alarm!", e);
97   - }
98   - }
99   -
100   - @Override
101   - public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTs) {
102   - return alarmService.clearAlarm(alarmId, clearTs);
103   - }
104   -
105   - private void checkEvent(Event event) {
106   - if (event.getTenantId() == null) {
107   - event.setTenantId(tenantId);
108   - } else if (!tenantId.equals(event.getTenantId())) {
109   - throw new IllegalArgumentException("Invalid Tenant id!");
110   - }
111   - if (event.getEntityId() == null) {
112   - event.setEntityId(deviceId);
113   - }
114   - }
115   -}
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import org.thingsboard.rule.engine.api.ListeningExecutor;
  19 +import org.thingsboard.rule.engine.api.TbContext;
  20 +import org.thingsboard.server.actors.ActorSystemContext;
  21 +import org.thingsboard.server.common.msg.TbMsg;
  22 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  23 +import org.thingsboard.server.dao.alarm.AlarmService;
  24 +import org.thingsboard.server.dao.asset.AssetService;
  25 +import org.thingsboard.server.dao.attributes.AttributesService;
  26 +import org.thingsboard.server.dao.customer.CustomerService;
  27 +import org.thingsboard.server.dao.device.DeviceService;
  28 +import org.thingsboard.server.dao.plugin.PluginService;
  29 +import org.thingsboard.server.dao.relation.RelationService;
  30 +import org.thingsboard.server.dao.rule.RuleChainService;
  31 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  32 +import org.thingsboard.server.dao.user.UserService;
  33 +
  34 +import java.util.Set;
  35 +
  36 +/**
  37 + * Created by ashvayka on 19.03.18.
  38 + */
  39 +class DefaultTbContext implements TbContext {
  40 +
  41 + private final ActorSystemContext mainCtx;
  42 + private final RuleNodeCtx nodeCtx;
  43 +
  44 + public DefaultTbContext(ActorSystemContext mainCtx, RuleNodeCtx nodeCtx) {
  45 + this.mainCtx = mainCtx;
  46 + this.nodeCtx = nodeCtx;
  47 + }
  48 +
  49 + @Override
  50 + public void tellNext(TbMsg msg) {
  51 + tellNext(msg, (String) null);
  52 + }
  53 +
  54 + @Override
  55 + public void tellNext(TbMsg msg, String relationType) {
  56 + if (nodeCtx.getSelf().isDebugMode()) {
  57 + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg);
  58 + }
  59 + nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor());
  60 + }
  61 +
  62 + @Override
  63 + public void tellSelf(TbMsg msg, long delayMs) {
  64 + throw new RuntimeException("Not Implemented!");
  65 + }
  66 +
  67 + @Override
  68 + public void tellOthers(TbMsg msg) {
  69 + throw new RuntimeException("Not Implemented!");
  70 + }
  71 +
  72 + @Override
  73 + public void tellSibling(TbMsg msg, ServerAddress address) {
  74 + throw new RuntimeException("Not Implemented!");
  75 + }
  76 +
  77 + @Override
  78 + public void spawn(TbMsg msg) {
  79 + throw new RuntimeException("Not Implemented!");
  80 + }
  81 +
  82 + @Override
  83 + public void ack(TbMsg msg) {
  84 +
  85 + }
  86 +
  87 + @Override
  88 + public void tellError(TbMsg msg, Throwable th) {
  89 + if (nodeCtx.getSelf().isDebugMode()) {
  90 + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th);
  91 + }
  92 + nodeCtx.getSelfActor().tell(new RuleNodeToSelfErrorMsg(msg, th), nodeCtx.getSelfActor());
  93 + }
  94 +
  95 + @Override
  96 + public void tellNext(TbMsg msg, Set<String> relationTypes) {
  97 + relationTypes.forEach(type -> tellNext(msg, type));
  98 + }
  99 +
  100 + @Override
  101 + public ListeningExecutor getJsExecutor() {
  102 + return mainCtx.getExecutor();
  103 + }
  104 +
  105 + @Override
  106 + public AttributesService getAttributesService() {
  107 + return mainCtx.getAttributesService();
  108 + }
  109 +
  110 + @Override
  111 + public CustomerService getCustomerService() {
  112 + return mainCtx.getCustomerService();
  113 + }
  114 +
  115 + @Override
  116 + public UserService getUserService() {
  117 + return mainCtx.getUserService();
  118 + }
  119 +
  120 + @Override
  121 + public PluginService getPluginService() {
  122 + return mainCtx.getPluginService();
  123 + }
  124 +
  125 + @Override
  126 + public AssetService getAssetService() {
  127 + return mainCtx.getAssetService();
  128 + }
  129 +
  130 + @Override
  131 + public DeviceService getDeviceService() {
  132 + return mainCtx.getDeviceService();
  133 + }
  134 +
  135 + @Override
  136 + public AlarmService getAlarmService() {
  137 + return mainCtx.getAlarmService();
  138 + }
  139 +
  140 + @Override
  141 + public RuleChainService getRuleChainService() {
  142 + return mainCtx.getRuleChainService();
  143 + }
  144 +
  145 + @Override
  146 + public TimeseriesService getTimeseriesService() {
  147 + return mainCtx.getTsService();
  148 + }
  149 +
  150 + @Override
  151 + public RelationService getRelationService() {
  152 + return mainCtx.getRelationService();
  153 + }
  154 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import akka.actor.OneForOneStrategy;
  19 +import akka.actor.SupervisorStrategy;
  20 +import org.thingsboard.server.actors.ActorSystemContext;
  21 +import org.thingsboard.server.actors.service.ComponentActor;
  22 +import org.thingsboard.server.actors.service.ContextBasedCreator;
  23 +import org.thingsboard.server.common.data.id.RuleChainId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.common.msg.TbActorMsg;
  26 +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  27 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
  28 +import scala.concurrent.duration.Duration;
  29 +
  30 +public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMessageProcessor> {
  31 +
  32 + private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId) {
  33 + super(systemContext, tenantId, ruleChainId);
  34 + setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChainId, systemContext,
  35 + logger, context().parent(), context().self()));
  36 + }
  37 +
  38 + @Override
  39 + protected boolean process(TbActorMsg msg) {
  40 + switch (msg.getMsgType()) {
  41 + case COMPONENT_LIFE_CYCLE_MSG:
  42 + onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  43 + break;
  44 + case SERVICE_TO_RULE_ENGINE_MSG:
  45 + processor.onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
  46 + break;
  47 + case RULE_TO_RULE_CHAIN_TELL_NEXT_MSG:
  48 + processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg);
  49 + break;
  50 + default:
  51 + return false;
  52 + }
  53 + return true;
  54 + }
  55 +
  56 + public static class ActorCreator extends ContextBasedCreator<RuleChainActor> {
  57 + private static final long serialVersionUID = 1L;
  58 +
  59 + private final TenantId tenantId;
  60 + private final RuleChainId ruleChainId;
  61 +
  62 + public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId pluginId) {
  63 + super(context);
  64 + this.tenantId = tenantId;
  65 + this.ruleChainId = pluginId;
  66 + }
  67 +
  68 + @Override
  69 + public RuleChainActor create() throws Exception {
  70 + return new RuleChainActor(context, tenantId, ruleChainId);
  71 + }
  72 + }
  73 +
  74 + @Override
  75 + protected long getErrorPersistFrequency() {
  76 + return systemContext.getRuleChainErrorPersistFrequency();
  77 + }
  78 +
  79 + @Override
  80 + public SupervisorStrategy supervisorStrategy() {
  81 + return strategy;
  82 + }
  83 +
  84 + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> {
  85 + logAndPersist("Unknown Failure", ActorSystemContext.toException(t));
  86 + return SupervisorStrategy.resume();
  87 + });
  88 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import akka.actor.ActorContext;
  19 +import akka.actor.ActorRef;
  20 +import akka.actor.Props;
  21 +import akka.event.LoggingAdapter;
  22 +import org.thingsboard.server.actors.ActorSystemContext;
  23 +import org.thingsboard.server.actors.service.DefaultActorService;
  24 +import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
  25 +import org.thingsboard.server.common.data.EntityType;
  26 +import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.RuleChainId;
  28 +import org.thingsboard.server.common.data.id.RuleNodeId;
  29 +import org.thingsboard.server.common.data.id.TenantId;
  30 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  31 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
  32 +import org.thingsboard.server.common.data.relation.EntityRelation;
  33 +import org.thingsboard.server.common.data.rule.RuleChain;
  34 +import org.thingsboard.server.common.data.rule.RuleNode;
  35 +import org.thingsboard.server.common.msg.TbMsg;
  36 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  37 +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  38 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
  39 +import org.thingsboard.server.dao.rule.RuleChainService;
  40 +
  41 +import java.util.ArrayList;
  42 +import java.util.HashMap;
  43 +import java.util.List;
  44 +import java.util.Map;
  45 +import java.util.Set;
  46 +import java.util.stream.Collectors;
  47 +
  48 +/**
  49 + * @author Andrew Shvayka
  50 + */
  51 +public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
  52 +
  53 + private final ActorRef parent;
  54 + private final ActorRef self;
  55 + private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
  56 + private final Map<RuleNodeId, List<RuleNodeRelation>> nodeRoutes;
  57 + private final RuleChainService service;
  58 +
  59 + private RuleNodeId firstId;
  60 + private RuleNodeCtx firstNode;
  61 +
  62 + RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext
  63 + , LoggingAdapter logger, ActorRef parent, ActorRef self) {
  64 + super(systemContext, logger, tenantId, ruleChainId);
  65 + this.parent = parent;
  66 + this.self = self;
  67 + this.nodeActors = new HashMap<>();
  68 + this.nodeRoutes = new HashMap<>();
  69 + this.service = systemContext.getRuleChainService();
  70 + }
  71 +
  72 + @Override
  73 + public void start(ActorContext context) throws Exception {
  74 + RuleChain ruleChain = service.findRuleChainById(entityId);
  75 + List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
  76 + // Creating and starting the actors;
  77 + for (RuleNode ruleNode : ruleNodeList) {
  78 + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
  79 + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  80 + }
  81 + initRoutes(ruleChain, ruleNodeList);
  82 + }
  83 +
  84 + @Override
  85 + public void onUpdate(ActorContext context) throws Exception {
  86 + RuleChain ruleChain = service.findRuleChainById(entityId);
  87 + List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
  88 +
  89 + for (RuleNode ruleNode : ruleNodeList) {
  90 + RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
  91 + if (existing == null) {
  92 + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
  93 + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  94 + } else {
  95 + existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
  96 + }
  97 + }
  98 +
  99 + Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
  100 + List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
  101 + removedRules.forEach(ruleNodeId -> {
  102 + RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
  103 + removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
  104 + });
  105 +
  106 + initRoutes(ruleChain, ruleNodeList);
  107 + }
  108 +
  109 + @Override
  110 + public void stop(ActorContext context) throws Exception {
  111 + nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop);
  112 + nodeActors.clear();
  113 + nodeRoutes.clear();
  114 + context.stop(self);
  115 + }
  116 +
  117 + @Override
  118 + public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
  119 +
  120 + }
  121 +
  122 + private ActorRef createRuleNodeActor(ActorContext context, RuleNode ruleNode) {
  123 + String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ?
  124 + DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME;
  125 + return context.actorOf(
  126 + Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getId()))
  127 + .withDispatcher(dispatcherName), ruleNode.getId().toString());
  128 + }
  129 +
  130 + private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {
  131 + nodeRoutes.clear();
  132 + // Populating the routes map;
  133 + for (RuleNode ruleNode : ruleNodeList) {
  134 + List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId());
  135 + for (EntityRelation relation : relations) {
  136 + if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
  137 + RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId()));
  138 + if (ruleNodeCtx == null) {
  139 + throw new IllegalArgumentException("Rule Node [" + relation.getFrom() + "] has invalid relation to Rule node [" + relation.getTo() + "]");
  140 + }
  141 + }
  142 + nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
  143 + .add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
  144 + }
  145 + }
  146 +
  147 + firstId = ruleChain.getFirstRuleNodeId();
  148 + firstNode = nodeActors.get(ruleChain.getFirstRuleNodeId());
  149 + state = ComponentLifecycleState.ACTIVE;
  150 + }
  151 +
  152 + void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
  153 + checkActive();
  154 + TbMsg tbMsg = envelope.getTbMsg();
  155 + //TODO: push to queue and act on ack in async way
  156 + pushMstToNode(firstNode, tbMsg);
  157 + }
  158 +
  159 + void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
  160 + checkActive();
  161 + RuleNodeId originator = envelope.getOriginator();
  162 + String targetRelationType = envelope.getRelationType();
  163 + List<RuleNodeRelation> relations = nodeRoutes.get(originator);
  164 + if (relations == null) {
  165 + return;
  166 + }
  167 + boolean copy = relations.size() > 1;
  168 + for (RuleNodeRelation relation : relations) {
  169 + TbMsg msg = envelope.getMsg();
  170 + if (copy) {
  171 + msg = msg.copy();
  172 + }
  173 + if (targetRelationType == null || targetRelationType.equalsIgnoreCase(relation.getType())) {
  174 + switch (relation.getOut().getEntityType()) {
  175 + case RULE_NODE:
  176 + RuleNodeId targetRuleNodeId = new RuleNodeId(relation.getOut().getId());
  177 + RuleNodeCtx targetRuleNode = nodeActors.get(targetRuleNodeId);
  178 + pushMstToNode(targetRuleNode, msg);
  179 + break;
  180 + case RULE_CHAIN:
  181 +// TODO: implement
  182 + break;
  183 + }
  184 + }
  185 + }
  186 + }
  187 +
  188 + private void pushMstToNode(RuleNodeCtx nodeCtx, TbMsg msg) {
  189 + if (nodeCtx != null) {
  190 + nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg), self);
  191 + }
  192 + }
  193 +
  194 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import org.thingsboard.server.actors.ActorSystemContext;
  20 +import org.thingsboard.server.actors.service.ContextAwareActor;
  21 +import org.thingsboard.server.actors.shared.plugin.PluginManager;
  22 +import org.thingsboard.server.actors.shared.rulechain.RuleChainManager;
  23 +import org.thingsboard.server.common.data.id.EntityId;
  24 +import org.thingsboard.server.common.data.id.PluginId;
  25 +import org.thingsboard.server.common.data.id.RuleChainId;
  26 +import org.thingsboard.server.dao.rule.RuleChainService;
  27 +
  28 +/**
  29 + * Created by ashvayka on 15.03.18.
  30 + */
  31 +public abstract class RuleChainManagerActor extends ContextAwareActor {
  32 +
  33 + protected final RuleChainManager ruleChainManager;
  34 + protected final PluginManager pluginManager;
  35 + protected final RuleChainService ruleChainService;
  36 +
  37 + public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager, PluginManager pluginManager) {
  38 + super(systemContext);
  39 + this.ruleChainManager = ruleChainManager;
  40 + this.pluginManager = pluginManager;
  41 + this.ruleChainService = systemContext.getRuleChainService();
  42 + }
  43 +
  44 + protected void initRuleChains() {
  45 + pluginManager.init(this.context());
  46 + ruleChainManager.init(this.context());
  47 + }
  48 +
  49 + protected ActorRef getEntityActorRef(EntityId entityId) {
  50 + ActorRef target = null;
  51 + switch (entityId.getEntityType()) {
  52 + case PLUGIN:
  53 + target = pluginManager.getOrCreateActor(this.context(), (PluginId) entityId);
  54 + break;
  55 + case RULE_CHAIN:
  56 + target = ruleChainManager.getOrCreateActor(this.context(), (RuleChainId) entityId);
  57 + break;
  58 + }
  59 + return target;
  60 + }
  61 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.rule.engine.api.TbContext;
  20 +import org.thingsboard.server.common.msg.MsgType;
  21 +import org.thingsboard.server.common.msg.TbActorMsg;
  22 +import org.thingsboard.server.common.msg.TbMsg;
  23 +
  24 +/**
  25 + * Created by ashvayka on 19.03.18.
  26 + */
  27 +@Data
  28 +final class RuleChainToRuleNodeMsg implements TbActorMsg {
  29 +
  30 + private final TbContext ctx;
  31 + private final TbMsg msg;
  32 +
  33 + @Override
  34 + public MsgType getMsgType() {
  35 + return MsgType.RULE_CHAIN_TO_RULE_MSG;
  36 + }
  37 +}
... ...
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java
... ... @@ -13,78 +13,84 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.server.actors.ruleChain;
17 17
18 18 import org.thingsboard.server.actors.ActorSystemContext;
19 19 import org.thingsboard.server.actors.service.ComponentActor;
20 20 import org.thingsboard.server.actors.service.ContextBasedCreator;
21   -import org.thingsboard.server.actors.stats.StatsPersistTick;
22   -import org.thingsboard.server.common.data.id.RuleId;
  21 +import org.thingsboard.server.common.data.id.RuleChainId;
  22 +import org.thingsboard.server.common.data.id.RuleNodeId;
23 23 import org.thingsboard.server.common.data.id.TenantId;
24   -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  24 +import org.thingsboard.server.common.msg.TbActorMsg;
25 25 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
26   -import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
27 26
28   -public class RuleActor extends ComponentActor<RuleId, RuleActorMessageProcessor> {
  27 +public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessageProcessor> {
29 28
30   - private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) {
31   - super(systemContext, tenantId, ruleId);
32   - setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger));
  29 + private final RuleChainId ruleChainId;
  30 +
  31 + private RuleNodeActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
  32 + super(systemContext, tenantId, ruleNodeId);
  33 + this.ruleChainId = ruleChainId;
  34 + setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext,
  35 + logger, context().parent(), context().self()));
33 36 }
34 37
35 38 @Override
36   - public void onReceive(Object msg) throws Exception {
37   - logger.debug("[{}] Received message: {}", id, msg);
38   - if (msg instanceof RuleProcessingMsg) {
39   - try {
40   - processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg);
41   - increaseMessagesProcessedCount();
42   - } catch (Exception e) {
43   - logAndPersist("onDeviceMsg", e);
44   - }
45   - } else if (msg instanceof PluginToRuleMsg<?>) {
46   - try {
47   - processor.onPluginMsg(context(), (PluginToRuleMsg<?>) msg);
48   - } catch (Exception e) {
49   - logAndPersist("onPluginMsg", e);
50   - }
51   - } else if (msg instanceof ComponentLifecycleMsg) {
52   - onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
53   - } else if (msg instanceof ClusterEventMsg) {
54   - onClusterEventMsg((ClusterEventMsg) msg);
55   - } else if (msg instanceof RuleToPluginTimeoutMsg) {
56   - try {
57   - processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg);
58   - } catch (Exception e) {
59   - logAndPersist("onTimeoutMsg", e);
60   - }
61   - } else if (msg instanceof StatsPersistTick) {
62   - onStatsPersistTick(id);
63   - } else {
64   - logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName());
  39 + protected boolean process(TbActorMsg msg) {
  40 + switch (msg.getMsgType()) {
  41 + case COMPONENT_LIFE_CYCLE_MSG:
  42 + onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  43 + break;
  44 + case RULE_CHAIN_TO_RULE_MSG:
  45 + onRuleChainToRuleNodeMsg((RuleChainToRuleNodeMsg) msg);
  46 + break;
  47 + case RULE_TO_SELF_ERROR_MSG:
  48 + onRuleNodeToSelfErrorMsg((RuleNodeToSelfErrorMsg) msg);
  49 + break;
  50 + default:
  51 + return false;
  52 + }
  53 + return true;
  54 + }
  55 +
  56 + private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) {
  57 + logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
  58 + try {
  59 + processor.onRuleChainToRuleNodeMsg(msg);
  60 + increaseMessagesProcessedCount();
  61 + } catch (Exception e) {
  62 + logAndPersist("onRuleMsg", e);
65 63 }
66 64 }
67 65
68   - public static class ActorCreator extends ContextBasedCreator<RuleActor> {
  66 + private void onRuleNodeToSelfErrorMsg(RuleNodeToSelfErrorMsg msg) {
  67 + logAndPersist("onRuleMsg", ActorSystemContext.toException(msg.getError()));
  68 + }
  69 +
  70 + public static class ActorCreator extends ContextBasedCreator<RuleNodeActor> {
69 71 private static final long serialVersionUID = 1L;
70 72
71 73 private final TenantId tenantId;
72   - private final RuleId ruleId;
  74 + private final RuleChainId ruleChainId;
  75 + private final RuleNodeId ruleNodeId;
73 76
74   - public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) {
  77 + public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
75 78 super(context);
76 79 this.tenantId = tenantId;
77   - this.ruleId = ruleId;
  80 + this.ruleChainId = ruleChainId;
  81 + this.ruleNodeId = ruleNodeId;
  82 +
78 83 }
79 84
80 85 @Override
81   - public RuleActor create() throws Exception {
82   - return new RuleActor(context, tenantId, ruleId);
  86 + public RuleNodeActor create() throws Exception {
  87 + return new RuleNodeActor(context, tenantId, ruleChainId, ruleNodeId);
83 88 }
84 89 }
85 90
86 91 @Override
87 92 protected long getErrorPersistFrequency() {
88   - return systemContext.getRuleErrorPersistFrequency();
  93 + return systemContext.getRuleNodeErrorPersistFrequency();
89 94 }
  95 +
90 96 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import akka.actor.ActorContext;
  19 +import akka.actor.ActorRef;
  20 +import akka.event.LoggingAdapter;
  21 +import org.thingsboard.rule.engine.api.TbNode;
  22 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  23 +import org.thingsboard.rule.engine.api.TbNodeState;
  24 +import org.thingsboard.server.actors.ActorSystemContext;
  25 +import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
  26 +import org.thingsboard.server.common.data.id.RuleChainId;
  27 +import org.thingsboard.server.common.data.id.RuleNodeId;
  28 +import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
  30 +import org.thingsboard.server.common.data.rule.RuleNode;
  31 +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  32 +import org.thingsboard.server.dao.rule.RuleChainService;
  33 +
  34 +/**
  35 + * @author Andrew Shvayka
  36 + */
  37 +public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNodeId> {
  38 +
  39 + private final ActorRef parent;
  40 + private final ActorRef self;
  41 + private final RuleChainService service;
  42 + private RuleNode ruleNode;
  43 + private TbNode tbNode;
  44 +
  45 + RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext
  46 + , LoggingAdapter logger, ActorRef parent, ActorRef self) {
  47 + super(systemContext, logger, tenantId, ruleNodeId);
  48 + this.parent = parent;
  49 + this.self = self;
  50 + this.service = systemContext.getRuleChainService();
  51 + this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(entityId);
  52 + }
  53 +
  54 + @Override
  55 + public void start(ActorContext context) throws Exception {
  56 + tbNode = initComponent(ruleNode);
  57 + state = ComponentLifecycleState.ACTIVE;
  58 + }
  59 +
  60 + @Override
  61 + public void onUpdate(ActorContext context) throws Exception {
  62 + RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(entityId);
  63 + boolean restartRequired = !(ruleNode.getType().equals(newRuleNode.getType())
  64 + && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
  65 + this.ruleNode = newRuleNode;
  66 + if (restartRequired) {
  67 + tbNode.destroy();
  68 + start(context);
  69 + }
  70 + }
  71 +
  72 + @Override
  73 + public void stop(ActorContext context) throws Exception {
  74 + tbNode.destroy();
  75 + context.stop(self);
  76 + }
  77 +
  78 + @Override
  79 + public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
  80 +
  81 + }
  82 +
  83 + void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
  84 + checkActive();
  85 + if (ruleNode.isDebugMode()) {
  86 + systemContext.persistDebugInput(tenantId, entityId, msg.getMsg());
  87 + }
  88 + tbNode.onMsg(msg.getCtx(), msg.getMsg());
  89 + }
  90 +
  91 + private TbNode initComponent(RuleNode ruleNode) throws Exception {
  92 + Class<?> componentClazz = Class.forName(ruleNode.getType());
  93 + TbNode tbNode = (TbNode) (componentClazz.newInstance());
  94 + tbNode.init(new TbNodeConfiguration(ruleNode.getConfiguration()), new TbNodeState());
  95 + return tbNode;
  96 + }
  97 +
  98 +
  99 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.data.rule.RuleNode;
  22 +
  23 +/**
  24 + * Created by ashvayka on 19.03.18.
  25 + */
  26 +@Data
  27 +final class RuleNodeCtx {
  28 + private final TenantId tenantId;
  29 + private final ActorRef chainActor;
  30 + private final ActorRef selfActor;
  31 + private final RuleNode self;
  32 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.EntityId;
  20 +
  21 +/**
  22 + * Created by ashvayka on 19.03.18.
  23 + */
  24 +
  25 +@Data
  26 +final class RuleNodeRelation {
  27 +
  28 + private final EntityId in;
  29 + private final EntityId out;
  30 + private final String type;
  31 +
  32 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
  20 +import org.thingsboard.server.common.msg.MsgType;
  21 +import org.thingsboard.server.common.msg.TbActorMsg;
  22 +import org.thingsboard.server.common.msg.TbMsg;
  23 +
  24 +/**
  25 + * Created by ashvayka on 19.03.18.
  26 + */
  27 +@Data
  28 +final class RuleNodeToRuleChainTellNextMsg implements TbActorMsg {
  29 +
  30 + private final RuleNodeId originator;
  31 + private final String relationType;
  32 + private final TbMsg msg;
  33 +
  34 + @Override
  35 + public MsgType getMsgType() {
  36 + return MsgType.RULE_TO_RULE_CHAIN_TELL_NEXT_MSG;
  37 + }
  38 +
  39 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.ruleChain;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.msg.MsgType;
  20 +import org.thingsboard.server.common.msg.TbActorMsg;
  21 +import org.thingsboard.server.common.msg.TbMsg;
  22 +
  23 +/**
  24 + * Created by ashvayka on 19.03.18.
  25 + */
  26 +@Data
  27 +final class RuleNodeToSelfErrorMsg implements TbActorMsg {
  28 +
  29 + private final TbMsg msg;
  30 + private final Throwable error;
  31 +
  32 + @Override
  33 + public MsgType getMsgType() {
  34 + return MsgType.RULE_TO_SELF_ERROR_MSG;
  35 + }
  36 +
  37 +}
... ...
... ... @@ -15,20 +15,19 @@
15 15 */
16 16 package org.thingsboard.server.actors.service;
17 17
18   -import org.thingsboard.server.common.data.id.DeviceId;
19   -import org.thingsboard.server.common.data.id.PluginId;
20   -import org.thingsboard.server.common.data.id.RuleId;
21   -import org.thingsboard.server.common.data.id.TenantId;
  18 +import org.thingsboard.server.common.data.id.*;
22 19 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  20 +import org.thingsboard.server.common.msg.TbMsg;
  21 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
23 22 import org.thingsboard.server.common.transport.SessionMsgProcessor;
24 23 import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
25 24 import org.thingsboard.server.service.cluster.rpc.RpcMsgListener;
26 25
27 26 public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor, RestMsgProcessor, RpcMsgListener, DiscoveryServiceListener {
28 27
29   - void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state);
  28 + void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state);
30 29
31   - void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state);
  30 + void onMsg(ServiceToRuleEngineMsg msg);
32 31
33 32 void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId);
34 33
... ...
... ... @@ -54,7 +54,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
54 54 @Override
55 55 public void preStart() {
56 56 try {
57   - processor.start();
  57 + processor.start(context());
58 58 logLifecycleEvent(ComponentLifecycleEvent.STARTED);
59 59 if (systemContext.isStatisticsEnabled()) {
60 60 scheduleStatsPersistTick();
... ... @@ -78,7 +78,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
78 78 @Override
79 79 public void postStop() {
80 80 try {
81   - processor.stop();
  81 + processor.stop(context());
82 82 logLifecycleEvent(ComponentLifecycleEvent.STOPPED);
83 83 } catch (Exception e) {
84 84 logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
... ... @@ -141,7 +141,6 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
141 141 messagesProcessed++;
142 142 }
143 143
144   -
145 144 protected void logAndPersist(String method, Exception e) {
146 145 logAndPersist(method, e, false);
147 146 }
... ...
... ... @@ -16,9 +16,13 @@
16 16 package org.thingsboard.server.actors.service;
17 17
18 18 import akka.actor.UntypedActor;
  19 +import akka.event.Logging;
  20 +import akka.event.LoggingAdapter;
19 21 import org.thingsboard.server.actors.ActorSystemContext;
  22 +import org.thingsboard.server.common.msg.TbActorMsg;
20 23
21 24 public abstract class ContextAwareActor extends UntypedActor {
  25 + protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
22 26
23 27 public static final int ENTITY_PACK_LIMIT = 1024;
24 28
... ... @@ -28,4 +32,20 @@ public abstract class ContextAwareActor extends UntypedActor {
28 32 super();
29 33 this.systemContext = systemContext;
30 34 }
  35 +
  36 + @Override
  37 + public void onReceive(Object msg) throws Exception {
  38 + if (logger.isDebugEnabled()) {
  39 + logger.debug("Processing msg: {}", msg);
  40 + }
  41 + if (msg instanceof TbActorMsg) {
  42 + if(!process((TbActorMsg) msg)){
  43 + logger.warning("Unknown message: {}!", msg);
  44 + }
  45 + } else {
  46 + logger.warning("Unknown message: {}!", msg);
  47 + }
  48 + }
  49 +
  50 + protected abstract boolean process(TbActorMsg msg);
31 51 }
... ...
... ... @@ -30,16 +30,14 @@ import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
30 30 import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
31 31 import org.thingsboard.server.actors.session.SessionManagerActor;
32 32 import org.thingsboard.server.actors.stats.StatsActor;
33   -import org.thingsboard.server.common.data.id.DeviceId;
34   -import org.thingsboard.server.common.data.id.PluginId;
35   -import org.thingsboard.server.common.data.id.RuleId;
36   -import org.thingsboard.server.common.data.id.TenantId;
  33 +import org.thingsboard.server.common.data.id.*;
37 34 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
38 35 import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
39 36 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
40 37 import org.thingsboard.server.common.msg.cluster.ServerAddress;
41 38 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
42 39 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
  40 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
43 41 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
44 42 import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
45 43 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
... ... @@ -129,6 +127,11 @@ public class DefaultActorService implements ActorService {
129 127 }
130 128
131 129 @Override
  130 + public void onMsg(ServiceToRuleEngineMsg msg) {
  131 + appActor.tell(msg, ActorRef.noSender());
  132 + }
  133 +
  134 + @Override
132 135 public void process(SessionAwareMsg msg) {
133 136 log.debug("Processing session aware msg: {}", msg);
134 137 sessionManagerActor.tell(msg, ActorRef.noSender());
... ... @@ -212,15 +215,9 @@ public class DefaultActorService implements ActorService {
212 215 }
213 216
214 217 @Override
215   - public void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state) {
216   - log.trace("[{}] Processing onPluginStateChange event: {}", pluginId, state);
217   - broadcast(ComponentLifecycleMsg.forPlugin(tenantId, pluginId, state));
218   - }
219   -
220   - @Override
221   - public void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state) {
222   - log.trace("[{}] Processing onRuleStateChange event: {}", ruleId, state);
223   - broadcast(ComponentLifecycleMsg.forRule(tenantId, ruleId, state));
  218 + public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) {
  219 + log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state);
  220 + broadcast(new ComponentLifecycleMsg(tenantId, entityId, state));
224 221 }
225 222
226 223 @Override
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.actors.service.ContextAwareActor;
23 23 import org.thingsboard.server.actors.service.ContextBasedCreator;
24 24 import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
25 25 import org.thingsboard.server.common.data.id.SessionId;
  26 +import org.thingsboard.server.common.msg.TbActorMsg;
26 27 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
27 28 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
28 29 import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
... ... @@ -61,6 +62,12 @@ public class SessionActor extends ContextAwareActor {
61 62 }
62 63
63 64 @Override
  65 + protected boolean process(TbActorMsg msg) {
  66 + //TODO Move everything here, to work with TbActorMsg
  67 + return false;
  68 + }
  69 +
  70 + @Override
64 71 public void onReceive(Object msg) throws Exception {
65 72 logger.debug("[{}] Processing: {}.", sessionId, msg);
66 73 if (msg instanceof ToDeviceActorSessionMsg) {
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
26 26 import org.thingsboard.server.actors.service.DefaultActorService;
27 27 import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
28 28 import org.thingsboard.server.common.data.id.SessionId;
  29 +import org.thingsboard.server.common.msg.TbActorMsg;
29 30 import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
30 31
31 32 import akka.event.Logging;
... ... @@ -49,6 +50,12 @@ public class SessionManagerActor extends ContextAwareActor {
49 50 }
50 51
51 52 @Override
  53 + protected boolean process(TbActorMsg msg) {
  54 + //TODO Move everything here, to work with TbActorMsg
  55 + return false;
  56 + }
  57 +
  58 + @Override
52 59 public void onReceive(Object msg) throws Exception {
53 60 if (msg instanceof SessionCtrlMsg) {
54 61 onSessionCtrlMsg((SessionCtrlMsg) msg);
... ...
... ... @@ -102,9 +102,6 @@ public abstract class AbstractContextAwareMsgProcessor {
102 102 case FILTER:
103 103 configurationClazz = ((Filter) componentClazz.getAnnotation(Filter.class)).configuration();
104 104 break;
105   - case PROCESSOR:
106   - configurationClazz = ((Processor) componentClazz.getAnnotation(Processor.class)).configuration();
107   - break;
108 105 case ACTION:
109 106 configurationClazz = ((Action) componentClazz.getAnnotation(Action.class)).configuration();
110 107 break;
... ...
... ... @@ -20,12 +20,14 @@ import akka.event.LoggingAdapter;
20 20 import org.thingsboard.server.actors.ActorSystemContext;
21 21 import org.thingsboard.server.actors.stats.StatsPersistTick;
22 22 import org.thingsboard.server.common.data.id.TenantId;
  23 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
23 24 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
24 25
25 26 public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgProcessor {
26 27
27 28 protected final TenantId tenantId;
28 29 protected final T entityId;
  30 + protected ComponentLifecycleState state;
29 31
30 32 protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) {
31 33 super(systemContext, logger);
... ... @@ -33,23 +35,44 @@ public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgPr
33 35 this.entityId = id;
34 36 }
35 37
36   - public abstract void start() throws Exception;
  38 + public abstract void start(ActorContext context) throws Exception;
37 39
38   - public abstract void stop() throws Exception;
  40 + public abstract void stop(ActorContext context) throws Exception;
39 41
40   - public abstract void onCreated(ActorContext context) throws Exception;
  42 + public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception;
41 43
42   - public abstract void onUpdate(ActorContext context) throws Exception;
  44 + public void onCreated(ActorContext context) throws Exception {
  45 + start(context);
  46 + }
43 47
44   - public abstract void onActivate(ActorContext context) throws Exception;
  48 + public void onUpdate(ActorContext context) throws Exception {
  49 + restart(context);
  50 + }
45 51
46   - public abstract void onSuspend(ActorContext context) throws Exception;
  52 + public void onActivate(ActorContext context) throws Exception {
  53 + restart(context);
  54 + }
47 55
48   - public abstract void onStop(ActorContext context) throws Exception;
  56 + public void onSuspend(ActorContext context) throws Exception {
  57 + stop(context);
  58 + }
49 59
50   - public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception;
  60 + public void onStop(ActorContext context) throws Exception {
  61 + stop(context);
  62 + }
  63 +
  64 + private void restart(ActorContext context) throws Exception {
  65 + stop(context);
  66 + start(context);
  67 + }
51 68
52 69 public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) {
53 70 schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency);
54 71 }
  72 +
  73 + protected void checkActive() {
  74 + if (state != ComponentLifecycleState.ACTIVE) {
  75 + throw new IllegalStateException("Rule chain is not active!");
  76 + }
  77 + }
55 78 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.shared;
  17 +
  18 +import akka.actor.ActorContext;
  19 +import akka.actor.ActorRef;
  20 +import akka.actor.Props;
  21 +import akka.actor.UntypedActor;
  22 +import akka.japi.Creator;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.thingsboard.server.actors.ActorSystemContext;
  25 +import org.thingsboard.server.actors.service.ContextAwareActor;
  26 +import org.thingsboard.server.common.data.SearchTextBased;
  27 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  28 +import org.thingsboard.server.common.data.id.EntityId;
  29 +import org.thingsboard.server.common.data.id.TenantId;
  30 +import org.thingsboard.server.common.data.id.UUIDBased;
  31 +import org.thingsboard.server.common.data.page.PageDataIterable;
  32 +import org.thingsboard.server.common.data.plugin.PluginMetaData;
  33 +
  34 +import java.util.HashMap;
  35 +import java.util.Map;
  36 +
  37 +/**
  38 + * Created by ashvayka on 15.03.18.
  39 + */
  40 +@Slf4j
  41 +public abstract class EntityActorsManager<T extends EntityId, A extends UntypedActor, M extends SearchTextBased<? extends UUIDBased>> {
  42 +
  43 + protected final ActorSystemContext systemContext;
  44 + protected final Map<T, ActorRef> actors;
  45 +
  46 + public EntityActorsManager(ActorSystemContext systemContext) {
  47 + this.systemContext = systemContext;
  48 + this.actors = new HashMap<>();
  49 + }
  50 +
  51 + protected abstract TenantId getTenantId();
  52 +
  53 + protected abstract String getDispatcherName();
  54 +
  55 + protected abstract Creator<A> creator(T entityId);
  56 +
  57 + protected abstract PageDataIterable.FetchFunction<M> getFetchEntitiesFunction();
  58 +
  59 + public void init(ActorContext context) {
  60 + for (M entity : new PageDataIterable<>(getFetchEntitiesFunction(), ContextAwareActor.ENTITY_PACK_LIMIT)) {
  61 + T entityId = (T) entity.getId();
  62 + log.debug("[{}|{}] Creating entity actor", entityId.getEntityType(), entityId.getId());
  63 + //TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa.
  64 + ActorRef actorRef = getOrCreateActor(context, entityId);
  65 + visit(entity, actorRef);
  66 + log.debug("[{}|{}] Entity actor created.", entityId.getEntityType(), entityId.getId());
  67 + }
  68 + }
  69 +
  70 + protected void visit(M entity, ActorRef actorRef) {}
  71 +
  72 + public ActorRef getOrCreateActor(ActorContext context, T entityId) {
  73 + return actors.computeIfAbsent(entityId, eId ->
  74 + context.actorOf(Props.create(creator(eId))
  75 + .withDispatcher(getDispatcherName()), eId.toString()));
  76 + }
  77 +
  78 + public void broadcast(Object msg) {
  79 + actors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
  80 + }
  81 +
  82 + public void remove(T id) {
  83 + actors.remove(id);
  84 + }
  85 +
  86 +}
... ...
... ... @@ -15,63 +15,28 @@
15 15 */
16 16 package org.thingsboard.server.actors.shared.plugin;
17 17
18   -import akka.actor.ActorContext;
19   -import akka.actor.ActorRef;
20   -import akka.actor.Props;
  18 +import akka.japi.Creator;
21 19 import lombok.extern.slf4j.Slf4j;
22 20 import org.thingsboard.server.actors.ActorSystemContext;
23 21 import org.thingsboard.server.actors.plugin.PluginActor;
24   -import org.thingsboard.server.actors.service.ContextAwareActor;
  22 +import org.thingsboard.server.actors.shared.EntityActorsManager;
25 23 import org.thingsboard.server.common.data.id.PluginId;
26   -import org.thingsboard.server.common.data.id.TenantId;
27   -import org.thingsboard.server.common.data.page.PageDataIterable;
28   -import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
29 24 import org.thingsboard.server.common.data.plugin.PluginMetaData;
30 25 import org.thingsboard.server.dao.plugin.PluginService;
31 26
32   -import java.util.HashMap;
33   -import java.util.Map;
34   -
35 27 @Slf4j
36   -public abstract class PluginManager {
  28 +public abstract class PluginManager extends EntityActorsManager<PluginId, PluginActor, PluginMetaData> {
37 29
38   - protected final ActorSystemContext systemContext;
39 30 protected final PluginService pluginService;
40   - protected final Map<PluginId, ActorRef> pluginActors;
41 31
42 32 public PluginManager(ActorSystemContext systemContext) {
43   - this.systemContext = systemContext;
  33 + super(systemContext);
44 34 this.pluginService = systemContext.getPluginService();
45   - this.pluginActors = new HashMap<>();
46 35 }
47 36
48   - public void init(ActorContext context) {
49   - PageDataIterable<PluginMetaData> pluginIterator = new PageDataIterable<>(getFetchPluginsFunction(),
50   - ContextAwareActor.ENTITY_PACK_LIMIT);
51   - for (PluginMetaData plugin : pluginIterator) {
52   - log.debug("[{}] Creating plugin actor", plugin.getId());
53   - getOrCreatePluginActor(context, plugin.getId());
54   - log.debug("Plugin actor created.");
55   - }
  37 + @Override
  38 + public Creator<PluginActor> creator(PluginId entityId){
  39 + return new PluginActor.ActorCreator(systemContext, getTenantId(), entityId);
56 40 }
57 41
58   - abstract FetchFunction<PluginMetaData> getFetchPluginsFunction();
59   -
60   - abstract TenantId getTenantId();
61   -
62   - abstract String getDispatcherName();
63   -
64   - public ActorRef getOrCreatePluginActor(ActorContext context, PluginId pluginId) {
65   - return pluginActors.computeIfAbsent(pluginId, pId ->
66   - context.actorOf(Props.create(new PluginActor.ActorCreator(systemContext, getTenantId(), pId))
67   - .withDispatcher(getDispatcherName()), pId.toString()));
68   - }
69   -
70   - public void broadcast(Object msg) {
71   - pluginActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
72   - }
73   -
74   - public void remove(PluginId id) {
75   - pluginActors.remove(id);
76   - }
77 42 }
... ...
... ... @@ -29,12 +29,12 @@ public class SystemPluginManager extends PluginManager {
29 29 }
30 30
31 31 @Override
32   - FetchFunction<PluginMetaData> getFetchPluginsFunction() {
  32 + protected FetchFunction<PluginMetaData> getFetchEntitiesFunction() {
33 33 return pluginService::findSystemPlugins;
34 34 }
35 35
36 36 @Override
37   - TenantId getTenantId() {
  37 + protected TenantId getTenantId() {
38 38 return BasePluginService.SYSTEM_TENANT;
39 39 }
40 40
... ...
... ... @@ -19,6 +19,7 @@ import akka.actor.ActorContext;
19 19 import org.thingsboard.server.actors.ActorSystemContext;
20 20 import org.thingsboard.server.actors.service.DefaultActorService;
21 21 import org.thingsboard.server.common.data.id.TenantId;
  22 +import org.thingsboard.server.common.data.page.PageDataIterable;
22 23 import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
23 24 import org.thingsboard.server.common.data.plugin.PluginMetaData;
24 25
... ... @@ -39,12 +40,12 @@ public class TenantPluginManager extends PluginManager {
39 40 }
40 41
41 42 @Override
42   - FetchFunction<PluginMetaData> getFetchPluginsFunction() {
  43 + protected FetchFunction<PluginMetaData> getFetchEntitiesFunction() {
43 44 return link -> pluginService.findTenantPlugins(tenantId, link);
44 45 }
45 46
46 47 @Override
47   - TenantId getTenantId() {
  48 + protected TenantId getTenantId() {
48 49 return tenantId;
49 50 }
50 51
... ...
1   -/**
2   - * Copyright © 2016-2018 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package org.thingsboard.server.actors.shared.rule;
17   -
18   -import akka.actor.ActorContext;
19   -import akka.actor.ActorRef;
20   -import akka.actor.Props;
21   -import lombok.extern.slf4j.Slf4j;
22   -import org.thingsboard.server.actors.ActorSystemContext;
23   -import org.thingsboard.server.actors.rule.RuleActor;
24   -import org.thingsboard.server.actors.rule.RuleActorChain;
25   -import org.thingsboard.server.actors.rule.RuleActorMetaData;
26   -import org.thingsboard.server.actors.rule.SimpleRuleActorChain;
27   -import org.thingsboard.server.actors.service.ContextAwareActor;
28   -import org.thingsboard.server.common.data.id.RuleId;
29   -import org.thingsboard.server.common.data.id.TenantId;
30   -import org.thingsboard.server.common.data.page.PageDataIterable;
31   -import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
32   -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
33   -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
34   -import org.thingsboard.server.common.data.rule.RuleMetaData;
35   -import org.thingsboard.server.dao.rule.RuleService;
36   -
37   -import java.util.*;
38   -
39   -@Slf4j
40   -public abstract class RuleManager {
41   -
42   - protected final ActorSystemContext systemContext;
43   - protected final RuleService ruleService;
44   - protected final Map<RuleId, ActorRef> ruleActors;
45   - protected final TenantId tenantId;
46   -
47   - private Map<RuleMetaData, RuleActorMetaData> ruleMap;
48   - private RuleActorChain ruleChain;
49   -
50   - public RuleManager(ActorSystemContext systemContext, TenantId tenantId) {
51   - this.systemContext = systemContext;
52   - this.ruleService = systemContext.getRuleService();
53   - this.ruleActors = new HashMap<>();
54   - this.tenantId = tenantId;
55   - }
56   -
57   - public void init(ActorContext context) {
58   - doInit(context);
59   - }
60   -
61   - private void doInit(ActorContext context) {
62   - PageDataIterable<RuleMetaData> ruleIterator = new PageDataIterable<>(getFetchRulesFunction(),
63   - ContextAwareActor.ENTITY_PACK_LIMIT);
64   - ruleMap = new HashMap<>();
65   -
66   - for (RuleMetaData rule : ruleIterator) {
67   - log.debug("[{}] Creating rule actor {}", rule.getId(), rule);
68   - ActorRef ref = getOrCreateRuleActor(context, rule.getId());
69   - ruleMap.put(rule, RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref));
70   - log.debug("[{}] Rule actor created.", rule.getId());
71   - }
72   -
73   - refreshRuleChain();
74   - }
75   -
76   - public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) {
77   - if (ruleMap == null) {
78   - doInit(context);
79   - }
80   - RuleMetaData rule;
81   - if (event != ComponentLifecycleEvent.DELETED) {
82   - rule = systemContext.getRuleService().findRuleById(ruleId);
83   - } else {
84   - rule = ruleMap.keySet().stream()
85   - .filter(r -> r.getId().equals(ruleId))
86   - .peek(r -> r.setState(ComponentLifecycleState.SUSPENDED))
87   - .findFirst()
88   - .orElse(null);
89   - if (rule != null) {
90   - ruleMap.remove(rule);
91   - ruleActors.remove(ruleId);
92   - }
93   - }
94   - if (rule != null) {
95   - RuleActorMetaData actorMd = ruleMap.get(rule);
96   - if (actorMd == null) {
97   - ActorRef ref = getOrCreateRuleActor(context, rule.getId());
98   - actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref);
99   - ruleMap.put(rule, actorMd);
100   - }
101   - refreshRuleChain();
102   - return Optional.of(actorMd.getActorRef());
103   - } else {
104   - log.warn("[{}] Can't process unknown rule!", ruleId);
105   - return Optional.empty();
106   - }
107   - }
108   -
109   - abstract FetchFunction<RuleMetaData> getFetchRulesFunction();
110   -
111   - abstract String getDispatcherName();
112   -
113   - public ActorRef getOrCreateRuleActor(ActorContext context, RuleId ruleId) {
114   - return ruleActors.computeIfAbsent(ruleId, rId ->
115   - context.actorOf(Props.create(new RuleActor.ActorCreator(systemContext, tenantId, rId))
116   - .withDispatcher(getDispatcherName()), rId.toString()));
117   - }
118   -
119   - public RuleActorChain getRuleChain(ActorContext context) {
120   - if (ruleChain == null) {
121   - doInit(context);
122   - }
123   - return ruleChain;
124   - }
125   -
126   - private void refreshRuleChain() {
127   - Set<RuleActorMetaData> activeRuleSet = new HashSet<>();
128   - for (Map.Entry<RuleMetaData, RuleActorMetaData> rule : ruleMap.entrySet()) {
129   - if (rule.getKey().getState() == ComponentLifecycleState.ACTIVE) {
130   - activeRuleSet.add(rule.getValue());
131   - }
132   - }
133   - ruleChain = new SimpleRuleActorChain(activeRuleSet);
134   - }
135   -}
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.actors.shared.rulechain;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import akka.japi.Creator;
  20 +import lombok.Getter;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.thingsboard.server.actors.ActorSystemContext;
  23 +import org.thingsboard.server.actors.ruleChain.RuleChainActor;
  24 +import org.thingsboard.server.actors.shared.EntityActorsManager;
  25 +import org.thingsboard.server.common.data.id.RuleChainId;
  26 +import org.thingsboard.server.common.data.rule.RuleChain;
  27 +import org.thingsboard.server.dao.rule.RuleChainService;
  28 +
  29 +/**
  30 + * Created by ashvayka on 15.03.18.
  31 + */
  32 +@Slf4j
  33 +public abstract class RuleChainManager extends EntityActorsManager<RuleChainId, RuleChainActor, RuleChain> {
  34 +
  35 + protected final RuleChainService service;
  36 + @Getter
  37 + protected RuleChain rootChain;
  38 + @Getter
  39 + protected ActorRef rootChainActor;
  40 +
  41 + public RuleChainManager(ActorSystemContext systemContext) {
  42 + super(systemContext);
  43 + this.service = systemContext.getRuleChainService();
  44 + }
  45 +
  46 + @Override
  47 + public Creator<RuleChainActor> creator(RuleChainId entityId) {
  48 + return new RuleChainActor.ActorCreator(systemContext, getTenantId(), entityId);
  49 + }
  50 +
  51 + @Override
  52 + protected void visit(RuleChain entity, ActorRef actorRef) {
  53 + if (entity.isRoot()) {
  54 + rootChain = entity;
  55 + rootChainActor = actorRef;
  56 + }
  57 + }
  58 +
  59 +}
... ...
application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java renamed from application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java
... ... @@ -13,28 +13,35 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.shared.rule;
  16 +package org.thingsboard.server.actors.shared.rulechain;
17 17
18 18 import org.thingsboard.server.actors.ActorSystemContext;
19 19 import org.thingsboard.server.actors.service.DefaultActorService;
  20 +import org.thingsboard.server.actors.shared.plugin.PluginManager;
20 21 import org.thingsboard.server.common.data.id.TenantId;
21 22 import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
22   -import org.thingsboard.server.common.data.rule.RuleMetaData;
23   -import org.thingsboard.server.dao.model.ModelConstants;
  23 +import org.thingsboard.server.common.data.plugin.PluginMetaData;
  24 +import org.thingsboard.server.common.data.rule.RuleChain;
  25 +import org.thingsboard.server.dao.plugin.BasePluginService;
24 26
25   -public class SystemRuleManager extends RuleManager {
  27 +public class SystemRuleChainManager extends RuleChainManager {
26 28
27   - public SystemRuleManager(ActorSystemContext systemContext) {
28   - super(systemContext, new TenantId(ModelConstants.NULL_UUID));
  29 + public SystemRuleChainManager(ActorSystemContext systemContext) {
  30 + super(systemContext);
29 31 }
30 32
31 33 @Override
32   - FetchFunction<RuleMetaData> getFetchRulesFunction() {
33   - return ruleService::findSystemRules;
  34 + protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
  35 + return service::findSystemRuleChains;
34 36 }
35 37
36 38 @Override
37   - String getDispatcherName() {
  39 + protected TenantId getTenantId() {
  40 + return BasePluginService.SYSTEM_TENANT;
  41 + }
  42 +
  43 + @Override
  44 + protected String getDispatcherName() {
38 45 return DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME;
39 46 }
40 47 }
... ...
application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java renamed from application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java
... ... @@ -13,19 +13,22 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.shared.rule;
  16 +package org.thingsboard.server.actors.shared.rulechain;
17 17
18 18 import akka.actor.ActorContext;
19 19 import org.thingsboard.server.actors.ActorSystemContext;
20 20 import org.thingsboard.server.actors.service.DefaultActorService;
21 21 import org.thingsboard.server.common.data.id.TenantId;
22 22 import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
23   -import org.thingsboard.server.common.data.rule.RuleMetaData;
  23 +import org.thingsboard.server.common.data.rule.RuleChain;
24 24
25   -public class TenantRuleManager extends RuleManager {
26   -
27   - public TenantRuleManager(ActorSystemContext systemContext, TenantId tenantId) {
28   - super(systemContext, tenantId);
  25 +public class TenantRuleChainManager extends RuleChainManager {
  26 +
  27 + private final TenantId tenantId;
  28 +
  29 + public TenantRuleChainManager(ActorSystemContext systemContext, TenantId tenantId) {
  30 + super(systemContext);
  31 + this.tenantId = tenantId;
29 32 }
30 33
31 34 @Override
... ... @@ -36,13 +39,17 @@ public class TenantRuleManager extends RuleManager {
36 39 }
37 40
38 41 @Override
39   - FetchFunction<RuleMetaData> getFetchRulesFunction() {
40   - return link -> ruleService.findTenantRules(tenantId, link);
  42 + protected TenantId getTenantId() {
  43 + return tenantId;
41 44 }
42 45
43 46 @Override
44   - String getDispatcherName() {
  47 + protected String getDispatcherName() {
45 48 return DefaultActorService.TENANT_RULE_DISPATCHER_NAME;
46 49 }
47 50
  51 + @Override
  52 + protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
  53 + return link -> service.findTenantRuleChains(tenantId, link);
  54 + }
48 55 }
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.server.actors.service.ContextAwareActor;
24 24 import org.thingsboard.server.actors.service.ContextBasedCreator;
25 25 import org.thingsboard.server.common.data.DataConstants;
26 26 import org.thingsboard.server.common.data.Event;
  27 +import org.thingsboard.server.common.msg.TbActorMsg;
27 28 import org.thingsboard.server.common.msg.cluster.ServerAddress;
28 29
29 30 public class StatsActor extends ContextAwareActor {
... ... @@ -36,6 +37,12 @@ public class StatsActor extends ContextAwareActor {
36 37 }
37 38
38 39 @Override
  40 + protected boolean process(TbActorMsg msg) {
  41 + //TODO Move everything here, to work with TbActorMsg\
  42 + return false;
  43 + }
  44 +
  45 + @Override
39 46 public void onReceive(Object msg) throws Exception {
40 47 logger.debug("Received message: {}", msg);
41 48 if (msg instanceof StatsPersistMsg) {
... ...
... ... @@ -15,52 +15,38 @@
15 15 */
16 16 package org.thingsboard.server.actors.tenant;
17 17
18   -import java.util.HashMap;
19   -import java.util.Map;
20   -import java.util.Optional;
21   -
  18 +import akka.actor.ActorRef;
  19 +import akka.actor.Props;
  20 +import akka.event.Logging;
  21 +import akka.event.LoggingAdapter;
22 22 import org.thingsboard.server.actors.ActorSystemContext;
23 23 import org.thingsboard.server.actors.device.DeviceActor;
24 24 import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
25   -import org.thingsboard.server.actors.rule.ComplexRuleActorChain;
26   -import org.thingsboard.server.actors.rule.RuleActorChain;
27   -import org.thingsboard.server.actors.service.ContextAwareActor;
  25 +import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
28 26 import org.thingsboard.server.actors.service.ContextBasedCreator;
29 27 import org.thingsboard.server.actors.service.DefaultActorService;
30   -import org.thingsboard.server.actors.shared.plugin.PluginManager;
31 28 import org.thingsboard.server.actors.shared.plugin.TenantPluginManager;
32   -import org.thingsboard.server.actors.shared.rule.RuleManager;
33   -import org.thingsboard.server.actors.shared.rule.TenantRuleManager;
  29 +import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
34 30 import org.thingsboard.server.common.data.id.DeviceId;
35   -import org.thingsboard.server.common.data.id.PluginId;
36   -import org.thingsboard.server.common.data.id.RuleId;
37 31 import org.thingsboard.server.common.data.id.TenantId;
38   -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  32 +import org.thingsboard.server.common.msg.TbActorMsg;
39 33 import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
40   -
41   -import akka.actor.ActorRef;
42   -import akka.actor.Props;
43   -import akka.event.Logging;
44   -import akka.event.LoggingAdapter;
45 34 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  35 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
46 36 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
47 37 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
48   -import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
49 38
50   -public class TenantActor extends ContextAwareActor {
  39 +import java.util.HashMap;
  40 +import java.util.Map;
51 41
52   - private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
  42 +public class TenantActor extends RuleChainManagerActor {
53 43
54 44 private final TenantId tenantId;
55   - private final RuleManager ruleManager;
56   - private final PluginManager pluginManager;
57 45 private final Map<DeviceId, ActorRef> deviceActors;
58 46
59 47 private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
60   - super(systemContext);
  48 + super(systemContext, new TenantRuleChainManager(systemContext, tenantId), new TenantPluginManager(systemContext, tenantId));
61 49 this.tenantId = tenantId;
62   - this.ruleManager = new TenantRuleManager(systemContext, tenantId);
63   - this.pluginManager = new TenantPluginManager(systemContext, tenantId);
64 50 this.deviceActors = new HashMap<>();
65 51 }
66 52
... ... @@ -68,8 +54,7 @@ public class TenantActor extends ContextAwareActor {
68 54 public void preStart() {
69 55 logger.info("[{}] Starting tenant actor.", tenantId);
70 56 try {
71   - ruleManager.init(this.context());
72   - pluginManager.init(this.context());
  57 + initRuleChains();
73 58 logger.info("[{}] Tenant actor started.", tenantId);
74 59 } catch (Exception e) {
75 60 logger.error(e, "[{}] Unknown failure", tenantId);
... ... @@ -77,29 +62,45 @@ public class TenantActor extends ContextAwareActor {
77 62 }
78 63
79 64 @Override
80   - public void onReceive(Object msg) throws Exception {
81   - logger.debug("[{}] Received message: {}", tenantId, msg);
82   - if (msg instanceof RuleChainDeviceMsg) {
83   - process((RuleChainDeviceMsg) msg);
84   - } else if (msg instanceof ToDeviceActorMsg) {
85   - onToDeviceActorMsg((ToDeviceActorMsg) msg);
86   - } else if (msg instanceof ToPluginActorMsg) {
87   - onToPluginMsg((ToPluginActorMsg) msg);
88   - } else if (msg instanceof ToRuleActorMsg) {
89   - onToRuleMsg((ToRuleActorMsg) msg);
90   - } else if (msg instanceof ToDeviceActorNotificationMsg) {
91   - onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
92   - } else if (msg instanceof ClusterEventMsg) {
93   - broadcast(msg);
94   - } else if (msg instanceof ComponentLifecycleMsg) {
95   - onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
96   - } else if (msg instanceof PluginTerminationMsg) {
97   - onPluginTerminated((PluginTerminationMsg) msg);
98   - } else {
99   - logger.warning("[{}] Unknown message: {}!", tenantId, msg);
  65 + protected boolean process(TbActorMsg msg) {
  66 + switch (msg.getMsgType()) {
  67 + case COMPONENT_LIFE_CYCLE_MSG:
  68 + onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  69 + break;
  70 + case SERVICE_TO_RULE_ENGINE_MSG:
  71 + onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
  72 + break;
  73 + default:
  74 + return false;
100 75 }
  76 + return true;
101 77 }
102 78
  79 + private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
  80 + ruleChainManager.getRootChainActor().tell(msg, self());
  81 + }
  82 +
  83 +
  84 +// @Override
  85 +// public void onReceive(Object msg) throws Exception {
  86 +// logger.debug("[{}] Received message: {}", tenantId, msg);
  87 +// if (msg instanceof ToDeviceActorMsg) {
  88 +// onToDeviceActorMsg((ToDeviceActorMsg) msg);
  89 +// } else if (msg instanceof ToPluginActorMsg) {
  90 +// onToPluginMsg((ToPluginActorMsg) msg);
  91 +// } else if (msg instanceof ToDeviceActorNotificationMsg) {
  92 +// onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
  93 +// } else if (msg instanceof ClusterEventMsg) {
  94 +// broadcast(msg);
  95 +// } else if (msg instanceof ComponentLifecycleMsg) {
  96 +// onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
  97 +// } else if (msg instanceof PluginTerminationMsg) {
  98 +// onPluginTerminated((PluginTerminationMsg) msg);
  99 +// } else {
  100 +// logger.warning("[{}] Unknown message: {}!", tenantId, msg);
  101 +// }
  102 +// }
  103 +
103 104 private void broadcast(Object msg) {
104 105 pluginManager.broadcast(msg);
105 106 deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
... ... @@ -113,14 +114,9 @@ public class TenantActor extends ContextAwareActor {
113 114 getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
114 115 }
115 116
116   - private void onToRuleMsg(ToRuleActorMsg msg) {
117   - ActorRef target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
118   - target.tell(msg, ActorRef.noSender());
119   - }
120   -
121 117 private void onToPluginMsg(ToPluginActorMsg msg) {
122 118 if (msg.getPluginTenantId().equals(tenantId)) {
123   - ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
  119 + ActorRef pluginActor = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
124 120 pluginActor.tell(msg, ActorRef.noSender());
125 121 } else {
126 122 context().parent().tell(msg, ActorRef.noSender());
... ... @@ -128,23 +124,11 @@ public class TenantActor extends ContextAwareActor {
128 124 }
129 125
130 126 private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
131   - Optional<PluginId> pluginId = msg.getPluginId();
132   - Optional<RuleId> ruleId = msg.getRuleId();
133   - if (pluginId.isPresent()) {
134   - ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), pluginId.get());
135   - pluginActor.tell(msg, ActorRef.noSender());
136   - } else if (ruleId.isPresent()) {
137   - ActorRef target;
138   - Optional<ActorRef> ref = ruleManager.update(this.context(), ruleId.get(), msg.getEvent());
139   - if (ref.isPresent()) {
140   - target = ref.get();
141   - } else {
142   - logger.debug("Failed to find actor for rule: [{}]", ruleId);
143   - return;
144   - }
  127 + ActorRef target = getEntityActorRef(msg.getEntityId());
  128 + if (target != null) {
145 129 target.tell(msg, ActorRef.noSender());
146 130 } else {
147   - logger.debug("[{}] Invalid component lifecycle msg.", tenantId);
  131 + logger.debug("Invalid component lifecycle msg: {}", msg);
148 132 }
149 133 }
150 134
... ... @@ -152,13 +136,6 @@ public class TenantActor extends ContextAwareActor {
152 136 pluginManager.remove(msg.getId());
153 137 }
154 138
155   - private void process(RuleChainDeviceMsg msg) {
156   - ToDeviceActorMsg toDeviceActorMsg = msg.getToDeviceActorMsg();
157   - ActorRef deviceActor = getOrCreateDeviceActor(toDeviceActorMsg.getDeviceId());
158   - RuleActorChain tenantChain = ruleManager.getRuleChain(this.context());
159   - RuleActorChain chain = new ComplexRuleActorChain(msg.getRuleChain(), tenantChain);
160   - deviceActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, chain), context().self());
161   - }
162 139
163 140 private ActorRef getOrCreateDeviceActor(DeviceId deviceId) {
164 141 return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId))
... ...
... ... @@ -71,7 +71,7 @@ public class PluginController extends BaseController {
71 71 boolean created = source.getId() == null;
72 72 source.setTenantId(getCurrentUser().getTenantId());
73 73 PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source));
74   - actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(),
  74 + actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(),
75 75 created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
76 76
77 77 logEntityAction(plugin.getId(), plugin,
... ... @@ -97,7 +97,7 @@ public class PluginController extends BaseController {
97 97 PluginId pluginId = new PluginId(toUUID(strPluginId));
98 98 PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
99 99 pluginService.activatePluginById(pluginId);
100   - actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED);
  100 + actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED);
101 101
102 102 logEntityAction(plugin.getId(), plugin,
103 103 null,
... ... @@ -123,7 +123,7 @@ public class PluginController extends BaseController {
123 123 PluginId pluginId = new PluginId(toUUID(strPluginId));
124 124 PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
125 125 pluginService.suspendPluginById(pluginId);
126   - actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED);
  126 + actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED);
127 127
128 128 logEntityAction(plugin.getId(), plugin,
129 129 null,
... ... @@ -221,7 +221,7 @@ public class PluginController extends BaseController {
221 221 PluginId pluginId = new PluginId(toUUID(strPluginId));
222 222 PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
223 223 pluginService.deletePluginById(pluginId);
224   - actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED);
  224 + actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED);
225 225
226 226 logEntityAction(pluginId, plugin,
227 227 null,
... ...
... ... @@ -78,6 +78,9 @@ public class RuleChainController extends BaseController {
78 78 ruleChain.setTenantId(getCurrentUser().getTenantId());
79 79 RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
80 80
  81 + actorService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(),
  82 + created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  83 +
81 84 logEntityAction(savedRuleChain.getId(), savedRuleChain,
82 85 null,
83 86 created ? ActionType.ADDED : ActionType.UPDATED, null);
... ... @@ -100,6 +103,8 @@ public class RuleChainController extends BaseController {
100 103 RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId());
101 104 RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(ruleChainMetaData));
102 105
  106 + actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
  107 +
103 108 logEntityAction(ruleChain.getId(), ruleChain,
104 109 null,
105 110 ActionType.UPDATED, null, ruleChainMetaData);
... ... @@ -183,6 +188,8 @@ public class RuleChainController extends BaseController {
183 188 RuleChain ruleChain = checkRuleChain(ruleChainId);
184 189 ruleChainService.deleteRuleChainById(ruleChainId);
185 190
  191 + actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED);
  192 +
186 193 logEntityAction(ruleChainId, ruleChain,
187 194 null,
188 195 ActionType.DELETED, null, strRuleChainId);
... ...
... ... @@ -73,7 +73,7 @@ public class RuleController extends BaseController {
73 73 boolean created = source.getId() == null;
74 74 source.setTenantId(getCurrentUser().getTenantId());
75 75 RuleMetaData rule = checkNotNull(ruleService.saveRule(source));
76   - actorService.onRuleStateChange(rule.getTenantId(), rule.getId(),
  76 + actorService.onEntityStateChange(rule.getTenantId(), rule.getId(),
77 77 created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
78 78
79 79 logEntityAction(rule.getId(), rule,
... ... @@ -99,7 +99,7 @@ public class RuleController extends BaseController {
99 99 RuleId ruleId = new RuleId(toUUID(strRuleId));
100 100 RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
101 101 ruleService.activateRuleById(ruleId);
102   - actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED);
  102 + actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED);
103 103
104 104 logEntityAction(rule.getId(), rule,
105 105 null,
... ... @@ -125,7 +125,7 @@ public class RuleController extends BaseController {
125 125 RuleId ruleId = new RuleId(toUUID(strRuleId));
126 126 RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
127 127 ruleService.suspendRuleById(ruleId);
128   - actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED);
  128 + actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED);
129 129
130 130 logEntityAction(rule.getId(), rule,
131 131 null,
... ... @@ -219,7 +219,7 @@ public class RuleController extends BaseController {
219 219 RuleId ruleId = new RuleId(toUUID(strRuleId));
220 220 RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
221 221 ruleService.deleteRuleById(ruleId);
222   - actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED);
  222 + actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED);
223 223
224 224 logEntityAction(ruleId, rule,
225 225 null,
... ...
... ... @@ -26,6 +26,10 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
26 26 import org.springframework.core.env.Environment;
27 27 import org.springframework.core.type.filter.AnnotationTypeFilter;
28 28 import org.springframework.stereotype.Service;
  29 +import org.thingsboard.rule.engine.api.ActionNode;
  30 +import org.thingsboard.rule.engine.api.EnrichmentNode;
  31 +import org.thingsboard.rule.engine.api.FilterNode;
  32 +import org.thingsboard.rule.engine.api.TransformationNode;
29 33 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
30 34 import org.thingsboard.server.common.data.plugin.ComponentType;
31 35 import org.thingsboard.server.dao.component.ComponentDescriptorService;
... ... @@ -79,8 +83,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
79 83 private List<ComponentDescriptor> persist(Set<BeanDefinition> filterDefs, ComponentType type) {
80 84 List<ComponentDescriptor> result = new ArrayList<>();
81 85 for (BeanDefinition def : filterDefs) {
82   - ComponentDescriptor scannedComponent = scanAndPersistComponent(def, type);
83   - result.add(scannedComponent);
  86 + result.add(scanAndPersistComponent(def, type));
84 87 }
85 88 return result;
86 89 }
... ... @@ -93,24 +96,36 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
93 96 Class<?> clazz = Class.forName(clazzName);
94 97 String descriptorResourceName;
95 98 switch (type) {
  99 + case ENRICHMENT:
  100 + EnrichmentNode enrichmentAnnotation = clazz.getAnnotation(EnrichmentNode.class);
  101 + scannedComponent.setName(enrichmentAnnotation.name());
  102 + scannedComponent.setScope(enrichmentAnnotation.scope());
  103 + descriptorResourceName = enrichmentAnnotation.descriptor();
  104 + break;
96 105 case FILTER:
97   - Filter filterAnnotation = clazz.getAnnotation(Filter.class);
  106 + FilterNode filterAnnotation = clazz.getAnnotation(FilterNode.class);
98 107 scannedComponent.setName(filterAnnotation.name());
99 108 scannedComponent.setScope(filterAnnotation.scope());
100 109 descriptorResourceName = filterAnnotation.descriptor();
101 110 break;
102   - case PROCESSOR:
103   - Processor processorAnnotation = clazz.getAnnotation(Processor.class);
104   - scannedComponent.setName(processorAnnotation.name());
105   - scannedComponent.setScope(processorAnnotation.scope());
106   - descriptorResourceName = processorAnnotation.descriptor();
  111 + case TRANSFORMATION:
  112 + TransformationNode trAnnotation = clazz.getAnnotation(TransformationNode.class);
  113 + scannedComponent.setName(trAnnotation.name());
  114 + scannedComponent.setScope(trAnnotation.scope());
  115 + descriptorResourceName = trAnnotation.descriptor();
107 116 break;
108 117 case ACTION:
109   - Action actionAnnotation = clazz.getAnnotation(Action.class);
  118 + ActionNode actionAnnotation = clazz.getAnnotation(ActionNode.class);
110 119 scannedComponent.setName(actionAnnotation.name());
111 120 scannedComponent.setScope(actionAnnotation.scope());
112 121 descriptorResourceName = actionAnnotation.descriptor();
113 122 break;
  123 + case OLD_ACTION:
  124 + Action oldActionAnnotation = clazz.getAnnotation(Action.class);
  125 + scannedComponent.setName(oldActionAnnotation.name());
  126 + scannedComponent.setScope(oldActionAnnotation.scope());
  127 + descriptorResourceName = oldActionAnnotation.descriptor();
  128 + break;
114 129 case PLUGIN:
115 130 Plugin pluginAnnotation = clazz.getAnnotation(Plugin.class);
116 131 scannedComponent.setName(pluginAnnotation.name());
... ... @@ -122,12 +137,12 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
122 137 log.error("Can't initialize plugin {}, due to missing action {}!", def.getBeanClassName(), actionClazz.getName());
123 138 return new ClassNotFoundException("Action: " + actionClazz.getName() + "is missing!");
124 139 });
125   - if (actionComponent.getType() != ComponentType.ACTION) {
  140 + if (actionComponent.getType() != ComponentType.OLD_ACTION) {
126 141 log.error("Plugin {} action {} has wrong component type!", def.getBeanClassName(), actionClazz.getName(), actionComponent.getType());
127 142 throw new RuntimeException("Plugin " + def.getBeanClassName() + "action " + actionClazz.getName() + " has wrong component type!");
128 143 }
129 144 }
130   - scannedComponent.setActions(Arrays.stream(pluginAnnotation.actions()).map(action -> action.getName()).collect(Collectors.joining(",")));
  145 + scannedComponent.setActions(Arrays.stream(pluginAnnotation.actions()).map(Class::getName).collect(Collectors.joining(",")));
131 146 break;
132 147 default:
133 148 throw new RuntimeException(type + " is not supported yet!");
... ... @@ -168,11 +183,15 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
168 183
169 184 @Override
170 185 public void discoverComponents() {
171   - registerComponents(ComponentType.FILTER, Filter.class);
  186 + registerComponents(ComponentType.ENRICHMENT, EnrichmentNode.class);
  187 +
  188 + registerComponents(ComponentType.FILTER, FilterNode.class);
  189 +
  190 + registerComponents(ComponentType.TRANSFORMATION, TransformationNode.class);
172 191
173   - registerComponents(ComponentType.PROCESSOR, Processor.class);
  192 + registerComponents(ComponentType.ACTION, ActionNode.class);
174 193
175   - registerComponents(ComponentType.ACTION, Action.class);
  194 + registerComponents(ComponentType.OLD_ACTION, Action.class);
176 195
177 196 registerComponents(ComponentType.PLUGIN, Plugin.class);
178 197
... ... @@ -199,7 +218,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
199 218 }
200 219 List<ComponentDescriptor> result = new ArrayList<>();
201 220 for (String action : plugin.getActions().split(",")) {
202   - getComponent(action).ifPresent(v -> result.add(v));
  221 + getComponent(action).ifPresent(result::add);
203 222 }
204 223 return result;
205 224 } else {
... ...
... ... @@ -62,7 +62,7 @@ cluster:
62 62 # Plugins configuration parameters
63 63 plugins:
64 64 # Comma seperated package list used during classpath scanning for plugins
65   - scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions}"
  65 + scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}"
66 66
67 67 # JWT Token parameters
68 68 security.jwt:
... ... @@ -215,6 +215,12 @@ actors:
215 215 termination.delay: "${ACTORS_RULE_TERMINATION_DELAY:30000}"
216 216 # Errors for particular actor are persisted once per specified amount of milliseconds
217 217 error_persist_frequency: "${ACTORS_RULE_ERROR_FREQUENCY:3000}"
  218 + chain:
  219 + # Errors for particular actor are persisted once per specified amount of milliseconds
  220 + error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}"
  221 + node:
  222 + # Errors for particular actor are persisted once per specified amount of milliseconds
  223 + error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}"
218 224 statistics:
219 225 # Enable/disable actor statistics
220 226 enabled: "${ACTORS_STATISTICS_ENABLED:true}"
... ...
... ... @@ -96,6 +96,8 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC
96 96 @Slf4j
97 97 public abstract class AbstractControllerTest {
98 98
  99 + protected ObjectMapper mapper = new ObjectMapper();
  100 +
99 101 protected static final String TEST_TENANT_NAME = "TEST TENANT";
100 102
101 103 protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org";
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.thingsboard.server.common.data.DataConstants;
  20 +import org.thingsboard.server.common.data.Event;
  21 +import org.thingsboard.server.common.data.id.EntityId;
  22 +import org.thingsboard.server.common.data.id.RuleChainId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.page.TimePageData;
  25 +import org.thingsboard.server.common.data.page.TimePageLink;
  26 +import org.thingsboard.server.common.data.rule.RuleChain;
  27 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  28 +
  29 +/**
  30 + * Created by ashvayka on 20.03.18.
  31 + */
  32 +public class AbstractRuleEngineControllerTest extends AbstractControllerTest {
  33 +
  34 + protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception {
  35 + return doPost("/api/ruleChain", ruleChain, RuleChain.class);
  36 + }
  37 +
  38 + protected RuleChain getRuleChain(RuleChainId ruleChainId) throws Exception {
  39 + return doGet("/api/ruleChain/" + ruleChainId.getId().toString(), RuleChain.class);
  40 + }
  41 +
  42 + protected RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMD) throws Exception {
  43 + return doPost("/api/ruleChain/metadata", ruleChainMD, RuleChainMetaData.class);
  44 + }
  45 +
  46 + protected RuleChainMetaData getRuleChainMetaData(RuleChainId ruleChainId) throws Exception {
  47 + return doGet("/api/ruleChain/metadata/" + ruleChainId.getId().toString(), RuleChainMetaData.class);
  48 + }
  49 +
  50 + protected TimePageData<Event> getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception {
  51 + TimePageLink pageLink = new TimePageLink(limit);
  52 + return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&",
  53 + new TypeReference<TimePageData<Event>>() {
  54 + }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG, tenantId.getId());
  55 + }
  56 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.rules;
  17 +
  18 +import org.junit.ClassRule;
  19 +import org.junit.extensions.cpsuite.ClasspathSuite;
  20 +import org.junit.runner.RunWith;
  21 +import org.thingsboard.server.dao.CustomSqlUnit;
  22 +
  23 +import java.util.Arrays;
  24 +
  25 +@RunWith(ClasspathSuite.class)
  26 +@ClasspathSuite.ClassnameFilters({
  27 + "org.thingsboard.server.rules.flow.*Test"})
  28 +public class RuleEngineSqlTestSuite {
  29 +
  30 + @ClassRule
  31 + public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
  32 + Arrays.asList("sql/schema.sql", "sql/system-data.sql"),
  33 + "sql/drop-all-tables.sql",
  34 + "sql-test.properties");
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.rules.flow;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import lombok.Data;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.junit.After;
  22 +import org.junit.Assert;
  23 +import org.junit.Before;
  24 +import org.junit.Test;
  25 +import org.springframework.beans.factory.annotation.Autowired;
  26 +import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
  27 +import org.thingsboard.server.actors.service.ActorService;
  28 +import org.thingsboard.server.common.data.*;
  29 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  30 +import org.thingsboard.server.common.data.kv.StringDataEntry;
  31 +import org.thingsboard.server.common.data.page.TimePageData;
  32 +import org.thingsboard.server.common.data.rule.RuleChain;
  33 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  34 +import org.thingsboard.server.common.data.rule.RuleNode;
  35 +import org.thingsboard.server.common.data.security.Authority;
  36 +import org.thingsboard.server.common.msg.TbMsg;
  37 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  38 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
  39 +import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
  40 +import org.thingsboard.server.dao.attributes.AttributesService;
  41 +import org.thingsboard.server.dao.rule.RuleChainService;
  42 +
  43 +import java.util.Arrays;
  44 +import java.util.Collections;
  45 +
  46 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  47 +
  48 +/**
  49 + * @author Valerii Sosliuk
  50 + */
  51 +@Slf4j
  52 +public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRuleEngineControllerTest {
  53 +
  54 + protected Tenant savedTenant;
  55 + protected User tenantAdmin;
  56 +
  57 + @Autowired
  58 + protected ActorService actorService;
  59 +
  60 + @Autowired
  61 + protected AttributesService attributesService;
  62 +
  63 + @Autowired
  64 + protected RuleChainService ruleChainService;
  65 +
  66 + @Before
  67 + public void beforeTest() throws Exception {
  68 + loginSysAdmin();
  69 +
  70 + Tenant tenant = new Tenant();
  71 + tenant.setTitle("My tenant");
  72 + savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  73 + Assert.assertNotNull(savedTenant);
  74 +
  75 + tenantAdmin = new User();
  76 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  77 + tenantAdmin.setTenantId(savedTenant.getId());
  78 + tenantAdmin.setEmail("tenant2@thingsboard.org");
  79 + tenantAdmin.setFirstName("Joe");
  80 + tenantAdmin.setLastName("Downs");
  81 +
  82 + createUserAndLogin(tenantAdmin, "testPassword1");
  83 + }
  84 +
  85 + @After
  86 + public void afterTest() throws Exception {
  87 + loginSysAdmin();
  88 + if (savedTenant != null) {
  89 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
  90 + }
  91 + }
  92 +
  93 + @Test
  94 + public void testRuleChainWithTwoRules() throws Exception {
  95 + // Creating Rule Chain
  96 + RuleChain ruleChain = new RuleChain();
  97 + ruleChain.setName("Simple Rule Chain");
  98 + ruleChain.setTenantId(savedTenant.getId());
  99 + ruleChain.setRoot(true);
  100 + ruleChain.setDebugMode(true);
  101 + ruleChain = saveRuleChain(ruleChain);
  102 + Assert.assertNull(ruleChain.getFirstRuleNodeId());
  103 +
  104 + RuleChainMetaData metaData = new RuleChainMetaData();
  105 + metaData.setRuleChainId(ruleChain.getId());
  106 +
  107 + RuleNode ruleNode1 = new RuleNode();
  108 + ruleNode1.setName("Simple Rule Node 1");
  109 + ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
  110 + ruleNode1.setDebugMode(true);
  111 + TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration();
  112 + configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
  113 + ruleNode1.setConfiguration(mapper.valueToTree(configuration1));
  114 +
  115 + RuleNode ruleNode2 = new RuleNode();
  116 + ruleNode2.setName("Simple Rule Node 2");
  117 + ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
  118 + ruleNode2.setDebugMode(true);
  119 + TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration();
  120 + configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2"));
  121 + ruleNode2.setConfiguration(mapper.valueToTree(configuration2));
  122 +
  123 +
  124 + metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2));
  125 + metaData.setFirstNodeIndex(0);
  126 + metaData.addConnectionInfo(0, 1, "Success");
  127 + metaData = saveRuleChainMetaData(metaData);
  128 + Assert.assertNotNull(metaData);
  129 +
  130 + ruleChain = getRuleChain(ruleChain.getId());
  131 + Assert.assertNotNull(ruleChain.getFirstRuleNodeId());
  132 +
  133 + // Saving the device
  134 + Device device = new Device();
  135 + device.setName("My device");
  136 + device.setType("default");
  137 + device = doPost("/api/device", device, Device.class);
  138 +
  139 + attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
  140 + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis())));
  141 + attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
  142 + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis())));
  143 +
  144 +
  145 + Thread.sleep(1000);
  146 +
  147 + // Pushing Message to the system
  148 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(),
  149 + "CUSTOM",
  150 + device.getId(),
  151 + new TbMsgMetaData(),
  152 + new byte[]{});
  153 + actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
  154 +
  155 + Thread.sleep(3000);
  156 +
  157 + TimePageData<Event> events = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
  158 +
  159 + Assert.assertEquals(2, events.getData().size());
  160 +
  161 + Event inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
  162 + Assert.assertEquals(ruleChain.getFirstRuleNodeId(), inEvent.getEntityId());
  163 + Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
  164 +
  165 + Event outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
  166 + Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
  167 + Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
  168 +
  169 + Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText());
  170 +
  171 + RuleChain finalRuleChain = ruleChain;
  172 + RuleNode lastRuleNode = metaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get();
  173 +
  174 + events = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000);
  175 +
  176 + Assert.assertEquals(2, events.getData().size());
  177 +
  178 + inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
  179 + Assert.assertEquals(lastRuleNode.getId(), inEvent.getEntityId());
  180 + Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
  181 +
  182 + outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
  183 + Assert.assertEquals(lastRuleNode.getId(), outEvent.getEntityId());
  184 + Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
  185 +
  186 + Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText());
  187 + Assert.assertEquals("serverAttributeValue2", outEvent.getBody().get("metadata").get("ss.serverAttributeKey2").asText());
  188 + }
  189 +
  190 +}
... ...
application/src/test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java renamed from dao/src/test/java/org/thingsboard/server/dao/service/rule/sql/RuleServiceSqlTest.java
... ... @@ -13,11 +13,14 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.dao.service.rule.sql;
  16 +package org.thingsboard.server.rules.flow;
17 17
18 18 import org.thingsboard.server.dao.service.DaoSqlTest;
19   -import org.thingsboard.server.dao.service.rule.BaseRuleServiceTest;
  19 +import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest;
20 20
  21 +/**
  22 + * Created by Valerii Sosliuk on 8/22/2017.
  23 + */
21 24 @DaoSqlTest
22   -public class RuleServiceSqlTest extends BaseRuleServiceTest {
  25 +public class RuleEngineFlowSqlIntegrationTest extends AbstractRuleEngineFlowIntegrationTest {
23 26 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.rules.lifecycle;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.junit.After;
  21 +import org.junit.Assert;
  22 +import org.junit.Before;
  23 +import org.junit.Test;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
  26 +import org.thingsboard.server.actors.service.ActorService;
  27 +import org.thingsboard.server.common.data.DataConstants;
  28 +import org.thingsboard.server.common.data.Device;
  29 +import org.thingsboard.server.common.data.Event;
  30 +import org.thingsboard.server.common.data.Tenant;
  31 +import org.thingsboard.server.common.data.User;
  32 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  33 +import org.thingsboard.server.common.data.kv.StringDataEntry;
  34 +import org.thingsboard.server.common.data.page.TimePageData;
  35 +import org.thingsboard.server.common.data.rule.RuleChain;
  36 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  37 +import org.thingsboard.server.common.data.rule.RuleNode;
  38 +import org.thingsboard.server.common.data.security.Authority;
  39 +import org.thingsboard.server.common.msg.TbMsg;
  40 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  41 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
  42 +import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
  43 +import org.thingsboard.server.dao.attributes.AttributesService;
  44 +
  45 +import java.util.Collections;
  46 +
  47 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  48 +
  49 +/**
  50 + * @author Valerii Sosliuk
  51 + */
  52 +@Slf4j
  53 +public abstract class AbstractRuleEngineLifecycleIntegrationTest extends AbstractRuleEngineControllerTest {
  54 +
  55 + protected Tenant savedTenant;
  56 + protected User tenantAdmin;
  57 +
  58 + @Autowired
  59 + protected ActorService actorService;
  60 +
  61 + @Autowired
  62 + protected AttributesService attributesService;
  63 +
  64 + @Before
  65 + public void beforeTest() throws Exception {
  66 + loginSysAdmin();
  67 +
  68 + Tenant tenant = new Tenant();
  69 + tenant.setTitle("My tenant");
  70 + savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  71 + Assert.assertNotNull(savedTenant);
  72 +
  73 + tenantAdmin = new User();
  74 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  75 + tenantAdmin.setTenantId(savedTenant.getId());
  76 + tenantAdmin.setEmail("tenant2@thingsboard.org");
  77 + tenantAdmin.setFirstName("Joe");
  78 + tenantAdmin.setLastName("Downs");
  79 +
  80 + createUserAndLogin(tenantAdmin, "testPassword1");
  81 + }
  82 +
  83 + @After
  84 + public void afterTest() throws Exception {
  85 + loginSysAdmin();
  86 + if (savedTenant != null) {
  87 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
  88 + }
  89 + }
  90 +
  91 + @Test
  92 + public void testRuleChainWithOneRule() throws Exception {
  93 + // Creating Rule Chain
  94 + RuleChain ruleChain = new RuleChain();
  95 + ruleChain.setName("Simple Rule Chain");
  96 + ruleChain.setTenantId(savedTenant.getId());
  97 + ruleChain.setRoot(true);
  98 + ruleChain.setDebugMode(true);
  99 + ruleChain = saveRuleChain(ruleChain);
  100 + Assert.assertNull(ruleChain.getFirstRuleNodeId());
  101 +
  102 + RuleChainMetaData metaData = new RuleChainMetaData();
  103 + metaData.setRuleChainId(ruleChain.getId());
  104 +
  105 + RuleNode ruleNode = new RuleNode();
  106 + ruleNode.setName("Simple Rule Node");
  107 + ruleNode.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName());
  108 + ruleNode.setDebugMode(true);
  109 + TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration();
  110 + configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey"));
  111 + ruleNode.setConfiguration(mapper.valueToTree(configuration));
  112 +
  113 + metaData.setNodes(Collections.singletonList(ruleNode));
  114 + metaData.setFirstNodeIndex(0);
  115 +
  116 + metaData = saveRuleChainMetaData(metaData);
  117 + Assert.assertNotNull(metaData);
  118 +
  119 + ruleChain = getRuleChain(ruleChain.getId());
  120 + Assert.assertNotNull(ruleChain.getFirstRuleNodeId());
  121 +
  122 + // Saving the device
  123 + Device device = new Device();
  124 + device.setName("My device");
  125 + device.setType("default");
  126 + device = doPost("/api/device", device, Device.class);
  127 +
  128 + attributesService.save(device.getId(), DataConstants.SERVER_SCOPE,
  129 + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis())));
  130 +
  131 + Thread.sleep(1000);
  132 +
  133 + // Pushing Message to the system
  134 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(),
  135 + "CUSTOM",
  136 + device.getId(),
  137 + new TbMsgMetaData(),
  138 + new byte[]{});
  139 + actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
  140 +
  141 + Thread.sleep(3000);
  142 +
  143 + TimePageData<Event> events = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
  144 +
  145 + Assert.assertEquals(2, events.getData().size());
  146 +
  147 + Event inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
  148 + Assert.assertEquals(ruleChain.getFirstRuleNodeId(), inEvent.getEntityId());
  149 + Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
  150 +
  151 + Event outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
  152 + Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
  153 + Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
  154 +
  155 + Assert.assertEquals("serverAttributeValue", outEvent.getBody().get("metadata").get("ss.serverAttributeKey").asText());
  156 + }
  157 +
  158 +}
... ...
application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java
... ... @@ -13,29 +13,14 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.server.rules.lifecycle;
17 17
18   -import akka.actor.ActorRef;
19   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
20   -import org.thingsboard.server.extensions.api.device.DeviceMetaData;
  18 +import org.thingsboard.server.dao.service.DaoSqlTest;
  19 +import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest;
21 20
22 21 /**
23   - * Immutable part of chain processing data;
24   - *
25   - * @author ashvayka
  22 + * Created by Valerii Sosliuk on 8/22/2017.
26 23 */
27   -public final class ChainProcessingMetaData {
28   -
29   - final RuleActorChain chain;
30   - final ToDeviceActorMsg inMsg;
31   - final ActorRef originator;
32   - final DeviceMetaData deviceMetaData;
33   -
34   - public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceMetaData deviceMetaData, ActorRef originator) {
35   - super();
36   - this.chain = chain;
37   - this.inMsg = inMsg;
38   - this.originator = originator;
39   - this.deviceMetaData = deviceMetaData;
40   - }
  24 +@DaoSqlTest
  25 +public class RuleEngineLifecycleSqlIntegrationTest extends AbstractRuleEngineLifecycleIntegrationTest {
41 26 }
... ...
... ... @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
23 23
24 24 import com.fasterxml.jackson.databind.JsonNode;
25 25
26   -public class Customer extends ContactBased<CustomerId> implements HasName {
  26 +public class Customer extends ContactBased<CustomerId> implements HasName, HasTenantId {
27 27
28 28 private static final long serialVersionUID = -1599722990298929275L;
29 29
... ...
... ... @@ -37,7 +37,12 @@ public class DataConstants {
37 37 public static final String ERROR = "ERROR";
38 38 public static final String LC_EVENT = "LC_EVENT";
39 39 public static final String STATS = "STATS";
  40 + public static final String DEBUG = "DEBUG";
40 41
41 42 public static final String ONEWAY = "ONEWAY";
42 43 public static final String TWOWAY = "TWOWAY";
  44 +
  45 + public static final String IN = "IN";
  46 + public static final String OUT = "OUT";
  47 +
43 48 }
... ...
... ... @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
23 23 import com.fasterxml.jackson.databind.JsonNode;
24 24
25 25 @EqualsAndHashCode(callSuper = true)
26   -public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName {
  26 +public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName, HasTenantId, HasCustomerId {
27 27
28 28 private static final long serialVersionUID = 2807343040519543363L;
29 29
... ...
common/data/src/main/java/org/thingsboard/server/common/data/HasCustomerId.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java
... ... @@ -13,8 +13,11 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.server.common.data;
17 17
18   -public class CompoundRuleActorChain {
  18 +import org.thingsboard.server.common.data.id.CustomerId;
19 19
  20 +public interface HasCustomerId {
  21 +
  22 + CustomerId getCustomerId();
20 23 }
... ...
common/data/src/main/java/org/thingsboard/server/common/data/HasTenantId.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java
... ... @@ -13,12 +13,11 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.server.common.data;
17 17
18   -public interface RuleActorChain {
  18 +import org.thingsboard.server.common.data.id.TenantId;
19 19
20   - int size();
21   -
22   - RuleActorMetaData getRuleActorMd(int index);
  20 +public interface HasTenantId {
23 21
  22 + TenantId getTenantId();
24 23 }
... ...
... ... @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.security.Authority;
25 25 import com.fasterxml.jackson.databind.JsonNode;
26 26
27 27 @EqualsAndHashCode(callSuper = true)
28   -public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName {
  28 +public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId {
29 29
30 30 private static final long serialVersionUID = 8250339805336035966L;
31 31
... ...
... ... @@ -22,6 +22,7 @@ import lombok.Builder;
22 22 import lombok.Data;
23 23 import org.thingsboard.server.common.data.BaseData;
24 24 import org.thingsboard.server.common.data.HasName;
  25 +import org.thingsboard.server.common.data.HasTenantId;
25 26 import org.thingsboard.server.common.data.id.EntityId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28
... ... @@ -31,7 +32,7 @@ import org.thingsboard.server.common.data.id.TenantId;
31 32 @Data
32 33 @Builder
33 34 @AllArgsConstructor
34   -public class Alarm extends BaseData<AlarmId> implements HasName {
  35 +public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId {
35 36
36 37 private TenantId tenantId;
37 38 private String type;
... ...
... ... @@ -17,16 +17,13 @@ package org.thingsboard.server.common.data.asset;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.EqualsAndHashCode;
20   -import org.thingsboard.server.common.data.HasAdditionalInfo;
21   -import org.thingsboard.server.common.data.HasName;
22   -import org.thingsboard.server.common.data.SearchTextBased;
23   -import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  20 +import org.thingsboard.server.common.data.*;
24 21 import org.thingsboard.server.common.data.id.AssetId;
25 22 import org.thingsboard.server.common.data.id.CustomerId;
26 23 import org.thingsboard.server.common.data.id.TenantId;
27 24
28 25 @EqualsAndHashCode(callSuper = true)
29   -public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName {
  26 +public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId {
30 27
31 28 private static final long serialVersionUID = 2807343040519543363L;
32 29
... ...
... ... @@ -20,6 +20,7 @@ import java.util.List;
20 20 import java.util.NoSuchElementException;
21 21
22 22 import org.thingsboard.server.common.data.SearchTextBased;
  23 +import org.thingsboard.server.common.data.id.EntityId;
23 24 import org.thingsboard.server.common.data.id.UUIDBased;
24 25
25 26 public class PageDataIterable<T extends SearchTextBased<? extends UUIDBased>> implements Iterable<T>, Iterator<T> {
... ...
... ... @@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin;
20 20 */
21 21 public enum ComponentType {
22 22
23   - FILTER, PROCESSOR, ACTION, PLUGIN
  23 + ENRICHMENT, FILTER, TRANSFORMATION, ACTION, OLD_ACTION, PLUGIN
24 24
25 25 }
... ...
... ... @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
21 21 import lombok.EqualsAndHashCode;
22 22 import lombok.extern.slf4j.Slf4j;
23 23 import org.thingsboard.server.common.data.HasName;
  24 +import org.thingsboard.server.common.data.HasTenantId;
24 25 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
25 26 import org.thingsboard.server.common.data.id.PluginId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -32,7 +33,7 @@ import java.io.IOException;
32 33
33 34 @EqualsAndHashCode(callSuper = true)
34 35 @Slf4j
35   -public class PluginMetaData extends SearchTextBasedWithAdditionalInfo<PluginId> implements HasName {
  36 +public class PluginMetaData extends SearchTextBasedWithAdditionalInfo<PluginId> implements HasName, HasTenantId {
36 37
37 38 private static final long serialVersionUID = 1L;
38 39
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.rule;
  17 +
  18 +import lombok.Data;
  19 +
  20 +/**
  21 + * Created by ashvayka on 21.03.18.
  22 + */
  23 +@Data
  24 +public class NodeConnectionInfo {
  25 + private int fromIndex;
  26 + private int toIndex;
  27 + private String type;
  28 +}
... ...
... ... @@ -21,6 +21,7 @@ import lombok.Data;
21 21 import lombok.EqualsAndHashCode;
22 22 import lombok.extern.slf4j.Slf4j;
23 23 import org.thingsboard.server.common.data.HasName;
  24 +import org.thingsboard.server.common.data.HasTenantId;
24 25 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
25 26 import org.thingsboard.server.common.data.id.RuleChainId;
26 27 import org.thingsboard.server.common.data.id.RuleNodeId;
... ... @@ -29,7 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId;
29 30 @Data
30 31 @EqualsAndHashCode(callSuper = true)
31 32 @Slf4j
32   -public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> implements HasName {
  33 +public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> implements HasName, HasTenantId {
33 34
34 35 private static final long serialVersionUID = -5656679015121935465L;
35 36
... ... @@ -37,6 +38,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
37 38 private String name;
38 39 private RuleNodeId firstRuleNodeId;
39 40 private boolean root;
  41 + private boolean debugMode;
40 42 private transient JsonNode configuration;
41 43 @JsonIgnore
42 44 private byte[] configurationBytes;
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.rule;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.data.id.RuleChainId;
  21 +
  22 +/**
  23 + * Created by ashvayka on 21.03.18.
  24 + */
  25 +@Data
  26 +public class RuleChainConnectionInfo {
  27 + private int fromIndex;
  28 + private RuleChainId targetRuleChainId;
  29 + private JsonNode additionalInfo;
  30 + private String type;
  31 +}
... ...
... ... @@ -59,20 +59,4 @@ public class RuleChainMetaData {
59 59 }
60 60 ruleChainConnections.add(connectionInfo);
61 61 }
62   -
63   - @Data
64   - public static class NodeConnectionInfo {
65   - private int fromIndex;
66   - private int toIndex;
67   - private String type;
68   - }
69   -
70   - @Data
71   - public static class RuleChainConnectionInfo {
72   - private int fromIndex;
73   - private RuleChainId targetRuleChainId;
74   - private JsonNode additionalInfo;
75   - private String type;
76   - }
77   -
78 62 }
... ...
... ... @@ -23,6 +23,7 @@ import lombok.Data;
23 23 import lombok.EqualsAndHashCode;
24 24 import lombok.extern.slf4j.Slf4j;
25 25 import org.thingsboard.server.common.data.HasName;
  26 +import org.thingsboard.server.common.data.HasTenantId;
26 27 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
27 28 import org.thingsboard.server.common.data.id.RuleId;
28 29 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -31,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
31 32 @Data
32 33 @EqualsAndHashCode(callSuper = true)
33 34 @Slf4j
34   -public class RuleMetaData extends SearchTextBasedWithAdditionalInfo<RuleId> implements HasName {
  35 +public class RuleMetaData extends SearchTextBasedWithAdditionalInfo<RuleId> implements HasName, HasTenantId {
35 36
36 37 private static final long serialVersionUID = -5656679015122935465L;
37 38
... ...
... ... @@ -34,6 +34,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
34 34
35 35 private String type;
36 36 private String name;
  37 + private boolean debugMode;
37 38 private transient JsonNode configuration;
38 39 @JsonIgnore
39 40 private byte[] configurationBytes;
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.msg;
  17 +
  18 +/**
  19 + * Created by ashvayka on 15.03.18.
  20 + */
  21 +public enum MsgType {
  22 +
  23 + /**
  24 + * ADDED/UPDATED/DELETED events for main entities.
  25 + *
  26 + * @See {@link org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg}
  27 + */
  28 + COMPONENT_LIFE_CYCLE_MSG,
  29 +
  30 + /**
  31 + * Misc messages from the REST API/SERVICE layer to the new rule engine.
  32 + *
  33 + * @See {@link org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg}
  34 + */
  35 + SERVICE_TO_RULE_ENGINE_MSG,
  36 +
  37 +
  38 + SESSION_TO_DEVICE_ACTOR_MSG,
  39 + DEVICE_ACTOR_TO_SESSION_MSG,
  40 +
  41 +
  42 + /**
  43 + * Message that is sent by RuleChainActor to RuleActor with command to process TbMsg.
  44 + */
  45 + RULE_CHAIN_TO_RULE_MSG,
  46 +
  47 + /**
  48 + * Message that is sent by RuleActor to RuleChainActor with command to process TbMsg by next nodes in chain.
  49 + */
  50 + RULE_TO_RULE_CHAIN_TELL_NEXT_MSG,
  51 +
  52 + /**
  53 + * Message that is sent by RuleActor implementation to RuleActor itself to log the error.
  54 + */
  55 + RULE_TO_SELF_ERROR_MSG,
  56 +
  57 +}
... ...
common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java
... ... @@ -13,18 +13,13 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
17   -
18   -import org.thingsboard.server.actors.shared.ActorTerminationMsg;
19   -import org.thingsboard.server.common.data.id.PluginId;
20   -import org.thingsboard.server.common.data.id.RuleId;
  16 +package org.thingsboard.server.common.msg;
21 17
22 18 /**
23   - * @author Andrew Shvayka
  19 + * Created by ashvayka on 15.03.18.
24 20 */
25   -public class RuleTerminationMsg extends ActorTerminationMsg<RuleId> {
  21 +public interface TbActorMsg {
  22 +
  23 + MsgType getMsgType();
26 24
27   - public RuleTerminationMsg(RuleId id) {
28   - super(id);
29   - }
30 25 }
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.common.msg;
17 17
18 18 import com.google.protobuf.ByteString;
19 19 import com.google.protobuf.InvalidProtocolBufferException;
  20 +import lombok.AllArgsConstructor;
20 21 import lombok.Data;
21 22 import org.thingsboard.server.common.data.id.EntityId;
22 23 import org.thingsboard.server.common.data.id.EntityIdFactory;
... ... @@ -30,15 +31,25 @@ import java.util.UUID;
30 31 * Created by ashvayka on 13.01.18.
31 32 */
32 33 @Data
  34 +@AllArgsConstructor
33 35 public final class TbMsg implements Serializable {
34 36
35 37 private final UUID id;
36 38 private final String type;
37 39 private final EntityId originator;
38 40 private final TbMsgMetaData metaData;
39   -
  41 + private final TbMsgDataType dataType;
40 42 private final byte[] data;
41 43
  44 + public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, byte[] data) {
  45 + this.id = id;
  46 + this.type = type;
  47 + this.originator = originator;
  48 + this.metaData = metaData;
  49 + this.dataType = TbMsgDataType.JSON;
  50 + this.data = data;
  51 + }
  52 +
42 53 public static ByteBuffer toBytes(TbMsg msg) {
43 54 MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder();
44 55 builder.setId(msg.getId().toString());
... ... @@ -49,11 +60,10 @@ public final class TbMsg implements Serializable {
49 60 }
50 61
51 62 if (msg.getMetaData() != null) {
52   - MsgProtos.TbMsgProto.TbMsgMetaDataProto.Builder metadataBuilder = MsgProtos.TbMsgProto.TbMsgMetaDataProto.newBuilder();
53   - metadataBuilder.putAllData(msg.getMetaData().getData());
54   - builder.addMetaData(metadataBuilder.build());
  63 + builder.setMetaData(MsgProtos.TbMsgMetaDataProto.newBuilder().putAllData(msg.getMetaData().getData()).build());
55 64 }
56 65
  66 + builder.setDataType(msg.getDataType().ordinal());
57 67 builder.setData(ByteString.copyFrom(msg.getData()));
58 68 byte[] bytes = builder.build().toByteArray();
59 69 return ByteBuffer.wrap(bytes);
... ... @@ -62,19 +72,19 @@ public final class TbMsg implements Serializable {
62 72 public static TbMsg fromBytes(ByteBuffer buffer) {
63 73 try {
64 74 MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array());
65   - TbMsgMetaData metaData = new TbMsgMetaData();
66   - if (proto.getMetaDataCount() > 0) {
67   - metaData.setData(proto.getMetaData(0).getDataMap());
68   - }
69   -
70   - EntityId entityId = null;
71   - if (proto.getEntityId() != null) {
72   - entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId());
73   - }
74   -
75   - return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, proto.getData().toByteArray());
  75 + TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap());
  76 + EntityId entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId());
  77 + TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
  78 + return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData().toByteArray());
76 79 } catch (InvalidProtocolBufferException e) {
77 80 throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
78 81 }
79 82 }
  83 +
  84 + public TbMsg copy() {
  85 + int dataSize = data.length;
  86 + byte[] dataCopy = new byte[dataSize];
  87 + System.arraycopy( data, 0, dataCopy, 0, data.length );
  88 + return new TbMsg(id, type, originator, metaData.copy(), dataType, dataCopy);
  89 + }
80 90 }
... ...
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgDataType.java renamed from common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java
... ... @@ -15,24 +15,12 @@
15 15 */
16 16 package org.thingsboard.server.common.msg;
17 17
18   -import org.thingsboard.server.common.data.rule.Scope;
19   -import org.thingsboard.server.common.data.rule.RuleType;
20   -import org.thingsboard.server.common.msg.aware.RuleAwareMsg;
21   -
22 18 /**
23   - * Message that is used to deliver some data to the rule instance.
24   - * For example: aggregated statistics or command decoded from http request.
25   - *
26   - * @author ashvayka
27   - *
28   - * @param <V> - payload
  19 + * Created by ashvayka on 15.03.18.
29 20 */
30   -public interface RuleMsg<V> extends RuleAwareMsg {
  21 +public enum TbMsgDataType {
  22 +
  23 + // Do not change ordering. We use ordinal to save some bytes on serialization
  24 + JSON, TEXT, BINARY;
31 25
32   - Scope getRuleLevel();
33   -
34   - RuleType getRuleType();
35   -
36   - V getPayload();
37   -
38 26 }
... ...
... ... @@ -15,9 +15,12 @@
15 15 */
16 16 package org.thingsboard.server.common.msg;
17 17
  18 +import lombok.AllArgsConstructor;
18 19 import lombok.Data;
  20 +import lombok.NoArgsConstructor;
19 21
20 22 import java.io.Serializable;
  23 +import java.util.HashMap;
21 24 import java.util.Map;
22 25 import java.util.concurrent.ConcurrentHashMap;
23 26
... ... @@ -25,10 +28,15 @@ import java.util.concurrent.ConcurrentHashMap;
25 28 * Created by ashvayka on 13.01.18.
26 29 */
27 30 @Data
  31 +@NoArgsConstructor
28 32 public final class TbMsgMetaData implements Serializable {
29 33
30 34 private Map<String, String> data = new ConcurrentHashMap<>();
31 35
  36 + TbMsgMetaData(Map<String, String> data) {
  37 + this.data = data;
  38 + }
  39 +
32 40 public String getValue(String key) {
33 41 return data.get(key);
34 42 }
... ... @@ -37,4 +45,7 @@ public final class TbMsgMetaData implements Serializable {
37 45 data.put(key, value);
38 46 }
39 47
  48 + public TbMsgMetaData copy() {
  49 + return new TbMsgMetaData(new ConcurrentHashMap<>(data));
  50 + }
40 51 }
... ...
... ... @@ -15,14 +15,14 @@
15 15 */
16 16 package org.thingsboard.server.common.msg.plugin;
17 17
18   -import lombok.Data;
19 18 import lombok.Getter;
20 19 import lombok.ToString;
21   -import org.thingsboard.server.common.data.id.PluginId;
22   -import org.thingsboard.server.common.data.id.RuleId;
23   -import org.thingsboard.server.common.data.id.TenantId;
  20 +import org.thingsboard.server.common.data.EntityType;
  21 +import org.thingsboard.server.common.data.id.*;
24 22 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
25   -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
  23 +import org.thingsboard.server.common.data.rule.RuleChain;
  24 +import org.thingsboard.server.common.msg.MsgType;
  25 +import org.thingsboard.server.common.msg.TbActorMsg;
26 26 import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
27 27 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
28 28
... ... @@ -32,34 +32,34 @@ import java.util.Optional;
32 32 * @author Andrew Shvayka
33 33 */
34 34 @ToString
35   -public class ComponentLifecycleMsg implements TenantAwareMsg, ToAllNodesMsg {
  35 +public class ComponentLifecycleMsg implements TbActorMsg, TenantAwareMsg, ToAllNodesMsg {
36 36 @Getter
37 37 private final TenantId tenantId;
38   - private final PluginId pluginId;
39   - private final RuleId ruleId;
  38 + @Getter
  39 + private final EntityId entityId;
40 40 @Getter
41 41 private final ComponentLifecycleEvent event;
42 42
43   - public static ComponentLifecycleMsg forPlugin(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent event) {
44   - return new ComponentLifecycleMsg(tenantId, pluginId, null, event);
45   - }
46   -
47   - public static ComponentLifecycleMsg forRule(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent event) {
48   - return new ComponentLifecycleMsg(tenantId, null, ruleId, event);
49   - }
50   -
51   - private ComponentLifecycleMsg(TenantId tenantId, PluginId pluginId, RuleId ruleId, ComponentLifecycleEvent event) {
  43 + public ComponentLifecycleMsg(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent event) {
52 44 this.tenantId = tenantId;
53   - this.pluginId = pluginId;
54   - this.ruleId = ruleId;
  45 + this.entityId = entityId;
55 46 this.event = event;
56 47 }
57 48
58 49 public Optional<PluginId> getPluginId() {
59   - return Optional.ofNullable(pluginId);
  50 + return entityId.getEntityType() == EntityType.PLUGIN ? Optional.of((PluginId) entityId) : Optional.empty();
60 51 }
61 52
62 53 public Optional<RuleId> getRuleId() {
63   - return Optional.ofNullable(ruleId);
  54 + return entityId.getEntityType() == EntityType.RULE ? Optional.of((RuleId) entityId) : Optional.empty();
  55 + }
  56 +
  57 + public Optional<RuleChainId> getRuleChainId() {
  58 + return entityId.getEntityType() == EntityType.RULE_CHAIN ? Optional.of((RuleChainId) entityId) : Optional.empty();
  59 + }
  60 +
  61 + @Override
  62 + public MsgType getMsgType() {
  63 + return MsgType.COMPONENT_LIFE_CYCLE_MSG;
64 64 }
65 65 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.msg.system;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.TenantId;
  20 +import org.thingsboard.server.common.msg.MsgType;
  21 +import org.thingsboard.server.common.msg.TbActorMsg;
  22 +import org.thingsboard.server.common.msg.TbMsg;
  23 +
  24 +/**
  25 + * Created by ashvayka on 15.03.18.
  26 + */
  27 +@Data
  28 +public final class ServiceToRuleEngineMsg implements TbActorMsg {
  29 +
  30 + private final TenantId tenantId;
  31 + private final TbMsg tbMsg;
  32 +
  33 + @Override
  34 + public MsgType getMsgType() {
  35 + return MsgType.SERVICE_TO_RULE_ENGINE_MSG;
  36 + }
  37 +}
... ...
... ... @@ -19,6 +19,9 @@ package msgqueue;
19 19 option java_package = "org.thingsboard.server.common.msg.gen";
20 20 option java_outer_classname = "MsgProtos";
21 21
  22 +message TbMsgMetaDataProto {
  23 + map<string, string> data = 1;
  24 +}
22 25
23 26 message TbMsgProto {
24 27 string id = 1;
... ... @@ -26,11 +29,8 @@ message TbMsgProto {
26 29 string entityType = 3;
27 30 string entityId = 4;
28 31
29   - message TbMsgMetaDataProto {
30   - map<string, string> data = 1;
31   - }
  32 + TbMsgMetaDataProto metaData = 5;
32 33
33   - repeated TbMsgMetaDataProto metaData = 5;
34   -
35   - bytes data = 6;
  34 + int32 dataType = 6;
  35 + bytes data = 7;
36 36 }
\ No newline at end of file
... ...
... ... @@ -332,6 +332,8 @@ public class ModelConstants {
332 332 public static final String EVENT_BY_TYPE_AND_ID_VIEW_NAME = "event_by_type_and_id";
333 333 public static final String EVENT_BY_ID_VIEW_NAME = "event_by_id";
334 334
  335 + public static final String DEBUG_MODE = "debug_mode";
  336 +
335 337 /**
336 338 * Cassandra rule chain constants.
337 339 */
... ...
... ... @@ -22,6 +22,8 @@ import com.datastax.driver.mapping.annotations.PartitionKey;
22 22 import com.datastax.driver.mapping.annotations.Table;
23 23 import com.fasterxml.jackson.databind.JsonNode;
24 24 import lombok.EqualsAndHashCode;
  25 +import lombok.Getter;
  26 +import lombok.Setter;
25 27 import lombok.ToString;
26 28 import org.thingsboard.server.common.data.id.RuleChainId;
27 29 import org.thingsboard.server.common.data.id.RuleNodeId;
... ... @@ -54,6 +56,10 @@ public class RuleChainEntity implements SearchTextEntity<RuleChain> {
54 56 private UUID firstRuleNodeId;
55 57 @Column(name = RULE_CHAIN_ROOT_PROPERTY)
56 58 private boolean root;
  59 + @Getter
  60 + @Setter
  61 + @Column(name = DEBUG_MODE)
  62 + private boolean debugMode;
57 63 @Column(name = RULE_CHAIN_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
58 64 private JsonNode configuration;
59 65 @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
... ... @@ -71,6 +77,7 @@ public class RuleChainEntity implements SearchTextEntity<RuleChain> {
71 77 this.searchText = ruleChain.getName();
72 78 this.firstRuleNodeId = DaoUtil.getId(ruleChain.getFirstRuleNodeId());
73 79 this.root = ruleChain.isRoot();
  80 + this.debugMode = ruleChain.isDebugMode();
74 81 this.configuration = ruleChain.getConfiguration();
75 82 this.additionalInfo = ruleChain.getAdditionalInfo();
76 83 }
... ... @@ -157,6 +164,7 @@ public class RuleChainEntity implements SearchTextEntity<RuleChain> {
157 164 ruleChain.setFirstRuleNodeId(new RuleNodeId(this.firstRuleNodeId));
158 165 }
159 166 ruleChain.setRoot(this.root);
  167 + ruleChain.setDebugMode(this.debugMode);
160 168 ruleChain.setConfiguration(this.configuration);
161 169 ruleChain.setAdditionalInfo(this.additionalInfo);
162 170 return ruleChain;
... ...
... ... @@ -21,6 +21,8 @@ import com.datastax.driver.mapping.annotations.PartitionKey;
21 21 import com.datastax.driver.mapping.annotations.Table;
22 22 import com.fasterxml.jackson.databind.JsonNode;
23 23 import lombok.EqualsAndHashCode;
  24 +import lombok.Getter;
  25 +import lombok.Setter;
24 26 import lombok.ToString;
25 27 import org.thingsboard.server.common.data.id.RuleNodeId;
26 28 import org.thingsboard.server.common.data.rule.RuleNode;
... ... @@ -49,6 +51,11 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
49 51 private JsonNode configuration;
50 52 @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
51 53 private JsonNode additionalInfo;
  54 + @Getter
  55 + @Setter
  56 + @Column(name = DEBUG_MODE)
  57 + private boolean debugMode;
  58 +
52 59
53 60 public RuleNodeEntity() {
54 61 }
... ... @@ -59,6 +66,7 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
59 66 }
60 67 this.type = ruleNode.getType();
61 68 this.name = ruleNode.getName();
  69 + this.debugMode = ruleNode.isDebugMode();
62 70 this.searchText = ruleNode.getName();
63 71 this.configuration = ruleNode.getConfiguration();
64 72 this.additionalInfo = ruleNode.getAdditionalInfo();
... ... @@ -126,6 +134,7 @@ public class RuleNodeEntity implements SearchTextEntity<RuleNode> {
126 134 ruleNode.setCreatedTime(UUIDs.unixTimestamp(id));
127 135 ruleNode.setType(this.type);
128 136 ruleNode.setName(this.name);
  137 + ruleNode.setDebugMode(this.debugMode);
129 138 ruleNode.setConfiguration(this.configuration);
130 139 ruleNode.setAdditionalInfo(this.additionalInfo);
131 140 return ruleNode;
... ...
... ... @@ -58,6 +58,9 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
58 58 @Column(name = ModelConstants.RULE_CHAIN_ROOT_PROPERTY)
59 59 private boolean root;
60 60
  61 + @Column(name = ModelConstants.DEBUG_MODE)
  62 + private boolean debugMode;
  63 +
61 64 @Type(type = "json")
62 65 @Column(name = ModelConstants.RULE_CHAIN_CONFIGURATION_PROPERTY)
63 66 private JsonNode configuration;
... ... @@ -80,6 +83,7 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
80 83 this.firstRuleNodeId = UUIDConverter.fromTimeUUID(ruleChain.getFirstRuleNodeId().getId());
81 84 }
82 85 this.root = ruleChain.isRoot();
  86 + this.debugMode = ruleChain.isDebugMode();
83 87 this.configuration = ruleChain.getConfiguration();
84 88 this.additionalInfo = ruleChain.getAdditionalInfo();
85 89 }
... ... @@ -104,6 +108,7 @@ public class RuleChainEntity extends BaseSqlEntity<RuleChain> implements SearchT
104 108 ruleChain.setFirstRuleNodeId(new RuleNodeId(UUIDConverter.fromString(firstRuleNodeId)));
105 109 }
106 110 ruleChain.setRoot(root);
  111 + ruleChain.setDebugMode(debugMode);
107 112 ruleChain.setConfiguration(configuration);
108 113 ruleChain.setAdditionalInfo(additionalInfo);
109 114 return ruleChain;
... ...
... ... @@ -56,6 +56,9 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
56 56 @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY)
57 57 private JsonNode additionalInfo;
58 58
  59 + @Column(name = ModelConstants.DEBUG_MODE)
  60 + private boolean debugMode;
  61 +
59 62 public RuleNodeEntity() {
60 63 }
61 64
... ... @@ -65,6 +68,7 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
65 68 }
66 69 this.type = ruleNode.getType();
67 70 this.name = ruleNode.getName();
  71 + this.debugMode = ruleNode.isDebugMode();
68 72 this.searchText = ruleNode.getName();
69 73 this.configuration = ruleNode.getConfiguration();
70 74 this.additionalInfo = ruleNode.getAdditionalInfo();
... ... @@ -86,6 +90,7 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
86 90 ruleNode.setCreatedTime(UUIDs.unixTimestamp(getId()));
87 91 ruleNode.setType(type);
88 92 ruleNode.setName(name);
  93 + ruleNode.setDebugMode(debugMode);
89 94 ruleNode.setConfiguration(configuration);
90 95 ruleNode.setAdditionalInfo(additionalInfo);
91 96 return ruleNode;
... ...
... ... @@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
32 32 import org.springframework.context.annotation.Bean;
33 33 import org.springframework.context.annotation.ComponentScan;
34 34 import org.thingsboard.server.common.msg.TbMsg;
  35 +import org.thingsboard.server.common.msg.TbMsgDataType;
35 36 import org.thingsboard.server.common.msg.TbMsgMetaData;
36 37
37 38 import javax.annotation.Nullable;
... ... @@ -125,7 +126,7 @@ public class QueueBenchmark implements CommandLineRunner {
125 126 TbMsgMetaData metaData = new TbMsgMetaData();
126 127 metaData.putValue("key", "value");
127 128 String dataStr = "someContent";
128   - return new TbMsg(UUIDs.timeBased(), "type", null, metaData, dataStr.getBytes());
  129 + return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr.getBytes());
129 130 }
130 131
131 132 @Bean
... ...
... ... @@ -31,7 +31,9 @@ import org.thingsboard.server.common.data.page.TextPageData;
31 31 import org.thingsboard.server.common.data.page.TextPageLink;
32 32 import org.thingsboard.server.common.data.relation.EntityRelation;
33 33 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  34 +import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
34 35 import org.thingsboard.server.common.data.rule.RuleChain;
  36 +import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
35 37 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
36 38 import org.thingsboard.server.common.data.rule.RuleNode;
37 39 import org.thingsboard.server.dao.entity.AbstractEntityService;
... ... @@ -148,7 +150,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
148 150 ruleChainDao.save(ruleChain);
149 151 }
150 152 if (ruleChainMetaData.getConnections() != null) {
151   - for (RuleChainMetaData.NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) {
  153 + for (NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) {
152 154 EntityId from = nodes.get(nodeConnection.getFromIndex()).getId();
153 155 EntityId to = nodes.get(nodeConnection.getToIndex()).getId();
154 156 String type = nodeConnection.getType();
... ... @@ -161,7 +163,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
161 163 }
162 164 }
163 165 if (ruleChainMetaData.getRuleChainConnections() != null) {
164   - for (RuleChainMetaData.RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) {
  166 + for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) {
165 167 EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId();
166 168 EntityId to = nodeToRuleChainConnection.getTargetRuleChainId();
167 169 String type = nodeToRuleChainConnection.getType();
... ... @@ -220,6 +222,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
220 222 }
221 223
222 224 @Override
  225 + public RuleNode findRuleNodeById(RuleNodeId ruleNodeId) {
  226 + Validator.validateId(ruleNodeId, "Incorrect rule node id for search request.");
  227 + return ruleNodeDao.findById(ruleNodeId.getId());
  228 + }
  229 +
  230 + @Override
223 231 public ListenableFuture<RuleChain> findRuleChainByIdAsync(RuleChainId ruleChainId) {
224 232 Validator.validateId(ruleChainId, "Incorrect rule chain id for search request.");
225 233 return ruleChainDao.findByIdAsync(ruleChainId.getId());
... ... @@ -308,7 +316,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
308 316
309 317 private void createRelation(EntityRelation relation) throws ExecutionException, InterruptedException {
310 318 log.debug("Creating relation: {}", relation);
311   - relationService.saveRelationAsync(relation).get();
  319 + relationService.saveRelation(relation);
312 320 }
313 321
314 322 private DataValidator<RuleChain> ruleChainValidator =
... ... @@ -325,7 +333,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
325 333 }
326 334 if (ruleChain.isRoot()) {
327 335 RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
328   - if (ruleChain.getId() == null || !ruleChain.getId().equals(rootRuleChain.getId())) {
  336 + if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) {
329 337 throw new DataValidationException("Another root rule chain is present in scope of current tenant!");
330 338 }
331 339 }
... ...
... ... @@ -16,7 +16,6 @@
16 16 package org.thingsboard.server.dao.rule;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19   -import com.fasterxml.jackson.databind.node.ArrayNode;
20 19 import com.google.common.util.concurrent.ListenableFuture;
21 20 import lombok.extern.slf4j.Slf4j;
22 21 import org.apache.commons.lang3.StringUtils;
... ... @@ -67,67 +66,7 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic
67 66
68 67 @Override
69 68 public RuleMetaData saveRule(RuleMetaData rule) {
70   - ruleValidator.validate(rule);
71   - if (rule.getTenantId() == null) {
72   - log.trace("Save system rule metadata with predefined id {}", systemTenantId);
73   - rule.setTenantId(systemTenantId);
74   - }
75   - if (rule.getId() != null) {
76   - RuleMetaData oldVersion = ruleDao.findById(rule.getId());
77   - if (rule.getState() == null) {
78   - rule.setState(oldVersion.getState());
79   - } else if (rule.getState() != oldVersion.getState()) {
80   - throw new IncorrectParameterException("Use Activate/Suspend method to control state of the rule!");
81   - }
82   - } else {
83   - if (rule.getState() == null) {
84   - rule.setState(ComponentLifecycleState.SUSPENDED);
85   - } else if (rule.getState() != ComponentLifecycleState.SUSPENDED) {
86   - throw new IncorrectParameterException("Use Activate/Suspend method to control state of the rule!");
87   - }
88   - }
89   -
90   - validateFilters(rule.getFilters());
91   - if (rule.getProcessor() != null && !rule.getProcessor().isNull()) {
92   - validateComponentJson(rule.getProcessor(), ComponentType.PROCESSOR);
93   - }
94   - if (rule.getAction() != null && !rule.getAction().isNull()) {
95   - validateComponentJson(rule.getAction(), ComponentType.ACTION);
96   - }
97   - validateRuleAndPluginState(rule);
98   - return ruleDao.save(rule);
99   - }
100   -
101   - private void validateFilters(JsonNode filtersJson) {
102   - if (filtersJson == null || filtersJson.isNull()) {
103   - throw new IncorrectParameterException("Rule filters are required!");
104   - }
105   - if (!filtersJson.isArray()) {
106   - throw new IncorrectParameterException("Filters json is not an array!");
107   - }
108   - ArrayNode filtersArray = (ArrayNode) filtersJson;
109   - for (int i = 0; i < filtersArray.size(); i++) {
110   - validateComponentJson(filtersArray.get(i), ComponentType.FILTER);
111   - }
112   - }
113   -
114   - private void validateComponentJson(JsonNode json, ComponentType type) {
115   - if (json == null || json.isNull()) {
116   - throw new IncorrectParameterException(type.name() + " is required!");
117   - }
118   - String clazz = getIfValid(type.name(), json, "clazz", JsonNode::isTextual, JsonNode::asText);
119   - String name = getIfValid(type.name(), json, "name", JsonNode::isTextual, JsonNode::asText);
120   - JsonNode configuration = getIfValid(type.name(), json, "configuration", JsonNode::isObject, node -> node);
121   - ComponentDescriptor descriptor = componentDescriptorService.findByClazz(clazz);
122   - if (descriptor == null) {
123   - throw new IncorrectParameterException(type.name() + " clazz " + clazz + " is not a valid component!");
124   - }
125   - if (descriptor.getType() != type) {
126   - throw new IncorrectParameterException("Clazz " + clazz + " is not a valid " + type.name() + " component!");
127   - }
128   - if (!componentDescriptorService.validate(descriptor, configuration)) {
129   - throw new IncorrectParameterException(type.name() + " configuration is not valid!");
130   - }
  69 + throw new RuntimeException("Not supported since v1.5!");
131 70 }
132 71
133 72 private void validateRuleAndPluginState(RuleMetaData rule) {
... ...
... ... @@ -42,6 +42,8 @@ public interface RuleChainService {
42 42
43 43 RuleChain findRuleChainById(RuleChainId ruleChainId);
44 44
  45 + RuleNode findRuleNodeById(RuleNodeId ruleNodeId);
  46 +
45 47 ListenableFuture<RuleChain> findRuleChainByIdAsync(RuleChainId ruleChainId);
46 48
47 49 RuleChain getRootTenantRuleChain(TenantId tenantId);
... ...
... ... @@ -669,6 +669,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_chain (
669 669 search_text text,
670 670 first_rule_node_id uuid,
671 671 root boolean,
  672 + debug_mode boolean,
672 673 configuration text,
673 674 additional_info text,
674 675 PRIMARY KEY (id, tenant_id)
... ... @@ -685,6 +686,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
685 686 id uuid,
686 687 type text,
687 688 name text,
  689 + debug_mode boolean,
688 690 search_text text,
689 691 configuration text,
690 692 additional_info text,
... ...
... ... @@ -263,6 +263,7 @@ CREATE TABLE IF NOT EXISTS rule_chain (
263 263 name varchar(255),
264 264 first_rule_node_id varchar(31),
265 265 root boolean,
  266 + debug_mode boolean,
266 267 search_text varchar(255),
267 268 tenant_id varchar(31)
268 269 );
... ... @@ -273,5 +274,6 @@ CREATE TABLE IF NOT EXISTS rule_node (
273 274 configuration varchar(10000000),
274 275 type varchar(255),
275 276 name varchar(255),
  277 + debug_mode boolean,
276 278 search_text varchar(255)
277 279 );
... ...
... ... @@ -217,10 +217,10 @@ public abstract class AbstractServiceTest {
217 217 ruleMetaData.setWeight(weight);
218 218 ruleMetaData.setPluginToken(pluginToken);
219 219
220   - ruleMetaData.setAction(createNode(ComponentScope.TENANT, ComponentType.ACTION,
  220 + ruleMetaData.setAction(createNode(ComponentScope.TENANT, ComponentType.OLD_ACTION,
221 221 "org.thingsboard.component.ActionTest", "TestJsonDescriptor.json", "TestJsonData.json"));
222   - ruleMetaData.setProcessor(createNode(ComponentScope.TENANT, ComponentType.PROCESSOR,
223   - "org.thingsboard.component.ProcessorTest", "TestJsonDescriptor.json", "TestJsonData.json"));
  222 +// ruleMetaData.setProcessor(createNode(ComponentScope.TENANT, ComponentType.PROCESSOR,
  223 +// "org.thingsboard.component.ProcessorTest", "TestJsonDescriptor.json", "TestJsonData.json"));
224 224 ruleMetaData.setFilters(mapper.createArrayNode().add(
225 225 createNode(ComponentScope.TENANT, ComponentType.FILTER,
226 226 "org.thingsboard.component.FilterTest", "TestJsonDescriptor.json", "TestJsonData.json")
... ...
... ... @@ -33,8 +33,8 @@ public class UnprocessedMsgFilterTest {
33 33 public void acknowledgedMsgsAreFilteredOut() {
34 34 UUID id1 = UUID.randomUUID();
35 35 UUID id2 = UUID.randomUUID();
36   - TbMsg msg1 = new TbMsg(id1, "T", null, null, null);
37   - TbMsg msg2 = new TbMsg(id2, "T", null, null, null);
  36 + TbMsg msg1 = new TbMsg(id1, "T", null, null, null, null);
  37 + TbMsg msg2 = new TbMsg(id2, "T", null, null, null, null);
38 38 List<TbMsg> msgs = Lists.newArrayList(msg1, msg2);
39 39 List<MsgAck> acks = Lists.newArrayList(new MsgAck(id2, UUID.randomUUID(), 1L, 1L));
40 40 Collection<TbMsg> actual = msgFilter.filter(msgs, acks);
... ...
... ... @@ -24,6 +24,7 @@ import org.junit.Test;
24 24 import org.springframework.beans.factory.annotation.Autowired;
25 25 import org.thingsboard.server.common.data.id.DeviceId;
26 26 import org.thingsboard.server.common.msg.TbMsg;
  27 +import org.thingsboard.server.common.msg.TbMsgDataType;
27 28 import org.thingsboard.server.common.msg.TbMsgMetaData;
28 29 import org.thingsboard.server.dao.service.AbstractServiceTest;
29 30 import org.thingsboard.server.dao.service.DaoNoSqlTest;
... ... @@ -44,7 +45,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
44 45
45 46 @Test
46 47 public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException {
47   - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]);
  48 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, new byte[4]);
48 49 UUID nodeId = UUIDs.timeBased();
49 50 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L);
50 51 future.get();
... ... @@ -54,7 +55,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
54 55
55 56 @Test
56 57 public void expiredMsgsAreNotReturned() throws ExecutionException, InterruptedException {
57   - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]);
  58 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, new byte[4]);
58 59 UUID nodeId = UUIDs.timeBased();
59 60 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 2L, 2L, 2L);
60 61 future.get();
... ... @@ -67,7 +68,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
67 68 TbMsgMetaData metaData = new TbMsgMetaData();
68 69 metaData.putValue("key", "value");
69 70 String dataStr = "someContent";
70   - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, dataStr.getBytes());
  71 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, TbMsgDataType.JSON, dataStr.getBytes());
71 72 UUID nodeId = UUIDs.timeBased();
72 73 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L);
73 74 future.get();
... ...
1   -/**
2   - * Copyright © 2016-2018 The Thingsboard Authors
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package org.thingsboard.server.dao.service.rule;
17   -
18   -import com.datastax.driver.core.utils.UUIDs;
19   -import org.junit.Assert;
20   -import org.junit.Test;
21   -import org.thingsboard.server.common.data.id.TenantId;
22   -import org.thingsboard.server.common.data.page.TextPageData;
23   -import org.thingsboard.server.common.data.page.TextPageLink;
24   -import org.thingsboard.server.common.data.plugin.PluginMetaData;
25   -import org.thingsboard.server.common.data.rule.RuleMetaData;
26   -import org.thingsboard.server.dao.model.ModelConstants;
27   -import org.thingsboard.server.dao.service.AbstractServiceTest;
28   -
29   -import java.util.List;
30   -import java.util.concurrent.ThreadLocalRandom;
31   -
32   -public abstract class BaseRuleServiceTest extends AbstractServiceTest {
33   -
34   - @Test
35   - public void saveRule() throws Exception {
36   - PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt());
37   - pluginService.savePlugin(plugin);
38   - RuleMetaData ruleMetaData = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken()));
39   - Assert.assertNotNull(ruleMetaData.getId());
40   - Assert.assertNotNull(ruleMetaData.getAdditionalInfo());
41   - ruleMetaData.setAdditionalInfo(mapper.readTree("{\"description\":\"test\"}"));
42   - RuleMetaData newRuleMetaData = ruleService.saveRule(ruleMetaData);
43   - Assert.assertEquals(ruleMetaData.getAdditionalInfo(), newRuleMetaData.getAdditionalInfo());
44   - }
45   -
46   - @Test
47   - public void findRuleById() throws Exception {
48   - PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt());
49   - pluginService.savePlugin(plugin);
50   -
51   - RuleMetaData expected = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken()));
52   - Assert.assertNotNull(expected.getId());
53   - RuleMetaData found = ruleService.findRuleById(expected.getId());
54   - Assert.assertEquals(expected, found);
55   - }
56   -
57   - @Test
58   - public void findPluginRules() throws Exception {
59   - TenantId tenantIdA = new TenantId(UUIDs.timeBased());
60   - TenantId tenantIdB = new TenantId(UUIDs.timeBased());
61   -
62   - PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt());
63   - PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt());
64   - pluginService.savePlugin(pluginA);
65   - pluginService.savePlugin(pluginB);
66   -
67   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
68   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
69   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
70   -
71   - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
72   - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
73   -
74   - List<RuleMetaData> foundA = ruleService.findPluginRules(pluginA.getApiToken());
75   - Assert.assertEquals(3, foundA.size());
76   -
77   - List<RuleMetaData> foundB = ruleService.findPluginRules(pluginB.getApiToken());
78   - Assert.assertEquals(2, foundB.size());
79   - }
80   -
81   - @Test
82   - public void findSystemRules() throws Exception {
83   - TenantId systemTenant = new TenantId(ModelConstants.NULL_UUID); // system tenant id
84   -
85   - PluginMetaData plugin = generatePlugin(systemTenant, "testPluginToken" + ThreadLocalRandom.current().nextInt());
86   - pluginService.savePlugin(plugin);
87   - ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken()));
88   - ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken()));
89   - ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken()));
90   - TextPageData<RuleMetaData> found = ruleService.findSystemRules(new TextPageLink(100));
91   - Assert.assertEquals(3, found.getData().size());
92   - }
93   -
94   - @Test
95   - public void findTenantRules() throws Exception {
96   - TenantId tenantIdA = new TenantId(UUIDs.timeBased());
97   - TenantId tenantIdB = new TenantId(UUIDs.timeBased());
98   -
99   - PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt());
100   - PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt());
101   - pluginService.savePlugin(pluginA);
102   - pluginService.savePlugin(pluginB);
103   -
104   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
105   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
106   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
107   -
108   - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
109   - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
110   -
111   - TextPageData<RuleMetaData> foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100));
112   - Assert.assertEquals(3, foundA.getData().size());
113   -
114   - TextPageData<RuleMetaData> foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100));
115   - Assert.assertEquals(2, foundB.getData().size());
116   - }
117   -
118   - @Test
119   - public void deleteRuleById() throws Exception {
120   - PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt());
121   - pluginService.savePlugin(plugin);
122   -
123   - RuleMetaData expected = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken()));
124   - Assert.assertNotNull(expected.getId());
125   - RuleMetaData found = ruleService.findRuleById(expected.getId());
126   - Assert.assertEquals(expected, found);
127   - ruleService.deleteRuleById(expected.getId());
128   - found = ruleService.findRuleById(expected.getId());
129   - Assert.assertNull(found);
130   - }
131   -
132   - @Test
133   - public void deleteRulesByTenantId() throws Exception {
134   - TenantId tenantIdA = new TenantId(UUIDs.timeBased());
135   - TenantId tenantIdB = new TenantId(UUIDs.timeBased());
136   -
137   - PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt());
138   - PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt());
139   - pluginService.savePlugin(pluginA);
140   - pluginService.savePlugin(pluginB);
141   -
142   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
143   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
144   - ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
145   -
146   - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
147   - ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
148   -
149   - TextPageData<RuleMetaData> foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100));
150   - Assert.assertEquals(3, foundA.getData().size());
151   -
152   - TextPageData<RuleMetaData> foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100));
153   - Assert.assertEquals(2, foundB.getData().size());
154   -
155   - ruleService.deleteRulesByTenantId(tenantIdA);
156   -
157   - foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100));
158   - Assert.assertEquals(0, foundA.getData().size());
159   -
160   - foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100));
161   - Assert.assertEquals(2, foundB.getData().size());
162   - }
163   -}
\ No newline at end of file
... ... @@ -379,6 +379,11 @@
379 379 <version>${project.version}</version>
380 380 </dependency>
381 381 <dependency>
  382 + <groupId>org.thingsboard.rule-engine</groupId>
  383 + <artifactId>rule-engine-components</artifactId>
  384 + <version>${project.version}</version>
  385 + </dependency>
  386 + <dependency>
382 387 <groupId>org.thingsboard.common</groupId>
383 388 <artifactId>message</artifactId>
384 389 <version>${project.version}</version>
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.api;
  17 +
  18 +import org.thingsboard.server.common.data.plugin.ComponentScope;
  19 +import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration;
  20 +
  21 +import java.lang.annotation.ElementType;
  22 +import java.lang.annotation.Retention;
  23 +import java.lang.annotation.RetentionPolicy;
  24 +import java.lang.annotation.Target;
  25 +
  26 +/**
  27 + * @author Andrew Shvayka
  28 + */
  29 +@Retention(RetentionPolicy.RUNTIME)
  30 +@Target(ElementType.TYPE)
  31 +public @interface ActionNode {
  32 +
  33 + String name();
  34 +
  35 + ComponentScope scope() default ComponentScope.TENANT;
  36 +
  37 + String descriptor() default "EmptyNodeDescriptor.json";
  38 +
  39 + String[] relationTypes() default {"Success","Failure"};
  40 +
  41 + boolean customRelations() default false;
  42 +
  43 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.api;
  17 +
  18 +import org.thingsboard.server.common.data.plugin.ComponentScope;
  19 +import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration;
  20 +
  21 +import java.lang.annotation.ElementType;
  22 +import java.lang.annotation.Retention;
  23 +import java.lang.annotation.RetentionPolicy;
  24 +import java.lang.annotation.Target;
  25 +
  26 +/**
  27 + * @author Andrew Shvayka
  28 + */
  29 +@Retention(RetentionPolicy.RUNTIME)
  30 +@Target(ElementType.TYPE)
  31 +public @interface EnrichmentNode {
  32 +
  33 + String name();
  34 +
  35 + ComponentScope scope() default ComponentScope.TENANT;
  36 +
  37 + String descriptor() default "EmptyNodeDescriptor.json";
  38 +
  39 + String[] relationTypes() default {"Success","Failure"};
  40 +
  41 + boolean customRelations() default false;
  42 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.api;
  17 +
  18 +import org.thingsboard.server.common.data.plugin.ComponentScope;
  19 +import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration;
  20 +
  21 +import java.lang.annotation.ElementType;
  22 +import java.lang.annotation.Retention;
  23 +import java.lang.annotation.RetentionPolicy;
  24 +import java.lang.annotation.Target;
  25 +
  26 +/**
  27 + * @author Andrew Shvayka
  28 + */
  29 +@Retention(RetentionPolicy.RUNTIME)
  30 +@Target(ElementType.TYPE)
  31 +public @interface FilterNode {
  32 +
  33 + String name();
  34 +
  35 + ComponentScope scope() default ComponentScope.TENANT;
  36 +
  37 + String descriptor() default "EmptyNodeDescriptor.json";
  38 +
  39 + String[] relationTypes() default {"Success","Failure"};
  40 +
  41 + boolean customRelations() default false;
  42 +
  43 +}
... ...
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java
... ... @@ -13,21 +13,15 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.rule.engine.api;
17 17
18   -import org.thingsboard.server.actors.ActorSystemContext;
19   -import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
20   -import org.thingsboard.server.common.data.id.RuleId;
  18 +import com.google.common.util.concurrent.ListenableFuture;
21 19
22   -import akka.event.LoggingAdapter;
  20 +import java.util.concurrent.Callable;
23 21
24   -public class RuleContextAwareMsgProcessor extends AbstractContextAwareMsgProcessor {
  22 +public interface ListeningExecutor {
25 23
26   - private final RuleId ruleId;
27   -
28   - protected RuleContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, RuleId ruleId) {
29   - super(systemContext, logger);
30   - this.ruleId = ruleId;
31   - }
  24 + <T> ListenableFuture<T> executeAsync(Callable<T> task);
32 25
  26 + void onDestroy();
33 27 }
... ...
... ... @@ -17,8 +17,19 @@ package org.thingsboard.rule.engine.api;
17 17
18 18 import org.thingsboard.server.common.msg.TbMsg;
19 19 import org.thingsboard.server.common.msg.cluster.ServerAddress;
  20 +import org.thingsboard.server.dao.alarm.AlarmService;
  21 +import org.thingsboard.server.dao.asset.AssetService;
20 22 import org.thingsboard.server.dao.attributes.AttributesService;
  23 +import org.thingsboard.server.dao.customer.CustomerService;
  24 +import org.thingsboard.server.dao.device.DeviceService;
  25 +import org.thingsboard.server.dao.plugin.PluginService;
  26 +import org.thingsboard.server.dao.relation.RelationService;
  27 +import org.thingsboard.server.dao.rule.RuleChainService;
  28 +import org.thingsboard.server.dao.rule.RuleService;
  29 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  30 +import org.thingsboard.server.dao.user.UserService;
21 31
  32 +import java.util.Set;
22 33 import java.util.UUID;
23 34
24 35 /**
... ... @@ -30,6 +41,8 @@ public interface TbContext {
30 41
31 42 void tellNext(TbMsg msg, String relationType);
32 43
  44 + void tellNext(TbMsg msg, Set<String> relationTypes);
  45 +
33 46 void tellSelf(TbMsg msg, long delayMs);
34 47
35 48 void tellOthers(TbMsg msg);
... ... @@ -38,8 +51,30 @@ public interface TbContext {
38 51
39 52 void spawn(TbMsg msg);
40 53
41   - void ack(UUID msg);
  54 + void ack(TbMsg msg);
  55 +
  56 + void tellError(TbMsg msg, Throwable th);
42 57
43 58 AttributesService getAttributesService();
44 59
  60 + CustomerService getCustomerService();
  61 +
  62 + UserService getUserService();
  63 +
  64 + PluginService getPluginService();
  65 +
  66 + AssetService getAssetService();
  67 +
  68 + DeviceService getDeviceService();
  69 +
  70 + AlarmService getAlarmService();
  71 +
  72 + RuleChainService getRuleChainService();
  73 +
  74 + TimeseriesService getTimeseriesService();
  75 +
  76 + RelationService getRelationService();
  77 +
  78 + ListeningExecutor getJsExecutor();
  79 +
45 80 }
... ...
... ... @@ -22,8 +22,8 @@ import lombok.Data;
22 22 * Created by ashvayka on 19.01.18.
23 23 */
24 24 @Data
25   -public class TbNodeConfiguration {
  25 +public final class TbNodeConfiguration {
26 26
27   - private JsonNode data;
  27 + private final JsonNode data;
28 28
29 29 }
... ...
... ... @@ -22,6 +22,10 @@ import com.fasterxml.jackson.core.JsonProcessingException;
22 22 */
23 23 public class TbNodeException extends Exception {
24 24
  25 + public TbNodeException(String message) {
  26 + super(message);
  27 + }
  28 +
25 29 public TbNodeException(Exception e) {
26 30 super(e);
27 31 }
... ...
... ... @@ -18,5 +18,5 @@ package org.thingsboard.rule.engine.api;
18 18 /**
19 19 * Created by ashvayka on 19.01.18.
20 20 */
21   -public class TbNodeState {
  21 +public final class TbNodeState {
22 22 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.api;
  17 +
  18 +import org.thingsboard.server.common.data.plugin.ComponentScope;
  19 +import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration;
  20 +
  21 +import java.lang.annotation.ElementType;
  22 +import java.lang.annotation.Retention;
  23 +import java.lang.annotation.RetentionPolicy;
  24 +import java.lang.annotation.Target;
  25 +
  26 +/**
  27 + * @author Andrew Shvayka
  28 + */
  29 +@Retention(RetentionPolicy.RUNTIME)
  30 +@Target(ElementType.TYPE)
  31 +public @interface TransformationNode {
  32 +
  33 + String name();
  34 +
  35 + ComponentScope scope() default ComponentScope.TENANT;
  36 +
  37 + String descriptor() default "EmptyNodeDescriptor.json";
  38 +
  39 + String[] relationTypes() default {"Success","Failure"};
  40 +
  41 + boolean customRelations() default false;
  42 +
  43 +}
... ...
... ... @@ -88,11 +88,6 @@
88 88 <artifactId>mockito-all</artifactId>
89 89 <scope>test</scope>
90 90 </dependency>
91   - <dependency>
92   - <groupId>org.junit.jupiter</groupId>
93   - <artifactId>junit-jupiter-api</artifactId>
94   - <version>RELEASE</version>
95   - </dependency>
96 91
97 92 <!--<dependency>-->
98 93 <!--<groupId>org.springframework.boot</groupId>-->
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/DonAsynchron.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java
... ... @@ -13,31 +13,33 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.rule.engine;
17 17
18   -public class ComplexRuleActorChain implements RuleActorChain {
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
19 21
20   - private final RuleActorChain systemChain;
21   - private final RuleActorChain tenantChain;
  22 +import javax.annotation.Nullable;
  23 +import java.util.function.Consumer;
22 24
23   - public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) {
24   - super();
25   - this.systemChain = systemChain;
26   - this.tenantChain = tenantChain;
27   - }
  25 +public class DonAsynchron {
28 26
29   - @Override
30   - public int size() {
31   - return systemChain.size() + tenantChain.size();
32   - }
  27 + public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess, Consumer<Throwable> onFailure) {
  28 + Futures.addCallback(future, new FutureCallback<T>() {
  29 + @Override
  30 + public void onSuccess(@Nullable T result) {
  31 + try {
  32 + onSuccess.accept(result);
  33 + } catch (Throwable th) {
  34 + onFailure(th);
  35 + }
33 36
34   - @Override
35   - public RuleActorMetaData getRuleActorMd(int index) {
36   - if (index < systemChain.size()) {
37   - return systemChain.getRuleActorMd(index);
38   - } else {
39   - return tenantChain.getRuleActorMd(index - systemChain.size());
40   - }
41   - }
  37 + }
42 38
  39 + @Override
  40 + public void onFailure(Throwable t) {
  41 + onFailure.accept(t);
  42 + }
  43 + });
  44 + }
43 45 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.filter;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.rule.engine.TbNodeUtils;
  20 +import org.thingsboard.rule.engine.api.*;
  21 +import org.thingsboard.rule.engine.js.NashornJsEngine;
  22 +import org.thingsboard.server.common.msg.TbMsg;
  23 +
  24 +import javax.script.Bindings;
  25 +
  26 +import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  27 +
  28 +@Slf4j
  29 +public class TbJsFilterNode implements TbNode {
  30 +
  31 + private TbJsFilterNodeConfiguration config;
  32 + private NashornJsEngine jsEngine;
  33 +
  34 + @Override
  35 + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
  36 + this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class);
  37 + this.jsEngine = new NashornJsEngine(config.getJsScript());
  38 + }
  39 +
  40 + @Override
  41 + public void onMsg(TbContext ctx, TbMsg msg) {
  42 + ListeningExecutor jsExecutor = ctx.getJsExecutor();
  43 + withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(toBindings(msg))),
  44 + filterResult -> ctx.tellNext(msg, Boolean.toString(filterResult)),
  45 + t -> ctx.tellError(msg, t));
  46 + }
  47 +
  48 + private Bindings toBindings(TbMsg msg) {
  49 + return NashornJsEngine.bindMsg(msg);
  50 + }
  51 +
  52 + @Override
  53 + public void destroy() {
  54 + if (jsEngine != null) {
  55 + jsEngine.destroy();
  56 + }
  57 + }
  58 +}
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java renamed from dao/src/test/java/org/thingsboard/server/dao/service/rule/nosql/RuleServiceNoSqlTest.java
... ... @@ -13,11 +13,12 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.dao.service.rule.nosql;
  16 +package org.thingsboard.rule.engine.filter;
17 17
18   -import org.thingsboard.server.dao.service.DaoNoSqlTest;
19   -import org.thingsboard.server.dao.service.rule.BaseRuleServiceTest;
  18 +import lombok.Data;
20 19
21   -@DaoNoSqlTest
22   -public class RuleServiceNoSqlTest extends BaseRuleServiceTest {
  20 +@Data
  21 +public class TbJsFilterNodeConfiguration {
  22 +
  23 + private String jsScript;
23 24 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.filter;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.rule.engine.TbNodeUtils;
  20 +import org.thingsboard.rule.engine.api.*;
  21 +import org.thingsboard.rule.engine.js.NashornJsEngine;
  22 +import org.thingsboard.server.common.msg.TbMsg;
  23 +
  24 +import javax.script.Bindings;
  25 +import java.util.Set;
  26 +
  27 +import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  28 +
  29 +@Slf4j
  30 +public class TbJsSwitchNode implements TbNode {
  31 +
  32 + private TbJsSwitchNodeConfiguration config;
  33 + private NashornJsEngine jsEngine;
  34 +
  35 + @Override
  36 + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
  37 + this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
  38 + if (config.getAllowedRelations().size() < 1) {
  39 + String message = "Switch node should have at least 1 relation";
  40 + log.error(message);
  41 + throw new IllegalStateException(message);
  42 + }
  43 + if (!config.isRouteToAllWithNoCheck()) {
  44 + this.jsEngine = new NashornJsEngine(config.getJsScript());
  45 + }
  46 + }
  47 +
  48 + @Override
  49 + public void onMsg(TbContext ctx, TbMsg msg) {
  50 + if (config.isRouteToAllWithNoCheck()) {
  51 + ctx.tellNext(msg, config.getAllowedRelations());
  52 + return;
  53 + }
  54 + ListeningExecutor jsExecutor = ctx.getJsExecutor();
  55 + withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))),
  56 + result -> processSwitch(ctx, msg, result),
  57 + t -> ctx.tellError(msg, t));
  58 + }
  59 +
  60 + private void processSwitch(TbContext ctx, TbMsg msg, Set<String> nextRelations) {
  61 + if (validateRelations(nextRelations)) {
  62 + ctx.tellNext(msg, nextRelations);
  63 + } else {
  64 + ctx.tellError(msg, new IllegalStateException("Unsupported relation for switch " + nextRelations));
  65 + }
  66 + }
  67 +
  68 + private boolean validateRelations(Set<String> nextRelations) {
  69 + return config.getAllowedRelations().containsAll(nextRelations);
  70 + }
  71 +
  72 + private Bindings toBindings(TbMsg msg) {
  73 + return NashornJsEngine.bindMsg(msg);
  74 + }
  75 +
  76 + @Override
  77 + public void destroy() {
  78 + if (jsEngine != null) {
  79 + jsEngine.destroy();
  80 + }
  81 + }
  82 +}
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java
... ... @@ -13,27 +13,16 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.rule.engine.filter;
17 17
18   -import java.util.ArrayList;
19   -import java.util.List;
20   -import java.util.Set;
21   -
22   -public class SimpleRuleActorChain implements RuleActorChain {
23   -
24   - private final List<RuleActorMetaData> rules;
  18 +import lombok.Data;
25 19
26   - public SimpleRuleActorChain(Set<RuleActorMetaData> ruleSet) {
27   - rules = new ArrayList<>(ruleSet);
28   - rules.sort(RuleActorMetaData.RULE_ACTOR_MD_COMPARATOR);
29   - }
30   -
31   - public int size() {
32   - return rules.size();
33   - }
  20 +import java.util.Set;
34 21
35   - public RuleActorMetaData getRuleActorMd(int index) {
36   - return rules.get(index);
37   - }
  22 +@Data
  23 +public class TbJsSwitchNodeConfiguration {
38 24
  25 + private String jsScript;
  26 + private Set<String> allowedRelations;
  27 + private boolean routeToAllWithNoCheck;
39 28 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.js;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import com.google.common.util.concurrent.ListeningExecutorService;
  20 +import com.google.common.util.concurrent.MoreExecutors;
  21 +import org.thingsboard.rule.engine.api.ListeningExecutor;
  22 +
  23 +import javax.annotation.PreDestroy;
  24 +import java.util.concurrent.Callable;
  25 +import java.util.concurrent.Executors;
  26 +
  27 +public class JsExecutorService implements ListeningExecutor{
  28 +
  29 + private final ListeningExecutorService service;
  30 +
  31 + public JsExecutorService(int threadCount) {
  32 + this.service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(threadCount));
  33 + }
  34 +
  35 + @Override
  36 + public <T> ListenableFuture<T> executeAsync(Callable<T> task) {
  37 + return service.submit(task);
  38 + }
  39 +
  40 + @PreDestroy
  41 + @Override
  42 + public void onDestroy() {
  43 + service.shutdown();
  44 + }
  45 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.js;
  17 +
  18 +import com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
  21 +import com.google.common.collect.Sets;
  22 +import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
  23 +import jdk.nashorn.api.scripting.ScriptObjectMirror;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.apache.commons.lang3.ArrayUtils;
  26 +import org.thingsboard.server.common.msg.TbMsg;
  27 +
  28 +import javax.script.*;
  29 +import java.util.Collections;
  30 +import java.util.Map;
  31 +import java.util.Set;
  32 +
  33 +
  34 +@Slf4j
  35 +public class NashornJsEngine {
  36 +
  37 + public static final String METADATA = "meta";
  38 + public static final String DATA = "msg";
  39 + private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
  40 +
  41 + private CompiledScript engine;
  42 +
  43 + public NashornJsEngine(String script) {
  44 + engine = compileScript(script);
  45 + }
  46 +
  47 + private static CompiledScript compileScript(String script) {
  48 + ScriptEngine engine = factory.getScriptEngine(new String[]{"--no-java"});
  49 + Compilable compEngine = (Compilable) engine;
  50 + try {
  51 + return compEngine.compile(script);
  52 + } catch (ScriptException e) {
  53 + log.warn("Failed to compile JS script: {}", e.getMessage(), e);
  54 + throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
  55 + }
  56 + }
  57 +
  58 + public static Bindings bindMsg(TbMsg msg) {
  59 + try {
  60 + Bindings bindings = new SimpleBindings();
  61 + bindings.put(METADATA, msg.getMetaData().getData());
  62 +
  63 + if (ArrayUtils.isNotEmpty(msg.getData())) {
  64 + ObjectMapper mapper = new ObjectMapper();
  65 + JsonNode jsonNode = mapper.readTree(msg.getData());
  66 + Map map = mapper.treeToValue(jsonNode, Map.class);
  67 + bindings.put(DATA, map);
  68 + }
  69 +
  70 + return bindings;
  71 + } catch (Throwable th) {
  72 + throw new IllegalArgumentException("Cannot bind js args", th);
  73 + }
  74 + }
  75 +
  76 + private static TbMsg unbindMsg(Bindings bindings, TbMsg msg) throws JsonProcessingException {
  77 + for (Map.Entry<String, String> entry : msg.getMetaData().getData().entrySet()) {
  78 + Object obj = entry.getValue();
  79 + entry.setValue(obj.toString());
  80 + }
  81 +
  82 + Object payload = bindings.get(DATA);
  83 + if (payload != null) {
  84 + ObjectMapper mapper = new ObjectMapper();
  85 + byte[] bytes = mapper.writeValueAsBytes(payload);
  86 + return new TbMsg(msg.getId(), msg.getType(), msg.getOriginator(), msg.getMetaData(), bytes);
  87 + }
  88 +
  89 + return msg;
  90 + }
  91 +
  92 + public TbMsg executeUpdate(Bindings bindings, TbMsg msg) throws ScriptException {
  93 + try {
  94 + engine.eval(bindings);
  95 + return unbindMsg(bindings, msg);
  96 + } catch (Throwable th) {
  97 + th.printStackTrace();
  98 + throw new IllegalArgumentException("Cannot unbind js args", th);
  99 + }
  100 + }
  101 +
  102 + public boolean executeFilter(Bindings bindings) throws ScriptException {
  103 + Object eval = engine.eval(bindings);
  104 + if (eval instanceof Boolean) {
  105 + return (boolean) eval;
  106 + } else {
  107 + log.warn("Wrong result type: {}", eval);
  108 + throw new ScriptException("Wrong result type: " + eval);
  109 + }
  110 + }
  111 +
  112 + public Set<String> executeSwitch(Bindings bindings) throws ScriptException, NoSuchMethodException {
  113 + Object eval = this.engine.eval(bindings);
  114 + if (eval instanceof String) {
  115 + return Collections.singleton((String) eval);
  116 + } else if (eval instanceof ScriptObjectMirror) {
  117 + ScriptObjectMirror mir = (ScriptObjectMirror) eval;
  118 + if (mir.isArray()) {
  119 + Set<String> nextStates = Sets.newHashSet();
  120 + for (Map.Entry<String, Object> entry : mir.entrySet()) {
  121 + if (entry.getValue() instanceof String) {
  122 + nextStates.add((String) entry.getValue());
  123 + } else {
  124 + log.warn("Wrong result type: {}", eval);
  125 + throw new ScriptException("Wrong result type: " + eval);
  126 + }
  127 + }
  128 + return nextStates;
  129 + }
  130 + }
  131 +
  132 + log.warn("Wrong result type: {}", eval);
  133 + throw new ScriptException("Wrong result type: " + eval);
  134 + }
  135 +
  136 + public void destroy() {
  137 + engine = null;
  138 + }
  139 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.metadata;
  17 +
  18 +import com.google.common.base.Function;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import org.thingsboard.rule.engine.TbNodeUtils;
  22 +import org.thingsboard.rule.engine.api.*;
  23 +import org.thingsboard.server.common.data.id.EntityId;
  24 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  25 +import org.thingsboard.server.common.data.kv.KvEntry;
  26 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  27 +import org.thingsboard.server.common.msg.TbMsg;
  28 +
  29 +import java.util.List;
  30 +import java.util.stream.Collectors;
  31 +
  32 +import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  33 +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
  34 +
  35 +public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode {
  36 +
  37 + private TbGetEntityAttrNodeConfiguration config;
  38 +
  39 + @Override
  40 + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
  41 + this.config = TbNodeUtils.convert(configuration, TbGetEntityAttrNodeConfiguration.class);
  42 + }
  43 +
  44 + @Override
  45 + public void onMsg(TbContext ctx, TbMsg msg) {
  46 + try {
  47 + withCallback(
  48 + findEntityAsync(ctx, msg.getOriginator()),
  49 + entityId -> withCallback(
  50 + config.isTelemetry() ? getLatestTelemetry(ctx, entityId) : getAttributesAsync(ctx, entityId),
  51 + attributes -> putAttributesAndTell(ctx, msg, attributes),
  52 + t -> ctx.tellError(msg, t)
  53 + ),
  54 + t -> ctx.tellError(msg, t));
  55 + } catch (Throwable th) {
  56 + ctx.tellError(msg, th);
  57 + }
  58 + }
  59 +
  60 + private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId) {
  61 + ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet());
  62 + return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, ? extends List<KvEntry>>) l ->
  63 + l.stream().map(i -> (KvEntry) i).collect(Collectors.toList()));
  64 + }
  65 +
  66 + private ListenableFuture<List<KvEntry>> getLatestTelemetry(TbContext ctx, EntityId entityId) {
  67 + ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(entityId, config.getAttrMapping().keySet());
  68 + return Futures.transform(latest, (Function<? super List<TsKvEntry>, ? extends List<KvEntry>>) l ->
  69 + l.stream().map(i -> (KvEntry) i).collect(Collectors.toList()));
  70 + }
  71 +
  72 +
  73 + private void putAttributesAndTell(TbContext ctx, TbMsg msg, List<? extends KvEntry> attributes) {
  74 + attributes.forEach(r -> {
  75 + String attrName = config.getAttrMapping().get(r.getKey());
  76 + msg.getMetaData().putValue(attrName, r.getValueAsString());
  77 + });
  78 + ctx.tellNext(msg);
  79 + }
  80 +
  81 + @Override
  82 + public void destroy() {
  83 +
  84 + }
  85 +
  86 + protected abstract ListenableFuture<T> findEntityAsync(TbContext ctx, EntityId originator);
  87 +
  88 + public void setConfig(TbGetEntityAttrNodeConfiguration config) {
  89 + this.config = config;
  90 + }
  91 +
  92 +}
... ...
... ... @@ -15,23 +15,35 @@
15 15 */
16 16 package org.thingsboard.rule.engine.metadata;
17 17
  18 +import com.google.common.base.Function;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
18 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.apache.commons.collections.CollectionUtils;
19 23 import org.thingsboard.rule.engine.TbNodeUtils;
20   -import org.thingsboard.rule.engine.api.*;
21   -import org.thingsboard.server.common.data.DataConstants;
  24 +import org.thingsboard.rule.engine.api.TbContext;
  25 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  26 +import org.thingsboard.rule.engine.api.TbNodeException;
  27 +import org.thingsboard.rule.engine.api.TbNodeState;
  28 +import org.thingsboard.rule.engine.api.TbNode;
  29 +import org.thingsboard.rule.engine.api.EnrichmentNode;
22 30 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  31 +import org.thingsboard.server.common.data.kv.TsKvEntry;
23 32 import org.thingsboard.server.common.msg.TbMsg;
24   -import org.thingsboard.server.dao.attributes.AttributesService;
25 33
26 34 import java.util.List;
27 35
  36 +import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
  37 +import static org.thingsboard.server.common.data.DataConstants.*;
  38 +
28 39 /**
29 40 * Created by ashvayka on 19.01.18.
30 41 */
31 42 @Slf4j
  43 +@EnrichmentNode(name = "Get Attributes Node")
32 44 public class TbGetAttributesNode implements TbNode {
33 45
34   - TbGetAttributesNodeConfiguration config;
  46 + private TbGetAttributesNodeConfiguration config;
35 47
36 48 @Override
37 49 public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
... ... @@ -40,24 +52,40 @@ public class TbGetAttributesNode implements TbNode {
40 52
41 53 @Override
42 54 public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
43   - try {
44   - //TODO: refactor this to work async and fetch attributes from cache.
45   - AttributesService service = ctx.getAttributesService();
46   - fetchAttributes(msg, service, config.getClientAttributeNames(), DataConstants.CLIENT_SCOPE, "cs.");
47   - fetchAttributes(msg, service, config.getServerAttributeNames(), DataConstants.SERVER_SCOPE, "ss.");
48   - fetchAttributes(msg, service, config.getSharedAttributeNames(), DataConstants.SHARED_SCOPE, "shared.");
49   - ctx.tellNext(msg);
50   - } catch (Exception e) {
51   - log.warn("[{}][{}] Failed to fetch attributes", msg.getOriginator(), msg.getId(), e);
52   - throw new TbNodeException(e);
  55 + if (CollectionUtils.isNotEmpty(config.getLatestTsKeyNames())) {
  56 + withCallback(getLatestTelemetry(ctx, msg, config.getLatestTsKeyNames()),
  57 + i -> ctx.tellNext(msg),
  58 + t -> ctx.tellError(msg, t));
  59 + } else {
  60 + ListenableFuture<List<Void>> future = Futures.allAsList(
  61 + putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs."),
  62 + putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared."),
  63 + putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss."));
  64 +
  65 + withCallback(future, i -> ctx.tellNext(msg), t -> ctx.tellError(msg, t));
  66 + }
  67 + }
  68 +
  69 + private ListenableFuture<Void> putAttrAsync(TbContext ctx, TbMsg msg, String scope, List<String> keys, String prefix) {
  70 + if (keys == null) {
  71 + return Futures.immediateFuture(null);
53 72 }
  73 + ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, keys);
  74 + return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, Void>) l -> {
  75 + l.forEach(r -> msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString()));
  76 + return null;
  77 + });
54 78 }
55 79
56   - private void fetchAttributes(TbMsg msg, AttributesService service, List<String> attributeNames, String scope, String prefix) throws InterruptedException, java.util.concurrent.ExecutionException {
57   - if (attributeNames != null && attributeNames.isEmpty()) {
58   - List<AttributeKvEntry> attributes = service.find(msg.getOriginator(), scope, attributeNames).get();
59   - attributes.forEach(attr -> msg.getMetaData().putValue(prefix + attr.getKey(), attr.getValueAsString()));
  80 + private ListenableFuture<Void> getLatestTelemetry(TbContext ctx, TbMsg msg, List<String> keys) {
  81 + if (keys == null) {
  82 + return Futures.immediateFuture(null);
60 83 }
  84 + ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), keys);
  85 + return Futures.transform(latest, (Function<? super List<TsKvEntry>, Void>) l -> {
  86 + l.forEach(r -> msg.getMetaData().putValue(r.getKey(), r.getValueAsString()));
  87 + return null;
  88 + });
61 89 }
62 90
63 91 @Override
... ...
... ... @@ -29,4 +29,6 @@ public class TbGetAttributesNodeConfiguration {
29 29 private List<String> sharedAttributeNames;
30 30 private List<String> serverAttributeNames;
31 31
  32 + private List<String> latestTsKeyNames;
  33 +
32 34 }
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java
... ... @@ -13,22 +13,21 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.rule.engine.metadata;
17 17
18   -public class RulesProcessedMsg {
19   - private final ChainProcessingContext ctx;
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.rule.engine.api.EnrichmentNode;
  20 +import org.thingsboard.rule.engine.api.TbContext;
  21 +import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader;
  22 +import org.thingsboard.server.common.data.id.CustomerId;
  23 +import org.thingsboard.server.common.data.id.EntityId;
20 24
21   - public RulesProcessedMsg(ChainProcessingContext ctx) {
22   - super();
23   - this.ctx = ctx;
24   - }
25   -
26   - public ChainProcessingContext getCtx() {
27   - return ctx;
28   - }
  25 +@EnrichmentNode(name="Get Customer Attributes Node")
  26 +public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {
29 27
30 28 @Override
31   - public String toString() {
32   - return "RulesProcessedMsg [ctx=" + ctx + "]";
  29 + protected ListenableFuture<CustomerId> findEntityAsync(TbContext ctx, EntityId originator) {
  30 + return EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctx, originator);
33 31 }
  32 +
34 33 }
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java renamed from application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java
... ... @@ -13,28 +13,16 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.tenant;
  16 +package org.thingsboard.rule.engine.metadata;
17 17
18   -import org.thingsboard.server.actors.rule.RuleActorChain;
19   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  18 +import lombok.Data;
20 19
21   -public class RuleChainDeviceMsg {
  20 +import java.util.Map;
  21 +import java.util.Optional;
22 22
23   - private final ToDeviceActorMsg toDeviceActorMsg;
24   - private final RuleActorChain ruleChain;
25   -
26   - public RuleChainDeviceMsg(ToDeviceActorMsg toDeviceActorMsg, RuleActorChain ruleChain) {
27   - super();
28   - this.toDeviceActorMsg = toDeviceActorMsg;
29   - this.ruleChain = ruleChain;
30   - }
31   -
32   - public ToDeviceActorMsg getToDeviceActorMsg() {
33   - return toDeviceActorMsg;
34   - }
35   -
36   - public RuleActorChain getRuleChain() {
37   - return ruleChain;
38   - }
  23 +@Data
  24 +public class TbGetEntityAttrNodeConfiguration {
39 25
  26 + private Map<String, String> attrMapping;
  27 + private boolean isTelemetry = false;
40 28 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.metadata;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  20 +
  21 +@Data
  22 +public class TbGetRelatedAttrNodeConfiguration extends TbGetEntityAttrNodeConfiguration {
  23 +
  24 + private String relationType;
  25 + private EntitySearchDirection direction;
  26 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.metadata;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.rule.engine.TbNodeUtils;
  20 +import org.thingsboard.rule.engine.api.TbContext;
  21 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  22 +import org.thingsboard.rule.engine.api.TbNodeException;
  23 +import org.thingsboard.rule.engine.api.TbNodeState;
  24 +import org.thingsboard.rule.engine.api.EnrichmentNode;
  25 +import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader;
  26 +
  27 +import org.thingsboard.server.common.data.id.EntityId;
  28 +
  29 +@EnrichmentNode(name="Get Related Entity Attributes Node")
  30 +public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {
  31 +
  32 + private TbGetRelatedAttrNodeConfiguration config;
  33 +
  34 + @Override
  35 + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
  36 + this.config = TbNodeUtils.convert(configuration, TbGetRelatedAttrNodeConfiguration.class);
  37 + setConfig(config);
  38 + }
  39 +
  40 + @Override
  41 + protected ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator) {
  42 + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, originator, config.getDirection(), config.getRelationType());
  43 + }
  44 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.metadata;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.thingsboard.rule.engine.api.EnrichmentNode;
  21 +import org.thingsboard.rule.engine.api.TbContext;
  22 +import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader;
  23 +import org.thingsboard.server.common.data.id.EntityId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +
  26 +@Slf4j
  27 +@EnrichmentNode(name="Get Tenant Attributes Node")
  28 +public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {
  29 +
  30 + @Override
  31 + protected ListenableFuture<TenantId> findEntityAsync(TbContext ctx, EntityId originator) {
  32 + return EntitiesTenantIdAsyncLoader.findEntityIdAsync(ctx, originator);
  33 + }
  34 +
  35 +}
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java renamed from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNode.java
... ... @@ -15,54 +15,45 @@
15 15 */
16 16 package org.thingsboard.rule.engine.transform;
17 17
  18 +import com.google.common.util.concurrent.ListenableFuture;
18 19 import lombok.extern.slf4j.Slf4j;
19 20 import org.thingsboard.rule.engine.TbNodeUtils;
20 21 import org.thingsboard.rule.engine.api.*;
21   -import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
22   -import org.thingsboard.server.common.data.DataConstants;
23   -import org.thingsboard.server.common.data.kv.AttributeKvEntry;
24 22 import org.thingsboard.server.common.msg.TbMsg;
25   -import org.thingsboard.server.dao.attributes.AttributesService;
26 23
27   -import java.util.List;
  24 +import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
28 25
29 26 /**
30 27 * Created by ashvayka on 19.01.18.
31 28 */
32 29 @Slf4j
33   -public class TbTransformNode implements TbNode {
  30 +public abstract class TbAbstractTransformNode implements TbNode {
34 31
35   - TbGetAttributesNodeConfiguration config;
  32 + private TbTransformNodeConfiguration config;
36 33
37 34 @Override
38 35 public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
39   - this.config = TbNodeUtils.convert(configuration, TbGetAttributesNodeConfiguration.class);
  36 + this.config = TbNodeUtils.convert(configuration, TbTransformNodeConfiguration.class);
40 37 }
41 38
42 39 @Override
43   - public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
44   - try {
45   - //TODO: refactor this to work async and fetch attributes from cache.
46   - AttributesService service = ctx.getAttributesService();
47   - fetchAttributes(msg, service, config.getClientAttributeNames(), DataConstants.CLIENT_SCOPE, "cs.");
48   - fetchAttributes(msg, service, config.getServerAttributeNames(), DataConstants.SERVER_SCOPE, "ss.");
49   - fetchAttributes(msg, service, config.getSharedAttributeNames(), DataConstants.SHARED_SCOPE, "shared.");
50   - ctx.tellNext(msg);
51   - } catch (Exception e) {
52   - log.warn("[{}][{}] Failed to fetch attributes", msg.getOriginator(), msg.getId(), e);
53   - throw new TbNodeException(e);
54   - }
  40 + public void onMsg(TbContext ctx, TbMsg msg) {
  41 + withCallback(transform(ctx, msg),
  42 + m -> routeMsg(ctx, m),
  43 + t -> ctx.tellError(msg, t));
55 44 }
56 45
57   - private void fetchAttributes(TbMsg msg, AttributesService service, List<String> attributeNames, String scope, String prefix) throws InterruptedException, java.util.concurrent.ExecutionException {
58   - if (attributeNames != null && attributeNames.isEmpty()) {
59   - List<AttributeKvEntry> attributes = service.find(msg.getOriginator(), scope, attributeNames).get();
60   - attributes.forEach(attr -> msg.getMetaData().putValue(prefix + attr.getKey(), attr.getValueAsString()));
  46 + protected abstract ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg);
  47 +
  48 + private void routeMsg(TbContext ctx, TbMsg msg) {
  49 + if (config.isStartNewChain()) {
  50 + ctx.spawn(msg);
  51 + } else {
  52 + ctx.tellNext(msg);
61 53 }
62 54 }
63 55
64   - @Override
65   - public void destroy() {
66   -
  56 + public void setConfig(TbTransformNodeConfiguration config) {
  57 + this.config = config;
67 58 }
68 59 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.transform;
  17 +
  18 +import com.google.common.base.Function;
  19 +import com.google.common.collect.Sets;
  20 +import com.google.common.util.concurrent.Futures;
  21 +import com.google.common.util.concurrent.ListenableFuture;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.apache.commons.lang3.StringUtils;
  24 +import org.thingsboard.rule.engine.TbNodeUtils;
  25 +import org.thingsboard.rule.engine.api.TbContext;
  26 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  27 +import org.thingsboard.rule.engine.api.TbNodeException;
  28 +import org.thingsboard.rule.engine.api.TbNodeState;
  29 +import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader;
  30 +import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader;
  31 +import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader;
  32 +import org.thingsboard.server.common.data.id.EntityId;
  33 +import org.thingsboard.server.common.msg.TbMsg;
  34 +
  35 +import java.util.HashSet;
  36 +
  37 +@Slf4j
  38 +public class TbChangeOriginatorNode extends TbAbstractTransformNode {
  39 +
  40 + protected static final String CUSTOMER_SOURCE = "CUSTOMER";
  41 + protected static final String TENANT_SOURCE = "TENANT";
  42 + protected static final String RELATED_SOURCE = "RELATED";
  43 +
  44 + private TbChangeOriginatorNodeConfiguration config;
  45 +
  46 + @Override
  47 + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
  48 + this.config = TbNodeUtils.convert(configuration, TbChangeOriginatorNodeConfiguration.class);
  49 + validateConfig(config);
  50 + setConfig(config);
  51 + }
  52 +
  53 + @Override
  54 + protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) {
  55 + ListenableFuture<? extends EntityId> newOriginator = getNewOriginator(ctx, msg.getOriginator());
  56 + return Futures.transform(newOriginator, (Function<EntityId, TbMsg>) n -> new TbMsg(msg.getId(), msg.getType(), n, msg.getMetaData(), msg.getData()));
  57 + }
  58 +
  59 + private ListenableFuture<? extends EntityId> getNewOriginator(TbContext ctx, EntityId original) {
  60 + switch (config.getOriginatorSource()) {
  61 + case CUSTOMER_SOURCE:
  62 + return EntitiesCustomerIdAsyncLoader.findEntityIdAsync(ctx, original);
  63 + case TENANT_SOURCE:
  64 + return EntitiesTenantIdAsyncLoader.findEntityIdAsync(ctx, original);
  65 + case RELATED_SOURCE:
  66 + return EntitiesRelatedEntityIdAsyncLoader.findEntityAsync(ctx, original, config.getDirection(), config.getRelationType());
  67 + default:
  68 + return Futures.immediateFailedFuture(new IllegalStateException("Unexpected originator source " + config.getOriginatorSource()));
  69 + }
  70 + }
  71 +
  72 + private void validateConfig(TbChangeOriginatorNodeConfiguration conf) {
  73 + HashSet<String> knownSources = Sets.newHashSet(CUSTOMER_SOURCE, TENANT_SOURCE, RELATED_SOURCE);
  74 + if (!knownSources.contains(conf.getOriginatorSource())) {
  75 + log.error("Unsupported source [{}] for TbChangeOriginatorNode", conf.getOriginatorSource());
  76 + throw new IllegalArgumentException("Unsupported source TbChangeOriginatorNode" + conf.getOriginatorSource());
  77 + }
  78 +
  79 + if (conf.getOriginatorSource().equals(RELATED_SOURCE)) {
  80 + if (conf.getDirection() == null || StringUtils.isBlank(conf.getRelationType())) {
  81 + log.error("Related source for TbChangeOriginatorNode should have direction and relationType. Actual [{}] [{}]",
  82 + conf.getDirection(), conf.getRelationType());
  83 + throw new IllegalArgumentException("Wrong config for RElated Source in TbChangeOriginatorNode" + conf.getOriginatorSource());
  84 + }
  85 + }
  86 +
  87 + }
  88 +
  89 + @Override
  90 + public void destroy() {
  91 +
  92 + }
  93 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.transform;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  20 +
  21 +@Data
  22 +public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfiguration{
  23 +
  24 + private String originatorSource;
  25 + private EntitySearchDirection direction;
  26 + private String relationType;
  27 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.transform;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.rule.engine.TbNodeUtils;
  20 +import org.thingsboard.rule.engine.api.TbContext;
  21 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  22 +import org.thingsboard.rule.engine.api.TbNodeException;
  23 +import org.thingsboard.rule.engine.api.TbNodeState;
  24 +import org.thingsboard.rule.engine.js.NashornJsEngine;
  25 +import org.thingsboard.server.common.msg.TbMsg;
  26 +
  27 +import javax.script.Bindings;
  28 +
  29 +public class TbTransformMsgNode extends TbAbstractTransformNode {
  30 +
  31 + private TbTransformMsgNodeConfiguration config;
  32 + private NashornJsEngine jsEngine;
  33 +
  34 + @Override
  35 + public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException {
  36 + this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class);
  37 + this.jsEngine = new NashornJsEngine(config.getJsScript());
  38 + setConfig(config);
  39 + }
  40 +
  41 + @Override
  42 + protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) {
  43 + return ctx.getJsExecutor().executeAsync(() -> jsEngine.executeUpdate(toBindings(msg), msg));
  44 + }
  45 +
  46 + private Bindings toBindings(TbMsg msg) {
  47 + return NashornJsEngine.bindMsg(msg);
  48 + }
  49 +
  50 + @Override
  51 + public void destroy() {
  52 + if (jsEngine != null) {
  53 + jsEngine.destroy();
  54 + }
  55 + }
  56 +}
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java
... ... @@ -13,24 +13,12 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.rule.engine.transform;
17 17
18   -import java.io.Serializable;
19   -import java.util.UUID;
  18 +import lombok.Data;
20 19
21   -public class RuleToPluginTimeoutMsg implements Serializable {
22   -
23   - private static final long serialVersionUID = 1L;
24   -
25   - private final UUID msgId;
26   -
27   - public RuleToPluginTimeoutMsg(UUID msgId) {
28   - super();
29   - this.msgId = msgId;
30   - }
31   -
32   - public UUID getMsgId() {
33   - return msgId;
34   - }
  20 +@Data
  21 +public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration {
35 22
  23 + private String jsScript;
36 24 }
... ...
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNodeConfiguration.java renamed from application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java
... ... @@ -13,19 +13,12 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.actors.rule;
  16 +package org.thingsboard.rule.engine.transform;
17 17
18   -public class RuleProcessingMsg {
  18 +import lombok.Data;
19 19
20   - private final ChainProcessingContext ctx;
21   -
22   - public RuleProcessingMsg(ChainProcessingContext ctx) {
23   - super();
24   - this.ctx = ctx;
25   - }
26   -
27   - public ChainProcessingContext getCtx() {
28   - return ctx;
29   - }
  20 +@Data
  21 +public class TbTransformNodeConfiguration {
30 22
  23 + private boolean startNewChain = false;
31 24 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.util;
  17 +
  18 +import com.google.common.util.concurrent.AsyncFunction;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import org.thingsboard.rule.engine.api.TbContext;
  22 +import org.thingsboard.rule.engine.api.TbNodeException;
  23 +import org.thingsboard.server.common.data.HasCustomerId;
  24 +import org.thingsboard.server.common.data.id.*;
  25 +
  26 +public class EntitiesCustomerIdAsyncLoader {
  27 +
  28 +
  29 + public static ListenableFuture<CustomerId> findEntityIdAsync(TbContext ctx, EntityId original) {
  30 +
  31 + switch (original.getEntityType()) {
  32 + case CUSTOMER:
  33 + return Futures.immediateFuture((CustomerId) original);
  34 + case USER:
  35 + return getCustomerAsync(ctx.getUserService().findUserByIdAsync((UserId) original));
  36 + case ASSET:
  37 + return getCustomerAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) original));
  38 + case DEVICE:
  39 + return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) original));
  40 + default:
  41 + return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original));
  42 + }
  43 + }
  44 +
  45 + private static <T extends HasCustomerId> ListenableFuture<CustomerId> getCustomerAsync(ListenableFuture<T> future) {
  46 + return Futures.transform(future, (AsyncFunction<HasCustomerId, CustomerId>) in -> {
  47 + return in != null ? Futures.immediateFuture(in.getCustomerId())
  48 + : Futures.immediateFailedFuture(new IllegalStateException("Customer not found"));});
  49 + }
  50 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.util;
  17 +
  18 +import com.google.common.util.concurrent.AsyncFunction;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import org.apache.commons.collections.CollectionUtils;
  22 +import org.thingsboard.rule.engine.api.TbContext;
  23 +import org.thingsboard.server.common.data.id.EntityId;
  24 +import org.thingsboard.server.common.data.relation.EntityRelation;
  25 +import org.thingsboard.server.common.data.relation.EntitySearchDirection;
  26 +import org.thingsboard.server.dao.relation.RelationService;
  27 +
  28 +import java.util.List;
  29 +
  30 +import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON;
  31 +
  32 +public class EntitiesRelatedEntityIdAsyncLoader {
  33 +
  34 + public static ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator,
  35 + EntitySearchDirection direction, String relationType) {
  36 + RelationService relationService = ctx.getRelationService();
  37 + if (direction == EntitySearchDirection.FROM) {
  38 + ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByFromAndTypeAsync(originator, relationType, COMMON);
  39 + return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
  40 + r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo())
  41 + : Futures.immediateFailedFuture(new IllegalStateException("Relation not found")));
  42 + } else if (direction == EntitySearchDirection.TO) {
  43 + ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByToAndTypeAsync(originator, relationType, COMMON);
  44 + return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
  45 + r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom())
  46 + : Futures.immediateFailedFuture(new IllegalStateException("Relation not found")));
  47 + }
  48 +
  49 + return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction"));
  50 + }
  51 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.util;
  17 +
  18 +import com.google.common.util.concurrent.AsyncFunction;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import org.thingsboard.rule.engine.api.TbContext;
  22 +import org.thingsboard.rule.engine.api.TbNodeException;
  23 +import org.thingsboard.server.common.data.HasTenantId;
  24 +import org.thingsboard.server.common.data.alarm.AlarmId;
  25 +import org.thingsboard.server.common.data.id.*;
  26 +
  27 +public class EntitiesTenantIdAsyncLoader {
  28 +
  29 + public static ListenableFuture<TenantId> findEntityIdAsync(TbContext ctx, EntityId original) {
  30 +
  31 + switch (original.getEntityType()) {
  32 + case TENANT:
  33 + return Futures.immediateFuture((TenantId) original);
  34 + case CUSTOMER:
  35 + return getTenantAsync(ctx.getCustomerService().findCustomerByIdAsync((CustomerId) original));
  36 + case USER:
  37 + return getTenantAsync(ctx.getUserService().findUserByIdAsync((UserId) original));
  38 + case PLUGIN:
  39 + return getTenantAsync(ctx.getPluginService().findPluginByIdAsync((PluginId) original));
  40 + case ASSET:
  41 + return getTenantAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) original));
  42 + case DEVICE:
  43 + return getTenantAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) original));
  44 + case ALARM:
  45 + return getTenantAsync(ctx.getAlarmService().findAlarmByIdAsync((AlarmId) original));
  46 + case RULE_CHAIN:
  47 + return getTenantAsync(ctx.getRuleChainService().findRuleChainByIdAsync((RuleChainId) original));
  48 + default:
  49 + return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original));
  50 + }
  51 + }
  52 +
  53 + private static <T extends HasTenantId> ListenableFuture<TenantId> getTenantAsync(ListenableFuture<T> future) {
  54 + return Futures.transform(future, (AsyncFunction<HasTenantId, TenantId>) in -> {
  55 + return in != null ? Futures.immediateFuture(in.getTenantId())
  56 + : Futures.immediateFailedFuture(new IllegalStateException("Tenant not found"));});
  57 + }
  58 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.filter;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.util.concurrent.Futures;
  21 +import com.google.common.util.concurrent.ListenableFuture;
  22 +import org.junit.Test;
  23 +import org.junit.runner.RunWith;
  24 +import org.mockito.ArgumentCaptor;
  25 +import org.mockito.Matchers;
  26 +import org.mockito.Mock;
  27 +import org.mockito.runners.MockitoJUnitRunner;
  28 +import org.mockito.stubbing.Answer;
  29 +import org.thingsboard.rule.engine.api.ListeningExecutor;
  30 +import org.thingsboard.rule.engine.api.TbContext;
  31 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  32 +import org.thingsboard.rule.engine.api.TbNodeException;
  33 +import org.thingsboard.server.common.msg.TbMsg;
  34 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  35 +
  36 +import javax.script.ScriptException;
  37 +import java.util.concurrent.Callable;
  38 +
  39 +import static org.junit.Assert.assertEquals;
  40 +import static org.mockito.Mockito.*;
  41 +
  42 +@RunWith(MockitoJUnitRunner.class)
  43 +public class TbJsFilterNodeTest {
  44 +
  45 + private TbJsFilterNode node;
  46 +
  47 + @Mock
  48 + private TbContext ctx;
  49 + @Mock
  50 + private ListeningExecutor executor;
  51 +
  52 + @Test
  53 + public void falseEvaluationDoNotSendMsg() throws TbNodeException {
  54 + initWithScript("10 > 15;");
  55 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
  56 +
  57 + mockJsExecutor();
  58 +
  59 + node.onMsg(ctx, msg);
  60 + verify(ctx).getJsExecutor();
  61 + verify(ctx).tellNext(msg, "false");
  62 + verifyNoMoreInteractions(ctx);
  63 + }
  64 +
  65 + @Test
  66 + public void notValidMsgDataThrowsException() throws TbNodeException {
  67 + initWithScript("10 > 15;");
  68 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), new byte[4]);
  69 +
  70 + when(ctx.getJsExecutor()).thenReturn(executor);
  71 +
  72 + mockJsExecutor();
  73 +
  74 + node.onMsg(ctx, msg);
  75 + verifyError(msg, "Cannot bind js args", IllegalArgumentException.class);
  76 + }
  77 +
  78 + @Test
  79 + public void exceptionInJsThrowsException() throws TbNodeException {
  80 + initWithScript("meta.temp.curr < 15;");
  81 + TbMsgMetaData metaData = new TbMsgMetaData();
  82 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes());
  83 + mockJsExecutor();
  84 +
  85 + node.onMsg(ctx, msg);
  86 + String expectedMessage = "TypeError: Cannot get property \"curr\" of null in <eval> at line number 1";
  87 + verifyError(msg, expectedMessage, ScriptException.class);
  88 + }
  89 +
  90 + @Test(expected = IllegalArgumentException.class)
  91 + public void notValidScriptThrowsException() throws TbNodeException {
  92 + initWithScript("10 > 15 asdq out");
  93 + }
  94 +
  95 + @Test
  96 + public void metadataConditionCanBeFalse() throws TbNodeException {
  97 + initWithScript("meta.humidity < 15;");
  98 + TbMsgMetaData metaData = new TbMsgMetaData();
  99 + metaData.putValue("temp", "10");
  100 + metaData.putValue("humidity", "99");
  101 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes());
  102 + mockJsExecutor();
  103 +
  104 + node.onMsg(ctx, msg);
  105 + verify(ctx).getJsExecutor();
  106 + verify(ctx).tellNext(msg, "false");
  107 + verifyNoMoreInteractions(ctx);
  108 + }
  109 +
  110 + @Test
  111 + public void metadataConditionCanBeTrue() throws TbNodeException {
  112 + initWithScript("meta.temp < 15;");
  113 + TbMsgMetaData metaData = new TbMsgMetaData();
  114 + metaData.putValue("temp", "10");
  115 + metaData.putValue("humidity", "99");
  116 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes());
  117 + mockJsExecutor();
  118 +
  119 + node.onMsg(ctx, msg);
  120 + verify(ctx).getJsExecutor();
  121 + verify(ctx).tellNext(msg, "true");
  122 + }
  123 +
  124 + @Test
  125 + public void msgJsonParsedAndBinded() throws TbNodeException {
  126 + initWithScript("msg.passed < 15 && msg.name === 'Vit' && meta.temp == 10 && msg.bigObj.prop == 42;");
  127 + TbMsgMetaData metaData = new TbMsgMetaData();
  128 + metaData.putValue("temp", "10");
  129 + metaData.putValue("humidity", "99");
  130 + String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
  131 +
  132 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
  133 + mockJsExecutor();
  134 +
  135 + node.onMsg(ctx, msg);
  136 + verify(ctx).getJsExecutor();
  137 + verify(ctx).tellNext(msg, "true");
  138 + }
  139 +
  140 + private void initWithScript(String script) throws TbNodeException {
  141 + TbJsFilterNodeConfiguration config = new TbJsFilterNodeConfiguration();
  142 + config.setJsScript(script);
  143 + ObjectMapper mapper = new ObjectMapper();
  144 + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
  145 +
  146 + node = new TbJsFilterNode();
  147 + node.init(nodeConfiguration, null);
  148 + }
  149 +
  150 + private void mockJsExecutor() {
  151 + when(ctx.getJsExecutor()).thenReturn(executor);
  152 + doAnswer((Answer<ListenableFuture<Boolean>>) invocationOnMock -> {
  153 + try {
  154 + Callable task = (Callable) (invocationOnMock.getArguments())[0];
  155 + return Futures.immediateFuture((Boolean) task.call());
  156 + } catch (Throwable th) {
  157 + return Futures.immediateFailedFuture(th);
  158 + }
  159 + }).when(executor).executeAsync(Matchers.any(Callable.class));
  160 + }
  161 +
  162 + private void verifyError(TbMsg msg, String message, Class expectedClass) {
  163 + ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
  164 + verify(ctx).tellError(same(msg), captor.capture());
  165 +
  166 + Throwable value = captor.getValue();
  167 + assertEquals(expectedClass, value.getClass());
  168 + assertEquals(message, value.getMessage());
  169 + }
  170 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.filter;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.collect.Sets;
  21 +import com.google.common.util.concurrent.Futures;
  22 +import com.google.common.util.concurrent.ListenableFuture;
  23 +import org.junit.Test;
  24 +import org.junit.runner.RunWith;
  25 +import org.mockito.ArgumentCaptor;
  26 +import org.mockito.Matchers;
  27 +import org.mockito.Mock;
  28 +import org.mockito.runners.MockitoJUnitRunner;
  29 +import org.mockito.stubbing.Answer;
  30 +import org.thingsboard.rule.engine.api.ListeningExecutor;
  31 +import org.thingsboard.rule.engine.api.TbContext;
  32 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  33 +import org.thingsboard.rule.engine.api.TbNodeException;
  34 +import org.thingsboard.server.common.msg.TbMsg;
  35 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  36 +
  37 +import java.util.HashSet;
  38 +import java.util.Set;
  39 +import java.util.concurrent.Callable;
  40 +
  41 +import static org.junit.Assert.assertEquals;
  42 +import static org.mockito.Matchers.same;
  43 +import static org.mockito.Mockito.*;
  44 +
  45 +@RunWith(MockitoJUnitRunner.class)
  46 +public class TbJsSwitchNodeTest {
  47 +
  48 + private TbJsSwitchNode node;
  49 +
  50 + @Mock
  51 + private TbContext ctx;
  52 + @Mock
  53 + private ListeningExecutor executor;
  54 +
  55 + @Test
  56 + public void routeToAllDoNotEvaluatesJs() throws TbNodeException {
  57 + HashSet<String> relations = Sets.newHashSet("one", "two");
  58 + initWithScript("test qwerty", relations, true);
  59 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes());
  60 +
  61 + node.onMsg(ctx, msg);
  62 + verify(ctx).tellNext(msg, relations);
  63 + verifyNoMoreInteractions(ctx, executor);
  64 + }
  65 +
  66 + @Test
  67 + public void multipleRoutesAreAllowed() throws TbNodeException {
  68 + String jsCode = "function nextRelation(meta, msg) {\n" +
  69 + " if(msg.passed == 5 && meta.temp == 10)\n" +
  70 + " return ['three', 'one']\n" +
  71 + " else\n" +
  72 + " return 'two';\n" +
  73 + "};\n" +
  74 + "\n" +
  75 + "nextRelation(meta, msg);";
  76 + initWithScript(jsCode, Sets.newHashSet("one", "two", "three"), false);
  77 + TbMsgMetaData metaData = new TbMsgMetaData();
  78 + metaData.putValue("temp", "10");
  79 + metaData.putValue("humidity", "99");
  80 + String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
  81 +
  82 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
  83 + mockJsExecutor();
  84 +
  85 + node.onMsg(ctx, msg);
  86 + verify(ctx).getJsExecutor();
  87 + verify(ctx).tellNext(msg, Sets.newHashSet("one", "three"));
  88 + }
  89 +
  90 + @Test
  91 + public void allowedRelationPassed() throws TbNodeException {
  92 + String jsCode = "function nextRelation(meta, msg) {\n" +
  93 + " if(msg.passed == 5 && meta.temp == 10)\n" +
  94 + " return 'one'\n" +
  95 + " else\n" +
  96 + " return 'two';\n" +
  97 + "};\n" +
  98 + "\n" +
  99 + "nextRelation(meta, msg);";
  100 + initWithScript(jsCode, Sets.newHashSet("one", "two"), false);
  101 + TbMsgMetaData metaData = new TbMsgMetaData();
  102 + metaData.putValue("temp", "10");
  103 + metaData.putValue("humidity", "99");
  104 + String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
  105 +
  106 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
  107 + mockJsExecutor();
  108 +
  109 + node.onMsg(ctx, msg);
  110 + verify(ctx).getJsExecutor();
  111 + verify(ctx).tellNext(msg, Sets.newHashSet("one"));
  112 + }
  113 +
  114 + @Test
  115 + public void unknownRelationThrowsException() throws TbNodeException {
  116 + String jsCode = "function nextRelation(meta, msg) {\n" +
  117 + " return ['one','nine'];" +
  118 + "};\n" +
  119 + "\n" +
  120 + "nextRelation(meta, msg);";
  121 + initWithScript(jsCode, Sets.newHashSet("one", "two"), false);
  122 + TbMsgMetaData metaData = new TbMsgMetaData();
  123 + metaData.putValue("temp", "10");
  124 + metaData.putValue("humidity", "99");
  125 + String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
  126 +
  127 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
  128 + mockJsExecutor();
  129 +
  130 + node.onMsg(ctx, msg);
  131 + verify(ctx).getJsExecutor();
  132 + verifyError(msg, "Unsupported relation for switch [nine, one]", IllegalStateException.class);
  133 + }
  134 +
  135 + private void initWithScript(String script, Set<String> relations, boolean routeToAll) throws TbNodeException {
  136 + TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration();
  137 + config.setJsScript(script);
  138 + config.setAllowedRelations(relations);
  139 + config.setRouteToAllWithNoCheck(routeToAll);
  140 + ObjectMapper mapper = new ObjectMapper();
  141 + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
  142 +
  143 + node = new TbJsSwitchNode();
  144 + node.init(nodeConfiguration, null);
  145 + }
  146 +
  147 + private void mockJsExecutor() {
  148 + when(ctx.getJsExecutor()).thenReturn(executor);
  149 + doAnswer((Answer<ListenableFuture<Set<String>>>) invocationOnMock -> {
  150 + try {
  151 + Callable task = (Callable) (invocationOnMock.getArguments())[0];
  152 + return Futures.immediateFuture((Set<String>) task.call());
  153 + } catch (Throwable th) {
  154 + return Futures.immediateFailedFuture(th);
  155 + }
  156 + }).when(executor).executeAsync(Matchers.any(Callable.class));
  157 + }
  158 +
  159 + private void verifyError(TbMsg msg, String message, Class expectedClass) {
  160 + ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
  161 + verify(ctx).tellError(same(msg), captor.capture());
  162 +
  163 + Throwable value = captor.getValue();
  164 + assertEquals(expectedClass, value.getClass());
  165 + assertEquals(message, value.getMessage());
  166 + }
  167 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.metadata;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.collect.Lists;
  21 +import com.google.common.util.concurrent.Futures;
  22 +import org.junit.Before;
  23 +import org.junit.Test;
  24 +import org.junit.runner.RunWith;
  25 +import org.mockito.ArgumentCaptor;
  26 +import org.mockito.Mock;
  27 +import org.mockito.runners.MockitoJUnitRunner;
  28 +import org.thingsboard.rule.engine.api.TbContext;
  29 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  30 +import org.thingsboard.rule.engine.api.TbNodeException;
  31 +import org.thingsboard.server.common.data.Device;
  32 +import org.thingsboard.server.common.data.User;
  33 +import org.thingsboard.server.common.data.asset.Asset;
  34 +import org.thingsboard.server.common.data.id.AssetId;
  35 +import org.thingsboard.server.common.data.id.CustomerId;
  36 +import org.thingsboard.server.common.data.id.DeviceId;
  37 +import org.thingsboard.server.common.data.id.UserId;
  38 +import org.thingsboard.server.common.data.kv.*;
  39 +import org.thingsboard.server.common.msg.TbMsg;
  40 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  41 +import org.thingsboard.server.dao.asset.AssetService;
  42 +import org.thingsboard.server.dao.attributes.AttributesService;
  43 +import org.thingsboard.server.dao.device.DeviceService;
  44 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
  45 +import org.thingsboard.server.dao.user.UserService;
  46 +
  47 +import java.util.Collections;
  48 +import java.util.HashMap;
  49 +import java.util.List;
  50 +import java.util.Map;
  51 +
  52 +import static org.junit.Assert.assertEquals;
  53 +import static org.junit.Assert.assertTrue;
  54 +import static org.mockito.Matchers.same;
  55 +import static org.mockito.Mockito.verify;
  56 +import static org.mockito.Mockito.when;
  57 +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
  58 +
  59 +@RunWith(MockitoJUnitRunner.class)
  60 +public class TbGetCustomerAttributeNodeTest {
  61 +
  62 + private TbGetCustomerAttributeNode node;
  63 +
  64 + @Mock
  65 + private TbContext ctx;
  66 +
  67 + @Mock
  68 + private AttributesService attributesService;
  69 + @Mock
  70 + private TimeseriesService timeseriesService;
  71 + @Mock
  72 + private UserService userService;
  73 + @Mock
  74 + private AssetService assetService;
  75 + @Mock
  76 + private DeviceService deviceService;
  77 +
  78 + private TbMsg msg;
  79 +
  80 + @Before
  81 + public void init() throws TbNodeException {
  82 + TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration();
  83 + Map<String, String> attrMapping = new HashMap<>();
  84 + attrMapping.putIfAbsent("temperature", "tempo");
  85 + config.setAttrMapping(attrMapping);
  86 + config.setTelemetry(false);
  87 + ObjectMapper mapper = new ObjectMapper();
  88 + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
  89 +
  90 + node = new TbGetCustomerAttributeNode();
  91 + node.init(nodeConfiguration, null);
  92 + }
  93 +
  94 + @Test
  95 + public void errorThrownIfCannotLoadAttributes() {
  96 + UserId userId = new UserId(UUIDs.timeBased());
  97 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  98 + User user = new User();
  99 + user.setCustomerId(customerId);
  100 +
  101 + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]);
  102 +
  103 + when(ctx.getUserService()).thenReturn(userService);
  104 + when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
  105 +
  106 + when(ctx.getAttributesService()).thenReturn(attributesService);
  107 + when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature")))
  108 + .thenThrow(new IllegalStateException("something wrong"));
  109 +
  110 + node.onMsg(ctx, msg);
  111 + final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
  112 + verify(ctx).tellError(same(msg), captor.capture());
  113 +
  114 + Throwable value = captor.getValue();
  115 + assertEquals("something wrong", value.getMessage());
  116 + assertTrue(msg.getMetaData().getData().isEmpty());
  117 + }
  118 +
  119 + @Test
  120 + public void errorThrownIfCannotLoadAttributesAsync() {
  121 + UserId userId = new UserId(UUIDs.timeBased());
  122 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  123 + User user = new User();
  124 + user.setCustomerId(customerId);
  125 +
  126 + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]);
  127 +
  128 + when(ctx.getUserService()).thenReturn(userService);
  129 + when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
  130 +
  131 + when(ctx.getAttributesService()).thenReturn(attributesService);
  132 + when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature")))
  133 + .thenReturn(Futures.immediateFailedFuture(new IllegalStateException("something wrong")));
  134 +
  135 + node.onMsg(ctx, msg);
  136 + final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
  137 + verify(ctx).tellError(same(msg), captor.capture());
  138 +
  139 + Throwable value = captor.getValue();
  140 + assertEquals("something wrong", value.getMessage());
  141 + assertTrue(msg.getMetaData().getData().isEmpty());
  142 + }
  143 +
  144 + @Test
  145 + public void errorThrownIfCustomerCannotBeFound() {
  146 + UserId userId = new UserId(UUIDs.timeBased());
  147 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  148 + User user = new User();
  149 + user.setCustomerId(customerId);
  150 +
  151 + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]);
  152 +
  153 + when(ctx.getUserService()).thenReturn(userService);
  154 + when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null));
  155 +
  156 + node.onMsg(ctx, msg);
  157 + final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
  158 + verify(ctx).tellError(same(msg), captor.capture());
  159 +
  160 + Throwable value = captor.getValue();
  161 + assertEquals(IllegalStateException.class, value.getClass());
  162 + assertEquals("Customer not found", value.getMessage());
  163 + assertTrue(msg.getMetaData().getData().isEmpty());
  164 + }
  165 +
  166 + @Test
  167 + public void customerAttributeAddedInMetadata() {
  168 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  169 + msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), new byte[4]);
  170 + entityAttributeFetched(customerId);
  171 + }
  172 +
  173 + @Test
  174 + public void usersCustomerAttributesFetched() {
  175 + UserId userId = new UserId(UUIDs.timeBased());
  176 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  177 + User user = new User();
  178 + user.setCustomerId(customerId);
  179 +
  180 + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]);
  181 +
  182 + when(ctx.getUserService()).thenReturn(userService);
  183 + when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
  184 +
  185 + entityAttributeFetched(customerId);
  186 + }
  187 +
  188 + @Test
  189 + public void assetsCustomerAttributesFetched() {
  190 + AssetId assetId = new AssetId(UUIDs.timeBased());
  191 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  192 + Asset asset = new Asset();
  193 + asset.setCustomerId(customerId);
  194 +
  195 + msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), new byte[4]);
  196 +
  197 + when(ctx.getAssetService()).thenReturn(assetService);
  198 + when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
  199 +
  200 + entityAttributeFetched(customerId);
  201 + }
  202 +
  203 + @Test
  204 + public void deviceCustomerAttributesFetched() {
  205 + DeviceId deviceId = new DeviceId(UUIDs.timeBased());
  206 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  207 + Device device = new Device();
  208 + device.setCustomerId(customerId);
  209 +
  210 + msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), new byte[4]);
  211 +
  212 + when(ctx.getDeviceService()).thenReturn(deviceService);
  213 + when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device));
  214 +
  215 + entityAttributeFetched(customerId);
  216 + }
  217 +
  218 + @Test
  219 + public void deviceCustomerTelemetryFetched() throws TbNodeException {
  220 + TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration();
  221 + Map<String, String> attrMapping = new HashMap<>();
  222 + attrMapping.putIfAbsent("temperature", "tempo");
  223 + config.setAttrMapping(attrMapping);
  224 + config.setTelemetry(true);
  225 + ObjectMapper mapper = new ObjectMapper();
  226 + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
  227 +
  228 + node = new TbGetCustomerAttributeNode();
  229 + node.init(nodeConfiguration, null);
  230 +
  231 +
  232 + DeviceId deviceId = new DeviceId(UUIDs.timeBased());
  233 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  234 + Device device = new Device();
  235 + device.setCustomerId(customerId);
  236 +
  237 + msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), new byte[4]);
  238 +
  239 + when(ctx.getDeviceService()).thenReturn(deviceService);
  240 + when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device));
  241 +
  242 + List<TsKvEntry> timeseries = Lists.newArrayList(new BasicTsKvEntry(1L, new StringDataEntry("temperature", "highest")));
  243 +
  244 + when(ctx.getTimeseriesService()).thenReturn(timeseriesService);
  245 + when(timeseriesService.findLatest(customerId, Collections.singleton("temperature")))
  246 + .thenReturn(Futures.immediateFuture(timeseries));
  247 +
  248 + node.onMsg(ctx, msg);
  249 + verify(ctx).tellNext(msg);
  250 + assertEquals(msg.getMetaData().getValue("tempo"), "highest");
  251 + }
  252 +
  253 + private void entityAttributeFetched(CustomerId customerId) {
  254 + List<AttributeKvEntry> attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L));
  255 +
  256 + when(ctx.getAttributesService()).thenReturn(attributesService);
  257 + when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature")))
  258 + .thenReturn(Futures.immediateFuture(attributes));
  259 +
  260 + node.onMsg(ctx, msg);
  261 + verify(ctx).tellNext(msg);
  262 + assertEquals(msg.getMetaData().getValue("tempo"), "high");
  263 + }
  264 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.transform;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.util.concurrent.Futures;
  21 +import org.junit.Test;
  22 +import org.junit.runner.RunWith;
  23 +import org.mockito.ArgumentCaptor;
  24 +import org.mockito.Mock;
  25 +import org.mockito.runners.MockitoJUnitRunner;
  26 +import org.thingsboard.rule.engine.api.TbContext;
  27 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  28 +import org.thingsboard.rule.engine.api.TbNodeException;
  29 +import org.thingsboard.server.common.data.asset.Asset;
  30 +import org.thingsboard.server.common.data.id.AssetId;
  31 +import org.thingsboard.server.common.data.id.CustomerId;
  32 +import org.thingsboard.server.common.msg.TbMsg;
  33 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  34 +import org.thingsboard.server.dao.asset.AssetService;
  35 +
  36 +import static org.junit.Assert.assertEquals;
  37 +import static org.mockito.Matchers.same;
  38 +import static org.mockito.Mockito.verify;
  39 +import static org.mockito.Mockito.when;
  40 +
  41 +@RunWith(MockitoJUnitRunner.class)
  42 +public class TbChangeOriginatorNodeTest {
  43 +
  44 + private TbChangeOriginatorNode node;
  45 +
  46 + @Mock
  47 + private TbContext ctx;
  48 + @Mock
  49 + private AssetService assetService;
  50 +
  51 +
  52 + @Test
  53 + public void originatorCanBeChangedToCustomerId() throws TbNodeException {
  54 + init(false);
  55 + AssetId assetId = new AssetId(UUIDs.timeBased());
  56 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  57 + Asset asset = new Asset();
  58 + asset.setCustomerId(customerId);
  59 +
  60 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]);
  61 +
  62 + when(ctx.getAssetService()).thenReturn(assetService);
  63 + when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
  64 +
  65 + node.onMsg(ctx, msg);
  66 + ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
  67 + verify(ctx).tellNext(captor.capture());
  68 + TbMsg actualMsg = captor.getValue();
  69 + assertEquals(customerId, actualMsg.getOriginator());
  70 + assertEquals(msg.getId(), actualMsg.getId());
  71 + }
  72 +
  73 + @Test
  74 + public void newChainCanBeStarted() throws TbNodeException {
  75 + init(true);
  76 + AssetId assetId = new AssetId(UUIDs.timeBased());
  77 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  78 + Asset asset = new Asset();
  79 + asset.setCustomerId(customerId);
  80 +
  81 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]);
  82 +
  83 + when(ctx.getAssetService()).thenReturn(assetService);
  84 + when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
  85 +
  86 + node.onMsg(ctx, msg);
  87 + ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
  88 + verify(ctx).spawn(captor.capture());
  89 + TbMsg actualMsg = captor.getValue();
  90 + assertEquals(customerId, actualMsg.getOriginator());
  91 + assertEquals(msg.getId(), actualMsg.getId());
  92 + }
  93 +
  94 + @Test
  95 + public void exceptionThrownIfCannotFindNewOriginator() throws TbNodeException {
  96 + init(true);
  97 + AssetId assetId = new AssetId(UUIDs.timeBased());
  98 + CustomerId customerId = new CustomerId(UUIDs.timeBased());
  99 + Asset asset = new Asset();
  100 + asset.setCustomerId(customerId);
  101 +
  102 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]);
  103 +
  104 + when(ctx.getAssetService()).thenReturn(assetService);
  105 + when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("wrong")));
  106 +
  107 + node.onMsg(ctx, msg);
  108 + ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
  109 + verify(ctx).tellError(same(msg), captor.capture());
  110 + Throwable value = captor.getValue();
  111 + assertEquals("wrong", value.getMessage());
  112 + }
  113 +
  114 + public void init(boolean startNewChain) throws TbNodeException {
  115 + TbChangeOriginatorNodeConfiguration config = new TbChangeOriginatorNodeConfiguration();
  116 + config.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE);
  117 + config.setStartNewChain(startNewChain);
  118 + ObjectMapper mapper = new ObjectMapper();
  119 + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
  120 +
  121 + node = new TbChangeOriginatorNode();
  122 + node.init(nodeConfiguration, null);
  123 + }
  124 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.transform;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.util.concurrent.Futures;
  21 +import com.google.common.util.concurrent.ListenableFuture;
  22 +import org.junit.Test;
  23 +import org.junit.runner.RunWith;
  24 +import org.mockito.ArgumentCaptor;
  25 +import org.mockito.Matchers;
  26 +import org.mockito.Mock;
  27 +import org.mockito.runners.MockitoJUnitRunner;
  28 +import org.mockito.stubbing.Answer;
  29 +import org.thingsboard.rule.engine.api.ListeningExecutor;
  30 +import org.thingsboard.rule.engine.api.TbContext;
  31 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  32 +import org.thingsboard.rule.engine.api.TbNodeException;
  33 +import org.thingsboard.server.common.msg.TbMsg;
  34 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  35 +
  36 +import java.util.concurrent.Callable;
  37 +
  38 +import static org.junit.Assert.assertEquals;
  39 +import static org.mockito.Matchers.same;
  40 +import static org.mockito.Mockito.*;
  41 +
  42 +@RunWith(MockitoJUnitRunner.class)
  43 +public class TbTransformMsgNodeTest {
  44 +
  45 + private TbTransformMsgNode node;
  46 +
  47 + @Mock
  48 + private TbContext ctx;
  49 + @Mock
  50 + private ListeningExecutor executor;
  51 +
  52 + @Test
  53 + public void metadataCanBeUpdated() throws TbNodeException {
  54 + initWithScript("meta.temp = meta.temp * 10;");
  55 + TbMsgMetaData metaData = new TbMsgMetaData();
  56 + metaData.putValue("temp", "7");
  57 + metaData.putValue("humidity", "99");
  58 + String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
  59 +
  60 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
  61 + mockJsExecutor();
  62 +
  63 + node.onMsg(ctx, msg);
  64 + verify(ctx).getJsExecutor();
  65 + ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
  66 + verify(ctx).tellNext(captor.capture());
  67 + TbMsg actualMsg = captor.getValue();
  68 + assertEquals("70.0", actualMsg.getMetaData().getValue("temp"));
  69 + }
  70 +
  71 + @Test
  72 + public void metadataCanBeAdded() throws TbNodeException {
  73 + initWithScript("meta.newAttr = meta.humidity - msg.passed;");
  74 + TbMsgMetaData metaData = new TbMsgMetaData();
  75 + metaData.putValue("temp", "7");
  76 + metaData.putValue("humidity", "99");
  77 + String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
  78 +
  79 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
  80 + mockJsExecutor();
  81 +
  82 + node.onMsg(ctx, msg);
  83 + verify(ctx).getJsExecutor();
  84 + ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
  85 + verify(ctx).tellNext(captor.capture());
  86 + TbMsg actualMsg = captor.getValue();
  87 + assertEquals("94.0", actualMsg.getMetaData().getValue("newAttr"));
  88 + }
  89 +
  90 + @Test
  91 + public void payloadCanBeUpdated() throws TbNodeException {
  92 + initWithScript("msg.passed = msg.passed * meta.temp; msg.bigObj.newProp = 'Ukraine' ");
  93 + TbMsgMetaData metaData = new TbMsgMetaData();
  94 + metaData.putValue("temp", "7");
  95 + metaData.putValue("humidity", "99");
  96 + String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}";
  97 +
  98 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes());
  99 + mockJsExecutor();
  100 +
  101 + node.onMsg(ctx, msg);
  102 + verify(ctx).getJsExecutor();
  103 + ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
  104 + verify(ctx).tellNext(captor.capture());
  105 + TbMsg actualMsg = captor.getValue();
  106 + String expectedJson = "{\"name\":\"Vit\",\"passed\":35.0,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}";
  107 + assertEquals(expectedJson, new String(actualMsg.getData()));
  108 + }
  109 +
  110 + private void initWithScript(String script) throws TbNodeException {
  111 + TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration();
  112 + config.setJsScript(script);
  113 + ObjectMapper mapper = new ObjectMapper();
  114 + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
  115 +
  116 + node = new TbTransformMsgNode();
  117 + node.init(nodeConfiguration, null);
  118 + }
  119 +
  120 + private void mockJsExecutor() {
  121 + when(ctx.getJsExecutor()).thenReturn(executor);
  122 + doAnswer((Answer<ListenableFuture<TbMsg>>) invocationOnMock -> {
  123 + try {
  124 + Callable task = (Callable) (invocationOnMock.getArguments())[0];
  125 + return Futures.immediateFuture((TbMsg) task.call());
  126 + } catch (Throwable th) {
  127 + return Futures.immediateFailedFuture(th);
  128 + }
  129 + }).when(executor).executeAsync(Matchers.any(Callable.class));
  130 + }
  131 +
  132 + private void verifyError(TbMsg msg, String message, Class expectedClass) {
  133 + ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
  134 + verify(ctx).tellError(same(msg), captor.capture());
  135 +
  136 + Throwable value = captor.getValue();
  137 + assertEquals(expectedClass, value.getClass());
  138 + assertEquals(message, value.getMessage());
  139 + }
  140 +}
\ No newline at end of file
... ...