Merge pull request #218 from thingsboard/feature/TB-73
TB-73: Make Rule Action optional
This commit is contained in:
commit
f87e5d66bf
@ -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,9 +133,11 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void fetchPluginInfo() {
|
private void fetchPluginInfo() {
|
||||||
PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
|
if (!StringUtils.isEmpty(ruleMd.getPluginToken())) {
|
||||||
pluginTenantId = pluginMd.getTenantId();
|
PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
|
||||||
pluginId = pluginMd.getId();
|
pluginTenantId = pluginMd.getTenantId();
|
||||||
|
pluginId = pluginMd.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
|
protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
|
||||||
@ -162,25 +166,27 @@ 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);
|
||||||
Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);
|
if (action != null) {
|
||||||
if (ruleToPluginMsgOptional.isPresent()) {
|
Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);
|
||||||
RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();
|
if (ruleToPluginMsgOptional.isPresent()) {
|
||||||
logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg);
|
RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();
|
||||||
context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());
|
logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg);
|
||||||
if (action.isOneWayAction()) {
|
context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());
|
||||||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
|
if (action.isOneWayAction()) {
|
||||||
} else {
|
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
|
||||||
pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);
|
return;
|
||||||
scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout());
|
} else {
|
||||||
|
pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);
|
||||||
|
scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
|
|
||||||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_REQUEST_FROM_ACTIONS);
|
|
||||||
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(RuleLifecycleComponent::resume);
|
||||||
filters.forEach(f -> f.resume());
|
|
||||||
} else {
|
|
||||||
initFilters();
|
|
||||||
}
|
|
||||||
if (processor != null) {
|
if (processor != null) {
|
||||||
processor.resume();
|
processor.resume();
|
||||||
} else {
|
} else {
|
||||||
initProcessor();
|
initProcessor();
|
||||||
}
|
}
|
||||||
action.resume();
|
if (action != null) {
|
||||||
|
action.resume();
|
||||||
|
}
|
||||||
logger.info("[{}] Rule resumed.", entityId);
|
logger.info("[{}] Rule resumed.", entityId);
|
||||||
} else {
|
} else {
|
||||||
start();
|
start();
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
validateComponentJson(rule.getAction(), ComponentType.ACTION);
|
if (rule.getAction() != null && !rule.getAction().isNull()) {
|
||||||
|
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!");
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user