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 18 import java.util.*;
19 19
20 20 import com.fasterxml.jackson.core.JsonProcessingException;
  21 +import org.springframework.util.StringUtils;
21 22 import org.thingsboard.server.actors.ActorSystemContext;
22 23 import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper;
23 24 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
... ... @@ -113,8 +114,9 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
113 114 }
114 115
115 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 122 private void initProcessor() throws Exception {
... ... @@ -131,9 +133,11 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
131 133 }
132 134
133 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 143 protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
... ... @@ -162,25 +166,27 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
162 166 inMsgMd = new RuleProcessingMetaData();
163 167 }
164 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 190 RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid());
185 191 if (pendingMsg != null) {
186 192 ChainProcessingContext ctx = pendingMsg.getCtx();
... ... @@ -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 206 RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId());
201 207 if (pendingMsg != null) {
202 208 logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg);
... ... @@ -210,13 +216,13 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
210 216 ctx = ctx.withError(error);
211 217 }
212 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 220 ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
215 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 223 ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
218 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 226 ChainProcessingContext nextTask = ctx.getNext();
221 227 nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self());
222 228 }
... ... @@ -269,18 +275,16 @@ class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
269 275 public void onActivate(ActorContext context) throws Exception {
270 276 logger.info("[{}] Going to process onActivate rule.", entityId);
271 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 280 if (processor != null) {
279 281 processor.resume();
280 282 } else {
281 283 initProcessor();
282 284 }
283   - action.resume();
  285 + if (action != null) {
  286 + action.resume();
  287 + }
284 288 logger.info("[{}] Rule resumed.", entityId);
285 289 } else {
286 290 start();
... ...
... ... @@ -72,16 +72,19 @@ public abstract class RuleManager {
72 72 }
73 73
74 74 public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) {
75   - RuleMetaData rule = null;
  75 + RuleMetaData rule;
76 76 if (event != ComponentLifecycleEvent.DELETED) {
77 77 rule = systemContext.getRuleService().findRuleById(ruleId);
78   - }
79   - if (rule == null) {
  78 + } else {
80 79 rule = ruleMap.keySet().stream()
81 80 .filter(r -> r.getId().equals(ruleId))
82 81 .peek(r -> r.setState(ComponentLifecycleState.SUSPENDED))
83 82 .findFirst()
84 83 .orElse(null);
  84 + if (rule != null) {
  85 + ruleMap.remove(rule);
  86 + ruleActors.remove(ruleId);
  87 + }
85 88 }
86 89 if (rule != null) {
87 90 RuleActorMetaData actorMd = ruleMap.get(rule);
... ...
... ... @@ -25,7 +25,7 @@
25 25 </encoder>
26 26 </appender>
27 27
28   - <logger name="org.thingsboard.server" level="INFO" />
  28 + <logger name="org.thingsboard.server" level="TRACE" />
29 29 <logger name="akka" level="INFO" />
30 30
31 31 <root level="INFO">
... ...
... ... @@ -176,7 +176,7 @@ actors:
176 176 statistics:
177 177 # Enable/disable actor statistics
178 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 181 # Cache parameters
182 182 cache:
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.rule;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
20 21 import org.thingsboard.server.common.data.HasName;
21 22 import org.thingsboard.server.common.data.SearchTextBased;
22 23 import org.thingsboard.server.common.data.id.RuleId;
... ... @@ -24,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId;
24 25 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
25 26
26 27 @Data
  28 +@EqualsAndHashCode(callSuper = true)
27 29 public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
28 30
29 31 private static final long serialVersionUID = -5656679015122935465L;
... ...
... ... @@ -91,7 +91,9 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic
91 91 if (rule.getProcessor() != null && !rule.getProcessor().isNull()) {
92 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 97 validateRuleAndPluginState(rule);
96 98 return ruleDao.save(rule);
97 99 }
... ... @@ -129,6 +131,9 @@ public class BaseRuleService extends AbstractEntityService implements RuleServic
129 131 }
130 132
131 133 private void validateRuleAndPluginState(RuleMetaData rule) {
  134 + if (org.springframework.util.StringUtils.isEmpty(rule.getPluginToken())) {
  135 + return;
  136 + }
132 137 PluginMetaData pluginMd = pluginService.findPluginByApiToken(rule.getPluginToken());
133 138 if (pluginMd == null) {
134 139 throw new IncorrectParameterException("Rule points to non-existent plugin!");
... ...
... ... @@ -125,10 +125,10 @@ public class AlarmProcessor implements RuleProcessor<AlarmProcessorConfiguration
125 125 Alarm alarm = buildAlarm(ctx, msg);
126 126 existing = ctx.createOrUpdateAlarm(alarm);
127 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 129 md.put(IS_NEW_ALARM, Boolean.TRUE);
130 130 } else {
131   - log.debug("[{}][{}] Existing Active Alarm detected");
  131 + log.debug("[{}][{}] Existing Active Alarm detected", ctx.getRuleId(), existing.getId());
132 132 md.put(IS_EXISTING_ALARM, Boolean.TRUE);
133 133 }
134 134 } else if (isClearedAlarm) {
... ...
... ... @@ -165,11 +165,11 @@
165 165 <fieldset ng-disabled="loading || !isEdit || isReadOnly">
166 166 <md-input-container ng-if="!isEdit || isReadOnly" flex class="md-block">
167 167 <label translate>plugin.plugin</label>
168   - <input required name="name" ng-model="plugin.name">
  168 + <input name="name" ng-model="plugin.name">
169 169 </md-input-container>
170 170 <tb-plugin-select ng-show="isEdit && !isReadOnly" flex
171 171 ng-model="plugin"
172   - tb-required="true"
  172 + tb-required="false"
173 173 the-form="theForm"
174 174 plugins-scope="action">
175 175 </tb-plugin-select>
... ...
... ... @@ -85,10 +85,11 @@ export default function RuleDirective($compile, $templateCache, $mdDialog, $docu
85 85 if (scope.rule) {
86 86 var valid = scope.rule.filters && scope.rule.filters.length > 0;
87 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 161 scope.$watch('rule.processor', function(newVal, prevVal) {
161 162 if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
162 163 scope.theForm.$setDirty();
  164 + scope.updateValidity();
163 165 }
164 166 }, true);
165 167
... ...