Replaced "classic" rule engine by a DSLRuleProvider for the NGRE (#1451)

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer 2020-06-14 10:36:25 +02:00 committed by GitHub
parent 1fddac192b
commit 173c93081d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1467 additions and 3415 deletions

View File

@ -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>

View File

@ -1,2 +0,0 @@
Bundle-SymbolicName: ${project.artifactId}
Bundle-Activator: org.openhab.core.internal.CoreActivator

View File

@ -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 {
}
}

View File

@ -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>

View File

@ -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 {
}

View File

@ -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());
}
}

View File

@ -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();
}
/**

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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,\

View File

@ -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);
}
}

View File

@ -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.
*/

View File

@ -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) {
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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);
}

View File

@ -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;
}
};
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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,\

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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
]
]
}
}

View File

@ -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());

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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"

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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));
}

View File

@ -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, () -> {

View File

@ -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);
}

View File

@ -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>

View File

@ -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:="(&amp;(feature=xtext)(version&gt;=2.19.0)(!(version&gt;=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>

View File

@ -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)'

View File

@ -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)'

View File

@ -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)'

View File

@ -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)'

View File

@ -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>

View File

@ -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'

View File

@ -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));
}
}

View File

@ -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);
});
}
}

View File

@ -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)));
}
}

View File

@ -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)'

View File

@ -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)));
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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)'