mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-25 11:45:49 +01:00
Replaced "classic" rule engine by a DSLRuleProvider for the NGRE (#1451)
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
parent
1fddac192b
commit
173c93081d
@ -94,12 +94,6 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.scheduler</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.semantics</artifactId>
|
||||
|
@ -1,2 +0,0 @@
|
||||
Bundle-SymbolicName: ${project.artifactId}
|
||||
Bundle-Activator: org.openhab.core.internal.CoreActivator
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.internal;
|
||||
|
||||
import org.openhab.core.model.rule.runtime.RuleEngine;
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
|
||||
/**
|
||||
* This is the activator of the core openHAB bundle.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Thomas Eichstaedt-Engelen - Initial contribution
|
||||
*/
|
||||
public class CoreActivator implements BundleActivator {
|
||||
|
||||
@Override
|
||||
public void start(BundleContext context) throws Exception {
|
||||
startRuleEngine(context);
|
||||
}
|
||||
|
||||
private void startRuleEngine(BundleContext context) throws InterruptedException {
|
||||
// TODO: This is a workaround as long as we cannot determine the time when all models have been loaded
|
||||
Thread.sleep(2000);
|
||||
|
||||
// we now request the RuleEngine, so that it is activated and starts processing the rules
|
||||
// TODO: This should probably better be moved in a new bundle, so that the core bundle does
|
||||
// not have a (optional) dependency on model.rule.runtime anymore.
|
||||
try {
|
||||
ServiceTracker<RuleEngine, RuleEngine> tracker = new ServiceTracker<>(context, RuleEngine.class, null);
|
||||
tracker.open();
|
||||
tracker.waitForService(10000);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
|
||||
*/
|
||||
@Override
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
}
|
||||
}
|
@ -20,6 +20,11 @@
|
||||
<artifactId>org.openhab.core.model.rule</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.script.runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This is a marker interface for Rule Engines.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RuleEngine {
|
||||
|
||||
}
|
@ -0,0 +1,435 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
|
||||
import org.eclipse.xtext.xbase.XBlockExpression;
|
||||
import org.eclipse.xtext.xbase.XExpression;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
|
||||
import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.automation.RuleProvider;
|
||||
import org.openhab.core.automation.Trigger;
|
||||
import org.openhab.core.automation.util.ActionBuilder;
|
||||
import org.openhab.core.automation.util.RuleBuilder;
|
||||
import org.openhab.core.automation.util.TriggerBuilder;
|
||||
import org.openhab.core.common.registry.ProviderChangeListener;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.model.core.EventType;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.core.ModelRepositoryChangeListener;
|
||||
import org.openhab.core.model.rule.jvmmodel.RulesRefresher;
|
||||
import org.openhab.core.model.rule.rules.ChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.CommandEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.EventEmittedTrigger;
|
||||
import org.openhab.core.model.rule.rules.EventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberCommandEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberUpdateEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.RuleModel;
|
||||
import org.openhab.core.model.rule.rules.SystemOnShutdownTrigger;
|
||||
import org.openhab.core.model.rule.rules.SystemOnStartupTrigger;
|
||||
import org.openhab.core.model.rule.rules.ThingStateChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.ThingStateUpdateEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.TimerTrigger;
|
||||
import org.openhab.core.model.rule.rules.UpdateEventTrigger;
|
||||
import org.openhab.core.model.script.runtime.DSLScriptContextProvider;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
import org.openhab.core.service.ReadyMarkerFilter;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
import org.openhab.core.service.ReadyService.ReadyTracker;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This RuleProvider provides rules that are defined in DSL rule files.
|
||||
* All rules consist out of a list of triggers and a single script action.
|
||||
* No rule conditions are used as this concept does not exist for DSL rules.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true, service = { DSLRuleProvider.class, RuleProvider.class, DSLScriptContextProvider.class })
|
||||
public class DSLRuleProvider
|
||||
implements RuleProvider, ModelRepositoryChangeListener, DSLScriptContextProvider, ReadyTracker {
|
||||
|
||||
private static final String RULES_MODEL_NAME = "rules";
|
||||
private static final String ITEMS_MODEL_NAME = "items";
|
||||
private static final String THINGS_MODEL_NAME = "things";
|
||||
static final String MIMETYPE_OPENHAB_DSL_RULE = "application/vnd.openhab.dsl.rule";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSLRuleProvider.class);
|
||||
private final Collection<ProviderChangeListener<Rule>> listeners = new ArrayList<>();
|
||||
private final Map<String, Rule> rules = new HashMap<>();
|
||||
private final Map<String, IEvaluationContext> contexts = new HashMap<>();
|
||||
private final Map<String, XExpression> xExpressions = new HashMap<>();
|
||||
private int triggerId = 0;
|
||||
private Set<String> markers = new HashSet<>();
|
||||
|
||||
private final ModelRepository modelRepository;
|
||||
private final ReadyService readyService;
|
||||
|
||||
@Activate
|
||||
public DSLRuleProvider(@Reference ModelRepository modelRepository, @Reference ReadyService readyService) {
|
||||
this.modelRepository = modelRepository;
|
||||
this.readyService = readyService;
|
||||
}
|
||||
|
||||
@Activate
|
||||
protected void activate() {
|
||||
readyService.registerTracker(this, new ReadyMarkerFilter().withType("dsl"));
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
modelRepository.removeModelRepositoryChangeListener(this);
|
||||
rules.clear();
|
||||
contexts.clear();
|
||||
xExpressions.clear();
|
||||
markers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProviderChangeListener(ProviderChangeListener<Rule> listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Rule> getAll() {
|
||||
return rules.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProviderChangeListener(ProviderChangeListener<Rule> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modelChanged(String modelFileName, EventType type) {
|
||||
String ruleModelName = modelFileName.substring(0, modelFileName.indexOf("."));
|
||||
switch (type) {
|
||||
case ADDED:
|
||||
EObject model = modelRepository.getModel(modelFileName);
|
||||
if (model instanceof RuleModel) {
|
||||
RuleModel ruleModel = (RuleModel) model;
|
||||
int index = 1;
|
||||
for (org.openhab.core.model.rule.rules.Rule rule : ruleModel.getRules()) {
|
||||
addRule(toRule(ruleModelName, rule, index));
|
||||
xExpressions.put(ruleModelName + "-" + index, rule.getScript());
|
||||
index++;
|
||||
}
|
||||
handleVarDeclarations(ruleModelName, ruleModel);
|
||||
}
|
||||
break;
|
||||
case MODIFIED:
|
||||
removeRuleModel(ruleModelName);
|
||||
EObject modifiedModel = modelRepository.getModel(modelFileName);
|
||||
if (modifiedModel instanceof RuleModel) {
|
||||
RuleModel ruleModel = (RuleModel) modifiedModel;
|
||||
int index = 1;
|
||||
for (org.openhab.core.model.rule.rules.Rule rule : ruleModel.getRules()) {
|
||||
Rule newRule = toRule(ruleModelName, rule, index);
|
||||
Rule oldRule = rules.get(ruleModelName);
|
||||
updateRule(oldRule, newRule);
|
||||
xExpressions.put(ruleModelName + "-" + index, rule.getScript());
|
||||
index++;
|
||||
}
|
||||
handleVarDeclarations(ruleModelName, ruleModel);
|
||||
}
|
||||
break;
|
||||
case REMOVED:
|
||||
removeRuleModel(ruleModelName);
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown event type.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable IEvaluationContext getContext(String contextName) {
|
||||
return contexts.get(contextName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable XExpression getParsedScript(String modelName, String index) {
|
||||
return xExpressions.get(modelName + "-" + index);
|
||||
}
|
||||
|
||||
private void handleVarDeclarations(String modelName, RuleModel ruleModel) {
|
||||
IEvaluationContext context = RuleContextHelper.getContext(ruleModel);
|
||||
contexts.put(modelName, context);
|
||||
}
|
||||
|
||||
private void addRule(Rule rule) {
|
||||
rules.put(rule.getUID(), rule);
|
||||
|
||||
for (ProviderChangeListener<Rule> providerChangeListener : listeners) {
|
||||
providerChangeListener.added(this, rule);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRule(@Nullable Rule oldRule, Rule newRule) {
|
||||
if (oldRule != null) {
|
||||
rules.remove(oldRule.getUID());
|
||||
for (ProviderChangeListener<Rule> providerChangeListener : listeners) {
|
||||
providerChangeListener.updated(this, oldRule, newRule);
|
||||
}
|
||||
} else {
|
||||
for (ProviderChangeListener<Rule> providerChangeListener : listeners) {
|
||||
providerChangeListener.added(this, newRule);
|
||||
}
|
||||
}
|
||||
rules.put(newRule.getUID(), newRule);
|
||||
}
|
||||
|
||||
private void removeRuleModel(String modelName) {
|
||||
Iterator<Entry<String, Rule>> it = rules.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Entry<String, Rule> entry = it.next();
|
||||
if (entry.getKey().startsWith(modelName + "-")) {
|
||||
removeRule(entry.getValue());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
Iterator<Entry<String, XExpression>> it2 = xExpressions.entrySet().iterator();
|
||||
while (it2.hasNext()) {
|
||||
Entry<String, XExpression> entry = it2.next();
|
||||
if (entry.getKey().startsWith(modelName + "-")) {
|
||||
it2.remove();
|
||||
}
|
||||
}
|
||||
contexts.remove(modelName);
|
||||
}
|
||||
|
||||
private void removeRule(Rule rule) {
|
||||
for (ProviderChangeListener<Rule> providerChangeListener : listeners) {
|
||||
providerChangeListener.removed(this, rule);
|
||||
}
|
||||
}
|
||||
|
||||
private Rule toRule(String modelName, org.openhab.core.model.rule.rules.Rule rule, int index) {
|
||||
String name = rule.getName();
|
||||
String uid = modelName + "-" + index;
|
||||
|
||||
// Create Triggers
|
||||
triggerId = 0;
|
||||
List<Trigger> triggers = new ArrayList<>();
|
||||
for (EventTrigger t : rule.getEventtrigger()) {
|
||||
Trigger trigger = mapTrigger(t);
|
||||
if (trigger != null) {
|
||||
triggers.add(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
// Create Action
|
||||
String context = DSLScriptContextProvider.CONTEXT_IDENTIFIER + modelName + "-" + index + "\n";
|
||||
XBlockExpression expression = rule.getScript();
|
||||
String script = NodeModelUtils.findActualNodeFor(expression).getText();
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("script", context + removeIndentation(script));
|
||||
cfg.put("type", MIMETYPE_OPENHAB_DSL_RULE);
|
||||
List<Action> actions = Collections.singletonList(ActionBuilder.create().withId("script")
|
||||
.withTypeUID("script.ScriptAction").withConfiguration(cfg).build());
|
||||
|
||||
return RuleBuilder.create(uid).withName(name).withTriggers(triggers).withActions(actions).build();
|
||||
}
|
||||
|
||||
private String removeIndentation(String script) {
|
||||
String s = script;
|
||||
// first let's remove empty lines at the beginning and add an empty line at the end to beautify the yaml style.
|
||||
if (s.startsWith("\n")) {
|
||||
s = s.substring(1);
|
||||
}
|
||||
if (s.startsWith("\r\n")) {
|
||||
s = s.substring(2);
|
||||
}
|
||||
if (!(s.endsWith("\n\n") || s.endsWith("\r\n\r\n"))) {
|
||||
s += "\n\n";
|
||||
}
|
||||
String firstLine = s.lines().findFirst().orElse("");
|
||||
String indentation = firstLine.substring(0, firstLine.length() - firstLine.stripLeading().length());
|
||||
return s.lines().map(line -> {
|
||||
return line.startsWith(indentation) ? line.substring(indentation.length()) : line;
|
||||
}).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private @Nullable Trigger mapTrigger(EventTrigger t) {
|
||||
if (t instanceof SystemOnStartupTrigger) {
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("startlevel", 20);
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
|
||||
.withTypeUID("core.SystemStartlevelTrigger").withConfiguration(cfg).build();
|
||||
} else if (t instanceof SystemOnShutdownTrigger) {
|
||||
logger.warn("System shutdown rule triggers are no longer supported!");
|
||||
return null;
|
||||
} else if (t instanceof CommandEventTrigger) {
|
||||
CommandEventTrigger ceTrigger = (CommandEventTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("itemName", ceTrigger.getItem());
|
||||
if (ceTrigger.getCommand() != null) {
|
||||
cfg.put("command", ceTrigger.getCommand().getValue());
|
||||
}
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID("core.ItemCommandTrigger")
|
||||
.withConfiguration(cfg).build();
|
||||
} else if (t instanceof GroupMemberCommandEventTrigger) {
|
||||
GroupMemberCommandEventTrigger ceTrigger = (GroupMemberCommandEventTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("groupName", ceTrigger.getGroup());
|
||||
if (ceTrigger.getCommand() != null) {
|
||||
cfg.put("command", ceTrigger.getCommand().getValue());
|
||||
}
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID("core.GroupCommandTrigger")
|
||||
.withConfiguration(cfg).build();
|
||||
} else if (t instanceof UpdateEventTrigger) {
|
||||
UpdateEventTrigger ueTrigger = (UpdateEventTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("itemName", ueTrigger.getItem());
|
||||
if (ueTrigger.getState() != null) {
|
||||
cfg.put("state", ueTrigger.getState().getValue());
|
||||
}
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
|
||||
.withTypeUID("core.ItemStateUpdateTrigger").withConfiguration(cfg).build();
|
||||
} else if (t instanceof GroupMemberUpdateEventTrigger) {
|
||||
GroupMemberUpdateEventTrigger ueTrigger = (GroupMemberUpdateEventTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("groupName", ueTrigger.getGroup());
|
||||
if (ueTrigger.getState() != null) {
|
||||
cfg.put("state", ueTrigger.getState().getValue());
|
||||
}
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
|
||||
.withTypeUID("core.GroupStateUpdateTrigger").withConfiguration(cfg).build();
|
||||
} else if (t instanceof ChangedEventTrigger) {
|
||||
ChangedEventTrigger ceTrigger = (ChangedEventTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("itemName", ceTrigger.getItem());
|
||||
if (ceTrigger.getNewState() != null) {
|
||||
cfg.put("state", ceTrigger.getNewState().getValue());
|
||||
}
|
||||
if (ceTrigger.getOldState() != null) {
|
||||
cfg.put("previousState", ceTrigger.getOldState().getValue());
|
||||
}
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
|
||||
.withTypeUID("core.ItemStateChangeTrigger").withConfiguration(cfg).build();
|
||||
} else if (t instanceof GroupMemberChangedEventTrigger) {
|
||||
GroupMemberChangedEventTrigger ceTrigger = (GroupMemberChangedEventTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("groupName", ceTrigger.getGroup());
|
||||
if (ceTrigger.getNewState() != null) {
|
||||
cfg.put("state", ceTrigger.getNewState().getValue());
|
||||
}
|
||||
if (ceTrigger.getOldState() != null) {
|
||||
cfg.put("previousState", ceTrigger.getOldState().getValue());
|
||||
}
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
|
||||
.withTypeUID("core.GroupStateChangeTrigger").withConfiguration(cfg).build();
|
||||
} else if (t instanceof TimerTrigger) {
|
||||
TimerTrigger tt = (TimerTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
String id;
|
||||
if (tt.getCron() != null) {
|
||||
id = tt.getCron();
|
||||
cfg.put("cronExpression", tt.getCron());
|
||||
} else {
|
||||
id = tt.getTime();
|
||||
if (id.equals("noon")) {
|
||||
cfg.put("cronExpression", "0 0 12 * * ?");
|
||||
} else if (id.equals("midnight")) {
|
||||
cfg.put("cronExpression", "0 0 0 * * ?");
|
||||
} else {
|
||||
logger.warn("Unrecognized time expression '{}' in rule trigger", tt.getTime());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID("timer.GenericCronTrigger")
|
||||
.withConfiguration(cfg).build();
|
||||
} else if (t instanceof EventEmittedTrigger) {
|
||||
EventEmittedTrigger eeTrigger = (EventEmittedTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("channelUID", eeTrigger.getChannel());
|
||||
if (eeTrigger.getTrigger() != null) {
|
||||
cfg.put("event", eeTrigger.getTrigger().getValue());
|
||||
}
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID("core.ChannelEventTrigger")
|
||||
.withConfiguration(cfg).build();
|
||||
} else if (t instanceof ThingStateUpdateEventTrigger) {
|
||||
ThingStateUpdateEventTrigger tsuTrigger = (ThingStateUpdateEventTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("thingUID", tsuTrigger.getThing());
|
||||
cfg.put("status", tsuTrigger.getState());
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
|
||||
.withTypeUID("core.ThingStatusUpdateTrigger").withConfiguration(cfg).build();
|
||||
} else if (t instanceof ThingStateChangedEventTrigger) {
|
||||
ThingStateChangedEventTrigger tscTrigger = (ThingStateChangedEventTrigger) t;
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put("thingUID", tscTrigger.getThing());
|
||||
cfg.put("status", tscTrigger.getNewState());
|
||||
cfg.put("previousStatus", tscTrigger.getOldState());
|
||||
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
|
||||
.withTypeUID("core.ThingStatusChangeTrigger").withConfiguration(cfg).build();
|
||||
} else {
|
||||
logger.warn("Unknown trigger type '{}' - ignoring it.", t.getClass().getSimpleName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isReady() {
|
||||
return markers.containsAll(
|
||||
Set.of(ITEMS_MODEL_NAME, THINGS_MODEL_NAME, RULES_MODEL_NAME, RulesRefresher.RULES_REFRESH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadyMarkerAdded(ReadyMarker readyMarker) {
|
||||
markers.add(readyMarker.getIdentifier());
|
||||
if (isReady()) {
|
||||
for (String ruleFileName : modelRepository.getAllModelNamesOfType(RULES_MODEL_NAME)) {
|
||||
EObject model = modelRepository.getModel(ruleFileName);
|
||||
String ruleModelName = ruleFileName.substring(0, ruleFileName.indexOf("."));
|
||||
if (model instanceof RuleModel) {
|
||||
RuleModel ruleModel = (RuleModel) model;
|
||||
int index = 1;
|
||||
for (org.openhab.core.model.rule.rules.Rule rule : ruleModel.getRules()) {
|
||||
addRule(toRule(ruleModelName, rule, index));
|
||||
xExpressions.put(ruleModelName + "-" + index, rule.getScript());
|
||||
index++;
|
||||
}
|
||||
handleVarDeclarations(ruleModelName, ruleModel);
|
||||
}
|
||||
}
|
||||
modelRepository.addModelRepositoryChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
|
||||
markers.remove(readyMarker.getIdentifier());
|
||||
}
|
||||
}
|
@ -10,13 +10,15 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.model.rule.runtime.internal.engine;
|
||||
package org.openhab.core.model.rule.runtime.internal;
|
||||
|
||||
import org.eclipse.emf.common.notify.Adapter;
|
||||
import org.eclipse.emf.ecore.util.EContentAdapter;
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.xtext.naming.QualifiedName;
|
||||
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
|
||||
import org.openhab.core.model.rule.rules.Rule;
|
||||
import org.openhab.core.model.rule.RulesStandaloneSetup;
|
||||
import org.openhab.core.model.rule.rules.RuleModel;
|
||||
import org.openhab.core.model.rule.rules.VariableDeclaration;
|
||||
import org.openhab.core.model.script.engine.ScriptEngine;
|
||||
@ -32,19 +34,19 @@ import com.google.inject.Provider;
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class RuleContextHelper {
|
||||
|
||||
/**
|
||||
* Retrieves the evaluation context (= set of variables) for a rule. The context is shared with all rules in the
|
||||
* Retrieves the evaluation context (= set of variables) for a ruleModel. The context is shared with all rules in
|
||||
* the
|
||||
* same model (= rule file).
|
||||
*
|
||||
* @param rule the rule to get the context for
|
||||
* @param ruleModel the ruleModel to get the context for
|
||||
* @return the evaluation context
|
||||
*/
|
||||
public static synchronized IEvaluationContext getContext(Rule rule, Injector injector) {
|
||||
public static synchronized IEvaluationContext getContext(RuleModel ruleModel) {
|
||||
Logger logger = LoggerFactory.getLogger(RuleContextHelper.class);
|
||||
RuleModel ruleModel = (RuleModel) rule.eContainer();
|
||||
Injector injector = RulesStandaloneSetup.getInjector();
|
||||
|
||||
// check if a context already exists on the resource
|
||||
for (Adapter adapter : ruleModel.eAdapters()) {
|
||||
@ -52,28 +54,30 @@ public class RuleContextHelper {
|
||||
return ((RuleContextAdapter) adapter).getContext();
|
||||
}
|
||||
}
|
||||
Provider<IEvaluationContext> contextProvider = injector.getProvider(IEvaluationContext.class);
|
||||
Provider<@NonNull IEvaluationContext> contextProvider = injector.getProvider(IEvaluationContext.class);
|
||||
// no evaluation context found, so create a new one
|
||||
ScriptEngine scriptEngine = injector.getInstance(ScriptEngine.class);
|
||||
if (scriptEngine != null) {
|
||||
IEvaluationContext evaluationContext = contextProvider.get();
|
||||
for (VariableDeclaration var : ruleModel.getVariables()) {
|
||||
try {
|
||||
Object initialValue = var.getRight() == null ? null
|
||||
: scriptEngine.newScriptFromXExpression(var.getRight()).execute();
|
||||
evaluationContext.newValue(QualifiedName.create(var.getName()), initialValue);
|
||||
} catch (ScriptExecutionException e) {
|
||||
logger.warn("Variable '{}' on rule file '{}' cannot be initialized with value '{}': {}",
|
||||
var.getName(), ruleModel.eResource().getURI().path(), var.getRight(), e.getMessage());
|
||||
}
|
||||
IEvaluationContext evaluationContext = contextProvider.get();
|
||||
for (VariableDeclaration var : ruleModel.getVariables()) {
|
||||
try {
|
||||
Object initialValue = var.getRight() == null ? null
|
||||
: scriptEngine.newScriptFromXExpression(var.getRight()).execute();
|
||||
evaluationContext.newValue(QualifiedName.create(var.getName()), initialValue);
|
||||
} catch (ScriptExecutionException e) {
|
||||
logger.warn("Variable '{}' on rule file '{}' cannot be initialized with value '{}': {}", var.getName(),
|
||||
ruleModel.eResource().getURI().path(), var.getRight(), e.getMessage());
|
||||
}
|
||||
ruleModel.eAdapters().add(new RuleContextAdapter(evaluationContext));
|
||||
return evaluationContext;
|
||||
} else {
|
||||
logger.debug("Rule variables of rule {} cannot be evaluated as no scriptengine is available!",
|
||||
ruleModel.eResource().getURI().path());
|
||||
return contextProvider.get();
|
||||
}
|
||||
ruleModel.eAdapters().add(new RuleContextAdapter(evaluationContext));
|
||||
return evaluationContext;
|
||||
}
|
||||
|
||||
public static synchronized String getVariableDeclaration(RuleModel ruleModel) {
|
||||
StringBuilder vars = new StringBuilder();
|
||||
for (VariableDeclaration var : ruleModel.getVariables()) {
|
||||
vars.append(NodeModelUtils.findActualNodeFor(var).getText());
|
||||
}
|
||||
return vars.toString();
|
||||
}
|
||||
|
||||
/**
|
@ -10,13 +10,12 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.model.rule.runtime.internal.engine;
|
||||
package org.openhab.core.model.rule.runtime.internal;
|
||||
|
||||
import org.eclipse.xtext.naming.QualifiedName;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
|
||||
import org.eclipse.xtext.xbase.interpreter.impl.DefaultEvaluationContext;
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public class RuleEvaluationContext extends DefaultEvaluationContext {
|
||||
|
||||
private IEvaluationContext globalContext = null;
|
@ -1,88 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime.internal.engine;
|
||||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.rule.rules.Rule;
|
||||
import org.openhab.core.model.rule.rules.RuleModel;
|
||||
import org.openhab.core.model.script.engine.Script;
|
||||
import org.openhab.core.model.script.engine.ScriptEngine;
|
||||
import org.openhab.core.model.script.engine.ScriptExecutionException;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
/**
|
||||
* Implementation of Quartz {@link Job}-Interface. It takes a rule
|
||||
* and simply executes it.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
public class ExecuteRuleJob implements Job {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ExecuteRuleJob.class);
|
||||
|
||||
public static final String JOB_DATA_RULEMODEL = "model";
|
||||
public static final String JOB_DATA_RULENAME = "rule";
|
||||
|
||||
@Inject
|
||||
private Injector injector;
|
||||
|
||||
@Inject
|
||||
private ModelRepository modelRepository;
|
||||
|
||||
@Inject
|
||||
private ScriptEngine scriptEngine;
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
String modelName = (String) context.getJobDetail().getJobDataMap().get(JOB_DATA_RULEMODEL);
|
||||
String ruleName = (String) context.getJobDetail().getJobDataMap().get(JOB_DATA_RULENAME);
|
||||
|
||||
if (modelRepository != null && scriptEngine != null) {
|
||||
EObject model = modelRepository.getModel(modelName);
|
||||
if (model instanceof RuleModel) {
|
||||
RuleModel ruleModel = (RuleModel) model;
|
||||
Rule rule = getRule(ruleModel, ruleName);
|
||||
if (rule != null) {
|
||||
Script script = scriptEngine.newScriptFromXExpression(rule.getScript());
|
||||
logger.debug("Executing scheduled rule '{}'", rule.getName());
|
||||
try {
|
||||
script.execute(RuleContextHelper.getContext(rule, injector));
|
||||
} catch (ScriptExecutionException e) {
|
||||
logger.error("Error during the execution of rule '{}': {}", rule.getName(), e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.debug("Scheduled rule '{}' does not exist", ruleName);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Rule file '{}' does not exist", modelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Rule getRule(RuleModel ruleModel, String ruleName) {
|
||||
for (Rule rule : ruleModel.getRules()) {
|
||||
if (rule.getName().equals(ruleName)) {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime.internal.engine;
|
||||
|
||||
import org.quartz.Job;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.spi.JobFactory;
|
||||
import org.quartz.spi.TriggerFiredBundle;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
/**
|
||||
* The {@link GuiceAwareJobFactory} instantiates {@link Job}s using the Guice injector. This said it's possible to use
|
||||
* Guice injection within the Quartz jobs.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
*/
|
||||
public class GuiceAwareJobFactory implements JobFactory {
|
||||
|
||||
@Inject
|
||||
private Injector injector;
|
||||
|
||||
@Override
|
||||
public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
|
||||
|
||||
return injector.getInstance(bundle.getJobDetail().getJobClass());
|
||||
}
|
||||
}
|
@ -1,409 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime.internal.engine;
|
||||
|
||||
import static org.openhab.core.model.rule.runtime.internal.engine.RuleTriggerManager.TriggerTypes.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventFilter;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.ItemRegistryChangeListener;
|
||||
import org.openhab.core.items.StateChangeListener;
|
||||
import org.openhab.core.items.events.ItemCommandEvent;
|
||||
import org.openhab.core.items.events.ItemStateEvent;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.events.ChannelTriggeredEvent;
|
||||
import org.openhab.core.thing.events.ThingStatusInfoChangedEvent;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.model.core.EventType;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.core.ModelRepositoryChangeListener;
|
||||
import org.openhab.core.model.rule.RulesStandaloneSetup;
|
||||
import org.openhab.core.model.rule.jvmmodel.RulesJvmModelInferrer;
|
||||
import org.openhab.core.model.rule.rules.Rule;
|
||||
import org.openhab.core.model.rule.rules.RuleModel;
|
||||
import org.openhab.core.model.rule.runtime.RuleEngine;
|
||||
import org.openhab.core.model.rule.runtime.internal.RuleRuntimeActivator;
|
||||
import org.openhab.core.model.script.engine.Script;
|
||||
import org.openhab.core.model.script.engine.ScriptEngine;
|
||||
import org.openhab.core.model.script.engine.ScriptExecutionException;
|
||||
import org.eclipse.xtext.naming.QualifiedName;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
/**
|
||||
* This class is the core of the openHAB rule engine.
|
||||
* It listens to changes to the rules folder, evaluates the trigger conditions of the rules and
|
||||
* schedules them for execution dependent on their triggering conditions.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Oliver Libutzki - Bugfixing
|
||||
*/
|
||||
@Component(immediate = true, service = { EventSubscriber.class, RuleEngine.class })
|
||||
@NonNullByDefault
|
||||
public class RuleEngineImpl implements ItemRegistryChangeListener, StateChangeListener, ModelRepositoryChangeListener,
|
||||
RuleEngine, EventSubscriber {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RuleEngineImpl.class);
|
||||
|
||||
private static final String THREAD_POOL_NAME = "ruleEngine";
|
||||
|
||||
protected final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(THREAD_POOL_NAME);
|
||||
|
||||
private final ItemRegistry itemRegistry;
|
||||
private final ModelRepository modelRepository;
|
||||
private final ScriptEngine scriptEngine;
|
||||
|
||||
private final Injector injector;
|
||||
private final RuleTriggerManager triggerManager;
|
||||
|
||||
private @Nullable ScheduledFuture<?> startupJob;
|
||||
|
||||
// This flag is used to signal that items are still being added and that we hence do not consider the rule engine
|
||||
// ready to be operational.
|
||||
// This field is package private to allow access for unit tests.
|
||||
boolean starting = true;
|
||||
|
||||
@Activate
|
||||
public RuleEngineImpl(final @Reference ItemRegistry itemRegistry, final @Reference ModelRepository modelRepository,
|
||||
final @Reference ScriptEngine scriptEngine) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.modelRepository = modelRepository;
|
||||
this.scriptEngine = scriptEngine;
|
||||
|
||||
this.injector = RulesStandaloneSetup.getInjector();
|
||||
this.triggerManager = new RuleTriggerManager(injector);
|
||||
}
|
||||
|
||||
@Activate
|
||||
public void activate() {
|
||||
logger.debug("Started rule engine");
|
||||
|
||||
// read all rule files
|
||||
for (String ruleModelName : modelRepository.getAllModelNamesOfType("rules")) {
|
||||
EObject model = modelRepository.getModel(ruleModelName);
|
||||
if (model instanceof RuleModel) {
|
||||
RuleModel ruleModel = (RuleModel) model;
|
||||
triggerManager.addRuleModel(ruleModel);
|
||||
}
|
||||
}
|
||||
|
||||
// register us as listeners
|
||||
itemRegistry.addRegistryChangeListener(this);
|
||||
modelRepository.addModelRepositoryChangeListener(this);
|
||||
|
||||
// register us on all items which are already available in the registry
|
||||
for (Item item : itemRegistry.getItems()) {
|
||||
internalItemAdded(item);
|
||||
}
|
||||
scheduleStartupRules();
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
// unregister listeners
|
||||
for (Item item : itemRegistry.getItems()) {
|
||||
internalItemRemoved(item);
|
||||
}
|
||||
modelRepository.removeModelRepositoryChangeListener(this);
|
||||
itemRegistry.removeRegistryChangeListener(this);
|
||||
|
||||
// execute all scripts that were registered for system shutdown
|
||||
executeRules(triggerManager.getRules(SHUTDOWN));
|
||||
triggerManager.clearAll();
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setRuleRuntimeActivator(RuleRuntimeActivator ruleRuntimeActivator) {
|
||||
// noop - only make sure RuleRuntimeActivator gets "used", hence activated
|
||||
}
|
||||
|
||||
protected void unsetRuleRuntimeActivator(RuleRuntimeActivator ruleRuntimeActivator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allItemsChanged(Collection<String> oldItemNames) {
|
||||
// add the current items again
|
||||
Collection<Item> items = itemRegistry.getItems();
|
||||
for (Item item : items) {
|
||||
internalItemAdded(item);
|
||||
}
|
||||
scheduleStartupRules();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(Item item) {
|
||||
internalItemAdded(item);
|
||||
scheduleStartupRules();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Item item) {
|
||||
internalItemRemoved(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(Item item, State oldState, State newState) {
|
||||
if (!starting) {
|
||||
Iterable<Rule> rules = triggerManager.getRules(CHANGE, item, oldState, newState);
|
||||
|
||||
executeRules(rules, item, oldState, newState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateUpdated(Item item, State state) {
|
||||
if (!starting) {
|
||||
Iterable<Rule> rules = triggerManager.getRules(UPDATE, item, state);
|
||||
executeRules(rules, item, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveCommand(ItemCommandEvent commandEvent) {
|
||||
if (!starting) {
|
||||
String itemName = commandEvent.getItemName();
|
||||
Command command = commandEvent.getItemCommand();
|
||||
try {
|
||||
Item item = itemRegistry.getItem(itemName);
|
||||
Iterable<Rule> rules = triggerManager.getRules(COMMAND, item, command);
|
||||
|
||||
executeRules(rules, item, command);
|
||||
} catch (ItemNotFoundException e) {
|
||||
// ignore commands for non-existent items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveThingTrigger(ChannelTriggeredEvent event) {
|
||||
String triggerEvent = event.getEvent();
|
||||
String channel = event.getChannel().getAsString();
|
||||
|
||||
Iterable<Rule> rules = triggerManager.getRules(TRIGGER, channel, triggerEvent);
|
||||
executeRules(rules, event);
|
||||
}
|
||||
|
||||
private void receiveThingStatus(ThingStatusInfoChangedEvent event) {
|
||||
String thingUid = event.getThingUID().getAsString();
|
||||
ThingStatus oldStatus = event.getOldStatusInfo().getStatus();
|
||||
ThingStatus newStatus = event.getStatusInfo().getStatus();
|
||||
|
||||
Iterable<Rule> rules = triggerManager.getRules(THINGUPDATE, thingUid, newStatus);
|
||||
executeRules(rules);
|
||||
|
||||
if (oldStatus != newStatus) {
|
||||
rules = triggerManager.getRules(THINGCHANGE, thingUid, oldStatus, newStatus);
|
||||
executeRules(rules, oldStatus);
|
||||
}
|
||||
}
|
||||
|
||||
private void internalItemAdded(Item item) {
|
||||
if (item instanceof GenericItem) {
|
||||
GenericItem genericItem = (GenericItem) item;
|
||||
genericItem.addStateChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void internalItemRemoved(Item item) {
|
||||
if (item instanceof GenericItem) {
|
||||
GenericItem genericItem = (GenericItem) item;
|
||||
genericItem.removeStateChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modelChanged(String modelName, EventType type) {
|
||||
if (modelName.endsWith("rules")) {
|
||||
RuleModel model = (RuleModel) modelRepository.getModel(modelName);
|
||||
|
||||
// remove the rules from the trigger sets
|
||||
if (type == EventType.REMOVED || type == EventType.MODIFIED) {
|
||||
triggerManager.removeRuleModel(model);
|
||||
}
|
||||
|
||||
// add new and modified rules to the trigger sets
|
||||
if (model != null && (type == EventType.ADDED || type == EventType.MODIFIED)) {
|
||||
triggerManager.addRuleModel(model);
|
||||
// now execute all rules that are meant to trigger at startup
|
||||
scheduleStartupRules();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleStartupRules() {
|
||||
ScheduledFuture<?> job = startupJob;
|
||||
if (job != null && !job.isCancelled() && !job.isDone()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
startupJob = scheduler.schedule(() -> {
|
||||
runStartupRules();
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void runStartupRules() {
|
||||
for (Rule rule : triggerManager.getRules(STARTUP)) {
|
||||
scheduler.execute(() -> {
|
||||
try {
|
||||
Script script = scriptEngine.newScriptFromXExpression(rule.getScript());
|
||||
logger.debug("Executing startup rule '{}'", rule.getName());
|
||||
RuleEvaluationContext context = new RuleEvaluationContext();
|
||||
context.setGlobalContext(RuleContextHelper.getContext(rule, injector));
|
||||
script.execute(context);
|
||||
triggerManager.removeRule(STARTUP, rule);
|
||||
} catch (ScriptExecutionException e) {
|
||||
if (!e.getMessage().contains("cannot be resolved to an item or type")) {
|
||||
if (e.getCause() != null) {
|
||||
logger.error("Error during the execution of startup rule '{}': {}", rule.getName(),
|
||||
e.getCause().getMessage());
|
||||
} else {
|
||||
logger.error("Error during the execution of startup rule '{}': {}", rule.getName(),
|
||||
e.getMessage());
|
||||
}
|
||||
triggerManager.removeRule(STARTUP, rule);
|
||||
} else {
|
||||
logger.debug("Execution of startup rule '{}' has been postponed as items are still missing: {}",
|
||||
rule.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// now that we have scheduled the startup rules, we are ready for others as well
|
||||
starting = false;
|
||||
triggerManager.startTimerRuleExecution();
|
||||
}
|
||||
|
||||
protected synchronized void executeRule(Rule rule, RuleEvaluationContext context) {
|
||||
scheduler.execute(() -> {
|
||||
Script script = scriptEngine.newScriptFromXExpression(rule.getScript());
|
||||
|
||||
logger.debug("Executing rule '{}'", rule.getName());
|
||||
context.setGlobalContext(RuleContextHelper.getContext(rule, injector));
|
||||
try {
|
||||
script.execute(context);
|
||||
} catch (Exception e) {
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) {
|
||||
logger.error("Rule '{}'", rule.getName(), e.getCause());
|
||||
} else {
|
||||
logger.error("Rule '{}': {}", rule.getName(), msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected synchronized void executeRules(Iterable<Rule> rules) {
|
||||
for (Rule rule : rules) {
|
||||
RuleEvaluationContext context = new RuleEvaluationContext();
|
||||
executeRule(rule, context);
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void executeRules(Iterable<Rule> rules, ChannelTriggeredEvent event) {
|
||||
for (Rule rule : rules) {
|
||||
RuleEvaluationContext context = new RuleEvaluationContext();
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_RECEIVED_EVENT), event);
|
||||
executeRule(rule, context);
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void executeRules(Iterable<Rule> rules, Item item, State state) {
|
||||
for (Rule rule : rules) {
|
||||
RuleEvaluationContext context = new RuleEvaluationContext();
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_TRIGGERING_ITEM), item);
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_NEW_STATE), state);
|
||||
executeRule(rule, context);
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void executeRules(Iterable<Rule> rules, Item item, Command command) {
|
||||
for (Rule rule : rules) {
|
||||
RuleEvaluationContext context = new RuleEvaluationContext();
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_TRIGGERING_ITEM), item);
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_RECEIVED_COMMAND), command);
|
||||
executeRule(rule, context);
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void executeRules(Iterable<Rule> rules, Item item, State oldState, State newState) {
|
||||
for (Rule rule : rules) {
|
||||
RuleEvaluationContext context = new RuleEvaluationContext();
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_TRIGGERING_ITEM), item);
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_PREVIOUS_STATE), oldState);
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_NEW_STATE), newState);
|
||||
executeRule(rule, context);
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void executeRules(Iterable<Rule> rules, ThingStatus oldThingStatus) {
|
||||
for (Rule rule : rules) {
|
||||
RuleEvaluationContext context = new RuleEvaluationContext();
|
||||
context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_PREVIOUS_STATE), oldThingStatus.toString());
|
||||
executeRule(rule, context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Item oldItem, Item item) {
|
||||
removed(oldItem);
|
||||
added(item);
|
||||
}
|
||||
|
||||
private final Set<String> subscribedEventTypes = ImmutableSet.of(ItemStateEvent.TYPE, ItemCommandEvent.TYPE,
|
||||
ChannelTriggeredEvent.TYPE, ThingStatusInfoChangedEvent.TYPE);
|
||||
|
||||
@Override
|
||||
public Set<String> getSubscribedEventTypes() {
|
||||
return subscribedEventTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable EventFilter getEventFilter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Event event) {
|
||||
if (event instanceof ItemCommandEvent) {
|
||||
receiveCommand((ItemCommandEvent) event);
|
||||
} else if (event instanceof ChannelTriggeredEvent) {
|
||||
receiveThingTrigger((ChannelTriggeredEvent) event);
|
||||
} else if (event instanceof ThingStatusInfoChangedEvent) {
|
||||
receiveThingStatus((ThingStatusInfoChangedEvent) event);
|
||||
}
|
||||
}
|
||||
|
||||
RuleTriggerManager getTriggerManager() {
|
||||
return triggerManager;
|
||||
}
|
||||
}
|
@ -1,848 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime.internal.engine;
|
||||
|
||||
import static org.openhab.core.model.rule.runtime.internal.engine.RuleTriggerManager.TriggerTypes.*;
|
||||
import static org.quartz.JobBuilder.newJob;
|
||||
import static org.quartz.TriggerBuilder.newTrigger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.emf.ecore.util.EcoreUtil;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.model.rule.rules.ChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.CommandEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.EventEmittedTrigger;
|
||||
import org.openhab.core.model.rule.rules.EventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberCommandEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberUpdateEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.Rule;
|
||||
import org.openhab.core.model.rule.rules.RuleModel;
|
||||
import org.openhab.core.model.rule.rules.SystemOnShutdownTrigger;
|
||||
import org.openhab.core.model.rule.rules.SystemOnStartupTrigger;
|
||||
import org.openhab.core.model.rule.rules.ThingStateChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.ThingStateUpdateEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.TimerTrigger;
|
||||
import org.openhab.core.model.rule.rules.UpdateEventTrigger;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.TypeParser;
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.inject.Injector;
|
||||
|
||||
/**
|
||||
* This is a helper class which deals with everything about rule triggers.
|
||||
* It keeps lists of which rule must be executed for which trigger and takes
|
||||
* over the evaluation of states and trigger conditions for the rule engine.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
public class RuleTriggerManager {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RuleTriggerManager.class);
|
||||
|
||||
public enum TriggerTypes {
|
||||
UPDATE, // fires whenever a status update is received for an item
|
||||
CHANGE, // same as UPDATE, but only fires if the current item state is changed by the update
|
||||
COMMAND, // fires whenever a command is received for an item
|
||||
TRIGGER, // fires whenever a trigger is emitted on a channel
|
||||
STARTUP, // fires when the rule engine bundle starts and once as soon as all required items are available
|
||||
SHUTDOWN, // fires when the rule engine bundle is stopped
|
||||
TIMER, // fires at a given time
|
||||
THINGUPDATE, // fires whenever the thing state is updated.
|
||||
THINGCHANGE, // fires if the thing state is changed by the update
|
||||
}
|
||||
|
||||
// Group name prefix for maps
|
||||
private static final String GROUP_NAME_PREFIX = "*GROUP*";
|
||||
|
||||
// lookup maps for different triggering conditions
|
||||
private final Map<String, Set<Rule>> updateEventTriggeredRules = new HashMap<>();
|
||||
private final Map<String, Set<Rule>> changedEventTriggeredRules = new HashMap<>();
|
||||
private final Map<String, Set<Rule>> commandEventTriggeredRules = new HashMap<>();
|
||||
private final Map<String, Set<Rule>> thingUpdateEventTriggeredRules = new HashMap<>();
|
||||
private final Map<String, Set<Rule>> thingChangedEventTriggeredRules = new HashMap<>();
|
||||
// Maps from channelName -> Rules
|
||||
private final Map<String, Set<Rule>> triggerEventTriggeredRules = new HashMap<>();
|
||||
private final Set<Rule> systemStartupTriggeredRules = new CopyOnWriteArraySet<>();
|
||||
private final Set<Rule> systemShutdownTriggeredRules = new CopyOnWriteArraySet<>();
|
||||
private final Set<Rule> timerEventTriggeredRules = new CopyOnWriteArraySet<>();
|
||||
|
||||
// the scheduler used for timer events
|
||||
private Scheduler scheduler;
|
||||
|
||||
public RuleTriggerManager(Injector injector) {
|
||||
try {
|
||||
scheduler = StdSchedulerFactory.getDefaultScheduler();
|
||||
scheduler.setJobFactory(injector.getInstance(GuiceAwareJobFactory.class));
|
||||
|
||||
// we want to defer timer rule execution until after the startup rules have been executed.
|
||||
scheduler.standby();
|
||||
} catch (SchedulerException e) {
|
||||
logger.error("initializing scheduler throws exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all rules which have a trigger of a given type
|
||||
*
|
||||
* @param type the trigger type of the rules to return
|
||||
* @return rules with triggers of the given type
|
||||
*/
|
||||
public Iterable<Rule> getRules(TriggerTypes type) {
|
||||
Iterable<Rule> result;
|
||||
switch (type) {
|
||||
case STARTUP:
|
||||
result = systemStartupTriggeredRules;
|
||||
break;
|
||||
case SHUTDOWN:
|
||||
result = systemShutdownTriggeredRules;
|
||||
break;
|
||||
case TIMER:
|
||||
result = timerEventTriggeredRules;
|
||||
break;
|
||||
case UPDATE:
|
||||
result = updateEventTriggeredRules.values().stream().flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
break;
|
||||
case CHANGE:
|
||||
result = changedEventTriggeredRules.values().stream().flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
break;
|
||||
case COMMAND:
|
||||
result = commandEventTriggeredRules.values().stream().flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
break;
|
||||
case TRIGGER:
|
||||
result = triggerEventTriggeredRules.values().stream().flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
break;
|
||||
case THINGUPDATE:
|
||||
result = thingUpdateEventTriggeredRules.values().stream().flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
break;
|
||||
case THINGCHANGE:
|
||||
result = thingChangedEventTriggeredRules.values().stream().flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
break;
|
||||
default:
|
||||
result = new HashSet<>();
|
||||
}
|
||||
List<Rule> filteredList = new ArrayList<>();
|
||||
for (Rule rule : result) {
|
||||
// we really only want to return rules that are still loaded
|
||||
if (rule.eResource() != null && !rule.eIsProxy()) {
|
||||
filteredList.add(rule);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all rules for which the trigger condition is true for the given type, item and state.
|
||||
*
|
||||
* @param triggerType
|
||||
* @param item
|
||||
* @param state
|
||||
* @return all rules for which the trigger condition is true
|
||||
*/
|
||||
public Iterable<Rule> getRules(TriggerTypes triggerType, Item item, State state) {
|
||||
return internalGetRules(triggerType, item, null, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all rules for which the trigger condition is true for the given type, item and states.
|
||||
*
|
||||
* @param triggerType
|
||||
* @param item
|
||||
* @param oldState
|
||||
* @param newState
|
||||
* @return all rules for which the trigger condition is true
|
||||
*/
|
||||
public Iterable<Rule> getRules(TriggerTypes triggerType, Item item, State oldState, State newState) {
|
||||
return internalGetRules(triggerType, item, oldState, newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all rules for which the trigger condition is true for the given type, item and command.
|
||||
*
|
||||
* @param triggerType
|
||||
* @param item
|
||||
* @param command
|
||||
* @return all rules for which the trigger condition is true
|
||||
*/
|
||||
public Iterable<Rule> getRules(TriggerTypes triggerType, Item item, Command command) {
|
||||
return internalGetRules(triggerType, item, null, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all rules for which the trigger condition is true for the given type and channel.
|
||||
*
|
||||
* @param triggerType
|
||||
* @param channel
|
||||
* @return all rules for which the trigger condition is true
|
||||
*/
|
||||
public Iterable<Rule> getRules(TriggerTypes triggerType, String channel, String event) {
|
||||
List<Rule> result = new ArrayList<>();
|
||||
|
||||
switch (triggerType) {
|
||||
case TRIGGER:
|
||||
Set<Rule> rules = triggerEventTriggeredRules.get(channel);
|
||||
if (rules == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
for (Rule rule : rules) {
|
||||
for (EventTrigger t : rule.getEventtrigger()) {
|
||||
if (t instanceof EventEmittedTrigger) {
|
||||
EventEmittedTrigger et = (EventEmittedTrigger) t;
|
||||
|
||||
if (et.getChannel().equals(channel)
|
||||
&& (et.getTrigger() == null || et.getTrigger().getValue().equals(event))) {
|
||||
// if the rule does not have a specific event , execute it on any event
|
||||
result.add(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Iterable<Rule> getRules(TriggerTypes triggerType, String thingUid, ThingStatus state) {
|
||||
return internalGetThingRules(triggerType, thingUid, null, state);
|
||||
}
|
||||
|
||||
public Iterable<Rule> getRules(TriggerTypes triggerType, String thingUid, ThingStatus oldState,
|
||||
ThingStatus newState) {
|
||||
return internalGetThingRules(triggerType, thingUid, oldState, newState);
|
||||
}
|
||||
|
||||
private Iterable<Rule> getAllRules(TriggerTypes type, String name) {
|
||||
Iterable<Rule> rules = null;
|
||||
switch (type) {
|
||||
case STARTUP:
|
||||
rules = systemStartupTriggeredRules;
|
||||
break;
|
||||
case SHUTDOWN:
|
||||
rules = systemShutdownTriggeredRules;
|
||||
break;
|
||||
case UPDATE:
|
||||
rules = updateEventTriggeredRules.get(name);
|
||||
break;
|
||||
case CHANGE:
|
||||
rules = changedEventTriggeredRules.get(name);
|
||||
break;
|
||||
case COMMAND:
|
||||
rules = commandEventTriggeredRules.get(name);
|
||||
break;
|
||||
case THINGUPDATE:
|
||||
rules = thingUpdateEventTriggeredRules.get(name);
|
||||
break;
|
||||
case THINGCHANGE:
|
||||
rules = thingChangedEventTriggeredRules.get(name);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (rules == null) {
|
||||
rules = Collections.emptySet();
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
private void internalGetUpdateRules(String name, Boolean isGroup, List<Class<? extends State>> acceptedDataTypes,
|
||||
State state, List<Rule> result) {
|
||||
final String mapName = (isGroup) ? GROUP_NAME_PREFIX + name : name;
|
||||
for (Rule rule : getAllRules(UPDATE, mapName)) {
|
||||
for (EventTrigger t : rule.getEventtrigger()) {
|
||||
String triggerStateString = null;
|
||||
if ((!isGroup) && (t instanceof UpdateEventTrigger)) {
|
||||
final UpdateEventTrigger ut = (UpdateEventTrigger) t;
|
||||
if (ut.getItem().equals(name)) {
|
||||
triggerStateString = ut.getState() != null ? ut.getState().getValue() : null;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if ((isGroup) && (t instanceof GroupMemberUpdateEventTrigger)) {
|
||||
final GroupMemberUpdateEventTrigger gmut = (GroupMemberUpdateEventTrigger) t;
|
||||
if (gmut.getGroup().equals(name)) {
|
||||
triggerStateString = gmut.getState() != null ? gmut.getState().getValue() : null;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (triggerStateString != null) {
|
||||
final State triggerState = TypeParser.parseState(acceptedDataTypes, triggerStateString);
|
||||
if (!state.equals(triggerState)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void internalGetChangeRules(String name, Boolean isGroup, List<Class<? extends State>> acceptedDataTypes,
|
||||
State newState, State oldState, List<Rule> result) {
|
||||
final String mapName = (isGroup) ? GROUP_NAME_PREFIX + name : name;
|
||||
for (Rule rule : getAllRules(CHANGE, mapName)) {
|
||||
for (EventTrigger t : rule.getEventtrigger()) {
|
||||
String triggerOldStateString = null;
|
||||
String triggerNewStateString = null;
|
||||
if ((!isGroup) && (t instanceof ChangedEventTrigger)) {
|
||||
final ChangedEventTrigger ct = (ChangedEventTrigger) t;
|
||||
if (ct.getItem().equals(name)) {
|
||||
triggerOldStateString = ct.getOldState() != null ? ct.getOldState().getValue() : null;
|
||||
triggerNewStateString = ct.getNewState() != null ? ct.getNewState().getValue() : null;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if ((isGroup) && (t instanceof GroupMemberChangedEventTrigger)) {
|
||||
final GroupMemberChangedEventTrigger gmct = (GroupMemberChangedEventTrigger) t;
|
||||
if (gmct.getGroup().equals(name)) {
|
||||
triggerOldStateString = gmct.getOldState() != null ? gmct.getOldState().getValue() : null;
|
||||
triggerNewStateString = gmct.getNewState() != null ? gmct.getNewState().getValue() : null;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (triggerOldStateString != null) {
|
||||
final State triggerOldState = TypeParser.parseState(acceptedDataTypes, triggerOldStateString);
|
||||
if (!oldState.equals(triggerOldState)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (triggerNewStateString != null) {
|
||||
final State triggerNewState = TypeParser.parseState(acceptedDataTypes, triggerNewStateString);
|
||||
if (!newState.equals(triggerNewState)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void internalGetCommandRules(String name, Boolean isGroup,
|
||||
List<Class<? extends Command>> acceptedCommandTypes, Command command, List<Rule> result) {
|
||||
final String mapName = (isGroup) ? GROUP_NAME_PREFIX + name : name;
|
||||
for (Rule rule : getAllRules(COMMAND, mapName)) {
|
||||
for (final EventTrigger t : rule.getEventtrigger()) {
|
||||
String triggerCommandString = null;
|
||||
if ((!isGroup) && (t instanceof CommandEventTrigger)) {
|
||||
final CommandEventTrigger ct = (CommandEventTrigger) t;
|
||||
if (ct.getItem().equals(name)) {
|
||||
triggerCommandString = ct.getCommand() != null ? ct.getCommand().getValue() : null;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if ((isGroup) && (t instanceof GroupMemberCommandEventTrigger)) {
|
||||
final GroupMemberCommandEventTrigger gmct = (GroupMemberCommandEventTrigger) t;
|
||||
if (gmct.getGroup().equals(name)) {
|
||||
triggerCommandString = gmct.getCommand() != null ? gmct.getCommand().getValue() : null;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (triggerCommandString != null) {
|
||||
final Command triggerCommand = TypeParser.parseCommand(acceptedCommandTypes, triggerCommandString);
|
||||
if (!command.equals(triggerCommand)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<Rule> internalGetRules(TriggerTypes triggerType, Item item, Type oldType, Type newType) {
|
||||
List<Rule> result = new ArrayList<>();
|
||||
switch (triggerType) {
|
||||
case STARTUP:
|
||||
return systemStartupTriggeredRules;
|
||||
case SHUTDOWN:
|
||||
return systemShutdownTriggeredRules;
|
||||
case TIMER:
|
||||
return timerEventTriggeredRules;
|
||||
case UPDATE:
|
||||
if (newType instanceof State) {
|
||||
List<Class<? extends State>> acceptedDataTypes = item.getAcceptedDataTypes();
|
||||
final State state = (State) newType;
|
||||
internalGetUpdateRules(item.getName(), false, acceptedDataTypes, state, result);
|
||||
for (String groupName : item.getGroupNames()) {
|
||||
internalGetUpdateRules(groupName, true, acceptedDataTypes, state, result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CHANGE:
|
||||
if (newType instanceof State && oldType instanceof State) {
|
||||
List<Class<? extends State>> acceptedDataTypes = item.getAcceptedDataTypes();
|
||||
final State newState = (State) newType;
|
||||
final State oldState = (State) oldType;
|
||||
internalGetChangeRules(item.getName(), false, acceptedDataTypes, newState, oldState, result);
|
||||
for (String groupName : item.getGroupNames()) {
|
||||
internalGetChangeRules(groupName, true, acceptedDataTypes, newState, oldState, result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case COMMAND:
|
||||
if (newType instanceof Command) {
|
||||
List<Class<? extends Command>> acceptedCommandTypes = item.getAcceptedCommandTypes();
|
||||
final Command command = (Command) newType;
|
||||
internalGetCommandRules(item.getName(), false, acceptedCommandTypes, command, result);
|
||||
for (String groupName : item.getGroupNames()) {
|
||||
internalGetCommandRules(groupName, true, acceptedCommandTypes, command, result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Iterable<Rule> internalGetThingRules(TriggerTypes triggerType, String thingUid, ThingStatus oldStatus,
|
||||
ThingStatus newStatus) {
|
||||
List<Rule> result = new ArrayList<>();
|
||||
Iterable<Rule> rules = getAllRules(triggerType, thingUid);
|
||||
|
||||
switch (triggerType) {
|
||||
case THINGUPDATE:
|
||||
for (Rule rule : rules) {
|
||||
for (EventTrigger t : rule.getEventtrigger()) {
|
||||
if (t instanceof ThingStateUpdateEventTrigger) {
|
||||
ThingStateUpdateEventTrigger tt = (ThingStateUpdateEventTrigger) t;
|
||||
if (tt.getThing().equals(thingUid)) {
|
||||
String stateString = tt.getState();
|
||||
if (stateString != null) {
|
||||
ThingStatus triggerState = ThingStatus.valueOf(stateString);
|
||||
if (!newStatus.equals(triggerState)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case THINGCHANGE:
|
||||
for (Rule rule : rules) {
|
||||
for (EventTrigger t : rule.getEventtrigger()) {
|
||||
if (t instanceof ThingStateChangedEventTrigger) {
|
||||
ThingStateChangedEventTrigger ct = (ThingStateChangedEventTrigger) t;
|
||||
if (ct.getThing().equals(thingUid)) {
|
||||
String oldStatusString = ct.getOldState();
|
||||
if (oldStatusString != null) {
|
||||
ThingStatus triggerOldState = ThingStatus.valueOf(oldStatusString);
|
||||
if (!oldStatus.equals(triggerOldState)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String newStatusString = ct.getNewState();
|
||||
if (newStatusString != null) {
|
||||
ThingStatus triggerNewState = ThingStatus.valueOf(newStatusString);
|
||||
if (!newStatus.equals(triggerNewState)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all rules with a given trigger type from the mapping tables.
|
||||
*
|
||||
* @param type the trigger type
|
||||
*/
|
||||
public void clear(TriggerTypes type) {
|
||||
switch (type) {
|
||||
case STARTUP:
|
||||
systemStartupTriggeredRules.clear();
|
||||
break;
|
||||
case SHUTDOWN:
|
||||
systemShutdownTriggeredRules.clear();
|
||||
break;
|
||||
case UPDATE:
|
||||
updateEventTriggeredRules.clear();
|
||||
break;
|
||||
case CHANGE:
|
||||
changedEventTriggeredRules.clear();
|
||||
break;
|
||||
case COMMAND:
|
||||
commandEventTriggeredRules.clear();
|
||||
break;
|
||||
case TRIGGER:
|
||||
triggerEventTriggeredRules.clear();
|
||||
break;
|
||||
case TIMER:
|
||||
for (Rule rule : timerEventTriggeredRules) {
|
||||
removeTimerRule(rule);
|
||||
}
|
||||
timerEventTriggeredRules.clear();
|
||||
break;
|
||||
case THINGUPDATE:
|
||||
thingUpdateEventTriggeredRules.clear();
|
||||
break;
|
||||
case THINGCHANGE:
|
||||
thingChangedEventTriggeredRules.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all rules from all mapping tables.
|
||||
*/
|
||||
public void clearAll() {
|
||||
clear(STARTUP);
|
||||
clear(SHUTDOWN);
|
||||
clear(UPDATE);
|
||||
clear(CHANGE);
|
||||
clear(COMMAND);
|
||||
clear(TIMER);
|
||||
clear(TRIGGER);
|
||||
clear(THINGUPDATE);
|
||||
clear(THINGCHANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given rule to the mapping tables
|
||||
*
|
||||
* @param rule the rule to add
|
||||
*/
|
||||
public synchronized void addRule(Rule rule) {
|
||||
for (EventTrigger t : rule.getEventtrigger()) {
|
||||
// add the rule to the lookup map for the trigger kind
|
||||
if (t instanceof SystemOnStartupTrigger) {
|
||||
systemStartupTriggeredRules.add(rule);
|
||||
} else if (t instanceof SystemOnShutdownTrigger) {
|
||||
systemShutdownTriggeredRules.add(rule);
|
||||
} else if (t instanceof CommandEventTrigger) {
|
||||
CommandEventTrigger ceTrigger = (CommandEventTrigger) t;
|
||||
Set<Rule> rules = commandEventTriggeredRules.get(ceTrigger.getItem());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
commandEventTriggeredRules.put(ceTrigger.getItem(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
} else if (t instanceof GroupMemberCommandEventTrigger) {
|
||||
GroupMemberCommandEventTrigger gmceTrigger = (GroupMemberCommandEventTrigger) t;
|
||||
Set<Rule> rules = commandEventTriggeredRules.get(GROUP_NAME_PREFIX + gmceTrigger.getGroup());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
commandEventTriggeredRules.put(GROUP_NAME_PREFIX + gmceTrigger.getGroup(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
} else if (t instanceof UpdateEventTrigger) {
|
||||
UpdateEventTrigger ueTrigger = (UpdateEventTrigger) t;
|
||||
Set<Rule> rules = updateEventTriggeredRules.get(ueTrigger.getItem());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
updateEventTriggeredRules.put(ueTrigger.getItem(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
} else if (t instanceof GroupMemberUpdateEventTrigger) {
|
||||
GroupMemberUpdateEventTrigger gmueTrigger = (GroupMemberUpdateEventTrigger) t;
|
||||
Set<Rule> rules = updateEventTriggeredRules.get(GROUP_NAME_PREFIX + gmueTrigger.getGroup());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
updateEventTriggeredRules.put(GROUP_NAME_PREFIX + gmueTrigger.getGroup(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
} else if (t instanceof ChangedEventTrigger) {
|
||||
ChangedEventTrigger ceTrigger = (ChangedEventTrigger) t;
|
||||
Set<Rule> rules = changedEventTriggeredRules.get(ceTrigger.getItem());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
changedEventTriggeredRules.put(ceTrigger.getItem(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
} else if (t instanceof GroupMemberChangedEventTrigger) {
|
||||
GroupMemberChangedEventTrigger gmceTrigger = (GroupMemberChangedEventTrigger) t;
|
||||
Set<Rule> rules = changedEventTriggeredRules.get(GROUP_NAME_PREFIX + gmceTrigger.getGroup());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
changedEventTriggeredRules.put(GROUP_NAME_PREFIX + gmceTrigger.getGroup(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
} else if (t instanceof TimerTrigger) {
|
||||
try {
|
||||
createTimer(rule, (TimerTrigger) t);
|
||||
timerEventTriggeredRules.add(rule);
|
||||
} catch (SchedulerException e) {
|
||||
logger.error("Cannot create timer for rule '{}': {}", rule.getName(), e.getMessage());
|
||||
}
|
||||
} else if (t instanceof EventEmittedTrigger) {
|
||||
EventEmittedTrigger eeTrigger = (EventEmittedTrigger) t;
|
||||
Set<Rule> rules = triggerEventTriggeredRules.get(eeTrigger.getChannel());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
triggerEventTriggeredRules.put(eeTrigger.getChannel(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
} else if (t instanceof ThingStateUpdateEventTrigger) {
|
||||
ThingStateUpdateEventTrigger tsuTrigger = (ThingStateUpdateEventTrigger) t;
|
||||
Set<Rule> rules = thingUpdateEventTriggeredRules.get(tsuTrigger.getThing());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
thingUpdateEventTriggeredRules.put(tsuTrigger.getThing(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
} else if (t instanceof ThingStateChangedEventTrigger) {
|
||||
ThingStateChangedEventTrigger tscTrigger = (ThingStateChangedEventTrigger) t;
|
||||
Set<Rule> rules = thingChangedEventTriggeredRules.get(tscTrigger.getThing());
|
||||
if (rules == null) {
|
||||
rules = new HashSet<>();
|
||||
thingChangedEventTriggeredRules.put(tscTrigger.getThing(), rules);
|
||||
}
|
||||
rules.add(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given rule from the mapping tables of a certain trigger type
|
||||
*
|
||||
* @param type the trigger type for which the rule should be removed
|
||||
* @param rule the rule to add
|
||||
*/
|
||||
public void removeRule(TriggerTypes type, Rule rule) {
|
||||
switch (type) {
|
||||
case STARTUP:
|
||||
systemStartupTriggeredRules.remove(rule);
|
||||
break;
|
||||
case SHUTDOWN:
|
||||
systemShutdownTriggeredRules.remove(rule);
|
||||
break;
|
||||
case UPDATE:
|
||||
for (Set<Rule> rules : updateEventTriggeredRules.values()) {
|
||||
rules.remove(rule);
|
||||
}
|
||||
break;
|
||||
case CHANGE:
|
||||
for (Set<Rule> rules : changedEventTriggeredRules.values()) {
|
||||
rules.remove(rule);
|
||||
}
|
||||
break;
|
||||
case COMMAND:
|
||||
for (Set<Rule> rules : commandEventTriggeredRules.values()) {
|
||||
rules.remove(rule);
|
||||
}
|
||||
break;
|
||||
case TRIGGER:
|
||||
for (Set<Rule> rules : triggerEventTriggeredRules.values()) {
|
||||
rules.remove(rule);
|
||||
}
|
||||
break;
|
||||
case TIMER:
|
||||
timerEventTriggeredRules.remove(rule);
|
||||
removeTimerRule(rule);
|
||||
break;
|
||||
case THINGUPDATE:
|
||||
for (Set<Rule> rules : thingUpdateEventTriggeredRules.values()) {
|
||||
rules.remove(rule);
|
||||
}
|
||||
break;
|
||||
case THINGCHANGE:
|
||||
for (Set<Rule> rules : thingChangedEventTriggeredRules.values()) {
|
||||
rules.remove(rule);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all rules of a model to the mapping tables
|
||||
*
|
||||
* @param model the rule model
|
||||
*/
|
||||
public void addRuleModel(RuleModel model) {
|
||||
for (Rule rule : model.getRules()) {
|
||||
addRule(rule);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all rules of a given model (file) from the mapping tables.
|
||||
*
|
||||
* @param ruleModel the rule model
|
||||
*/
|
||||
public void removeRuleModel(RuleModel ruleModel) {
|
||||
removeRules(UPDATE, updateEventTriggeredRules.values(), ruleModel);
|
||||
removeRules(CHANGE, changedEventTriggeredRules.values(), ruleModel);
|
||||
removeRules(COMMAND, commandEventTriggeredRules.values(), ruleModel);
|
||||
removeRules(TRIGGER, triggerEventTriggeredRules.values(), ruleModel);
|
||||
removeRules(STARTUP, Collections.singletonList(systemStartupTriggeredRules), ruleModel);
|
||||
removeRules(SHUTDOWN, Collections.singletonList(systemShutdownTriggeredRules), ruleModel);
|
||||
removeRules(TIMER, Collections.singletonList(timerEventTriggeredRules), ruleModel);
|
||||
removeRules(THINGUPDATE, thingUpdateEventTriggeredRules.values(), ruleModel);
|
||||
removeRules(THINGCHANGE, thingChangedEventTriggeredRules.values(), ruleModel);
|
||||
}
|
||||
|
||||
private void removeRules(TriggerTypes type, Collection<? extends Collection<Rule>> ruleSets, RuleModel model) {
|
||||
for (Collection<Rule> ruleSet : ruleSets) {
|
||||
Set<Rule> clonedSet = new HashSet<>(ruleSet);
|
||||
// first remove all rules of the model, if not null (=non-existent)
|
||||
if (model != null) {
|
||||
for (Rule newRule : model.getRules()) {
|
||||
for (Rule oldRule : clonedSet) {
|
||||
if (newRule.getName().equals(oldRule.getName())) {
|
||||
ruleSet.remove(oldRule);
|
||||
if (type == TIMER) {
|
||||
removeTimerRule(oldRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now also remove all proxified rules from the set
|
||||
clonedSet = new HashSet<>(ruleSet);
|
||||
for (Rule rule : clonedSet) {
|
||||
if (rule.eResource() == null) {
|
||||
ruleSet.remove(rule);
|
||||
if (type == TIMER) {
|
||||
removeTimerRule(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTimerRule(Rule rule) {
|
||||
try {
|
||||
removeTimer(rule);
|
||||
} catch (SchedulerException e) {
|
||||
logger.error("Cannot remove timer for rule '{}'", rule.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and schedules a new quartz-job and trigger with model and rule name as jobData.
|
||||
*
|
||||
* @param rule the rule to schedule
|
||||
* @param trigger the defined trigger
|
||||
*
|
||||
* @throws SchedulerException if there is an internal Scheduler error.
|
||||
*/
|
||||
private void createTimer(Rule rule, TimerTrigger trigger) throws SchedulerException {
|
||||
String cronExpression = trigger.getCron();
|
||||
if (trigger.getTime() != null) {
|
||||
if ("noon".equals(trigger.getTime())) {
|
||||
cronExpression = "0 0 12 * * ?";
|
||||
} else if ("midnight".equals(trigger.getTime())) {
|
||||
cronExpression = "0 0 0 * * ?";
|
||||
} else {
|
||||
logger.warn("Unrecognized time expression '{}' in rule '{}'", trigger.getTime(), rule.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String jobIdentity = getJobIdentityString(rule, trigger);
|
||||
|
||||
try {
|
||||
JobDetail job = newJob(ExecuteRuleJob.class)
|
||||
.usingJobData(ExecuteRuleJob.JOB_DATA_RULEMODEL, rule.eResource().getURI().path())
|
||||
.usingJobData(ExecuteRuleJob.JOB_DATA_RULENAME, rule.getName()).withIdentity(jobIdentity).build();
|
||||
Trigger quartzTrigger = newTrigger().withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)).build();
|
||||
scheduler.scheduleJob(job, Collections.singleton(quartzTrigger), true);
|
||||
|
||||
logger.debug("Scheduled rule '{}' with cron expression '{}'", rule.getName(), cronExpression);
|
||||
} catch (RuntimeException e) {
|
||||
throw new SchedulerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all {@link Job}s of the DEFAULT group whose name starts with <code>rule.getName()</code>.
|
||||
*
|
||||
* @throws SchedulerException if there is an internal Scheduler error.
|
||||
*/
|
||||
private void removeTimer(Rule rule) throws SchedulerException {
|
||||
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(Scheduler.DEFAULT_GROUP));
|
||||
for (JobKey jobKey : jobKeys) {
|
||||
String jobIdentityString = getJobIdentityString(rule, null);
|
||||
if (jobKey.getName().startsWith(jobIdentityString)) {
|
||||
boolean success = scheduler.deleteJob(jobKey);
|
||||
if (!success) {
|
||||
logger.warn("Failed to delete cron job '{}'", jobKey.getName());
|
||||
} else {
|
||||
logger.debug("Removed scheduled cron job '{}'", jobKey.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getJobIdentityString(Rule rule, TimerTrigger trigger) {
|
||||
String jobIdentity = EcoreUtil.getURI(rule).trimFragment().appendFragment(rule.getName()).toString();
|
||||
if (trigger != null) {
|
||||
if (trigger.getTime() != null) {
|
||||
jobIdentity += "#" + trigger.getTime();
|
||||
} else if (trigger.getCron() != null) {
|
||||
jobIdentity += "#" + trigger.getCron();
|
||||
}
|
||||
}
|
||||
return jobIdentity;
|
||||
}
|
||||
|
||||
public void startTimerRuleExecution() {
|
||||
try {
|
||||
scheduler.start();
|
||||
} catch (SchedulerException e) {
|
||||
logger.error("Error while starting the scheduler service: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -12,8 +12,10 @@ Export-Package: org.openhab.core.model.rule,\
|
||||
org.openhab.core.model.rule.services,\
|
||||
org.openhab.core.model.rule.validation
|
||||
Import-Package: \
|
||||
org.openhab.core.common,\
|
||||
org.openhab.core.common.registry,\
|
||||
org.openhab.core.events,\
|
||||
org.openhab.core.events.system,\
|
||||
org.openhab.core.items,\
|
||||
org.openhab.core.library.items,\
|
||||
org.openhab.core.library.types,\
|
||||
@ -34,8 +36,6 @@ Import-Package: \
|
||||
org.eclipse.jdt.annotation;resolution:=optional,\
|
||||
org.eclipse.xtext.xbase.lib,\
|
||||
org.osgi.*,\
|
||||
org.quartz,\
|
||||
org.quartz.impl,\
|
||||
org.slf4j.*
|
||||
Require-Bundle: org.antlr.runtime,\
|
||||
org.eclipse.emf.common,\
|
||||
|
@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.jvmmodel;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.ItemRegistryChangeListener;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.script.engine.action.ActionService;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
|
||||
/**
|
||||
* The {@link RulesItemRefresher} is responsible for reloading rules resources every time an item is added or removed.
|
||||
*
|
||||
* @author Oliver Libutzki - Initial contribution
|
||||
* @author Kai Kreuzer - added delayed execution
|
||||
* @author Maoliang Huang - refactor
|
||||
*/
|
||||
@Component(service = {})
|
||||
public class RulesItemRefresher extends RulesRefresher implements ItemRegistryChangeListener {
|
||||
|
||||
private ItemRegistry itemRegistry;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setItemRegistry(ItemRegistry itemRegistry) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.itemRegistry.addRegistryChangeListener(this);
|
||||
}
|
||||
|
||||
public void unsetItemRegistry(ItemRegistry itemRegistry) {
|
||||
this.itemRegistry.removeRegistryChangeListener(this);
|
||||
this.itemRegistry = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(Item element) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Item element) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Item oldElement, Item element) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allItemsChanged(Collection<String> oldItemNames) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setModelRepository(ModelRepository modelRepository) {
|
||||
super.setModelRepository(modelRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetModelRepository(ModelRepository modelRepository) {
|
||||
super.unsetModelRepository(modelRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addActionService(ActionService actionService) {
|
||||
super.addActionService(actionService);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeActionService(ActionService actionService) {
|
||||
super.removeActionService(actionService);
|
||||
}
|
||||
|
||||
}
|
@ -54,21 +54,6 @@ class RulesJvmModelInferrer extends ScriptJvmModelInferrer {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RulesJvmModelInferrer)
|
||||
|
||||
/** Variable name for the item in a "state triggered" or "command triggered" rule */
|
||||
public static final String VAR_TRIGGERING_ITEM = "triggeringItem";
|
||||
|
||||
/** Variable name for the previous state of an item in a "changed state triggered" rule */
|
||||
public static final String VAR_PREVIOUS_STATE = "previousState";
|
||||
|
||||
/** Variable name for the new state of an item in a "changed state triggered" or "updated state triggered" rule */
|
||||
public static final String VAR_NEW_STATE = "newState";
|
||||
|
||||
/** Variable name for the received command in a "command triggered" rule */
|
||||
public static final String VAR_RECEIVED_COMMAND = "receivedCommand";
|
||||
|
||||
/** Variable name for the received event in a "trigger event" rule */
|
||||
public static final String VAR_RECEIVED_EVENT = "receivedEvent";
|
||||
|
||||
/**
|
||||
* conveninence API to build and initialize JvmTypes and their members.
|
||||
*/
|
||||
|
@ -12,13 +12,33 @@
|
||||
*/
|
||||
package org.openhab.core.model.rule.jvmmodel;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.events.system.StartlevelEvent;
|
||||
import org.openhab.core.events.system.SystemEventFactory;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.ItemRegistryChangeListener;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.script.engine.action.ActionService;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
import org.openhab.core.service.ReadyMarkerFilter;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
import org.openhab.core.service.ReadyService.ReadyTracker;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingRegistryChangeListener;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -29,51 +49,135 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Kai Kreuzer - added delayed execution
|
||||
* @author Maoliang Huang - refactor
|
||||
*/
|
||||
public class RulesRefresher {
|
||||
@Component(immediate = true, service = {})
|
||||
public class RulesRefresher implements ReadyTracker {
|
||||
|
||||
// delay before rule resources are refreshed after items or services have changed
|
||||
private static final long REFRESH_DELAY = 2000;
|
||||
// delay in ms before rule resources are refreshed after items or services have changed
|
||||
private static final long REFRESH_DELAY = 5000;
|
||||
|
||||
private static final String POOL_NAME = "automation";
|
||||
public static final String RULES_REFRESH = "rules_refresh";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RulesRefresher.class);
|
||||
|
||||
ModelRepository modelRepository;
|
||||
private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private ScheduledFuture<?> job;
|
||||
private ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(POOL_NAME);
|
||||
private boolean started;
|
||||
private ReadyMarker marker = new ReadyMarker("dsl", RULES_REFRESH);
|
||||
|
||||
public void setModelRepository(ModelRepository modelRepository) {
|
||||
private ItemRegistryChangeListener itemRegistryChangeListener;
|
||||
private ThingRegistryChangeListener thingRegistryChangeListener;
|
||||
|
||||
private final ModelRepository modelRepository;
|
||||
private final ItemRegistry itemRegistry;
|
||||
private final ThingRegistry thingRegistry;
|
||||
private final EventPublisher eventPublisher;
|
||||
private final ReadyService readyService;
|
||||
|
||||
@Activate
|
||||
public RulesRefresher(@Reference ModelRepository modelRepository, @Reference ItemRegistry itemRegistry,
|
||||
@Reference ThingRegistry thingRegistry, @Reference EventPublisher eventPublisher,
|
||||
@Reference ReadyService readyService) {
|
||||
this.modelRepository = modelRepository;
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.thingRegistry = thingRegistry;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.readyService = readyService;
|
||||
}
|
||||
|
||||
public void unsetModelRepository(ModelRepository modelRepository) {
|
||||
this.modelRepository = null;
|
||||
@Activate
|
||||
protected void activate() {
|
||||
readyService.registerTracker(this, new ReadyMarkerFilter().withType("dsl").withIdentifier("rules"));
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addActionService(ActionService actionService) {
|
||||
scheduleRuleRefresh();
|
||||
if (started) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeActionService(ActionService actionService) {
|
||||
scheduleRuleRefresh();
|
||||
if (started) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void scheduleRuleRefresh() {
|
||||
if (job != null && !job.isDone()) {
|
||||
job.cancel(false);
|
||||
}
|
||||
job = scheduler.schedule(runnable, REFRESH_DELAY, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
job = scheduler.schedule(() -> {
|
||||
try {
|
||||
if (modelRepository != null) {
|
||||
modelRepository.reloadAllModelsOfType("rules");
|
||||
}
|
||||
modelRepository.reloadAllModelsOfType("rules");
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
readyService.markReady(marker);
|
||||
}, REFRESH_DELAY, TimeUnit.MILLISECONDS);
|
||||
readyService.unmarkReady(marker);
|
||||
}
|
||||
|
||||
private void setStartLevel() {
|
||||
if (!started) {
|
||||
started = true;
|
||||
// TODO: This is still a very dirty hack in the absence of a proper system start level management.
|
||||
scheduler.schedule(() -> {
|
||||
StartlevelEvent startlevelEvent = SystemEventFactory.createStartlevelEvent(20);
|
||||
if (eventPublisher != null) {
|
||||
eventPublisher.post(startlevelEvent);
|
||||
}
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadyMarkerAdded(@NonNull ReadyMarker readyMarker) {
|
||||
itemRegistryChangeListener = new ItemRegistryChangeListener() {
|
||||
@Override
|
||||
public void added(Item element) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Item element) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Item oldElement, Item element) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allItemsChanged(Collection<String> oldItemNames) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
thingRegistryChangeListener = new ThingRegistryChangeListener() {
|
||||
@Override
|
||||
public void added(Thing element) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Thing element) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Thing oldElement, Thing element) {
|
||||
}
|
||||
};
|
||||
|
||||
itemRegistry.addRegistryChangeListener(itemRegistryChangeListener);
|
||||
thingRegistry.addRegistryChangeListener(thingRegistryChangeListener);
|
||||
|
||||
setStartLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadyMarkerRemoved(@NonNull ReadyMarker readyMarker) {
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.jvmmodel;
|
||||
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingRegistryChangeListener;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.script.engine.action.ActionService;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
|
||||
/**
|
||||
* The {@link RulesThingRefresher} is responsible for reloading rules resources every time a thing is added or removed.
|
||||
*
|
||||
* @author Maoliang Huang - Initial contribution
|
||||
*/
|
||||
@Component(service = {})
|
||||
public class RulesThingRefresher extends RulesRefresher implements ThingRegistryChangeListener {
|
||||
|
||||
private ThingRegistry thingRegistry;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setThingRegistry(ThingRegistry thingRegistry) {
|
||||
this.thingRegistry = thingRegistry;
|
||||
this.thingRegistry.addRegistryChangeListener(this);
|
||||
}
|
||||
|
||||
public void unsetThingRegistry(ThingRegistry thingRegistry) {
|
||||
this.thingRegistry.removeRegistryChangeListener(this);
|
||||
this.thingRegistry = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(Thing element) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Thing element) {
|
||||
scheduleRuleRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Thing oldElement, Thing element) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setModelRepository(ModelRepository modelRepository) {
|
||||
super.setModelRepository(modelRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetModelRepository(ModelRepository modelRepository) {
|
||||
super.unsetModelRepository(modelRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addActionService(ActionService actionService) {
|
||||
super.addActionService(actionService);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeActionService(ActionService actionService) {
|
||||
super.removeActionService(actionService);
|
||||
}
|
||||
|
||||
}
|
@ -20,6 +20,11 @@
|
||||
<artifactId>org.openhab.core.model.script</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.automation.module.script</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.runtime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.xtext.xbase.XExpression;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
|
||||
|
||||
/**
|
||||
* Interface of a provider that can provide Xbase-relevant object structures for
|
||||
* a purely string based script. This is required to support DSL rules, which
|
||||
* can have a context (variables) per file that is shared among multiple rules.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface DSLScriptContextProvider {
|
||||
|
||||
/**
|
||||
* Identifier for scripts that are created from a DSL rule file
|
||||
*/
|
||||
static final String CONTEXT_IDENTIFIER = "// context: ";
|
||||
|
||||
/**
|
||||
* Returns the evaluation context, i.e. the current state of the variables of the rule file.
|
||||
*
|
||||
* @param contextName the filename of the rule file in question
|
||||
* @return the evaluation context
|
||||
*/
|
||||
@Nullable
|
||||
IEvaluationContext getContext(String contextName);
|
||||
|
||||
/**
|
||||
* Returns the {@link XExpression}, which is the readily parsed script. As it might refer
|
||||
* to variables from the rule file scope, this script cannot be parsed independently.
|
||||
*
|
||||
* @param contextName the filename of the rule file in question
|
||||
* @param ruleIndex the index of the rule within the file
|
||||
* @return the parsed script
|
||||
*/
|
||||
@Nullable
|
||||
XExpression getParsedScript(String contextName, String ruleIndex);
|
||||
}
|
@ -0,0 +1,248 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.runtime.internal.engine;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.ScriptContext;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineFactory;
|
||||
import javax.script.ScriptException;
|
||||
import javax.script.SimpleScriptContext;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.xtext.naming.QualifiedName;
|
||||
import org.eclipse.xtext.xbase.XExpression;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
|
||||
import org.eclipse.xtext.xbase.interpreter.impl.DefaultEvaluationContext;
|
||||
import org.openhab.core.model.script.engine.ScriptExecutionException;
|
||||
import org.openhab.core.model.script.engine.ScriptParsingException;
|
||||
import org.openhab.core.model.script.runtime.DSLScriptContextProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A basic implementation of the {@link javax.script.ScriptEngine} interface for using DSL scripts
|
||||
* within a jsr223 scripting context in Java.
|
||||
* Most methods are left empty, because they aren't used in our rule engine.
|
||||
* The most important methods are the ones that return metadata about the script engine factory.
|
||||
*
|
||||
* Note: This class is not marked as NonNullByDefault as almost all parameters of all methods are
|
||||
* nullable as the interface is declared without null annotations.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
public class DSLScriptEngine implements javax.script.ScriptEngine {
|
||||
|
||||
public static final String MIMETYPE_OPENHAB_DSL_RULE = "application/vnd.openhab.dsl.rule";
|
||||
|
||||
private static final Map<String, String> implicitVars = Map.of("command", "receivedCommand", "event",
|
||||
"receivedEvent", "newState", "newState", "oldState", "previousState", "triggeringItem", "triggeringItem");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSLScriptEngine.class);
|
||||
|
||||
private final org.openhab.core.model.script.engine.ScriptEngine scriptEngine;
|
||||
private final @Nullable DSLScriptContextProvider contextProvider;
|
||||
private final ScriptContext context = new SimpleScriptContext();
|
||||
|
||||
public DSLScriptEngine(org.openhab.core.model.script.engine.ScriptEngine scriptEngine,
|
||||
@Nullable DSLScriptContextProvider contextProvider) {
|
||||
this.scriptEngine = scriptEngine;
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(String script, ScriptContext context) throws ScriptException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(Reader reader, ScriptContext context) throws ScriptException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(String script) throws ScriptException {
|
||||
String modelName = null;
|
||||
try {
|
||||
IEvaluationContext specificContext = null;
|
||||
org.openhab.core.model.script.engine.Script s = null;
|
||||
if (script.stripLeading().startsWith(DSLScriptContextProvider.CONTEXT_IDENTIFIER)) {
|
||||
String contextString = script.stripLeading().substring(
|
||||
DSLScriptContextProvider.CONTEXT_IDENTIFIER.length(), script.stripLeading().indexOf('\n'));
|
||||
String[] segments = contextString.split("-");
|
||||
if (segments.length == 2) {
|
||||
modelName = segments[0];
|
||||
String ruleIndex = segments[1];
|
||||
if (contextProvider != null) {
|
||||
DSLScriptContextProvider cp = contextProvider;
|
||||
logger.debug("Script uses context '{}'.", contextString);
|
||||
specificContext = cp.getContext(modelName);
|
||||
XExpression xExpression = cp.getParsedScript(modelName, ruleIndex);
|
||||
if (xExpression != null) {
|
||||
s = scriptEngine.newScriptFromXExpression(xExpression);
|
||||
} else {
|
||||
logger.warn("No pre-parsed script found for {}-{}.", modelName, ruleIndex);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.error("Script references context '{}', but no context provider is registered!",
|
||||
contextString);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.error("Script has an invalid context reference '{}'!", contextString);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
s = scriptEngine.newScriptFromString(script);
|
||||
}
|
||||
IEvaluationContext evalContext = createEvaluationContext(specificContext);
|
||||
s.execute(evalContext);
|
||||
} catch (ScriptExecutionException | ScriptParsingException e) {
|
||||
throw new ScriptException(e.getMessage(), modelName, -1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private DefaultEvaluationContext createEvaluationContext(IEvaluationContext specificContext) {
|
||||
DefaultEvaluationContext evalContext = new DefaultEvaluationContext(specificContext);
|
||||
for (Map.Entry<String, String> entry : implicitVars.entrySet()) {
|
||||
Object value = context.getAttribute(entry.getKey());
|
||||
if (value != null) {
|
||||
evalContext.newValue(QualifiedName.create(entry.getValue()), value);
|
||||
}
|
||||
}
|
||||
return evalContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(Reader reader) throws ScriptException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(String script, Bindings n) throws ScriptException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(Reader reader, Bindings n) throws ScriptException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, Object value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bindings getBindings(int scope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBindings(Bindings bindings, int scope) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bindings createBindings() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(ScriptContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptEngineFactory getFactory() {
|
||||
return new ScriptEngineFactory() {
|
||||
|
||||
@Override
|
||||
public ScriptEngine getScriptEngine() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProgram(String... statements) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParameter(String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOutputStatement(String toDisplay) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMimeTypes() {
|
||||
return Collections.singletonList(MIMETYPE_OPENHAB_DSL_RULE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodCallSyntax(String obj, String m, String... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageVersion() {
|
||||
return "v1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLanguageName() {
|
||||
return "Rule DSL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getExtensions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEngineVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEngineName() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.runtime.internal.engine;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.module.script.ScriptEngineFactory;
|
||||
import org.openhab.core.model.script.engine.ScriptEngine;
|
||||
import org.openhab.core.model.script.runtime.DSLScriptContextProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
|
||||
/**
|
||||
* An implementation of {@link ScriptEngineFactory} for DSL scripts.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ScriptEngineFactory.class)
|
||||
public class DSLScriptEngineFactory implements ScriptEngineFactory {
|
||||
|
||||
private static final String SCRIPT_TYPE = "dsl";
|
||||
|
||||
private @NonNullByDefault({}) DSLScriptEngine dslScriptEngine;
|
||||
|
||||
private final ScriptEngine scriptEngine;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
|
||||
protected @Nullable DSLScriptContextProvider contextProvider;
|
||||
|
||||
@Activate
|
||||
public DSLScriptEngineFactory(@Reference ScriptEngine scriptEngine) {
|
||||
this.scriptEngine = scriptEngine;
|
||||
}
|
||||
|
||||
@Activate
|
||||
protected void activate() {
|
||||
dslScriptEngine = new DSLScriptEngine(scriptEngine, contextProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getScriptTypes() {
|
||||
return List.of(SCRIPT_TYPE, DSLScriptEngine.MIMETYPE_OPENHAB_DSL_RULE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scopeValues(javax.script.ScriptEngine scriptEngine, Map<String, Object> scopeValues) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public javax.script.@Nullable ScriptEngine createScriptEngine(String scriptType) {
|
||||
return dslScriptEngine;
|
||||
}
|
||||
|
||||
}
|
@ -13,8 +13,6 @@
|
||||
package org.openhab.core.model.script.runtime.internal.engine;
|
||||
|
||||
import org.eclipse.emf.ecore.resource.Resource;
|
||||
import org.openhab.core.model.script.engine.Script;
|
||||
import org.openhab.core.model.script.engine.ScriptExecutionException;
|
||||
import org.eclipse.xtext.resource.IResourceServiceProvider;
|
||||
import org.eclipse.xtext.resource.XtextResource;
|
||||
import org.eclipse.xtext.util.CancelIndicator;
|
||||
@ -22,6 +20,8 @@ import org.eclipse.xtext.xbase.XExpression;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationResult;
|
||||
import org.eclipse.xtext.xbase.interpreter.IExpressionInterpreter;
|
||||
import org.openhab.core.model.script.engine.Script;
|
||||
import org.openhab.core.model.script.engine.ScriptExecutionException;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@ -30,7 +30,6 @@ import com.google.inject.Inject;
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class ScriptImpl implements Script {
|
||||
|
||||
private XExpression xExpression;
|
||||
|
@ -28,8 +28,10 @@ Import-Package: \
|
||||
org.openhab.core.library.types,\
|
||||
org.openhab.core.library.unit,\
|
||||
org.openhab.core.persistence,\
|
||||
org.openhab.core.scheduler,\
|
||||
org.openhab.core.thing,\
|
||||
org.openhab.core.thing.binding,\
|
||||
org.openhab.core.thing.events,\
|
||||
org.openhab.core.transform.actions,\
|
||||
org.openhab.core.types,\
|
||||
org.openhab.core.voice,\
|
||||
@ -48,7 +50,6 @@ Import-Package: \
|
||||
org.eclipse.jetty.http.*,\
|
||||
org.eclipse.xtext.xbase.lib,\
|
||||
org.osgi.*,\
|
||||
org.quartz.*,\
|
||||
org.slf4j.*
|
||||
Require-Bundle: org.antlr.runtime,\
|
||||
org.eclipse.emf.common,\
|
||||
|
@ -21,6 +21,7 @@ import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.script.engine.ScriptEngine;
|
||||
import org.openhab.core.model.script.engine.action.ActionService;
|
||||
import org.openhab.core.scheduler.Scheduler;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
@ -49,6 +50,7 @@ public class ScriptServiceUtil {
|
||||
private final ThingRegistry thingRegistry;
|
||||
private final EventPublisher eventPublisher;
|
||||
private final ModelRepository modelRepository;
|
||||
private final Scheduler scheduler;
|
||||
|
||||
private final AtomicReference<ScriptEngine> scriptEngine = new AtomicReference<>();
|
||||
public final List<ActionService> actionServices = new CopyOnWriteArrayList<>();
|
||||
@ -56,11 +58,13 @@ public class ScriptServiceUtil {
|
||||
|
||||
@Activate
|
||||
public ScriptServiceUtil(final @Reference ItemRegistry itemRegistry, final @Reference ThingRegistry thingRegistry,
|
||||
final @Reference EventPublisher eventPublisher, final @Reference ModelRepository modelRepository) {
|
||||
final @Reference EventPublisher eventPublisher, final @Reference ModelRepository modelRepository,
|
||||
final @Reference Scheduler scheduler) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.thingRegistry = thingRegistry;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.modelRepository = modelRepository;
|
||||
this.scheduler = scheduler;
|
||||
|
||||
if (instance != null) {
|
||||
throw new IllegalStateException("ScriptServiceUtil should only be activated once!");
|
||||
@ -103,6 +107,14 @@ public class ScriptServiceUtil {
|
||||
return modelRepository;
|
||||
}
|
||||
|
||||
public static Scheduler getScheduler() {
|
||||
return getInstance().scheduler;
|
||||
}
|
||||
|
||||
public Scheduler getSchedulerInstance() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public static ScriptEngine getScriptEngine() {
|
||||
return getInstance().scriptEngine.get();
|
||||
}
|
||||
|
@ -12,11 +12,7 @@
|
||||
*/
|
||||
package org.openhab.core.model.script.actions;
|
||||
|
||||
import static org.quartz.JobBuilder.newJob;
|
||||
import static org.quartz.TriggerBuilder.newTrigger;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.xtext.xbase.XExpression;
|
||||
import org.eclipse.xtext.xbase.lib.Procedures.Procedure0;
|
||||
@ -26,15 +22,8 @@ import org.openhab.core.model.script.ScriptServiceUtil;
|
||||
import org.openhab.core.model.script.engine.Script;
|
||||
import org.openhab.core.model.script.engine.ScriptEngine;
|
||||
import org.openhab.core.model.script.engine.ScriptExecutionException;
|
||||
import org.openhab.core.model.script.internal.actions.TimerExecutionJob;
|
||||
import org.openhab.core.model.script.internal.actions.TimerImpl;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.openhab.core.scheduler.Scheduler;
|
||||
|
||||
/**
|
||||
* The static methods of this class are made available as functions in the scripts.
|
||||
@ -44,8 +33,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class ScriptExecution {
|
||||
|
||||
private static int timerCounter = 0;
|
||||
|
||||
/**
|
||||
* Calls a script which must be located in the configurations/scripts folder.
|
||||
*
|
||||
@ -88,10 +75,12 @@ public class ScriptExecution {
|
||||
* @return a handle to the created timer, so that it can be canceled or rescheduled
|
||||
* @throws ScriptExecutionException if an error occurs during the execution
|
||||
*/
|
||||
public static Timer createTimer(Instant instant, Procedure0 closure) {
|
||||
JobDataMap dataMap = new JobDataMap();
|
||||
dataMap.put("procedure", closure);
|
||||
return makeTimer(instant, closure.toString(), dataMap);
|
||||
public static Timer createTimer(ZonedDateTime instant, Procedure0 closure) {
|
||||
Scheduler scheduler = ScriptServiceUtil.getScheduler();
|
||||
|
||||
return new TimerImpl(scheduler, instant, () -> {
|
||||
closure.apply();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,43 +93,12 @@ public class ScriptExecution {
|
||||
* @return a handle to the created timer, so that it can be canceled or rescheduled
|
||||
* @throws ScriptExecutionException if an error occurs during the execution
|
||||
*/
|
||||
public static Timer createTimerWithArgument(Instant instant, Object arg1, Procedure1<Object> closure) {
|
||||
JobDataMap dataMap = new JobDataMap();
|
||||
dataMap.put("procedure1", closure);
|
||||
dataMap.put("argument1", arg1);
|
||||
return makeTimer(instant, closure.toString(), dataMap);
|
||||
public static Timer createTimerWithArgument(ZonedDateTime instant, Object arg1, Procedure1<Object> closure) {
|
||||
Scheduler scheduler = ScriptServiceUtil.getScheduler();
|
||||
|
||||
return new TimerImpl(scheduler, instant, () -> {
|
||||
closure.apply(arg1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function to create the timer
|
||||
*
|
||||
* @param instant the point in time when the code should be executed
|
||||
* @param closure string for job id
|
||||
* @param dataMap job data map, preconfigured with arguments
|
||||
* @return
|
||||
*/
|
||||
private static Timer makeTimer(Instant instant, String closure, JobDataMap dataMap) {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(ScriptExecution.class);
|
||||
JobKey jobKey = new JobKey(getTimerId() + " " + instant.toString() + ": " + closure.toString());
|
||||
Trigger trigger = newTrigger().startAt(Date.from(instant)).build();
|
||||
Timer timer = new TimerImpl(jobKey, trigger.getKey(), dataMap, instant);
|
||||
try {
|
||||
JobDetail job = newJob(TimerExecutionJob.class).withIdentity(jobKey).usingJobData(dataMap).build();
|
||||
if (TimerImpl.scheduler.checkExists(job.getKey())) {
|
||||
TimerImpl.scheduler.deleteJob(job.getKey());
|
||||
logger.debug("Deleted existing Job {}", job.getKey().toString());
|
||||
}
|
||||
TimerImpl.scheduler.scheduleJob(job, trigger);
|
||||
logger.debug("Scheduled code for execution at {}", instant.toString());
|
||||
return timer;
|
||||
} catch (SchedulerException e) {
|
||||
logger.error("Failed to schedule code for execution.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized String getTimerId() {
|
||||
return String.format("Timer %d", ++timerCounter);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
package org.openhab.core.model.script.actions;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* A timer is a handle for a block of code that is scheduled for future execution. A timer
|
||||
@ -59,6 +59,6 @@ public interface Timer {
|
||||
* @param newTime the new time to execute the code
|
||||
* @return true, if the rescheduling was done successful
|
||||
*/
|
||||
public boolean reschedule(Instant newTime);
|
||||
public boolean reschedule(ZonedDateTime newTime);
|
||||
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.internal.actions;
|
||||
|
||||
import org.eclipse.xtext.xbase.lib.Procedures.Procedure0;
|
||||
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is a Quartz {@link Job} which executes the code of a closure that is passed
|
||||
* to the createTimer() extension method.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
public class TimerExecutionJob implements Job {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TimerExecutionJob.class);
|
||||
|
||||
/**
|
||||
* Runs the configured closure of this job
|
||||
*
|
||||
* @param context the execution context of the job
|
||||
*/
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
logger.debug("Executing timer '{}'", context.getJobDetail().getKey().toString());
|
||||
Procedure0 procedure = (Procedure0) context.getJobDetail().getJobDataMap().get("procedure");
|
||||
Procedure1<Object> procedure1 = (Procedure1<Object>) context.getJobDetail().getJobDataMap().get("procedure1");
|
||||
TimerImpl timer = (TimerImpl) context.getJobDetail().getJobDataMap().get("timer");
|
||||
Object argument1 = context.getJobDetail().getJobDataMap().get("argument1");
|
||||
if (argument1 != null) {
|
||||
procedure1.apply(argument1);
|
||||
} else {
|
||||
procedure.apply();
|
||||
}
|
||||
timer.setTerminated(true);
|
||||
}
|
||||
|
||||
}
|
@ -12,22 +12,12 @@
|
||||
*/
|
||||
package org.openhab.core.model.script.internal.actions;
|
||||
|
||||
import static org.quartz.JobBuilder.newJob;
|
||||
import static org.quartz.TriggerBuilder.newTrigger;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.openhab.core.model.script.actions.Timer;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.TriggerKey;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
import org.openhab.core.scheduler.ScheduledCompletableFuture;
|
||||
import org.openhab.core.scheduler.Scheduler;
|
||||
import org.openhab.core.scheduler.SchedulerRunnable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -41,93 +31,56 @@ public class TimerImpl implements Timer {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TimerImpl.class);
|
||||
|
||||
// the scheduler used for timer events
|
||||
public static Scheduler scheduler;
|
||||
|
||||
static {
|
||||
try {
|
||||
scheduler = StdSchedulerFactory.getDefaultScheduler();
|
||||
} catch (SchedulerException e) {
|
||||
LoggerFactory.getLogger(TimerImpl.class).error("initializing scheduler throws exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private final JobKey jobKey;
|
||||
private TriggerKey triggerKey;
|
||||
private final JobDataMap dataMap;
|
||||
private final Instant startTime;
|
||||
private ScheduledCompletableFuture<Object> future;
|
||||
private final ZonedDateTime startTime;
|
||||
|
||||
private boolean cancelled = false;
|
||||
private boolean terminated = false;
|
||||
|
||||
public TimerImpl(JobKey jobKey, TriggerKey triggerKey, JobDataMap dataMap, Instant startTime) {
|
||||
this.jobKey = jobKey;
|
||||
this.triggerKey = triggerKey;
|
||||
this.dataMap = dataMap;
|
||||
private Scheduler scheduler;
|
||||
private SchedulerRunnable runnable;
|
||||
|
||||
public TimerImpl(ScheduledCompletableFuture<Object> f, ZonedDateTime startTime) {
|
||||
this.future = f;
|
||||
this.startTime = startTime;
|
||||
dataMap.put("timer", this);
|
||||
}
|
||||
|
||||
public TimerImpl(Scheduler scheduler, ZonedDateTime startTime, SchedulerRunnable runnable) {
|
||||
this.scheduler = scheduler;
|
||||
this.startTime = startTime;
|
||||
this.runnable = runnable;
|
||||
|
||||
future = scheduler.schedule(runnable, startTime.toInstant());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
try {
|
||||
boolean result = scheduler.deleteJob(jobKey);
|
||||
if (result) {
|
||||
cancelled = true;
|
||||
}
|
||||
} catch (SchedulerException e) {
|
||||
logger.warn("An error occurred while cancelling the job '{}': {}", jobKey.toString(), e.getMessage());
|
||||
}
|
||||
return cancelled;
|
||||
cancelled = true;
|
||||
return future.cancel(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean reschedule(Instant newTime) {
|
||||
try {
|
||||
Trigger trigger = newTrigger().startAt(Date.from(newTime)).build();
|
||||
Date nextTriggerTime = scheduler.rescheduleJob(triggerKey, trigger);
|
||||
if (nextTriggerTime == null) {
|
||||
logger.debug("Scheduling a new job job '{}' because the original has already run", jobKey.toString());
|
||||
JobDetail job = newJob(TimerExecutionJob.class).withIdentity(jobKey).usingJobData(dataMap).build();
|
||||
TimerImpl.scheduler.scheduleJob(job, trigger);
|
||||
}
|
||||
this.triggerKey = trigger.getKey();
|
||||
this.cancelled = false;
|
||||
this.terminated = false;
|
||||
public boolean reschedule(ZonedDateTime newTime) {
|
||||
if (future.cancel(false)) {
|
||||
future = scheduler.schedule(runnable, newTime.toInstant());
|
||||
return true;
|
||||
} catch (SchedulerException e) {
|
||||
logger.warn("An error occurred while rescheduling the job '{}': {}", jobKey.toString(), e.getMessage());
|
||||
} else {
|
||||
logger.warn("Rescheduling failed as execution has already started!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return !terminated && !cancelled;
|
||||
return !future.isDone() && !cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
try {
|
||||
for (JobExecutionContext context : scheduler.getCurrentlyExecutingJobs()) {
|
||||
if (context.getJobDetail().getKey().equals(jobKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (SchedulerException e) {
|
||||
// fallback implementation
|
||||
logger.debug("An error occurred getting currently running jobs: {}", e.getMessage());
|
||||
return Instant.now().isAfter(startTime) && !terminated;
|
||||
}
|
||||
return cancelled || (ZonedDateTime.now().isAfter(startTime) && !future.isDone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTerminated() {
|
||||
return terminated;
|
||||
}
|
||||
|
||||
public void setTerminated(boolean terminated) {
|
||||
this.terminated = terminated;
|
||||
return future.isDone();
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.jvmmodel
|
||||
@ -22,10 +22,14 @@ import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
|
||||
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.openhab.core.items.Item
|
||||
import org.openhab.core.types.Command
|
||||
import org.openhab.core.types.State
|
||||
import org.openhab.core.thing.events.ChannelTriggeredEvent
|
||||
|
||||
/**
|
||||
* <p>Infers a JVM model from the source model.</p>
|
||||
*
|
||||
*
|
||||
* <p>The JVM model should contain all elements that would appear in the Java code
|
||||
* which is generated from the source model. Other models link against the JVM model rather than the source model.</p>
|
||||
*
|
||||
@ -34,63 +38,86 @@ import org.slf4j.LoggerFactory
|
||||
*/
|
||||
class ScriptJvmModelInferrer extends AbstractModelInferrer {
|
||||
|
||||
static private final Logger logger = LoggerFactory.getLogger(ScriptJvmModelInferrer)
|
||||
static private final Logger logger = LoggerFactory.getLogger(ScriptJvmModelInferrer)
|
||||
|
||||
/** Variable name for the item in a "state triggered" or "command triggered" rule */
|
||||
public static final String VAR_TRIGGERING_ITEM = "triggeringItem";
|
||||
|
||||
/** Variable name for the previous state of an item in a "changed state triggered" rule */
|
||||
public static final String VAR_PREVIOUS_STATE = "previousState";
|
||||
|
||||
/** Variable name for the new state of an item in a "changed state triggered" or "updated state triggered" rule */
|
||||
public static final String VAR_NEW_STATE = "newState";
|
||||
|
||||
/** Variable name for the received command in a "command triggered" rule */
|
||||
public static final String VAR_RECEIVED_COMMAND = "receivedCommand";
|
||||
|
||||
/** Variable name for the received event in a "trigger event" rule */
|
||||
public static final String VAR_RECEIVED_EVENT = "receivedEvent";
|
||||
|
||||
/**
|
||||
* conveninence API to build and initialize JvmTypes and their members.
|
||||
*/
|
||||
@Inject extension JvmTypesBuilder
|
||||
|
||||
@Inject extension JvmTypesBuilder
|
||||
|
||||
@Inject
|
||||
ItemRegistry itemRegistry
|
||||
@Inject
|
||||
ItemRegistry itemRegistry
|
||||
|
||||
@Inject
|
||||
StateAndCommandProvider stateAndCommandProvider
|
||||
@Inject
|
||||
StateAndCommandProvider stateAndCommandProvider
|
||||
|
||||
/**
|
||||
* Is called for each instance of the first argument's type contained in a resource.
|
||||
*
|
||||
* @param element - the model to create one or more JvmDeclaredTypes from.
|
||||
* @param acceptor - each created JvmDeclaredType without a container should be passed to the acceptor in order get attached to the
|
||||
* current resource.
|
||||
* @param isPreLinkingPhase - whether the method is called in a pre linking phase, i.e. when the global index isn't fully updated. You
|
||||
* must not rely on linking using the index if iPrelinkingPhase is <code>true</code>
|
||||
*/
|
||||
def dispatch void infer(Script script, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
|
||||
val className = script.eResource.URI.lastSegment.split("\\.").head.toFirstUpper + "Script"
|
||||
acceptor.accept(script.toClass(className)).initializeLater [
|
||||
|
||||
|
||||
val Set<String> fieldNames = newHashSet()
|
||||
|
||||
val types = stateAndCommandProvider.allTypes
|
||||
types.forEach [ type |
|
||||
val name = type.toString
|
||||
if (fieldNames.add(name)) {
|
||||
members += script.toField(name, script.newTypeRef(type.class)) [
|
||||
static = true
|
||||
]
|
||||
} else {
|
||||
logger.warn("Duplicate field: '{}'. Ignoring '{}'.", name, type.class.name)
|
||||
}
|
||||
]
|
||||
/**
|
||||
* Is called for each instance of the first argument's type contained in a resource.
|
||||
*
|
||||
* @param element - the model to create one or more JvmDeclaredTypes from.
|
||||
* @param acceptor - each created JvmDeclaredType without a container should be passed to the acceptor in order get attached to the
|
||||
* current resource.
|
||||
* @param isPreLinkingPhase - whether the method is called in a pre linking phase, i.e. when the global index isn't fully updated. You
|
||||
* must not rely on linking using the index if iPrelinkingPhase is <code>true</code>
|
||||
*/
|
||||
def dispatch void infer(Script script, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
|
||||
val className = script.eResource.URI.lastSegment.split("\\.").head.toFirstUpper + "Script"
|
||||
acceptor.accept(script.toClass(className)).initializeLater [
|
||||
|
||||
itemRegistry?.items?.forEach[ item |
|
||||
val name = item.name
|
||||
if (fieldNames.add(name)) {
|
||||
members += script.toField(item.name, script.newTypeRef(item.class)) [
|
||||
static = true
|
||||
]
|
||||
} else {
|
||||
logger.warn("Duplicate field: '{}'. Ignoring '{}'.", item.name, item.class.name)
|
||||
}
|
||||
]
|
||||
|
||||
members += script.toMethod("_script", null) [
|
||||
static = true
|
||||
body = script
|
||||
]
|
||||
]
|
||||
}
|
||||
val Set<String> fieldNames = newHashSet()
|
||||
|
||||
val types = stateAndCommandProvider.allTypes
|
||||
types.forEach [ type |
|
||||
val name = type.toString
|
||||
if (fieldNames.add(name)) {
|
||||
members += script.toField(name, script.newTypeRef(type.class)) [
|
||||
static = true
|
||||
]
|
||||
} else {
|
||||
logger.warn("Duplicate field: '{}'. Ignoring '{}'.", name, type.class.name)
|
||||
}
|
||||
]
|
||||
|
||||
itemRegistry?.items?.forEach [ item |
|
||||
val name = item.name
|
||||
if (fieldNames.add(name)) {
|
||||
members += script.toField(item.name, script.newTypeRef(item.class)) [
|
||||
static = true
|
||||
]
|
||||
} else {
|
||||
logger.warn("Duplicate field: '{}'. Ignoring '{}'.", item.name, item.class.name)
|
||||
}
|
||||
]
|
||||
|
||||
members += script.toMethod("_script", null) [
|
||||
static = true
|
||||
val itemTypeRef = script.newTypeRef(Item)
|
||||
parameters += script.toParameter(VAR_TRIGGERING_ITEM, itemTypeRef)
|
||||
val commandTypeRef = script.newTypeRef(Command)
|
||||
parameters += script.toParameter(VAR_RECEIVED_COMMAND, commandTypeRef)
|
||||
val stateTypeRef = script.newTypeRef(State)
|
||||
parameters += script.toParameter(VAR_PREVIOUS_STATE, stateTypeRef)
|
||||
val eventTypeRef = script.newTypeRef(ChannelTriggeredEvent)
|
||||
parameters += script.toParameter(VAR_RECEIVED_EVENT, eventTypeRef)
|
||||
val stateTypeRef2 = script.newTypeRef(State)
|
||||
parameters += script.toParameter(VAR_NEW_STATE, stateTypeRef2)
|
||||
body = script
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ package org.openhab.core.model.script.scoping;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URLEncoder;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -108,7 +107,6 @@ public class ScriptImplicitlyImportedTypes extends ImplicitlyImportedFeatures {
|
||||
result.add(BinaryPrefix.class);
|
||||
|
||||
// date time static functions
|
||||
result.add(Instant.class);
|
||||
result.add(ZonedDateTime.class);
|
||||
|
||||
result.addAll(getActionClasses());
|
||||
|
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.scheduler</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -1,227 +0,0 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-core
|
||||
|
||||
|
||||
== Third-party Content
|
||||
|
||||
org.quartz Version: 2.2.1
|
||||
* License: Apache License, 2.0
|
||||
* Project: http://quartz-scheduler.org/
|
||||
* Source: http://quartz-scheduler.org/community/source-code
|
||||
|
||||
== Third-party license(s)
|
||||
|
||||
=== Apache License, 2.0
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,30 +0,0 @@
|
||||
Bundle-SymbolicName: ${project.artifactId}
|
||||
Bundle-Activator: org.openhab.core.scheduler.internal.SchedulerActivator
|
||||
Import-Package: \
|
||||
com.mchange.v2.c3p0;resolution:=optional,\
|
||||
commonj.work;resolution:=optional,\
|
||||
javax.ejb;resolution:=optional,\
|
||||
javax.jms;resolution:=optional,\
|
||||
javax.mail;resolution:=optional,\
|
||||
javax.mail.internet;resolution:=optional,\
|
||||
javax.management;resolution:=optional,\
|
||||
javax.management.openmbean;resolution:=optional,\
|
||||
javax.naming;resolution:=optional,\
|
||||
javax.rmi;resolution:=optional,\
|
||||
javax.servlet;version="[3.1,4)";resolution:=optional,\
|
||||
javax.servlet.http;version="[3.1,4)";resolution:=optional,\
|
||||
javax.sql;resolution:=optional,\
|
||||
javax.transaction;resolution:=optional,\
|
||||
javax.xml.bind;resolution:=optional,\
|
||||
javax.xml.namespace;resolution:=optional,\
|
||||
javax.xml.parsers;resolution:=optional,\
|
||||
javax.xml.xpath;resolution:=optional,\
|
||||
oracle.sql;resolution:=optional,\
|
||||
org.jboss.logging;resolution:=optional,\
|
||||
org.jboss.naming;resolution:=optional,\
|
||||
org.jboss.system;resolution:=optional,\
|
||||
weblogic.jdbc.jts;resolution:=optional,\
|
||||
weblogic.jdbc.vendor.oracle;resolution:=optional,\
|
||||
*
|
||||
Export-Package: \
|
||||
org.quartz.*;version="2.2.1"
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.scheduler</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: Scheduler Service</name>
|
||||
|
||||
</project>
|
@ -1,71 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.scheduler.internal;
|
||||
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Extension of the default OSGi bundle activator
|
||||
*
|
||||
* @author Thomas Eichstaedt-Engelen - Initial contribution
|
||||
*/
|
||||
public final class SchedulerActivator implements BundleActivator {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SchedulerActivator.class);
|
||||
|
||||
private static BundleContext context;
|
||||
|
||||
/**
|
||||
* Called whenever the OSGi framework starts our bundle
|
||||
*/
|
||||
@Override
|
||||
public void start(BundleContext bc) throws Exception {
|
||||
context = bc;
|
||||
logger.debug("Scheduler has been started.");
|
||||
|
||||
try {
|
||||
StdSchedulerFactory.getDefaultScheduler().start();
|
||||
} catch (SchedulerException se) {
|
||||
logger.error("initializing scheduler throws exception", se);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the OSGi framework stops our bundle
|
||||
*/
|
||||
@Override
|
||||
public void stop(BundleContext bc) throws Exception {
|
||||
context = null;
|
||||
logger.debug("Scheduler has been stopped.");
|
||||
|
||||
try {
|
||||
StdSchedulerFactory.getDefaultScheduler().shutdown();
|
||||
} catch (SchedulerException se) {
|
||||
logger.error("shutting down scheduler throws exception", se);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bundle context of this bundle
|
||||
*
|
||||
* @return the bundle context
|
||||
*/
|
||||
public static BundleContext getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ package org.openhab.core.internal.scheduler;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.TemporalAdjuster;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
@ -24,7 +25,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.scheduler.ScheduledCompletableFuture;
|
||||
import org.openhab.core.scheduler.Scheduler;
|
||||
import org.openhab.core.scheduler.SchedulerRunnable;
|
||||
import org.openhab.core.scheduler.SchedulerTemporalAdjuster;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
@ -92,8 +92,7 @@ public class DelegatedSchedulerImpl implements Scheduler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ScheduledCompletableFuture<T> schedule(SchedulerRunnable runnable,
|
||||
SchedulerTemporalAdjuster temporalAdjuster) {
|
||||
public <T> ScheduledCompletableFuture<T> schedule(SchedulerRunnable runnable, TemporalAdjuster temporalAdjuster) {
|
||||
return add(delegate.schedule(runnable, temporalAdjuster));
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.TemporalAdjuster;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@ -138,8 +139,7 @@ public class SchedulerImpl implements Scheduler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ScheduledCompletableFuture<T> schedule(SchedulerRunnable runnable,
|
||||
SchedulerTemporalAdjuster temporalAdjuster) {
|
||||
public <T> ScheduledCompletableFuture<T> schedule(SchedulerRunnable runnable, TemporalAdjuster temporalAdjuster) {
|
||||
final ScheduledCompletableFutureRecurring<T> schedule = new ScheduledCompletableFutureRecurring<>();
|
||||
|
||||
schedule(schedule, runnable, temporalAdjuster);
|
||||
@ -147,16 +147,19 @@ public class SchedulerImpl implements Scheduler {
|
||||
}
|
||||
|
||||
private <T> void schedule(ScheduledCompletableFutureRecurring<T> schedule, SchedulerRunnable runnable,
|
||||
SchedulerTemporalAdjuster temporalAdjuster) {
|
||||
TemporalAdjuster temporalAdjuster) {
|
||||
final Temporal newTime = ZonedDateTime.now(clock).with(temporalAdjuster);
|
||||
final ScheduledCompletableFutureOnce<T> deferred = new ScheduledCompletableFutureOnce<>();
|
||||
|
||||
deferred.thenAccept(v -> {
|
||||
if (temporalAdjuster.isDone(newTime)) {
|
||||
schedule.complete(v);
|
||||
} else {
|
||||
schedule(schedule, runnable, temporalAdjuster);
|
||||
if (temporalAdjuster instanceof SchedulerTemporalAdjuster) {
|
||||
SchedulerTemporalAdjuster schedulerTemporalAdjuster = (SchedulerTemporalAdjuster) temporalAdjuster;
|
||||
if (!schedulerTemporalAdjuster.isDone(newTime)) {
|
||||
schedule(schedule, runnable, temporalAdjuster);
|
||||
return;
|
||||
}
|
||||
}
|
||||
schedule.complete(v);
|
||||
});
|
||||
schedule.setScheduledPromise(deferred);
|
||||
atInternal(deferred, () -> {
|
||||
|
@ -14,6 +14,7 @@ package org.openhab.core.scheduler;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.TemporalAdjuster;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@ -112,5 +113,5 @@ public interface Scheduler {
|
||||
* @param temporalAdjuster the temperalAdjuster to return the time the callable should run
|
||||
* @return A {@link ScheduledCompletableFuture}
|
||||
*/
|
||||
<T> ScheduledCompletableFuture<T> schedule(SchedulerRunnable callable, SchedulerTemporalAdjuster temporalAdjuster);
|
||||
<T> ScheduledCompletableFuture<T> schedule(SchedulerRunnable callable, TemporalAdjuster temporalAdjuster);
|
||||
}
|
||||
|
@ -40,7 +40,6 @@
|
||||
<module>org.openhab.core.extension.sample</module>
|
||||
<module>org.openhab.core.id</module>
|
||||
<module>org.openhab.core.persistence</module>
|
||||
<module>org.openhab.core.scheduler</module>
|
||||
<module>org.openhab.core.semantics</module>
|
||||
<module>org.openhab.core.thing</module>
|
||||
<module>org.openhab.core.thing.xml</module>
|
||||
|
@ -23,6 +23,9 @@
|
||||
<requirement>openhab.tp;filter:="(feature=base)"</requirement>
|
||||
<feature dependency="true">openhab.tp-base</feature>
|
||||
|
||||
<requirement>openhab.tp;filter:="(feature=jollyday)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jollyday</feature>
|
||||
|
||||
<feature dependency="true">openhab.tp-gson</feature>
|
||||
|
||||
<requirement>openhab.tp;filter:="(&(feature=xtext)(version>=2.19.0)(!(version>=2.20.0)))"</requirement>
|
||||
@ -34,6 +37,7 @@
|
||||
<requirement>openhab.tp;filter:="(feature=httpclient)"</requirement>
|
||||
<feature dependency="true">openhab.tp-httpclient</feature>
|
||||
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.automation/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.core/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.dispatch/${project.version}</bundle>
|
||||
@ -41,10 +45,10 @@
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core/${project.version}</bundle>
|
||||
<feature dependency="true">openhab-core-storage-json</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.binding.xml/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.ephemeris/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.id/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.persistence/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.semantics/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.scheduler/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.thing/${project.version}</bundle>
|
||||
<bundle start-level="75">mvn:org.openhab.core.bundles/org.openhab.core.thing.xml/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.transform/${project.version}</bundle>
|
||||
@ -72,7 +76,6 @@
|
||||
|
||||
<feature name="openhab-core-automation" version="${project.version}">
|
||||
<feature>openhab-core-base</feature>
|
||||
<feature>openhab-core-ephemeris</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.automation/${project.version}</bundle>
|
||||
</feature>
|
||||
|
||||
@ -114,13 +117,6 @@
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/${project.version}</bundle>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab-core-ephemeris" version="${project.version}">
|
||||
<requirement>openhab.tp;filter:="(feature=jollyday)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jollyday</feature>
|
||||
<feature>openhab-core-base</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.ephemeris/${project.version}</bundle>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab-core-io-bin2json" description="Binary to JSON converter" version="${project.version}">
|
||||
<feature>openhab-core-base</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.bin2json/${project.version}</bundle>
|
||||
@ -340,7 +336,7 @@
|
||||
|
||||
<feature name="openhab-core-model-script" version="${project.version}">
|
||||
<feature>openhab-core-base</feature>
|
||||
<feature>openhab-core-ephemeris</feature>
|
||||
<feature>openhab-core-automation-module-script</feature>
|
||||
<feature>openhab-core-model-persistence</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.model.script/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.model.script.runtime/${project.version}</bundle>
|
||||
@ -429,7 +425,10 @@
|
||||
<feature dependency="true">openhab.tp-commons-net</feature>
|
||||
<feature>openhab-core-base</feature>
|
||||
<feature>openhab-core-auth-jaas</feature>
|
||||
<feature>openhab-core-ephemeris</feature>
|
||||
<feature>openhab-core-automation-rest</feature>
|
||||
<feature>openhab-core-automation-module-script</feature>
|
||||
<feature>openhab-core-automation-module-media</feature>
|
||||
<feature>openhab-core-automation</feature>
|
||||
<feature>openhab-core-io-console-karaf</feature>
|
||||
<feature>openhab-core-io-http-auth</feature>
|
||||
<feature>openhab-core-io-rest-auth</feature>
|
||||
|
@ -70,4 +70,5 @@ Fragment-Host: org.openhab.core.io.rest.core
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
javax.xml.soap-api;version='[1.3.5,1.3.6)',\
|
||||
org.apache.servicemix.specs.jaxws-api-2.2;version='[2.9.0,2.9.1)'
|
||||
org.apache.servicemix.specs.jaxws-api-2.2;version='[2.9.0,2.9.1)',\
|
||||
jakarta.xml.bind-api;version='[2.3.2,2.3.3)'
|
||||
|
@ -90,9 +90,10 @@ Fragment-Host: org.openhab.core.model.core
|
||||
org.openhab.core.model.sitemap;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.persistence;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.scheduler;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.item.runtime;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.automation;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.automation.module.script;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing.runtime;version='[3.0.0,3.0.1)'
|
||||
|
@ -89,8 +89,10 @@ Fragment-Host: org.openhab.core.model.item
|
||||
org.openhab.core.model.sitemap;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.persistence;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.scheduler;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.automation;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.automation.module.script;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing.runtime;version='[3.0.0,3.0.1)'
|
||||
|
@ -105,9 +105,10 @@ Fragment-Host: org.openhab.core.model.persistence
|
||||
org.openhab.core.model.sitemap;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.persistence;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.scheduler;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.item.runtime;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.automation;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.automation.module.script;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing.runtime;version='[3.0.0,3.0.1)'
|
||||
|
@ -6,13 +6,6 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
@ -23,5 +16,12 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
@ -15,7 +15,6 @@ Fragment-Host: org.openhab.core.model.rule.runtime
|
||||
javax.measure.unit-api;version='[1.0.0,1.0.1)',\
|
||||
log4j;version='[1.2.17,1.2.18)',\
|
||||
org.antlr.runtime;version='[3.2.0,3.2.1)',\
|
||||
org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
|
||||
org.apache.felix.scr;version='[2.1.10,2.1.11)',\
|
||||
org.eclipse.emf.common;version='[2.12.0,2.12.1)',\
|
||||
org.eclipse.emf.ecore;version='[2.12.0,2.12.1)',\
|
||||
@ -23,6 +22,8 @@ Fragment-Host: org.openhab.core.model.rule.runtime
|
||||
org.glassfish.hk2.external.aopalliance-repackaged;version='[2.4.0,2.4.1)',\
|
||||
org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\
|
||||
org.osgi.service.event;version='[1.4.0,1.4.1)',\
|
||||
osgi.enroute.hamcrest.wrapper;version='[1.3.0,1.3.1)',\
|
||||
osgi.enroute.junit.wrapper;version='[4.12.0,4.12.1)',\
|
||||
ch.qos.logback.classic;version='[1.2.3,1.2.4)',\
|
||||
ch.qos.logback.core;version='[1.2.3,1.2.4)',\
|
||||
com.google.gson;version='[2.8.2,2.8.3)',\
|
||||
@ -30,11 +31,7 @@ Fragment-Host: org.openhab.core.model.rule.runtime
|
||||
org.apache.servicemix.specs.annotation-api-1.3;version='[1.3.0,1.3.1)',\
|
||||
org.apache.servicemix.specs.jaxb-api-2.2;version='[2.9.0,2.9.1)',\
|
||||
org.apache.servicemix.specs.stax-api-1.2;version='[2.9.0,2.9.1)',\
|
||||
org.apache.xbean.bundleutils;version='[4.12.0,4.12.1)',\
|
||||
org.apache.xbean.finder;version='[4.12.0,4.12.1)',\
|
||||
slf4j.api;version='[1.7.25,1.7.26)',\
|
||||
osgi.enroute.hamcrest.wrapper;version='[1.3.0,1.3.1)',\
|
||||
osgi.enroute.junit.wrapper;version='[4.12.0,4.12.1)',\
|
||||
org.apache.felix.configadmin;version='[1.9.8,1.9.9)',\
|
||||
org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\
|
||||
tec.uom.lib.uom-lib-common;version='[1.0.3,1.0.4)',\
|
||||
@ -48,12 +45,31 @@ Fragment-Host: org.openhab.core.model.rule.runtime
|
||||
org.eclipse.xtext;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtext.common.types;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtext.util;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtext.xbase.lib;version='[2.19.0,2.19.1)',\
|
||||
org.objectweb.asm;version='[7.1.0,7.1.1)',\
|
||||
jollyday;version='[0.5.8,0.5.9)',\
|
||||
org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
|
||||
org.apache.xbean.bundleutils;version='[4.12.0,4.12.1)',\
|
||||
org.apache.xbean.finder;version='[4.12.0,4.12.1)',\
|
||||
org.eclipse.xtext.xbase;version='[2.19.0,2.19.1)',\
|
||||
org.objectweb.asm.commons;version='[7.1.0,7.1.1)',\
|
||||
org.objectweb.asm.tree;version='[7.1.0,7.1.1)',\
|
||||
tec.uom.lib.uom-lib-common;version='[1.0.3,1.0.4)',\
|
||||
tec.uom.se;version='[1.0.10,1.0.11)',\
|
||||
com.google.guava;version='[27.1.0,27.1.1)',\
|
||||
com.google.guava.failureaccess;version='[1.0.1,1.0.2)',\
|
||||
io.github.classgraph;version='[4.8.35,4.8.36)',\
|
||||
org.eclipse.equinox.common;version='[3.10.400,3.10.401)',\
|
||||
org.eclipse.xtend.lib;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtend.lib.macro;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtext;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtext.common.types;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtext.util;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtext.xbase;version='[2.19.0,2.19.1)',\
|
||||
org.eclipse.xtext.xbase.lib;version='[2.19.0,2.19.1)',\
|
||||
org.objectweb.asm;version='[7.1.0,7.1.1)',\
|
||||
org.objectweb.asm.commons;version='[7.1.0,7.1.1)',\
|
||||
org.objectweb.asm.tree;version='[7.1.0,7.1.1)',\
|
||||
jollyday;version='[0.5.8,0.5.9)',\
|
||||
org.threeten.extra;version='[1.4.0,1.4.1)',\
|
||||
org.eclipse.jetty.client;version='[9.4.20,9.4.21)',\
|
||||
org.eclipse.jetty.http;version='[9.4.20,9.4.21)',\
|
||||
@ -82,16 +98,17 @@ Fragment-Host: org.openhab.core.model.rule.runtime
|
||||
org.openhab.core.model.item;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.persistence;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule.runtime;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule.tests;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.script;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.script.runtime;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.sitemap;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.persistence;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.scheduler;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.item.runtime;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.automation;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.automation.module.script;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule.runtime;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule.tests;version='[3.0.0,3.0.1)'
|
||||
-runblacklist: bnd.identity;id='jakarta.activation-api'
|
@ -0,0 +1,271 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.xtext.naming.QualifiedName;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.automation.RuleProvider;
|
||||
import org.openhab.core.automation.internal.module.handler.ChannelEventTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GenericCronTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GroupCommandTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GroupStateTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.ItemCommandTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.ItemStateTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.SystemTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.ThingStatusTriggerHandler;
|
||||
import org.openhab.core.automation.module.script.internal.handler.AbstractScriptModuleHandler;
|
||||
import org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.rule.jvmmodel.RulesRefresher;
|
||||
import org.openhab.core.model.rule.runtime.internal.DSLRuleProvider;
|
||||
import org.openhab.core.model.script.runtime.internal.engine.DSLScriptEngine;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
import org.openhab.core.test.java.JavaOSGiTest;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DSLRuleProviderTest extends JavaOSGiTest {
|
||||
|
||||
private static final String TESTMODEL_NAME = "dslruletest.rules";
|
||||
|
||||
private @NonNullByDefault({}) ModelRepository modelRepository;
|
||||
private @NonNullByDefault({}) DSLRuleProvider dslRuleProvider;
|
||||
private @NonNullByDefault({}) ReadyService readyService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
registerVolatileStorageService();
|
||||
|
||||
EventPublisher eventPublisher = event -> {
|
||||
};
|
||||
|
||||
registerService(eventPublisher);
|
||||
|
||||
dslRuleProvider = getService(RuleProvider.class, DSLRuleProvider.class);
|
||||
assertNotNull(dslRuleProvider);
|
||||
|
||||
modelRepository = getService(ModelRepository.class);
|
||||
assertThat(modelRepository, is(notNullValue()));
|
||||
modelRepository.removeModel(TESTMODEL_NAME);
|
||||
|
||||
readyService = getService(ReadyService.class);
|
||||
assertThat(readyService, is(notNullValue()));
|
||||
for (String id : Set.of("items", "things", "rules", RulesRefresher.RULES_REFRESH)) {
|
||||
readyService.markReady(new ReadyMarker("dsl", id));
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
modelRepository.removeModel(TESTMODEL_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRules() {
|
||||
Collection<Rule> rules = dslRuleProvider.getAll();
|
||||
assertThat(rules.size(), is(0));
|
||||
|
||||
String model = "rule RuleNumberOne\n" + //
|
||||
"when\n" + //
|
||||
" System started\n" + //
|
||||
"then\n" + //
|
||||
" logInfo('Test', 'Test')\n" + //
|
||||
"end\n\n" + //
|
||||
"rule 'Rule Number Two'\n" + //
|
||||
"when\n" + //
|
||||
" Item X changed\n" + //
|
||||
"then\n" + //
|
||||
" logInfo('Test', 'Test')\n" + //
|
||||
"end\n\n";
|
||||
|
||||
modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes()));
|
||||
Collection<Rule> actualRules = dslRuleProvider.getAll();
|
||||
|
||||
assertThat(actualRules.size(), is(2));
|
||||
|
||||
Iterator<Rule> it = actualRules.iterator();
|
||||
|
||||
Rule firstRule = it.next();
|
||||
|
||||
assertThat(firstRule.getUID(), is("dslruletest-1"));
|
||||
assertThat(firstRule.getName(), is("RuleNumberOne"));
|
||||
assertThat(firstRule.getTriggers().get(0).getTypeUID(), is(SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID));
|
||||
assertThat(firstRule.getActions().get(0).getTypeUID(), is(ScriptActionHandler.TYPE_ID));
|
||||
assertThat(firstRule.getActions().get(0).getConfiguration().get(AbstractScriptModuleHandler.SCRIPT_TYPE),
|
||||
is(DSLScriptEngine.MIMETYPE_OPENHAB_DSL_RULE));
|
||||
|
||||
Rule secondRule = it.next();
|
||||
|
||||
assertThat(secondRule.getUID(), is("dslruletest-2"));
|
||||
assertThat(secondRule.getName(), is("Rule Number Two"));
|
||||
assertThat(secondRule.getTriggers().get(0).getTypeUID(), is(ItemStateTriggerHandler.CHANGE_MODULE_TYPE_ID));
|
||||
assertThat(secondRule.getTriggers().get(0).getConfiguration().get(ItemStateTriggerHandler.CFG_ITEMNAME),
|
||||
is("X"));
|
||||
assertThat(secondRule.getActions().get(0).getTypeUID(), is(ScriptActionHandler.TYPE_ID));
|
||||
assertThat(secondRule.getActions().get(0).getConfiguration().get(AbstractScriptModuleHandler.SCRIPT_TYPE),
|
||||
is(DSLScriptEngine.MIMETYPE_OPENHAB_DSL_RULE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllTriggers() {
|
||||
Collection<Rule> rules = dslRuleProvider.getAll();
|
||||
assertThat(rules.size(), is(0));
|
||||
|
||||
String model = "rule RuleWithAllTriggers\n" + //
|
||||
"when\n" + //
|
||||
" System started or\n" + //
|
||||
" Time is noon or\n" + //
|
||||
" Time is midnight or\n" + //
|
||||
" Time cron \"0 0/1 * * * ?\" or\n" + //
|
||||
" Item X received command ON or\n" + //
|
||||
" Item Y received update \"A\" or\n" + //
|
||||
" Item Z changed from 1 to 2 or\n" + //
|
||||
" Member of G1 received command UP or\n" + //
|
||||
" Member of G2 received update \"10|°C\" or\n" + //
|
||||
" Member of G3 changed from PLAY to PAUSE or\n" + //
|
||||
" Thing \"T1\" received update OFFLINE or\n" + //
|
||||
" Thing \"T2\" changed from OFFLINE to ONLINE or\n" + //
|
||||
" Channel \"a:b:c:1\" triggered START\n" + //
|
||||
"then\n" + //
|
||||
" logInfo('Test', 'Test')\n" + //
|
||||
"end\n\n";
|
||||
|
||||
modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes()));
|
||||
Collection<Rule> actualRules = dslRuleProvider.getAll();
|
||||
|
||||
assertThat(actualRules.size(), is(1));
|
||||
|
||||
Iterator<Rule> it = actualRules.iterator();
|
||||
|
||||
Rule rule = it.next();
|
||||
|
||||
assertThat(rule.getUID(), is("dslruletest-1"));
|
||||
assertThat(rule.getName(), is("RuleWithAllTriggers"));
|
||||
assertThat(rule.getTriggers().size(), is(13));
|
||||
|
||||
assertThat(rule.getTriggers().get(0).getTypeUID(), is(SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(0).getConfiguration().get(SystemTriggerHandler.CFG_STARTLEVEL),
|
||||
is(new BigDecimal(20)));
|
||||
assertThat(rule.getTriggers().get(1).getTypeUID(), is(GenericCronTriggerHandler.MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(1).getConfiguration().get(GenericCronTriggerHandler.CFG_CRON_EXPRESSION),
|
||||
is("0 0 12 * * ?"));
|
||||
|
||||
assertThat(rule.getTriggers().get(2).getTypeUID(), is(GenericCronTriggerHandler.MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(2).getConfiguration().get(GenericCronTriggerHandler.CFG_CRON_EXPRESSION),
|
||||
is("0 0 0 * * ?"));
|
||||
|
||||
assertThat(rule.getTriggers().get(3).getTypeUID(), is(GenericCronTriggerHandler.MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(3).getConfiguration().get(GenericCronTriggerHandler.CFG_CRON_EXPRESSION),
|
||||
is("0 0/1 * * * ?"));
|
||||
|
||||
assertThat(rule.getTriggers().get(4).getTypeUID(), is(ItemCommandTriggerHandler.MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(4).getConfiguration().get(ItemCommandTriggerHandler.CFG_ITEMNAME), is("X"));
|
||||
assertThat(rule.getTriggers().get(4).getConfiguration().get(ItemCommandTriggerHandler.CFG_COMMAND), is("ON"));
|
||||
|
||||
assertThat(rule.getTriggers().get(5).getTypeUID(), is(ItemStateTriggerHandler.UPDATE_MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(5).getConfiguration().get(ItemStateTriggerHandler.CFG_ITEMNAME), is("Y"));
|
||||
assertThat(rule.getTriggers().get(5).getConfiguration().get(ItemStateTriggerHandler.CFG_STATE), is("A"));
|
||||
|
||||
assertThat(rule.getTriggers().get(6).getTypeUID(), is(ItemStateTriggerHandler.CHANGE_MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(6).getConfiguration().get(ItemStateTriggerHandler.CFG_ITEMNAME), is("Z"));
|
||||
assertThat(rule.getTriggers().get(6).getConfiguration().get(ItemStateTriggerHandler.CFG_PREVIOUS_STATE),
|
||||
is("1"));
|
||||
assertThat(rule.getTriggers().get(6).getConfiguration().get(ItemStateTriggerHandler.CFG_STATE), is("2"));
|
||||
|
||||
assertThat(rule.getTriggers().get(7).getTypeUID(), is(GroupCommandTriggerHandler.MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(7).getConfiguration().get(GroupCommandTriggerHandler.CFG_GROUPNAME),
|
||||
is("G1"));
|
||||
assertThat(rule.getTriggers().get(7).getConfiguration().get(GroupCommandTriggerHandler.CFG_COMMAND), is("UP"));
|
||||
|
||||
assertThat(rule.getTriggers().get(8).getTypeUID(), is(GroupStateTriggerHandler.UPDATE_MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(8).getConfiguration().get(GroupStateTriggerHandler.CFG_GROUPNAME), is("G2"));
|
||||
assertThat(rule.getTriggers().get(8).getConfiguration().get(GroupStateTriggerHandler.CFG_STATE), is("10|°C"));
|
||||
|
||||
assertThat(rule.getTriggers().get(9).getTypeUID(), is(GroupStateTriggerHandler.CHANGE_MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(9).getConfiguration().get(GroupStateTriggerHandler.CFG_GROUPNAME), is("G3"));
|
||||
assertThat(rule.getTriggers().get(9).getConfiguration().get(GroupStateTriggerHandler.CFG_PREVIOUS_STATE),
|
||||
is("PLAY"));
|
||||
assertThat(rule.getTriggers().get(9).getConfiguration().get(GroupStateTriggerHandler.CFG_STATE), is("PAUSE"));
|
||||
|
||||
assertThat(rule.getTriggers().get(10).getTypeUID(), is(ThingStatusTriggerHandler.UPDATE_MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(10).getConfiguration().get(ThingStatusTriggerHandler.CFG_THING_UID),
|
||||
is("T1"));
|
||||
assertThat(rule.getTriggers().get(10).getConfiguration().get(ThingStatusTriggerHandler.CFG_STATUS),
|
||||
is("OFFLINE"));
|
||||
|
||||
assertThat(rule.getTriggers().get(11).getTypeUID(), is(ThingStatusTriggerHandler.CHANGE_MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(11).getConfiguration().get(ThingStatusTriggerHandler.CFG_THING_UID),
|
||||
is("T2"));
|
||||
assertThat(rule.getTriggers().get(11).getConfiguration().get(ThingStatusTriggerHandler.CFG_PREVIOUS_STATUS),
|
||||
is("OFFLINE"));
|
||||
assertThat(rule.getTriggers().get(11).getConfiguration().get(ThingStatusTriggerHandler.CFG_STATUS),
|
||||
is("ONLINE"));
|
||||
|
||||
assertThat(rule.getTriggers().get(12).getTypeUID(), is(ChannelEventTriggerHandler.MODULE_TYPE_ID));
|
||||
assertThat(rule.getTriggers().get(12).getConfiguration().get(ChannelEventTriggerHandler.CFG_CHANNEL),
|
||||
is("a:b:c:1"));
|
||||
assertThat(rule.getTriggers().get(12).getConfiguration().get(ChannelEventTriggerHandler.CFG_CHANNEL_EVENT),
|
||||
is("START"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVars() {
|
||||
Collection<Rule> rules = dslRuleProvider.getAll();
|
||||
assertThat(rules.size(), is(0));
|
||||
|
||||
String model = "var x = 3 * 5\n\n" + //
|
||||
"rule FirstRule\n" + //
|
||||
"when\n" + //
|
||||
" System started\n" + //
|
||||
"then\n" + //
|
||||
" logInfo('Test', 'Test')\n" + //
|
||||
"end\n\n";
|
||||
|
||||
modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes()));
|
||||
Collection<Rule> actualRules = dslRuleProvider.getAll();
|
||||
|
||||
assertThat(actualRules.size(), is(1));
|
||||
|
||||
Iterator<Rule> it = actualRules.iterator();
|
||||
|
||||
Rule rule = it.next();
|
||||
|
||||
assertThat(rule.getUID(), is("dslruletest-1"));
|
||||
assertThat(rule.getName(), is("FirstRule"));
|
||||
assertThat(rule.getTriggers().size(), is(1));
|
||||
|
||||
IEvaluationContext context = dslRuleProvider.getContext("dslruletest");
|
||||
assertThat(context, is(notNullValue()));
|
||||
Object x = context.getValue(QualifiedName.create("x"));
|
||||
assertThat(x, is(15));
|
||||
}
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime.internal.engine;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventFilter;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.events.ItemCommandEvent;
|
||||
import org.openhab.core.items.events.ItemEventFactory;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.rule.rules.Rule;
|
||||
import org.openhab.core.model.rule.runtime.RuleEngine;
|
||||
import org.openhab.core.model.rule.runtime.internal.engine.RuleTriggerManager.TriggerTypes;
|
||||
import org.openhab.core.test.java.JavaOSGiTest;
|
||||
|
||||
/**
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
public class RuleExecutionTest extends JavaOSGiTest {
|
||||
|
||||
private static final String TESTMODEL_NAME = "testModel.rules";
|
||||
private ModelRepository modelRepository;
|
||||
private ItemRegistry itemRegistry;
|
||||
private EventPublisher eventPublisher;
|
||||
private Event resultEvent;
|
||||
|
||||
@NonNullByDefault
|
||||
private final EventSubscriber eventSubscriber = new EventSubscriber() {
|
||||
|
||||
@Override
|
||||
public void receive(Event e) {
|
||||
resultEvent = e;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSubscribedEventTypes() {
|
||||
return Collections.singleton(ItemCommandEvent.TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable EventFilter getEventFilter() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
registerVolatileStorageService();
|
||||
modelRepository = getService(ModelRepository.class);
|
||||
assertNotNull(modelRepository);
|
||||
itemRegistry = getService(ItemRegistry.class);
|
||||
assertNotNull(itemRegistry);
|
||||
eventPublisher = getService(EventPublisher.class);
|
||||
assertNotNull(eventPublisher);
|
||||
registerService(eventSubscriber, EventSubscriber.class.getName());
|
||||
|
||||
CoreItemFactory factory = new CoreItemFactory();
|
||||
GenericItem switchItem = factory.createItem("Switch", "TestSwitch");
|
||||
GenericItem resultItem = factory.createItem("Switch", "TestResult");
|
||||
GenericItem groupItem = new GroupItem("TestGroup");
|
||||
if (switchItem == null || resultItem == null) {
|
||||
throw new AssertionError("switchItem or resultItem is null");
|
||||
}
|
||||
switchItem.addGroupName(groupItem.getName());
|
||||
itemRegistry.add(switchItem);
|
||||
itemRegistry.add(resultItem);
|
||||
itemRegistry.add(groupItem);
|
||||
resultEvent = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateEventTrigger() throws Exception {
|
||||
String model = "rule Test " + //
|
||||
"when " + //
|
||||
" Item TestSwitch received update " + //
|
||||
"then " + //
|
||||
" TestResult.send(ON) " + //
|
||||
"end ";
|
||||
|
||||
assertExecutionWith(model, ItemEventFactory.createStateEvent("TestSwitch", OnOffType.ON), TriggerTypes.UPDATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangedEventTrigger() throws Exception {
|
||||
String model = "rule Test " + //
|
||||
"when " + //
|
||||
" Item TestSwitch changed " + //
|
||||
"then " + //
|
||||
" TestResult.send(ON) " + //
|
||||
"end ";
|
||||
|
||||
assertExecutionWith(model, ItemEventFactory.createStateEvent("TestSwitch", OnOffType.ON), TriggerTypes.CHANGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMemberUpdateEventTrigger() throws Exception {
|
||||
String model = "rule Test " + //
|
||||
"when " + //
|
||||
" Member of TestGroup received update " + //
|
||||
"then " + //
|
||||
" TestResult.send(ON) " + //
|
||||
"end ";
|
||||
|
||||
assertExecutionWith(model, ItemEventFactory.createStateEvent("TestSwitch", OnOffType.ON), TriggerTypes.UPDATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMemberChangedEventTrigger() throws Exception {
|
||||
String model = "rule Test " + //
|
||||
"when " + //
|
||||
" Member of TestGroup changed " + //
|
||||
"then " + //
|
||||
" TestResult.send(ON) " + //
|
||||
"end ";
|
||||
|
||||
assertExecutionWith(model, ItemEventFactory.createStateEvent("TestSwitch", OnOffType.ON), TriggerTypes.CHANGE);
|
||||
}
|
||||
|
||||
private <T> void assertExecutionWith(String model, Event event, TriggerTypes triggerType)
|
||||
throws InterruptedException {
|
||||
modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes()));
|
||||
RuleEngineImpl ruleEngine = (RuleEngineImpl) getService(RuleEngine.class);
|
||||
assertNotNull(ruleEngine);
|
||||
waitForAssert(() -> {
|
||||
assertFalse(ruleEngine.starting);
|
||||
Iterable<Rule> rules = ruleEngine.getTriggerManager().getRules(triggerType);
|
||||
Iterator<Rule> iterator = rules.iterator();
|
||||
assertTrue(iterator.hasNext());
|
||||
});
|
||||
|
||||
eventPublisher.post(event);
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertNotNull(resultEvent);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.rule.runtime.internal.engine;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.rule.rules.ChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.CommandEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.EventEmittedTrigger;
|
||||
import org.openhab.core.model.rule.rules.EventTrigger;
|
||||
import org.openhab.core.model.rule.rules.Rule;
|
||||
import org.openhab.core.model.rule.runtime.RuleEngine;
|
||||
import org.openhab.core.model.rule.runtime.internal.engine.RuleTriggerManager.TriggerTypes;
|
||||
import org.openhab.core.test.java.JavaOSGiTest;
|
||||
|
||||
/**
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
*/
|
||||
public class RuleTriggerTest extends JavaOSGiTest {
|
||||
|
||||
private static final String TESTMODEL_NAME = "testModel.rules";
|
||||
private ModelRepository modelRepository;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
modelRepository = getService(ModelRepository.class);
|
||||
assertNotNull(modelRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangedEventTriggerWithoutQuotes() throws Exception {
|
||||
String model = "rule\"State Machine Rule 1\" " + //
|
||||
"when " + //
|
||||
" Item test changed to world " + //
|
||||
"then " + //
|
||||
" logInfo(\"test says\", \"Boo!\") " + //
|
||||
"end ";
|
||||
|
||||
assertTriggerWith(model, TriggerTypes.CHANGE, ChangedEventTrigger.class,
|
||||
trigger -> trigger.getNewState().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangedEventTriggerWithQuotes() throws Exception {
|
||||
String model = "rule\"State Machine Rule 1\" " + //
|
||||
"when " + //
|
||||
" Item test changed to \"world\" " + //
|
||||
"then " + //
|
||||
" logInfo(\"test says\", \"Boo!\") " + //
|
||||
"end ";
|
||||
|
||||
assertTriggerWith(model, TriggerTypes.CHANGE, ChangedEventTrigger.class,
|
||||
trigger -> trigger.getNewState().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandEventTriggerWithoutQuotes() throws Exception {
|
||||
String model = "rule\"State Machine Rule 1\" " + //
|
||||
"when " + //
|
||||
" Item test received command world " + //
|
||||
"then " + //
|
||||
" logInfo(\"test says\", \"Boo!\") " + //
|
||||
"end ";
|
||||
|
||||
assertTriggerWith(model, TriggerTypes.COMMAND, CommandEventTrigger.class,
|
||||
trigger -> trigger.getCommand().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandEventTriggerWithQuotes() throws Exception {
|
||||
String model = "rule\"State Machine Rule 1\" " + //
|
||||
"when " + //
|
||||
" Item test received command \"world\" " + //
|
||||
"then " + //
|
||||
" logInfo(\"test says\", \"Boo!\") " + //
|
||||
"end ";
|
||||
|
||||
assertTriggerWith(model, TriggerTypes.COMMAND, CommandEventTrigger.class,
|
||||
trigger -> trigger.getCommand().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEventEmittedTriggerWithoutQuotes() throws Exception {
|
||||
String model = "rule\"State Machine Rule 1\" " + //
|
||||
"when " + //
|
||||
" Channel test triggered world " + //
|
||||
"then " + //
|
||||
" logInfo(\"test says\", \"Boo!\") " + //
|
||||
"end ";
|
||||
|
||||
assertTriggerWith(model, TriggerTypes.TRIGGER, EventEmittedTrigger.class,
|
||||
trigger -> trigger.getTrigger().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEventEmittedTriggerWithQuotes() throws Exception {
|
||||
String model = "rule\"State Machine Rule 1\" " + //
|
||||
"when " + //
|
||||
" Channel test triggered \"world\" " + //
|
||||
"then " + //
|
||||
" logInfo(\"test says\", \"Boo!\") " + //
|
||||
"end ";
|
||||
|
||||
assertTriggerWith(model, TriggerTypes.TRIGGER, EventEmittedTrigger.class,
|
||||
trigger -> trigger.getTrigger().getValue());
|
||||
}
|
||||
|
||||
private <T> void assertTriggerWith(String model, TriggerTypes triggerType, Class<T> triggerClass,
|
||||
Function<T, String> valueFunction) {
|
||||
modelRepository.addOrRefreshModel(TESTMODEL_NAME, new ByteArrayInputStream(model.getBytes()));
|
||||
|
||||
RuleEngineImpl ruleEngine = (RuleEngineImpl) getService(RuleEngine.class);
|
||||
assertNotNull(ruleEngine);
|
||||
RuleTriggerManager triggerManager = ruleEngine.getTriggerManager();
|
||||
waitForAssert(() -> {
|
||||
Iterable<Rule> rules = triggerManager.getRules(triggerType);
|
||||
Iterator<Rule> iterator = rules.iterator();
|
||||
assertTrue(iterator.hasNext());
|
||||
});
|
||||
Rule rule = triggerManager.getRules(triggerType).iterator().next();
|
||||
EventTrigger trigger = rule.getEventtrigger().get(0);
|
||||
assertTrue(triggerClass.isInstance(trigger));
|
||||
assertEquals("world", valueFunction.apply(triggerClass.cast(trigger)));
|
||||
}
|
||||
}
|
@ -88,9 +88,11 @@ Fragment-Host: org.openhab.core.model.script
|
||||
org.openhab.core.model.sitemap;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.persistence;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.scheduler;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.item.runtime;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.automation;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.automation.module.script;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing.runtime;version='[3.0.0,3.0.1)'
|
||||
|
@ -1,154 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.actions;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.model.script.scheduler.test.MockClosure.MockClosure0;
|
||||
import org.openhab.core.model.script.scheduler.test.MockClosure.MockClosure1;
|
||||
import org.openhab.core.model.script.scheduler.test.MockScheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.impl.SchedulerRepository;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
|
||||
/**
|
||||
* Tests for {@link ScriptExecution}
|
||||
*
|
||||
* @author Jon Evans - Initial contribution
|
||||
*/
|
||||
public class ScriptExecutionTest {
|
||||
private static MockScheduler scheduler;
|
||||
|
||||
/**
|
||||
* Set up Quartz to use our mock scheduler class
|
||||
*
|
||||
* @throws SchedulerException
|
||||
*/
|
||||
@BeforeClass
|
||||
public static void setUp() throws SchedulerException {
|
||||
scheduler = new MockScheduler();
|
||||
System.setProperty(StdSchedulerFactory.PROPERTIES_FILE, "../../../../../target/classes/quartz-test.properties");
|
||||
SchedulerRepository.getInstance().bind(scheduler);
|
||||
|
||||
assertThat(StdSchedulerFactory.getDefaultScheduler(), sameInstance(scheduler));
|
||||
}
|
||||
|
||||
private Timer createTimer(MockClosure0 closure) {
|
||||
Timer timer = ScriptExecution.createTimer(Instant.now(), closure);
|
||||
// The code in our mock closure needs access to the timer object
|
||||
closure.setTimer(timer);
|
||||
return timer;
|
||||
}
|
||||
|
||||
private Timer createTimer(Object arg, MockClosure1 closure) {
|
||||
Timer timer = ScriptExecution.createTimerWithArgument(Instant.now(), arg, closure);
|
||||
// The code in our mock closure needs access to the timer object
|
||||
closure.setTimer(timer);
|
||||
return timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a running Timer can be rescheduled from within its closure
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testRescheduleTimerDuringExecution() throws Exception {
|
||||
MockClosure0 closure = new MockClosure0(1);
|
||||
Timer t = createTimer(closure);
|
||||
|
||||
assertThat(t.isRunning(), is(equalTo(false)));
|
||||
assertThat(t.hasTerminated(), is(equalTo(false)));
|
||||
assertThat(closure.getApplyCount(), is(equalTo(0)));
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(1)));
|
||||
|
||||
// Run the scheduler twice
|
||||
scheduler.run();
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(1)));
|
||||
scheduler.run();
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(0)));
|
||||
|
||||
// Check that the Timer ran
|
||||
assertThat(closure.getApplyCount(), is(equalTo(2)));
|
||||
assertThat(t.isRunning(), is(equalTo(false)));
|
||||
assertThat(t.hasTerminated(), is(equalTo(true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a Timer can be rescheduled after it has terminated
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testRescheduleTimerAfterExecution() throws Exception {
|
||||
MockClosure0 closure = new MockClosure0();
|
||||
Timer t = createTimer(closure);
|
||||
|
||||
assertThat(t.isRunning(), is(equalTo(false)));
|
||||
assertThat(t.hasTerminated(), is(equalTo(false)));
|
||||
assertThat(closure.getApplyCount(), is(equalTo(0)));
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(1)));
|
||||
|
||||
// Run the scheduler
|
||||
scheduler.run();
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(0)));
|
||||
|
||||
// Check that the Timer ran
|
||||
assertThat(closure.getApplyCount(), is(equalTo(1)));
|
||||
assertThat(t.isRunning(), is(equalTo(false)));
|
||||
assertThat(t.hasTerminated(), is(equalTo(true)));
|
||||
|
||||
// Now try to reschedule the Timer to run again after 10ms
|
||||
boolean rescheduled = t.reschedule(Instant.now());
|
||||
assertThat(rescheduled, is(equalTo(true)));
|
||||
assertThat(t.hasTerminated(), is(equalTo(false)));
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(1)));
|
||||
|
||||
// Run the scheduler
|
||||
scheduler.run();
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(0)));
|
||||
|
||||
// Check that the Timer ran again
|
||||
assertThat(closure.getApplyCount(), is(equalTo(2)));
|
||||
assertThat(t.isRunning(), is(equalTo(false)));
|
||||
assertThat(t.hasTerminated(), is(equalTo(true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClosureWithOneArgument() throws Exception {
|
||||
Object arg = Integer.valueOf(42);
|
||||
MockClosure1 closure = new MockClosure1(arg, 1);
|
||||
Timer t = createTimer(arg, closure);
|
||||
|
||||
assertThat(t.isRunning(), is(equalTo(false)));
|
||||
assertThat(t.hasTerminated(), is(equalTo(false)));
|
||||
assertThat(closure.getApplyCount(), is(equalTo(0)));
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(1)));
|
||||
|
||||
// Run the scheduler twice
|
||||
scheduler.run();
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(1)));
|
||||
scheduler.run();
|
||||
assertThat(scheduler.getPendingJobCount(), is(equalTo(0)));
|
||||
|
||||
// Check that the Timer ran
|
||||
assertThat(closure.getApplyCount(), is(equalTo(2)));
|
||||
assertThat(t.isRunning(), is(equalTo(false)));
|
||||
assertThat(t.hasTerminated(), is(equalTo(true)));
|
||||
}
|
||||
}
|
@ -1,335 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.scheduler.test;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.quartz.Calendar;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.ListenerManager;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerContext;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.SchedulerMetaData;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.Trigger.TriggerState;
|
||||
import org.quartz.TriggerKey;
|
||||
import org.quartz.UnableToInterruptJobException;
|
||||
import org.quartz.impl.matchers.GroupMatcher;
|
||||
import org.quartz.spi.JobFactory;
|
||||
|
||||
/**
|
||||
* Abstract Quartz Scheduler.
|
||||
*
|
||||
* This hides all of the methods that we don't need to implement,
|
||||
* to make the {@see MockScheduler} class easier to read.
|
||||
*
|
||||
* @author Jon Evans - Initial contribution
|
||||
*/
|
||||
public abstract class AbstractScheduler implements Scheduler {
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() throws SchedulerException {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Anything below this point is unsupported, but we want to know
|
||||
// about it if tests break in the future (e.g. new version of
|
||||
// Quartz), so we throw UnsupportedException if any of these
|
||||
// methods are called
|
||||
|
||||
@Override
|
||||
public String getSchedulerName() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchedulerInstanceId() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerContext getContext() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDelayed(int seconds) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void standby() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInStandbyMode() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean waitForJobsToComplete) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerMetaData getMetaData() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JobExecutionContext> getCurrentlyExecutingJobs() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJobFactory(JobFactory factory) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenerManager getListenerManager() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date scheduleJob(Trigger trigger) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleJobs(Map<JobDetail, Set<? extends Trigger>> triggersAndJobs, boolean replace)
|
||||
throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleJob(JobDetail jobDetail, Set<? extends Trigger> triggersForJob, boolean replace)
|
||||
throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unscheduleJob(TriggerKey triggerKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unscheduleJobs(List<TriggerKey> triggerKeys) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addJob(JobDetail jobDetail, boolean replace) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addJob(JobDetail jobDetail, boolean replace, boolean storeNonDurableWhileAwaitingScheduling)
|
||||
throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteJob(JobKey jobKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteJobs(List<JobKey> jobKeys) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerJob(JobKey jobKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerJob(JobKey jobKey, JobDataMap data) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseJob(JobKey jobKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseJobs(GroupMatcher<JobKey> matcher) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseTrigger(TriggerKey triggerKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseTriggers(GroupMatcher<TriggerKey> matcher) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeJob(JobKey jobKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeJobs(GroupMatcher<JobKey> matcher) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeTrigger(TriggerKey triggerKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeTriggers(GroupMatcher<TriggerKey> matcher) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseAll() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeAll() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getJobGroupNames() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<JobKey> getJobKeys(GroupMatcher<JobKey> matcher) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Trigger> getTriggersOfJob(JobKey jobKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTriggerGroupNames() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TriggerKey> getTriggerKeys(GroupMatcher<TriggerKey> matcher) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPausedTriggerGroups() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobDetail getJobDetail(JobKey jobKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trigger getTrigger(TriggerKey triggerKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TriggerState getTriggerState(TriggerKey triggerKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers)
|
||||
throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteCalendar(String calName) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Calendar getCalendar(String calName) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCalendarNames() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interrupt(JobKey jobKey) throws UnableToInterruptJobException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interrupt(String fireInstanceId) throws UnableToInterruptJobException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkExists(JobKey jobKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkExists(TriggerKey triggerKey) throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() throws SchedulerException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.scheduler.test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.eclipse.xtext.xbase.lib.Procedures.Procedure0;
|
||||
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
|
||||
import org.openhab.core.model.script.actions.Timer;
|
||||
|
||||
/**
|
||||
* Mock implementations of Procedure0 and Procedure1
|
||||
*
|
||||
* @author Jon Evans - Initial contribution
|
||||
*/
|
||||
public class MockClosure {
|
||||
public static class MockClosure0 implements Procedure0 {
|
||||
int rescheduleCount;
|
||||
private int applyCount;
|
||||
private Timer timer;
|
||||
|
||||
public MockClosure0() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
public MockClosure0(int rescheduleCount) {
|
||||
this.rescheduleCount = rescheduleCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
this.applyCount++;
|
||||
// Timer#isRunning() should return true
|
||||
// from within the body of the closure
|
||||
assertThat(timer.isRunning(), is(equalTo(true)));
|
||||
|
||||
if (this.rescheduleCount > 0) {
|
||||
this.rescheduleCount--;
|
||||
boolean rescheduled = timer.reschedule(Instant.now());
|
||||
assertThat(rescheduled, is(equalTo(true)));
|
||||
}
|
||||
}
|
||||
|
||||
public void setTimer(Timer timer) {
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
public int getApplyCount() {
|
||||
return applyCount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MockClosure1 extends MockClosure0 implements Procedure1<Object> {
|
||||
private final Object expectedArgument;
|
||||
|
||||
public MockClosure1(Object expectedArgument) {
|
||||
this(expectedArgument, 0);
|
||||
}
|
||||
|
||||
public MockClosure1(Object expectedArgument, int rescheduleCount) {
|
||||
super(rescheduleCount);
|
||||
this.expectedArgument = expectedArgument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Object arg) {
|
||||
assertThat(arg, is(sameInstance(expectedArgument)));
|
||||
apply();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.core.model.script.scheduler.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.TriggerKey;
|
||||
import org.quartz.impl.JobExecutionContextImpl;
|
||||
import org.quartz.spi.OperableTrigger;
|
||||
import org.quartz.spi.TriggerFiredBundle;
|
||||
|
||||
/**
|
||||
* Mock version of the Quartz Scheduler.
|
||||
*
|
||||
* This is used to make the tests reliable, because
|
||||
* we don't need to wait for arbitrary lengths of time
|
||||
* for jobs to execute
|
||||
*
|
||||
* @author Jon Evans - Initial contribution
|
||||
*/
|
||||
public class MockScheduler extends AbstractScheduler {
|
||||
private final List<JobExecutionContext> currentlyExecutingJobs = new ArrayList<>();
|
||||
private final Map<Trigger, JobExecutionContext> jobs = new HashMap<>();
|
||||
private final Map<TriggerKey, Trigger> rescheduledJobs = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public String getSchedulerName() throws SchedulerException {
|
||||
return "MockScheduler";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JobExecutionContext> getCurrentlyExecutingJobs() throws SchedulerException {
|
||||
return currentlyExecutingJobs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkExists(JobKey jobKey) throws SchedulerException {
|
||||
for (JobExecutionContext context : jobs.values()) {
|
||||
if (jobKey.equals(context.getJobDetail().getKey())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException {
|
||||
TriggerFiredBundle tfb = new TriggerFiredBundle(jobDetail, (OperableTrigger) trigger, null, // cal
|
||||
false, // jobIsRecovering
|
||||
null, // fireTime
|
||||
null, // scheduledFireTime
|
||||
null, // prevFireTime
|
||||
null // nextFireTime)
|
||||
);
|
||||
JobExecutionContextImpl jec = new JobExecutionContextImpl(this, tfb, null);
|
||||
jobs.put(trigger, jec);
|
||||
return new Date();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date rescheduleJob(TriggerKey triggerKey, Trigger newTrigger) throws SchedulerException {
|
||||
for (Trigger trigger : jobs.keySet()) {
|
||||
if (triggerKey.equals(trigger.getKey())) {
|
||||
rescheduledJobs.put(triggerKey, newTrigger);
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Run" all of the jobs in the scheduler.
|
||||
*
|
||||
* NB this is a mock class. We ignore the time that the jobs are
|
||||
* actually scheduled for, and just run them all.
|
||||
*
|
||||
* @throws JobExecutionException
|
||||
* @throws IllegalAccessException
|
||||
* @throws InstantiationException
|
||||
*
|
||||
*/
|
||||
public void run() throws JobExecutionException, InstantiationException, IllegalAccessException {
|
||||
for (Entry<Trigger, JobExecutionContext> entry : jobs.entrySet()) {
|
||||
JobExecutionContext context = entry.getValue();
|
||||
try {
|
||||
currentlyExecutingJobs.add(context);
|
||||
Job job = context.getJobDetail().getJobClass().newInstance();
|
||||
job.execute(context);
|
||||
} finally {
|
||||
currentlyExecutingJobs.remove(context);
|
||||
jobs.remove(entry.getKey());
|
||||
Trigger newTrigger = rescheduledJobs.remove(context.getTrigger().getKey());
|
||||
if (newTrigger != null) {
|
||||
jobs.put(newTrigger, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getPendingJobCount() {
|
||||
return jobs.size();
|
||||
}
|
||||
}
|
@ -99,10 +99,11 @@ Fragment-Host: org.openhab.core.model.thing
|
||||
org.openhab.core.model.thing.tests;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.thing.testsupport;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.persistence;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.scheduler;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing.xml;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.automation;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.automation.module.script;version='[3.0.0,3.0.1)'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user