Add unmanaged scripts to rule engine (#3156)

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K 2022-11-13 10:50:42 +01:00 committed by GitHub
parent d4ceca9fe0
commit 64a31026cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 25 deletions

View File

@ -35,12 +35,14 @@ public interface RuleManager {
* This method gets <b>enabled</b> {@link RuleStatus} for a {@link Rule}.
* The <b>enabled</b> rule statuses are {@link RuleStatus#UNINITIALIZED}, {@link RuleStatus#IDLE} and
* {@link RuleStatus#RUNNING}.
* The <b>disabled</b> rule status is {@link RuleStatus#DISABLED}.
* The <b>disabled</b> rule status is {@link RuleStatus#UNINITIALIZED} with {@link RuleStatusDetail#DISABLED}.
*
* @param ruleUID UID of the {@link Rule}
* @return {@code true} when the {@link RuleStatus} is one of the {@link RuleStatus#UNINITIALIZED},
* @return {@code true} when the {@link RuleStatus} is one of the {@link RuleStatus#UNINITIALIZED} with any other
* {@link RuleStatusDetail} than {@link RuleStatusDetail#DISABLED},
* {@link RuleStatus#IDLE} and {@link RuleStatus#RUNNING}, {@code false} when it is
* {@link RuleStatus#DISABLED} and {@code null} when it is not available.
* {@link RuleStatus#UNINITIALIZED} with {@link RuleStatusDetail#DISABLED} and {@code null} when it is not
* available.
*/
@Nullable
Boolean isEnabled(String ruleUID);
@ -49,7 +51,7 @@ public interface RuleManager {
* This method is used for changing <b>enabled</b> state of the {@link Rule}.
* The <b>enabled</b> rule statuses are {@link RuleStatus#UNINITIALIZED}, {@link RuleStatus#IDLE} and
* {@link RuleStatus#RUNNING}.
* The <b>disabled</b> rule status is {@link RuleStatus#DISABLED}.
* The <b>disabled</b> rule status is {@link RuleStatus#UNINITIALIZED} with {@link RuleStatusDetail#DISABLED}.
*
* @param uid the unique identifier of the {@link Rule}.
* @param isEnabled a new <b>enabled / disabled</b> state of the {@link Rule}.
@ -81,19 +83,21 @@ public interface RuleManager {
* This should always be possible unless an action has a mandatory input that is linked to a trigger.
* In that case the action is skipped and the rule engine continues execution of rest actions.
*
* @param ruleUID id of the rule whose actions have to be executed.
* @param uid id of the rule whose actions have to be executed.
* @return a copy of the rule context, including possible return values
*/
void runNow(String uid);
Map<String, Object> runNow(String uid);
/**
* Same as {@link #runNow(String)} with the additional option to enable/disable evaluation of
* conditions defined in the target rule. The context can be set here, too, but also might be {@code null}.
*
* @param ruleUID id of the rule whose actions have to be executed.
* @param uid id of the rule whose actions have to be executed.
* @param considerConditions if {@code true} the conditions of the rule will be checked.
* @param context the context that is passed to the conditions and the actions of the rule.
* @return a copy of the rule context, including possible return values
*/
void runNow(String uid, boolean considerConditions, @Nullable Map<String, Object> context);
Map<String, Object> runNow(String uid, boolean considerConditions, @Nullable Map<String, Object> context);
/**
* Simulates the execution of all rules with tag 'Schedule' for the given time interval.

View File

@ -440,7 +440,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
/**
* This method add a new rule into rule engine. Scope identity of the Rule is the identity of the caller.
*
* @param rule a rule which has to be added.
* @param newRule a rule which has to be added.
*/
protected void addRule(Rule newRule) {
synchronized (this) {
@ -587,7 +587,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
* Gets {@link TriggerHandlerCallback} for passed {@link Rule}. If it does not exists, a callback object is
* created.
*
* @param rule rule object for which the callback is looking for.
* @param ruleUID rule object for which the callback is looking for.
* @return a {@link TriggerHandlerCallback} corresponding to the passed {@link Rule} object.
*/
private synchronized TriggerHandlerCallbackImpl getTriggerHandlerCallback(String ruleUID) {
@ -1005,17 +1005,19 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
}
@Override
public void runNow(String ruleUID, boolean considerConditions, @Nullable Map<String, Object> context) {
public Map<String, Object> runNow(String ruleUID, boolean considerConditions,
@Nullable Map<String, Object> context) {
Map<String, Object> returnContext = new HashMap<>();
final WrappedRule rule = getManagedRule(ruleUID);
if (rule == null) {
logger.warn("Failed to execute rule '{}': Invalid Rule UID", ruleUID);
return;
return returnContext;
}
synchronized (this) {
final RuleStatus ruleStatus = getRuleStatus(ruleUID);
if (ruleStatus != null && ruleStatus != RuleStatus.IDLE) {
logger.error("Failed to execute rule {}' with status '{}'", ruleUID, ruleStatus.name());
return;
return returnContext;
}
// change state to RUNNING
setStatus(ruleUID, new RuleStatusInfo(RuleStatus.RUNNING));
@ -1025,14 +1027,11 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
if (context != null && !context.isEmpty()) {
getContext(ruleUID, null).putAll(context);
}
if (considerConditions) {
if (calculateConditions(rule)) {
executeActions(rule, false);
}
} else {
if (!considerConditions || calculateConditions(rule)) {
executeActions(rule, false);
}
logger.debug("The rule '{}' is executed.", ruleUID);
returnContext.putAll(getContext(ruleUID, null));
} catch (Throwable t) {
logger.error("Failed to execute rule '{}': ", ruleUID, t);
}
@ -1042,11 +1041,12 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
setStatus(ruleUID, new RuleStatusInfo(RuleStatus.IDLE));
}
}
return returnContext;
}
@Override
public void runNow(String ruleUID) {
runNow(ruleUID, false, null);
public Map<String, Object> runNow(String ruleUID) {
return runNow(ruleUID, false, null);
}
/**
@ -1092,11 +1092,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
* @return copy of current context in rule engine
*/
private Map<String, Object> getContext(String ruleUID, @Nullable Set<Connection> connections) {
Map<String, Object> context = contextMap.get(ruleUID);
if (context == null) {
context = new HashMap<>();
contextMap.put(ruleUID, context);
}
Map<String, Object> context = contextMap.computeIfAbsent(ruleUID, k -> new HashMap<>());
if (connections != null) {
StringBuffer sb = new StringBuffer();
for (Connection c : connections) {

View File

@ -20,6 +20,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -58,6 +59,7 @@ import org.openhab.core.model.rule.rules.TimerTrigger;
import org.openhab.core.model.rule.rules.DateTimeTrigger;
import org.openhab.core.model.rule.rules.UpdateEventTrigger;
import org.openhab.core.model.script.runtime.DSLScriptContextProvider;
import org.openhab.core.model.script.script.Script;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.service.ReadyMarkerFilter;
import org.openhab.core.service.ReadyService;
@ -170,6 +172,28 @@ public class DSLRuleProvider
default:
logger.debug("Unknown event type.");
}
} else if ("script".equals(ruleModelType)) {
switch (type) {
case MODIFIED:
Rule oldRule = rules.remove(modelFileName);
if (oldRule != null) {
removeRule(oldRule);
}
case ADDED:
EObject model = modelRepository.getModel(modelFileName);
if (model instanceof Script) {
addRule(toRule(modelFileName, ((Script) model)));
}
break;
case REMOVED:
oldRule = rules.remove(modelFileName);
if (oldRule != null) {
removeRule(oldRule);
}
break;
default:
logger.debug("Unknown event type.");
}
}
}
@ -244,6 +268,18 @@ public class DSLRuleProvider
}
}
private Rule toRule(String modelName, Script script) {
String scriptText = NodeModelUtils.findActualNodeFor(script).getText();
Configuration cfg = new Configuration();
cfg.put("script", removeIndentation(scriptText));
cfg.put("type", MIMETYPE_OPENHAB_DSL_RULE);
List<Action> actions = List.of(ActionBuilder.create().withId("script").withTypeUID("script.ScriptAction")
.withConfiguration(cfg).build());
return RuleBuilder.create(modelName).withTags("Script").withName(modelName).withActions(actions).build();
}
private Rule toRule(String modelName, org.openhab.core.model.rule.rules.Rule rule, int index) {
String name = rule.getName();
String uid = modelName + "-" + index;