Skip to content

Commit

Permalink
Merge pull request #195 from rsopp/DynamicRuleRuilder
Browse files Browse the repository at this point in the history
Dynamic rule ruilder
  • Loading branch information
seaside1 committed Mar 10, 2024
2 parents 7d1afa2 + 9e808d5 commit b11a735
Show file tree
Hide file tree
Showing 19 changed files with 857 additions and 260 deletions.
31 changes: 31 additions & 0 deletions doc/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
+ [Example 40 - Delay rule execution](#example-40---delay-rule-execution)
+ [Example 41 - Get Metadata and Tags](#example-41---get-metadata-and-tags)
+ [Example 42 - Persist future data](#example-42---persist-future-data)
+ [Example 43 - Creating a rule dynamically using JRuleBuilder](#example-43---creating-a-rule-dynamically-using-jrulebuilder)

### Example 1 - Invoke another item Switch from rule

Expand Down Expand Up @@ -1094,3 +1095,33 @@ public class DemoRule extends JRule {
}
}
```

## Example 43 - Creating a rule dynamically using JRuleBuilder

Use case: Build a rule dynamically during runtime without static annotations. Can be used when rule parameters are not
known at compile time, e.g. when they are read from a configuration file

```java
package org.openhab.automation.jrule.rules.user;

import org.openhab.automation.jrule.internal.engine.JRuleEngine;
import org.openhab.automation.jrule.rules.JRule;
import org.openhab.automation.jrule.rules.JRuleMemberOf;

public class DynamicRuleModule extends JRule {

public DynamicRuleModule() {
registerDynamicRules();
}

private void registerDynamicRules() {
logInfo("Registering Dynamic JRules");

JRuleEngine.get().createJRuleBuilder("Example dynamic rule", event ->
logInfo("Received command {}", event)
)
.whenItemChange("MyItem", JRuleMemberOf.None, "OFF", "ON", null, null)
.build();
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.automation.jrule.internal.engine;

import static org.openhab.automation.jrule.internal.engine.JRuleEngine.EMPTY_LOG_TAGS;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.jdt.annotation.Nullable;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRuleChannelExecutionContext;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRuleItemChangeExecutionContext;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRuleItemExecutionContext;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRuleItemReceivedCommandExecutionContext;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRuleItemReceivedUpdateExecutionContext;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRulePreconditionContext;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRuleThingExecutionContext;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRuleTimeTimerExecutionContext;
import org.openhab.automation.jrule.internal.engine.excutioncontext.JRuleTimedCronExecutionContext;
import org.openhab.automation.jrule.internal.module.JRuleModuleEntry;
import org.openhab.automation.jrule.rules.JRuleMemberOf;
import org.openhab.automation.jrule.things.JRuleThingStatus;

/**
* The {@link JRuleBuilder}
*
* @author Rüdiger Sopp - Initial contribution
*/
public class JRuleBuilder {

private final JRuleEngine jRuleEngine;

private final JRuleInvocationCallback invocationCallback;
private final String ruleName;
private boolean enableRule = false;
private String uid = null;
private String logName = null;
private String[] loggingTags = EMPTY_LOG_TAGS;
private Duration timedLock = null;
private Duration delayed = null;

final private List<PreCondition> preConditions = new ArrayList<>();

final private List<WhenThingTrigger> whenThingTriggers = new ArrayList<>();
final private List<WhenChannelTrigger> whenChannelTriggers = new ArrayList<>();
final private List<WhenItemReceivedCommand> whenItemReceivedCommandTriggers = new ArrayList<>();
final private List<WhenItemChanged> whenItemChangedTriggers = new ArrayList<>();
final private List<WhenItemReceivedUpdate> whenItemReceivedUpdateTriggers = new ArrayList<>();
final private List<WhenCronTrigger> whenCronTriggers = new ArrayList<>();
final private List<WhenTimeTrigger> whenTimeTriggers = new ArrayList<>();

JRuleBuilder(JRuleEngine jRuleEngine, String ruleName, JRuleInvocationCallback invocationCallback) {
this.jRuleEngine = jRuleEngine;
this.ruleName = ruleName;
this.invocationCallback = invocationCallback;
}

public JRuleBuilder enableRule(boolean enableRule) {
this.enableRule = enableRule;
return this;
}

public JRuleBuilder uid(String uid) {
this.uid = uid;
return this;
}

public JRuleBuilder logName(String logName) {
this.logName = logName;
return this;
}

public JRuleBuilder loggingTags(String[] loggingTags) {
this.loggingTags = loggingTags;
return this;
}

public JRuleBuilder timedLock(Duration timedLock) {
this.timedLock = timedLock;
return this;
}

public JRuleBuilder delayed(Duration delayed) {
this.delayed = delayed;
return this;
}

public JRuleBuilder preCondition(String itemName, Condition condition) {
preConditions.add(new PreCondition(itemName, condition));
return this;
}

public JRuleBuilder whenThingTrigger(String thingName, @Nullable JRuleThingStatus from,
@Nullable JRuleThingStatus to) {
whenThingTriggers.add(new WhenThingTrigger(thingName, from, to));
return this;
}

public JRuleBuilder whenChannelTrigger(String channelName, @Nullable String event) {
whenChannelTriggers.add(new WhenChannelTrigger(channelName, event));
return this;
}

public JRuleBuilder whenItemReceivedCommand(String itemName, JRuleMemberOf memberOf, @Nullable String command,
@Nullable Condition condition) {
whenItemReceivedCommandTriggers.add(new WhenItemReceivedCommand(itemName, memberOf, command, condition));
return this;
}

public JRuleBuilder whenItemChange(String itemName, JRuleMemberOf memberOf, @Nullable String from,
@Nullable String to, @Nullable Condition previousCondition, @Nullable Condition condition) {
whenItemChangedTriggers.add(new WhenItemChanged(itemName, memberOf, from, to, previousCondition, condition));
return this;
}

public JRuleBuilder whenItemReceivedUpdate(String itemName, JRuleMemberOf memberOf, @Nullable String state,
@Nullable Condition condition) {
whenItemReceivedUpdateTriggers.add(new WhenItemReceivedUpdate(itemName, memberOf, state, condition));
return this;
}

public JRuleBuilder whenCronTrigger(String cron) {
whenCronTriggers.add(new WhenCronTrigger(cron));
return this;
}

public JRuleBuilder whenTimeTrigger(@Nullable Integer hour, @Nullable Integer minute, @Nullable Integer second) {
whenTimeTriggers.add(new WhenTimeTrigger(hour, minute, second));
return this;
}

public boolean build() {
if (uid == null) {
uid = UUID.randomUUID().toString();
}

if (logName == null) {
logName = ruleName;
}

final JRuleModuleEntry ruleModuleEntry = new JRuleModuleEntry(uid, ruleName);
ruleModuleEntry.addTags(loggingTags);

AtomicBoolean addedToContext = new AtomicBoolean(false);

List<JRulePreconditionContext> preconditionContexts = preConditions.stream()
.map(data -> new JRulePreconditionContext(data.itemName, Optional.ofNullable(data.condition.lt),
Optional.ofNullable(data.condition.lte), Optional.ofNullable(data.condition.gt),
Optional.ofNullable(data.condition.gte), Optional.ofNullable(data.condition.eq),
Optional.ofNullable(data.condition.neq)))
.toList();

whenThingTriggers.forEach(data -> {
JRuleThingExecutionContext context = new JRuleThingExecutionContext(uid, logName, loggingTags,
invocationCallback, Optional.ofNullable(data.thingName), Optional.ofNullable(data.from),
Optional.ofNullable(data.to), preconditionContexts, timedLock, delayed);
jRuleEngine.addToContext(context, enableRule);
jRuleEngine.ruleLoadingStatistics.addThingTrigger();
ruleModuleEntry.addJRuleWhenThingTrigger(context);
addedToContext.set(true);
});

whenChannelTriggers.forEach(data -> {
JRuleChannelExecutionContext context = new JRuleChannelExecutionContext(uid, logName, loggingTags,
invocationCallback, preconditionContexts, data.channelName, Optional.ofNullable(data.event),
timedLock, delayed);
jRuleEngine.addToContext(context, enableRule);
jRuleEngine.ruleLoadingStatistics.addChannelTrigger();
ruleModuleEntry.addJRuleWhenChannelTrigger(context);
addedToContext.set(true);
});

whenItemReceivedCommandTriggers.forEach(data -> {
JRuleItemReceivedCommandExecutionContext context = new JRuleItemReceivedCommandExecutionContext(uid,
logName, loggingTags, invocationCallback, data.itemName, data.memberOf,
Optional.ofNullable(data.condition).map(Condition::toJRuleConditionContext), preconditionContexts,
Optional.ofNullable(data.command), timedLock, delayed);

jRuleEngine.addToContext(context, enableRule);
jRuleEngine.ruleLoadingStatistics.addItemStateTrigger();
ruleModuleEntry.addJRuleWhenItemReceivedCommand(context);
addedToContext.set(true);
});

whenItemChangedTriggers.forEach(data -> {
JRuleItemChangeExecutionContext context = new JRuleItemChangeExecutionContext(uid, logName, loggingTags,
invocationCallback, data.itemName, data.memberOf,
Optional.ofNullable(data.condition).map(Condition::toJRuleConditionContext),
Optional.ofNullable(data.previousCondition).map(Condition::toJRuleConditionContext),
preconditionContexts, Optional.ofNullable(data.from), Optional.ofNullable(data.to), timedLock,
delayed);

jRuleEngine.addToContext(context, enableRule);
jRuleEngine.ruleLoadingStatistics.addItemStateTrigger();
ruleModuleEntry.addJRuleWhenItemChange(context);
addedToContext.set(true);
});

whenItemReceivedUpdateTriggers.forEach(data -> {
JRuleItemReceivedUpdateExecutionContext context = new JRuleItemReceivedUpdateExecutionContext(uid, logName,
loggingTags, invocationCallback, data.itemName, data.memberOf,
Optional.ofNullable(data.condition).map(Condition::toJRuleConditionContext), preconditionContexts,
Optional.ofNullable(data.state), timedLock, delayed);

jRuleEngine.addToContext(context, enableRule);
jRuleEngine.ruleLoadingStatistics.addItemStateTrigger();
ruleModuleEntry.addJRuleWhenItemReceivedUpdate(context);
addedToContext.set(true);
});

whenCronTriggers.forEach(data -> {
JRuleTimedCronExecutionContext context = new JRuleTimedCronExecutionContext(uid, logName, loggingTags,
invocationCallback, preconditionContexts, data.cron);

jRuleEngine.addToContext(context, enableRule);
jRuleEngine.ruleLoadingStatistics.addTimedTrigger();
ruleModuleEntry.addJRuleWhenCronTrigger(context);
addedToContext.set(true);
});

whenTimeTriggers.forEach(data -> {
JRuleTimeTimerExecutionContext context = new JRuleTimeTimerExecutionContext(uid, logName, loggingTags,
invocationCallback, preconditionContexts, Optional.ofNullable(data.hour),
Optional.ofNullable(data.minute), Optional.ofNullable(data.second));
jRuleEngine.addToContext(context, enableRule);
jRuleEngine.ruleLoadingStatistics.addTimedTrigger();
ruleModuleEntry.addJRuleWhenTimeTrigger(context);
addedToContext.set(true);
});

jRuleEngine.ruleProvider.add(ruleModuleEntry);

return addedToContext.get();
}

public record Condition(@Nullable Double lt, @Nullable Double lte, @Nullable Double gt, @Nullable Double gte,
@Nullable String eq, @Nullable String neq) {

private JRuleItemExecutionContext.JRuleConditionContext toJRuleConditionContext() {
return new JRuleItemExecutionContext.JRuleConditionContext(Optional.ofNullable(gt),
Optional.ofNullable(gte), Optional.ofNullable(lt), Optional.ofNullable(lte),
Optional.ofNullable(eq), Optional.ofNullable(neq));
}
}

private record PreCondition(String itemName, Condition condition) {
}

private record WhenThingTrigger(String thingName, JRuleThingStatus from, JRuleThingStatus to) {
}

private record WhenChannelTrigger(String channelName, String event) {
}

private record WhenItemReceivedCommand(String itemName, JRuleMemberOf memberOf, String command,
Condition condition) {
}

private record WhenItemChanged(String itemName, JRuleMemberOf memberOf, String from, String to,
Condition previousCondition, Condition condition) {
}

private record WhenItemReceivedUpdate(String itemName, JRuleMemberOf memberOf, String state, Condition condition) {
}

private record WhenCronTrigger(String cron) {
}

private record WhenTimeTrigger(Integer hour, Integer minute, Integer second) {
}
}
Loading

0 comments on commit b11a735

Please sign in to comment.