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

TB-73: Make Rule Action optional
This commit is contained in:
Andrew Shvayka 2017-07-24 18:49:30 +03:00 committed by GitHub
commit f87e5d66bf
9 changed files with 61 additions and 45 deletions

View File

@ -18,6 +18,7 @@ package org.thingsboard.server.actors.rule;
import java.util.*; import java.util.*;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.util.StringUtils;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper; import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
@ -113,8 +114,9 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
} }
private void initAction() throws Exception { private void initAction() throws Exception {
JsonNode actionMd = ruleMd.getAction(); if (ruleMd.getAction() != null && !ruleMd.getAction().isNull()) {
action = initComponent(actionMd); action = initComponent(ruleMd.getAction());
}
} }
private void initProcessor() throws Exception { private void initProcessor() throws Exception {
@ -131,10 +133,12 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
} }
private void fetchPluginInfo() { private void fetchPluginInfo() {
if (!StringUtils.isEmpty(ruleMd.getPluginToken())) {
PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken()); PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
pluginTenantId = pluginMd.getTenantId(); pluginTenantId = pluginMd.getTenantId();
pluginId = pluginMd.getId(); pluginId = pluginMd.getId();
} }
}
protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException { protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
if (state != ComponentLifecycleState.ACTIVE) { if (state != ComponentLifecycleState.ACTIVE) {
@ -162,6 +166,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
inMsgMd = new RuleProcessingMetaData(); inMsgMd = new RuleProcessingMetaData();
} }
logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg); logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg);
if (action != null) {
Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);
if (ruleToPluginMsgOptional.isPresent()) { if (ruleToPluginMsgOptional.isPresent()) {
RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get(); RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();
@ -169,18 +174,19 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());
if (action.isOneWayAction()) { if (action.isOneWayAction()) {
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
return;
} else { } else {
pendingMsgMap.put(ruleToPluginMsg.getUid(), msg); pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);
scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout()); scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout());
}
} else {
logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_REQUEST_FROM_ACTIONS);
return; return;
} }
} }
}
logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
}
public void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) { void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) {
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid()); RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid());
if (pendingMsg != null) { if (pendingMsg != null) {
ChainProcessingContext ctx = pendingMsg.getCtx(); ChainProcessingContext ctx = pendingMsg.getCtx();
@ -196,7 +202,7 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
} }
} }
public void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) { void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) {
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId()); RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId());
if (pendingMsg != null) { if (pendingMsg != null) {
logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg); logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg);
@ -210,13 +216,13 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
ctx = ctx.withError(error); ctx = ctx.withError(error);
} }
if (ctx.isFailure()) { if (ctx.isFailure()) {
logger.debug("[{}] Forwarding processing chain to device actor due to failure.", ctx.getInMsg().getDeviceId()); logger.debug("[{}][{}] Forwarding processing chain to device actor due to failure.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
} else if (!ctx.hasNext()) { } else if (!ctx.hasNext()) {
logger.debug("[{}] Forwarding processing chain to device actor due to end of chain.", ctx.getInMsg().getDeviceId()); logger.debug("[{}][{}] Forwarding processing chain to device actor due to end of chain.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
} else { } else {
logger.debug("[{}] Forwarding processing chain to next rule actor.", ctx.getInMsg().getDeviceId()); logger.debug("[{}][{}] Forwarding processing chain to next rule actor.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ChainProcessingContext nextTask = ctx.getNext(); ChainProcessingContext nextTask = ctx.getNext();
nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self()); nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self());
} }
@ -269,18 +275,16 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
public void onActivate(ActorContext context) throws Exception { public void onActivate(ActorContext context) throws Exception {
logger.info("[{}] Going to process onActivate rule.", entityId); logger.info("[{}] Going to process onActivate rule.", entityId);
this.state = ComponentLifecycleState.ACTIVE; this.state = ComponentLifecycleState.ACTIVE;
if (action != null) {
if (filters != null) { if (filters != null) {
filters.forEach(f -> f.resume()); filters.forEach(RuleLifecycleComponent::resume);
} else {
initFilters();
}
if (processor != null) { if (processor != null) {
processor.resume(); processor.resume();
} else { } else {
initProcessor(); initProcessor();
} }
if (action != null) {
action.resume(); action.resume();
}
logger.info("[{}] Rule resumed.", entityId); logger.info("[{}] Rule resumed.", entityId);
} else { } else {
start(); start();

View File

@ -72,16 +72,19 @@ public abstract class RuleManager {
} }
public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) { public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) {
RuleMetaData rule = null; RuleMetaData rule;
if (event != ComponentLifecycleEvent.DELETED) { if (event != ComponentLifecycleEvent.DELETED) {
rule = systemContext.getRuleService().findRuleById(ruleId); rule = systemContext.getRuleService().findRuleById(ruleId);
} } else {
if (rule == null) {
rule = ruleMap.keySet().stream() rule = ruleMap.keySet().stream()
.filter(r -> r.getId().equals(ruleId)) .filter(r -> r.getId().equals(ruleId))
.peek(r -> r.setState(ComponentLifecycleState.SUSPENDED)) .peek(r -> r.setState(ComponentLifecycleState.SUSPENDED))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
if (rule != null) {
ruleMap.remove(rule);
ruleActors.remove(ruleId);
}
} }
if (rule != null) { if (rule != null) {
RuleActorMetaData actorMd = ruleMap.get(rule); RuleActorMetaData actorMd = ruleMap.get(rule);

View File

@ -25,7 +25,7 @@
</encoder> </encoder>
</appender> </appender>
<logger name="org.thingsboard.server" level="INFO" /> <logger name="org.thingsboard.server" level="TRACE" />
<logger name="akka" level="INFO" /> <logger name="akka" level="INFO" />
<root level="INFO"> <root level="INFO">

View File

@ -176,7 +176,7 @@ actors:
statistics: statistics:
# Enable/disable actor statistics # Enable/disable actor statistics
enabled: "${ACTORS_STATISTICS_ENABLED:true}" enabled: "${ACTORS_STATISTICS_ENABLED:true}"
persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:60000}" persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
# Cache parameters # Cache parameters
cache: cache:

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.rule;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.RuleId; import org.thingsboard.server.common.data.id.RuleId;
@ -24,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
@Data @Data
@EqualsAndHashCode(callSuper = true)
public class RuleMetaData extends SearchTextBased<RuleId> implements HasName { public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
private static final long serialVersionUID = -5656679015122935465L; private static final long serialVersionUID = -5656679015122935465L;

View File

@ -91,7 +91,9 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic
if (rule.getProcessor() != null && !rule.getProcessor().isNull()) { if (rule.getProcessor() != null && !rule.getProcessor().isNull()) {
validateComponentJson(rule.getProcessor(), ComponentType.PROCESSOR); validateComponentJson(rule.getProcessor(), ComponentType.PROCESSOR);
} }
if (rule.getAction() != null && !rule.getAction().isNull()) {
validateComponentJson(rule.getAction(), ComponentType.ACTION); validateComponentJson(rule.getAction(), ComponentType.ACTION);
}
validateRuleAndPluginState(rule); validateRuleAndPluginState(rule);
return ruleDao.save(rule); return ruleDao.save(rule);
} }
@ -129,6 +131,9 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic
} }
private void validateRuleAndPluginState(RuleMetaData rule) { private void validateRuleAndPluginState(RuleMetaData rule) {
if (org.springframework.util.StringUtils.isEmpty(rule.getPluginToken())) {
return;
}
PluginMetaData pluginMd = pluginService.findPluginByApiToken(rule.getPluginToken()); PluginMetaData pluginMd = pluginService.findPluginByApiToken(rule.getPluginToken());
if (pluginMd == null) { if (pluginMd == null) {
throw new IncorrectParameterException("Rule points to non-existent plugin!"); throw new IncorrectParameterException("Rule points to non-existent plugin!");

View File

@ -125,10 +125,10 @@ public class AlarmProcessor implements RuleProcessor<AlarmProcessorConfiguration
Alarm alarm = buildAlarm(ctx, msg); Alarm alarm = buildAlarm(ctx, msg);
existing = ctx.createOrUpdateAlarm(alarm); existing = ctx.createOrUpdateAlarm(alarm);
if (existing.getStartTs() == alarm.getStartTs()) { if (existing.getStartTs() == alarm.getStartTs()) {
log.debug("[{}][{}] New Active Alarm detected"); log.debug("[{}][{}] New Active Alarm detected", ctx.getRuleId(), existing.getId());
md.put(IS_NEW_ALARM, Boolean.TRUE); md.put(IS_NEW_ALARM, Boolean.TRUE);
} else { } else {
log.debug("[{}][{}] Existing Active Alarm detected"); log.debug("[{}][{}] Existing Active Alarm detected", ctx.getRuleId(), existing.getId());
md.put(IS_EXISTING_ALARM, Boolean.TRUE); md.put(IS_EXISTING_ALARM, Boolean.TRUE);
} }
} else if (isClearedAlarm) { } else if (isClearedAlarm) {

View File

@ -165,11 +165,11 @@
<fieldset ng-disabled="loading || !isEdit || isReadOnly"> <fieldset ng-disabled="loading || !isEdit || isReadOnly">
<md-input-container ng-if="!isEdit || isReadOnly" flex class="md-block"> <md-input-container ng-if="!isEdit || isReadOnly" flex class="md-block">
<label translate>plugin.plugin</label> <label translate>plugin.plugin</label>
<input required name="name" ng-model="plugin.name"> <input name="name" ng-model="plugin.name">
</md-input-container> </md-input-container>
<tb-plugin-select ng-show="isEdit && !isReadOnly" flex <tb-plugin-select ng-show="isEdit && !isReadOnly" flex
ng-model="plugin" ng-model="plugin"
tb-required="true" tb-required="false"
the-form="theForm" the-form="theForm"
plugins-scope="action"> plugins-scope="action">
</tb-plugin-select> </tb-plugin-select>

View File

@ -85,10 +85,11 @@ export default function RuleDirective($compile, $templateCache, $mdDialog, $docu
if (scope.rule) { if (scope.rule) {
var valid = scope.rule.filters && scope.rule.filters.length > 0; var valid = scope.rule.filters && scope.rule.filters.length > 0;
scope.theForm.$setValidity('filters', valid); scope.theForm.$setValidity('filters', valid);
valid = angular.isDefined(scope.rule.pluginToken) && scope.rule.pluginToken != null; var processorDefined = angular.isDefined(scope.rule.processor) && scope.rule.processor != null;
scope.theForm.$setValidity('plugin', valid); var pluginDefined = angular.isDefined(scope.rule.pluginToken) && scope.rule.pluginToken != null;
valid = angular.isDefined(scope.rule.action) && scope.rule.action != null; var pluginActionDefined = angular.isDefined(scope.rule.action) && scope.rule.action != null;
scope.theForm.$setValidity('action', valid); valid = processorDefined && !pluginDefined || (pluginDefined && pluginActionDefined);
scope.theForm.$setValidity('processorOrPlugin', valid);
} }
}; };
@ -160,6 +161,7 @@ export default function RuleDirective($compile, $templateCache, $mdDialog, $docu
scope.$watch('rule.processor', function(newVal, prevVal) { scope.$watch('rule.processor', function(newVal, prevVal) {
if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) { if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
scope.theForm.$setDirty(); scope.theForm.$setDirty();
scope.updateValidity();
} }
}, true); }, true);