Commit f87e5d66bff0dcd9bf97ddebcdff0394f22081fb

Authored by Andrew Shvayka
Committed by GitHub
2 parents e9730fec 2e019b52

Merge pull request #218 from thingsboard/feature/TB-73

TB-73: Make Rule Action optional
@@ -18,6 +18,7 @@ package org.thingsboard.server.actors.rule; @@ -18,6 +18,7 @@ package org.thingsboard.server.actors.rule;
18 import java.util.*; 18 import java.util.*;
19 19
20 import com.fasterxml.jackson.core.JsonProcessingException; 20 import com.fasterxml.jackson.core.JsonProcessingException;
  21 +import org.springframework.util.StringUtils;
21 import org.thingsboard.server.actors.ActorSystemContext; 22 import org.thingsboard.server.actors.ActorSystemContext;
22 import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper; 23 import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper;
23 import org.thingsboard.server.actors.shared.ComponentMsgProcessor; 24 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
@@ -113,8 +114,9 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { @@ -113,8 +114,9 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
113 } 114 }
114 115
115 private void initAction() throws Exception { 116 private void initAction() throws Exception {
116 - JsonNode actionMd = ruleMd.getAction();  
117 - action = initComponent(actionMd); 117 + if (ruleMd.getAction() != null && !ruleMd.getAction().isNull()) {
  118 + action = initComponent(ruleMd.getAction());
  119 + }
118 } 120 }
119 121
120 private void initProcessor() throws Exception { 122 private void initProcessor() throws Exception {
@@ -131,9 +133,11 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { @@ -131,9 +133,11 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
131 } 133 }
132 134
133 private void fetchPluginInfo() { 135 private void fetchPluginInfo() {
134 - PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());  
135 - pluginTenantId = pluginMd.getTenantId();  
136 - pluginId = pluginMd.getId(); 136 + if (!StringUtils.isEmpty(ruleMd.getPluginToken())) {
  137 + PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
  138 + pluginTenantId = pluginMd.getTenantId();
  139 + pluginId = pluginMd.getId();
  140 + }
137 } 141 }
138 142
139 protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException { 143 protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
@@ -162,25 +166,27 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { @@ -162,25 +166,27 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
162 inMsgMd = new RuleProcessingMetaData(); 166 inMsgMd = new RuleProcessingMetaData();
163 } 167 }
164 logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg); 168 logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg);
165 - Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);  
166 - if (ruleToPluginMsgOptional.isPresent()) {  
167 - RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();  
168 - logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg);  
169 - context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());  
170 - if (action.isOneWayAction()) {  
171 - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);  
172 - } else {  
173 - pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);  
174 - scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout()); 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 converter 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 + }
175 } 183 }
176 - } else {  
177 - logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);  
178 - pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_REQUEST_FROM_ACTIONS);  
179 - return;  
180 } 184 }
  185 + logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
  186 + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
181 } 187 }
182 188
183 - public void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) { 189 + void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) {
184 RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid()); 190 RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid());
185 if (pendingMsg != null) { 191 if (pendingMsg != null) {
186 ChainProcessingContext ctx = pendingMsg.getCtx(); 192 ChainProcessingContext ctx = pendingMsg.getCtx();
@@ -196,7 +202,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { @@ -196,7 +202,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
196 } 202 }
197 } 203 }
198 204
199 - public void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) { 205 + void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) {
200 RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId()); 206 RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId());
201 if (pendingMsg != null) { 207 if (pendingMsg != null) {
202 logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg); 208 logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg);
@@ -210,13 +216,13 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { @@ -210,13 +216,13 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
210 ctx = ctx.withError(error); 216 ctx = ctx.withError(error);
211 } 217 }
212 if (ctx.isFailure()) { 218 if (ctx.isFailure()) {
213 - logger.debug("[{}] Forwarding processing chain to device actor due to failure.", ctx.getInMsg().getDeviceId()); 219 + logger.debug("[{}][{}] Forwarding processing chain to device actor due to failure.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
214 ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); 220 ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
215 } else if (!ctx.hasNext()) { 221 } else if (!ctx.hasNext()) {
216 - logger.debug("[{}] Forwarding processing chain to device actor due to end of chain.", ctx.getInMsg().getDeviceId()); 222 + logger.debug("[{}][{}] Forwarding processing chain to device actor due to end of chain.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
217 ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); 223 ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
218 } else { 224 } else {
219 - logger.debug("[{}] Forwarding processing chain to next rule actor.", ctx.getInMsg().getDeviceId()); 225 + logger.debug("[{}][{}] Forwarding processing chain to next rule actor.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
220 ChainProcessingContext nextTask = ctx.getNext(); 226 ChainProcessingContext nextTask = ctx.getNext();
221 nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self()); 227 nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self());
222 } 228 }
@@ -269,18 +275,16 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { @@ -269,18 +275,16 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
269 public void onActivate(ActorContext context) throws Exception { 275 public void onActivate(ActorContext context) throws Exception {
270 logger.info("[{}] Going to process onActivate rule.", entityId); 276 logger.info("[{}] Going to process onActivate rule.", entityId);
271 this.state = ComponentLifecycleState.ACTIVE; 277 this.state = ComponentLifecycleState.ACTIVE;
272 - if (action != null) {  
273 - if (filters != null) {  
274 - filters.forEach(f -> f.resume());  
275 - } else {  
276 - initFilters();  
277 - } 278 + if (filters != null) {
  279 + filters.forEach(RuleLifecycleComponent::resume);
278 if (processor != null) { 280 if (processor != null) {
279 processor.resume(); 281 processor.resume();
280 } else { 282 } else {
281 initProcessor(); 283 initProcessor();
282 } 284 }
283 - action.resume(); 285 + if (action != null) {
  286 + action.resume();
  287 + }
284 logger.info("[{}] Rule resumed.", entityId); 288 logger.info("[{}] Rule resumed.", entityId);
285 } else { 289 } else {
286 start(); 290 start();
@@ -72,16 +72,19 @@ public abstract class RuleManager { @@ -72,16 +72,19 @@ public abstract class RuleManager {
72 } 72 }
73 73
74 public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) { 74 public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) {
75 - RuleMetaData rule = null; 75 + RuleMetaData rule;
76 if (event != ComponentLifecycleEvent.DELETED) { 76 if (event != ComponentLifecycleEvent.DELETED) {
77 rule = systemContext.getRuleService().findRuleById(ruleId); 77 rule = systemContext.getRuleService().findRuleById(ruleId);
78 - }  
79 - if (rule == null) { 78 + } else {
80 rule = ruleMap.keySet().stream() 79 rule = ruleMap.keySet().stream()
81 .filter(r -> r.getId().equals(ruleId)) 80 .filter(r -> r.getId().equals(ruleId))
82 .peek(r -> r.setState(ComponentLifecycleState.SUSPENDED)) 81 .peek(r -> r.setState(ComponentLifecycleState.SUSPENDED))
83 .findFirst() 82 .findFirst()
84 .orElse(null); 83 .orElse(null);
  84 + if (rule != null) {
  85 + ruleMap.remove(rule);
  86 + ruleActors.remove(ruleId);
  87 + }
85 } 88 }
86 if (rule != null) { 89 if (rule != null) {
87 RuleActorMetaData actorMd = ruleMap.get(rule); 90 RuleActorMetaData actorMd = ruleMap.get(rule);
@@ -25,7 +25,7 @@ @@ -25,7 +25,7 @@
25 </encoder> 25 </encoder>
26 </appender> 26 </appender>
27 27
28 - <logger name="org.thingsboard.server" level="INFO" /> 28 + <logger name="org.thingsboard.server" level="TRACE" />
29 <logger name="akka" level="INFO" /> 29 <logger name="akka" level="INFO" />
30 30
31 <root level="INFO"> 31 <root level="INFO">
@@ -176,7 +176,7 @@ actors: @@ -176,7 +176,7 @@ actors:
176 statistics: 176 statistics:
177 # Enable/disable actor statistics 177 # Enable/disable actor statistics
178 enabled: "${ACTORS_STATISTICS_ENABLED:true}" 178 enabled: "${ACTORS_STATISTICS_ENABLED:true}"
179 - persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:60000}" 179 + persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
180 180
181 # Cache parameters 181 # Cache parameters
182 cache: 182 cache:
@@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.rule; @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.rule;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 import lombok.Data; 19 import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
20 import org.thingsboard.server.common.data.HasName; 21 import org.thingsboard.server.common.data.HasName;
21 import org.thingsboard.server.common.data.SearchTextBased; 22 import org.thingsboard.server.common.data.SearchTextBased;
22 import org.thingsboard.server.common.data.id.RuleId; 23 import org.thingsboard.server.common.data.id.RuleId;
@@ -24,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -24,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId;
24 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; 25 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
25 26
26 @Data 27 @Data
  28 +@EqualsAndHashCode(callSuper = true)
27 public class RuleMetaData extends SearchTextBased<RuleId> implements HasName { 29 public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
28 30
29 private static final long serialVersionUID = -5656679015122935465L; 31 private static final long serialVersionUID = -5656679015122935465L;
@@ -91,7 +91,9 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic @@ -91,7 +91,9 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic
91 if (rule.getProcessor() != null && !rule.getProcessor().isNull()) { 91 if (rule.getProcessor() != null && !rule.getProcessor().isNull()) {
92 validateComponentJson(rule.getProcessor(), ComponentType.PROCESSOR); 92 validateComponentJson(rule.getProcessor(), ComponentType.PROCESSOR);
93 } 93 }
94 - validateComponentJson(rule.getAction(), ComponentType.ACTION); 94 + if (rule.getAction() != null && !rule.getAction().isNull()) {
  95 + validateComponentJson(rule.getAction(), ComponentType.ACTION);
  96 + }
95 validateRuleAndPluginState(rule); 97 validateRuleAndPluginState(rule);
96 return ruleDao.save(rule); 98 return ruleDao.save(rule);
97 } 99 }
@@ -129,6 +131,9 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic @@ -129,6 +131,9 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic
129 } 131 }
130 132
131 private void validateRuleAndPluginState(RuleMetaData rule) { 133 private void validateRuleAndPluginState(RuleMetaData rule) {
  134 + if (org.springframework.util.StringUtils.isEmpty(rule.getPluginToken())) {
  135 + return;
  136 + }
132 PluginMetaData pluginMd = pluginService.findPluginByApiToken(rule.getPluginToken()); 137 PluginMetaData pluginMd = pluginService.findPluginByApiToken(rule.getPluginToken());
133 if (pluginMd == null) { 138 if (pluginMd == null) {
134 throw new IncorrectParameterException("Rule points to non-existent plugin!"); 139 throw new IncorrectParameterException("Rule points to non-existent plugin!");
@@ -125,10 +125,10 @@ public class AlarmProcessor implements RuleProcessor<AlarmProcessorConfiguration @@ -125,10 +125,10 @@ public class AlarmProcessor implements RuleProcessor<AlarmProcessorConfiguration
125 Alarm alarm = buildAlarm(ctx, msg); 125 Alarm alarm = buildAlarm(ctx, msg);
126 existing = ctx.createOrUpdateAlarm(alarm); 126 existing = ctx.createOrUpdateAlarm(alarm);
127 if (existing.getStartTs() == alarm.getStartTs()) { 127 if (existing.getStartTs() == alarm.getStartTs()) {
128 - log.debug("[{}][{}] New Active Alarm detected"); 128 + log.debug("[{}][{}] New Active Alarm detected", ctx.getRuleId(), existing.getId());
129 md.put(IS_NEW_ALARM, Boolean.TRUE); 129 md.put(IS_NEW_ALARM, Boolean.TRUE);
130 } else { 130 } else {
131 - log.debug("[{}][{}] Existing Active Alarm detected"); 131 + log.debug("[{}][{}] Existing Active Alarm detected", ctx.getRuleId(), existing.getId());
132 md.put(IS_EXISTING_ALARM, Boolean.TRUE); 132 md.put(IS_EXISTING_ALARM, Boolean.TRUE);
133 } 133 }
134 } else if (isClearedAlarm) { 134 } else if (isClearedAlarm) {
@@ -165,11 +165,11 @@ @@ -165,11 +165,11 @@
165 <fieldset ng-disabled="loading || !isEdit || isReadOnly"> 165 <fieldset ng-disabled="loading || !isEdit || isReadOnly">
166 <md-input-container ng-if="!isEdit || isReadOnly" flex class="md-block"> 166 <md-input-container ng-if="!isEdit || isReadOnly" flex class="md-block">
167 <label translate>plugin.plugin</label> 167 <label translate>plugin.plugin</label>
168 - <input required name="name" ng-model="plugin.name"> 168 + <input name="name" ng-model="plugin.name">
169 </md-input-container> 169 </md-input-container>
170 <tb-plugin-select ng-show="isEdit && !isReadOnly" flex 170 <tb-plugin-select ng-show="isEdit && !isReadOnly" flex
171 ng-model="plugin" 171 ng-model="plugin"
172 - tb-required="true" 172 + tb-required="false"
173 the-form="theForm" 173 the-form="theForm"
174 plugins-scope="action"> 174 plugins-scope="action">
175 </tb-plugin-select> 175 </tb-plugin-select>
@@ -85,10 +85,11 @@ export default function RuleDirective($compile, $templateCache, $mdDialog, $docu @@ -85,10 +85,11 @@ export default function RuleDirective($compile, $templateCache, $mdDialog, $docu
85 if (scope.rule) { 85 if (scope.rule) {
86 var valid = scope.rule.filters && scope.rule.filters.length > 0; 86 var valid = scope.rule.filters && scope.rule.filters.length > 0;
87 scope.theForm.$setValidity('filters', valid); 87 scope.theForm.$setValidity('filters', valid);
88 - valid = angular.isDefined(scope.rule.pluginToken) && scope.rule.pluginToken != null;  
89 - scope.theForm.$setValidity('plugin', valid);  
90 - valid = angular.isDefined(scope.rule.action) && scope.rule.action != null;  
91 - scope.theForm.$setValidity('action', valid); 88 + var processorDefined = angular.isDefined(scope.rule.processor) && scope.rule.processor != null;
  89 + var pluginDefined = angular.isDefined(scope.rule.pluginToken) && scope.rule.pluginToken != null;
  90 + var pluginActionDefined = angular.isDefined(scope.rule.action) && scope.rule.action != null;
  91 + valid = processorDefined && !pluginDefined || (pluginDefined && pluginActionDefined);
  92 + scope.theForm.$setValidity('processorOrPlugin', valid);
92 } 93 }
93 }; 94 };
94 95
@@ -160,6 +161,7 @@ export default function RuleDirective($compile, $templateCache, $mdDialog, $docu @@ -160,6 +161,7 @@ export default function RuleDirective($compile, $templateCache, $mdDialog, $docu
160 scope.$watch('rule.processor', function(newVal, prevVal) { 161 scope.$watch('rule.processor', function(newVal, prevVal) {
161 if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) { 162 if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
162 scope.theForm.$setDirty(); 163 scope.theForm.$setDirty();
  164 + scope.updateValidity();
163 } 165 }
164 }, true); 166 }, true);
165 167