Commit f87e5d66bff0dcd9bf97ddebcdff0394f22081fb
Committed by
GitHub
Merge pull request #218 from thingsboard/feature/TB-73
TB-73: Make Rule Action optional
Showing
9 changed files
with
61 additions
and
45 deletions
@@ -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 |