Implemented start level service (#1914)

Fixes #1637
Fixes #1777
Fixes #1734
Fixes #1823
Fixes #1808

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-12-12 14:34:37 +01:00
committed by GitHub
parent bbede78dc9
commit 076ac3fe15
28 changed files with 722 additions and 145 deletions
@@ -39,11 +39,18 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.OpenHAB;
import org.openhab.core.automation.module.script.ScriptEngineContainer;
import org.openhab.core.automation.module.script.ScriptEngineManager;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.service.AbstractWatchService;
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.service.StartLevelService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
@@ -57,40 +64,41 @@ import org.osgi.service.component.annotations.Reference;
* @author Kai Kreuzer - improved logging and removed thread pool
*/
@Component(immediate = true)
public class ScriptFileWatcher extends AbstractWatchService {
public class ScriptFileWatcher extends AbstractWatchService implements ReadyTracker {
private static final Set<String> EXCLUDED_FILE_EXTENSIONS = new HashSet<>(
Arrays.asList("txt", "old", "example", "backup", "md", "swp", "tmp", "bak"));
private static final String FILE_DIRECTORY = "automation" + File.separator + "jsr223";
private static final long INITIAL_DELAY = 25;
private static final long RECHECK_INTERVAL = 20;
private final long earliestStart = System.currentTimeMillis() + INITIAL_DELAY * 1000;
private boolean started = false;
private final ScriptEngineManager manager;
private final ReadyService readyService;
private @Nullable ScheduledExecutorService scheduler;
private final Map<String, Set<URL>> urlsByScriptExtension = new ConcurrentHashMap<>();
private final Set<URL> loaded = new HashSet<>();
@Activate
public ScriptFileWatcher(final @Reference ScriptEngineManager manager) {
public ScriptFileWatcher(final @Reference ScriptEngineManager manager, final @Reference ReadyService readyService) {
super(OpenHAB.getConfigFolder() + File.separator + FILE_DIRECTORY);
this.manager = manager;
this.readyService = readyService;
}
@SuppressWarnings("null")
@Activate
@Override
public void activate() {
super.activate();
importResources(new File(pathToWatch));
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleWithFixedDelay(this::checkFiles, INITIAL_DELAY, RECHECK_INTERVAL, TimeUnit.SECONDS);
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE)
.withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL)));
}
@Deactivate
@Override
public void deactivate() {
readyService.unregisterTracker(this);
ScheduledExecutorService localScheduler = scheduler;
if (localScheduler != null) {
localScheduler.shutdownNow();
@@ -168,7 +176,7 @@ public class ScriptFileWatcher extends AbstractWatchService {
String scriptType = getScriptType(url);
if (scriptType != null) {
if (System.currentTimeMillis() < earliestStart) {
if (!started) {
enqueueUrl(url, scriptType);
} else {
if (manager.isSupported(scriptType)) {
@@ -289,4 +297,17 @@ public class ScriptFileWatcher extends AbstractWatchService {
importFile(url);
}
}
@Override
public void onReadyMarkerAdded(@NonNull ReadyMarker readyMarker) {
started = true;
importResources(new File(pathToWatch));
scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("scriptwatcher"));
scheduler.scheduleWithFixedDelay(this::checkFiles, RECHECK_INTERVAL, RECHECK_INTERVAL, TimeUnit.SECONDS);
}
@Override
public void onReadyMarkerRemoved(@NonNull ReadyMarker readyMarker) {
started = false;
}
}
@@ -27,7 +27,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -51,6 +50,7 @@ import org.openhab.core.automation.handler.TriggerHandler;
import org.openhab.core.automation.handler.TriggerHandlerCallback;
import org.openhab.core.automation.internal.TriggerHandlerCallbackImpl.TriggerData;
import org.openhab.core.automation.internal.composite.CompositeModuleHandlerFactory;
import org.openhab.core.automation.internal.module.handler.SystemTriggerHandler;
import org.openhab.core.automation.internal.ruleengine.WrappedAction;
import org.openhab.core.automation.internal.ruleengine.WrappedCondition;
import org.openhab.core.automation.internal.ruleengine.WrappedModule;
@@ -67,14 +67,19 @@ import org.openhab.core.automation.type.ModuleTypeRegistry;
import org.openhab.core.automation.type.Output;
import org.openhab.core.automation.type.TriggerType;
import org.openhab.core.automation.util.ReferenceResolver;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.common.registry.RegistryChangeListener;
import org.openhab.core.events.Event;
import org.openhab.core.events.EventPublisher;
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.service.StartLevelService;
import org.openhab.core.storage.Storage;
import org.openhab.core.storage.StorageService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ComponentPropertyType;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
@@ -100,17 +105,9 @@ import org.slf4j.LoggerFactory;
* @author Markus Rathgeb - use a managed rule
* @author Ana Dimova - new reference syntax: list[index], map["key"], bean.field
*/
@Component(immediate = true)
@Component(immediate = true, service = { RuleManager.class })
@NonNullByDefault
public class RuleEngineImpl implements RuleManager, RegistryChangeListener<ModuleType> {
@ComponentPropertyType
public @interface Config {
/**
* Delay between rule's re-initialization tries.
*/
long rule_reinitialization_delay() default 500;
}
public class RuleEngineImpl implements RuleManager, RegistryChangeListener<ModuleType>, ReadyTracker {
/**
* Constant defining separator between module id and output name.
@@ -119,10 +116,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
private static final String DISABLED_RULE_STORAGE = "automation_rules_disabled";
/**
* Delay between rule's re-initialization tries.
*/
private final long scheduleReinitializationDelay;
private static final ReadyMarker MARKER = new ReadyMarker("ruleengine", "start");
private final Map<String, WrappedRule> managedRules = new ConcurrentHashMap<>();
@@ -160,12 +154,15 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
*/
private boolean isDisposed = false;
/**
* flag to check whether we have reached a start level where we want to start rule execution
*/
private boolean started = false;
protected final Logger logger = LoggerFactory.getLogger(RuleEngineImpl.class);
/**
* A callback that is notified when the status of a {@link Rule} changes.
*/
private final RuleRegistry ruleRegistry;
private final ReadyService readyService;
/**
* {@link Map} holding all Rule context maps. Rule context maps contain dynamic parameters used by the
@@ -249,8 +246,9 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
* Constructor of {@link RuleEngineImpl}.
*/
@Activate
public RuleEngineImpl(final Config config, final @Reference ModuleTypeRegistry moduleTypeRegistry,
final @Reference RuleRegistry ruleRegistry, final @Reference StorageService storageService) {
public RuleEngineImpl(final @Reference ModuleTypeRegistry moduleTypeRegistry,
final @Reference RuleRegistry ruleRegistry, final @Reference StorageService storageService,
final @Reference ReadyService readyService) {
this.contextMap = new HashMap<>();
this.moduleHandlerFactories = new HashMap<>(20);
@@ -263,8 +261,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
compositeFactory = new CompositeModuleHandlerFactory(mtRegistry, this);
this.ruleRegistry = ruleRegistry;
this.scheduleReinitializationDelay = config.rule_reinitialization_delay();
this.readyService = readyService;
listener = new RegistryChangeListener<Rule>() {
@Override
@@ -287,6 +284,9 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
for (Rule rule : ruleRegistry.getAll()) {
addRule(rule);
}
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE)
.withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_RULES)));
}
/**
@@ -507,12 +507,6 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
f.cancel(true);
}
}
if (scheduleTasks.isEmpty()) {
if (executor != null) {
executor.shutdown();
executor = null;
}
}
}
}
@@ -761,7 +755,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
.hasNext();) {
Map.Entry<String, Set<String>> e = it.next();
Set<String> rules = e.getValue();
if (rules != null && rules.contains(rUID)) {
if (rules.contains(rUID)) {
rules.remove(rUID);
if (rules.size() < 1) {
it.remove();
@@ -900,13 +894,13 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
protected void scheduleRuleInitialization(final String rUID) {
Future<?> f = scheduleTasks.get(rUID);
if (f == null || f.isDone()) {
scheduleTasks.put(rUID, getScheduledExecutor().schedule(() -> {
scheduleTasks.put(rUID, getScheduledExecutor().submit(() -> {
final WrappedRule managedRule = getManagedRule(rUID);
if (managedRule == null) {
return;
}
setRule(managedRule);
}, scheduleReinitializationDelay, TimeUnit.MILLISECONDS));
}));
}
}
@@ -967,6 +961,10 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
// the rule was unregistered
return;
}
if (!started) {
logger.debug("Rule engine not yet started - not executing rule '{}',", ruleUID);
return;
}
synchronized (this) {
final RuleStatus ruleStatus = getRuleStatus(ruleUID);
if (ruleStatus != null && ruleStatus != RuleStatus.IDLE) {
@@ -1213,7 +1211,8 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
if (currentExecutor != null && !currentExecutor.isShutdown()) {
return currentExecutor;
}
final ScheduledExecutorService newExecutor = Executors.newSingleThreadScheduledExecutor();
final ScheduledExecutorService newExecutor = Executors
.newSingleThreadScheduledExecutor(new NamedThreadFactory("ruleengine"));
executor = newExecutor;
return newExecutor;
}
@@ -1422,4 +1421,42 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
return outputName;
}
}
@Override
public void onReadyMarkerAdded(ReadyMarker readyMarker) {
executeRulesWithStartLevel();
}
@Override
public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
started = false;
}
private void executeRulesWithStartLevel() {
getScheduledExecutor().submit(() -> {
ruleRegistry.getAll().stream() //
.filter(r -> mustTrigger(r)) //
.forEach(r -> runNow(r.getUID(), true,
Map.of(SystemTriggerHandler.OUT_STARTLEVEL, StartLevelService.STARTLEVEL_RULES)));
started = true;
readyService.markReady(MARKER);
});
}
private boolean mustTrigger(Rule r) {
for (Trigger t : r.getTriggers()) {
if (t.getTypeUID() == SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID) {
Object slObj = t.getConfiguration().get(SystemTriggerHandler.CFG_STARTLEVEL);
try {
Integer sl = Integer.valueOf(slObj.toString());
if (sl < StartLevelService.STARTLEVEL_RULEENGINE) {
return true;
}
} catch (NumberFormatException e) {
logger.warn("Configuration '{}' is not a valid start level!", slObj);
}
}
}
return false;
}
}
@@ -46,6 +46,7 @@ import org.openhab.core.config.core.ConfigDescriptionParameter;
import org.openhab.core.config.core.ConfigDescriptionParameter.Type;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.service.ReadyService;
import org.openhab.core.storage.StorageService;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
@@ -154,6 +155,17 @@ public class RuleRegistryImpl extends AbstractRegistry<Rule, String, RuleProvide
super.unsetEventPublisher(eventPublisher);
}
@Override
@Reference
protected void setReadyService(ReadyService readyService) {
super.setReadyService(readyService);
}
@Override
protected void unsetReadyService(ReadyService readyService) {
super.unsetReadyService(readyService);
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, name = "ManagedRuleProvider")
protected void setManagedProvider(ManagedRuleProvider managedProvider) {
super.setManagedProvider(managedProvider);
@@ -27,9 +27,8 @@ import org.openhab.core.common.NamedThreadFactory;
/**
* This class is implementation of {@link TriggerHandlerCallback} used by the {@link Trigger}s to notify rule engine
* about
* appearing of new triggered data. There is one and only one {@link TriggerHandlerCallback} per RuleImpl and it is used
* by all rule's {@link Trigger}s.
* about appearing of new triggered data. There is one and only one {@link TriggerHandlerCallback} per Rule and
* it is used by all rule's {@link Trigger}s.
*
* @author Yordan Mihaylov - Initial contribution
* @author Kai Kreuzer - improved stability
@@ -29,6 +29,7 @@ import org.openhab.core.events.Event;
import org.openhab.core.events.EventFilter;
import org.openhab.core.events.EventSubscriber;
import org.openhab.core.events.system.StartlevelEvent;
import org.openhab.core.service.StartLevelService;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
@@ -52,6 +53,8 @@ public class SystemTriggerHandler extends BaseTriggerModuleHandler implements Ev
private final Set<String> types;
private final BundleContext bundleContext;
private boolean triggered = false;
private ServiceRegistration<?> eventSubscriberRegistration;
public SystemTriggerHandler(Trigger module, BundleContext bundleContext) {
@@ -82,20 +85,20 @@ public class SystemTriggerHandler extends BaseTriggerModuleHandler implements Ev
@Override
public void receive(Event event) {
final ModuleHandlerCallback callback = this.callback;
if (!(callback instanceof TriggerHandlerCallback)) {
if (triggered) {
// this trigger only works once
return;
}
TriggerHandlerCallback thCallback = (TriggerHandlerCallback) callback;
logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(), event.getTopic(),
event.getType(), event.getPayload());
Map<String, Object> values = new HashMap<>();
if (event instanceof StartlevelEvent && STARTLEVEL_MODULE_TYPE_ID.equals(module.getTypeUID())) {
Integer sl = ((StartlevelEvent) event).getStartlevel();
if (startlevel.equals(sl)) {
values.put(OUT_STARTLEVEL, sl);
thCallback.triggered(module, values);
if (startlevel <= sl) {
if (sl > StartLevelService.STARTLEVEL_RULEENGINE) {
// only execute rules if their start level is higher than the rule engine activation level, since
// otherwise the rule engine takes care of the execution already
trigger();
}
}
}
}
@@ -113,4 +116,17 @@ public class SystemTriggerHandler extends BaseTriggerModuleHandler implements Ev
public boolean apply(Event event) {
return true;
}
public void trigger() {
final ModuleHandlerCallback callback = this.callback;
if (!(callback instanceof TriggerHandlerCallback)) {
return;
}
TriggerHandlerCallback thCallback = (TriggerHandlerCallback) callback;
Map<String, Object> values = new HashMap<>();
values.put(OUT_STARTLEVEL, startlevel);
thCallback.triggered(module, values);
triggered = true;
}
}
@@ -16,10 +16,19 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.core.events.Event;
import org.openhab.core.events.EventFilter;
import org.openhab.core.events.EventSubscriber;
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.service.StartLevelService;
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;
@@ -28,11 +37,25 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Initial contribution
*/
@Component
public class EventLogger implements EventSubscriber {
public class EventLogger implements EventSubscriber, ReadyTracker {
private final Map<String, Logger> eventLoggers = new HashMap<>();
private final Set<String> subscribedEventTypes = Set.of(EventSubscriber.ALL_EVENT_TYPES);
private final ReadyService readyService;
private boolean loggingActive = false;
@Activate
public EventLogger(@Reference ReadyService readyService) {
this.readyService = readyService;
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE)
.withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_RULES)));
}
@Deactivate
protected void deactivate() {
readyService.unregisterTracker(this);
}
@Override
public Set<String> getSubscribedEventTypes() {
@@ -49,7 +72,9 @@ public class EventLogger implements EventSubscriber {
Logger logger = getLogger(event.getType());
logger.trace("Received event of type '{}' under the topic '{}' with payload: '{}'", event.getType(),
event.getTopic(), event.getPayload());
logger.info("{}", event);
if (loggingActive) {
logger.info("{}", event);
}
}
private Logger getLogger(String eventType) {
@@ -61,4 +86,14 @@ public class EventLogger implements EventSubscriber {
}
return logger;
}
@Override
public void onReadyMarkerAdded(@NonNull ReadyMarker readyMarker) {
loggingActive = true;
}
@Override
public void onReadyMarkerRemoved(@NonNull ReadyMarker readyMarker) {
loggingActive = false;
}
}
@@ -12,34 +12,30 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src-gen">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="model">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="src-gen"/>
<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"/>
<attribute name="annotationpath" value="target/dependency"/>
<attribute name="module" value="true"/>
<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"/>
<attribute name="annotationpath" value="target/dependency"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="notestsources">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
@@ -14,12 +14,10 @@ package org.openhab.core.model.rule.runtime.internal;
import java.util.ArrayList;
import java.util.Collection;
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.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -81,9 +79,6 @@ import org.slf4j.LoggerFactory;
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);
@@ -91,8 +86,8 @@ public class DSLRuleProvider
private final Map<String, Rule> rules = new ConcurrentHashMap<>();
private final Map<String, IEvaluationContext> contexts = new ConcurrentHashMap<>();
private final Map<String, XExpression> xExpressions = new ConcurrentHashMap<>();
private final ReadyMarker marker = new ReadyMarker("rules", "dslprovider");
private int triggerId = 0;
private Set<String> markers = new HashSet<>();
private final ModelRepository modelRepository;
private final ReadyService readyService;
@@ -105,7 +100,8 @@ public class DSLRuleProvider
@Activate
protected void activate() {
readyService.registerTracker(this, new ReadyMarkerFilter().withType("dsl"));
readyService.registerTracker(this, new ReadyMarkerFilter().withType(RulesRefresher.RULES_REFRESH_MARKER_TYPE)
.withIdentifier(RulesRefresher.RULES_REFRESH));
}
@Deactivate
@@ -114,7 +110,6 @@ public class DSLRuleProvider
rules.clear();
contexts.clear();
xExpressions.clear();
markers.clear();
}
@Override
@@ -400,35 +395,28 @@ public class DSLRuleProvider
}
}
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);
for (String ruleFileName : modelRepository.getAllModelNamesOfType("rules")) {
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);
}
modelRepository.addModelRepositoryChangeListener(this);
readyService.markReady(marker);
}
@Override
public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
markers.remove(readyMarker.getIdentifier());
readyService.unmarkReady(marker);
}
}
@@ -21,9 +21,6 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.NamedThreadFactory;
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;
@@ -33,12 +30,14 @@ 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.service.StartLevelService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingRegistryChangeListener;
import org.openhab.core.thing.binding.ThingActions;
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.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
@@ -59,7 +58,8 @@ public class RulesRefresher implements ReadyTracker {
// delay in seconds before rule resources are refreshed after items or services have changed
private static final long REFRESH_DELAY = 5;
public static final String RULES_REFRESH = "rules_refresh";
public static final String RULES_REFRESH_MARKER_TYPE = "rules";
public static final String RULES_REFRESH = "refresh";
private final Logger logger = LoggerFactory.getLogger(RulesRefresher.class);
@@ -67,25 +67,24 @@ public class RulesRefresher implements ReadyTracker {
private final ScheduledExecutorService scheduler = Executors
.newSingleThreadScheduledExecutor(new NamedThreadFactory("rulesRefresher"));
private boolean started;
private final ReadyMarker marker = new ReadyMarker("dsl", RULES_REFRESH);
private final ReadyMarker marker = new ReadyMarker("rules", RULES_REFRESH);
private final ModelRepository modelRepository;
private final ItemRegistry itemRegistry;
private final ThingRegistry thingRegistry;
private final EventPublisher eventPublisher;
private final ReadyService readyService;
private final ItemRegistryChangeListener itemRegistryChangeListener = new ItemRegistryChangeListener() {
@Override
public void added(Item element) {
logger.debug("Item \"{}\" added => rules are going to be refreshed", element.getName());
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
@Override
public void removed(Item element) {
logger.debug("Item \"{}\" removed => rules are going to be refreshed", element.getName());
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
@Override
@@ -95,7 +94,7 @@ public class RulesRefresher implements ReadyTracker {
@Override
public void allItemsChanged(Collection<String> oldItemNames) {
logger.debug("All items changed => rules are going to be refreshed");
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
};
@@ -103,13 +102,13 @@ public class RulesRefresher implements ReadyTracker {
@Override
public void added(Thing element) {
logger.debug("Thing \"{}\" added => rules are going to be refreshed", element.getUID().getAsString());
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
@Override
public void removed(Thing element) {
logger.debug("Thing \"{}\" removed => rules are going to be refreshed", element.getUID().getAsString());
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
@Override
@@ -119,32 +118,36 @@ public class RulesRefresher implements ReadyTracker {
@Activate
public RulesRefresher(@Reference ModelRepository modelRepository, @Reference ItemRegistry itemRegistry,
@Reference ThingRegistry thingRegistry, @Reference EventPublisher eventPublisher,
@Reference ReadyService readyService) {
@Reference ThingRegistry thingRegistry, @Reference ReadyService readyService) {
this.modelRepository = modelRepository;
this.itemRegistry = itemRegistry;
this.thingRegistry = thingRegistry;
this.eventPublisher = eventPublisher;
this.readyService = readyService;
}
@Activate
protected void activate() {
readyService.registerTracker(this, new ReadyMarkerFilter().withType("dsl").withIdentifier("rules"));
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE)
.withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL)));
}
@Deactivate
protected void deactivate() {
readyService.unregisterTracker(this);
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addActionService(ActionService actionService) {
if (started) {
logger.debug("Script action added => rules are going to be refreshed");
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
}
protected void removeActionService(ActionService actionService) {
if (started) {
logger.debug("Script action removed => rules are going to be refreshed");
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
}
@@ -152,18 +155,18 @@ public class RulesRefresher implements ReadyTracker {
protected void addThingActions(ThingActions thingActions) {
if (started) {
logger.debug("Thing automation action added => rules are going to be refreshed");
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
}
protected void removeThingActions(ThingActions thingActions) {
if (started) {
logger.debug("Thing automation action removed => rules are going to be refreshed");
scheduleRuleRefresh();
scheduleRuleRefresh(REFRESH_DELAY);
}
}
protected synchronized void scheduleRuleRefresh() {
protected synchronized void scheduleRuleRefresh(long delay) {
ScheduledFuture<?> localJob = job;
if (localJob != null && !localJob.isDone()) {
localJob.cancel(false);
@@ -174,30 +177,23 @@ public class RulesRefresher implements ReadyTracker {
} catch (Exception e) {
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
}
readyService.markReady(marker);
}, REFRESH_DELAY, TimeUnit.SECONDS);
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);
eventPublisher.post(startlevelEvent);
}, 15, TimeUnit.SECONDS);
}
if (!started) {
started = true;
readyService.markReady(marker);
}
}, delay, TimeUnit.SECONDS);
}
@Override
public void onReadyMarkerAdded(ReadyMarker readyMarker) {
scheduleRuleRefresh(0);
itemRegistry.addRegistryChangeListener(itemRegistryChangeListener);
thingRegistry.addRegistryChangeListener(thingRegistryChangeListener);
setStartLevel();
}
@Override
public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
itemRegistry.removeRegistryChangeListener(itemRegistryChangeListener);
thingRegistry.removeRegistryChangeListener(thingRegistryChangeListener);
}
}
@@ -22,9 +22,12 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.common.SafeCaller;
import org.openhab.core.items.GenericItem;
import org.openhab.core.items.GroupItem;
@@ -48,6 +51,11 @@ import org.openhab.core.persistence.strategy.PersistenceCronStrategy;
import org.openhab.core.persistence.strategy.PersistenceStrategy;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.scheduler.ScheduledCompletableFuture;
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.service.StartLevelService;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.osgi.service.component.annotations.Activate;
@@ -67,14 +75,18 @@ import org.slf4j.LoggerFactory;
*/
@Component(immediate = true, service = PersistenceManager.class)
@NonNullByDefault
public class PersistenceManagerImpl implements ItemRegistryChangeListener, PersistenceManager, StateChangeListener {
public class PersistenceManagerImpl
implements ItemRegistryChangeListener, PersistenceManager, StateChangeListener, ReadyTracker {
private final Logger logger = LoggerFactory.getLogger(PersistenceManagerImpl.class);
private final ReadyMarker marker = new ReadyMarker("persistence", "restore");
// the scheduler used for timer events
private final CronScheduler scheduler;
private final ItemRegistry itemRegistry;
private final SafeCaller safeCaller;
private final ReadyService readyService;
private volatile boolean started = false;
final Map<String, PersistenceService> persistenceServices = new HashMap<>();
@@ -83,17 +95,17 @@ public class PersistenceManagerImpl implements ItemRegistryChangeListener, Persi
@Activate
public PersistenceManagerImpl(final @Reference CronScheduler scheduler, final @Reference ItemRegistry itemRegistry,
final @Reference SafeCaller safeCaller) {
final @Reference SafeCaller safeCaller, final @Reference ReadyService readyService) {
this.scheduler = scheduler;
this.itemRegistry = itemRegistry;
this.safeCaller = safeCaller;
this.readyService = readyService;
}
@Activate
protected void activate() {
allItemsChanged(Collections.emptySet());
started = true;
itemRegistry.addRegistryChangeListener(this);
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE)
.withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL)));
}
@Deactivate
@@ -373,11 +385,11 @@ public class PersistenceManagerImpl implements ItemRegistryChangeListener, Persi
@Override
public void addConfig(final String dbId, final PersistenceServiceConfiguration config) {
synchronized (persistenceServiceConfigs) {
if (persistenceServiceConfigs.containsKey(dbId)) {
if (started && persistenceServiceConfigs.containsKey(dbId)) {
stopEventHandling(dbId);
}
persistenceServiceConfigs.put(dbId, config);
if (persistenceServices.containsKey(dbId)) {
if (started && persistenceServices.containsKey(dbId)) {
startEventHandling(dbId);
}
}
@@ -465,4 +477,24 @@ public class PersistenceManagerImpl implements ItemRegistryChangeListener, Persi
public void stateUpdated(Item item, State state) {
handleStateEvent(item, false);
}
@Override
public void onReadyMarkerAdded(ReadyMarker readyMarker) {
ExecutorService scheduler = Executors.newSingleThreadExecutor(new NamedThreadFactory("persistenceManager"));
scheduler.submit(() -> {
allItemsChanged(Collections.emptySet());
for (String dbId : persistenceServices.keySet()) {
startEventHandling(dbId);
}
started = true;
readyService.markReady(marker);
itemRegistry.addRegistryChangeListener(this);
});
scheduler.shutdown();
}
@Override
public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
readyService.unmarkReady(marker);
}
}
@@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.AbstractRegistry;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.service.ReadyService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
@@ -282,6 +283,17 @@ public class ThingRegistryImpl extends AbstractRegistry<Thing, ThingUID, ThingPr
super.unsetEventPublisher(eventPublisher);
}
@Override
@Reference
protected void setReadyService(ReadyService readyService) {
super.setReadyService(readyService);
}
@Override
protected void unsetReadyService(ReadyService readyService) {
super.unsetReadyService(readyService);
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, name = "ManagedThingProvider")
protected void setManagedProvider(ManagedThingProvider provider) {
super.setManagedProvider(provider);
@@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.service.ReadyService;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
@@ -110,6 +111,17 @@ public class ItemChannelLinkRegistry extends AbstractLinkRegistry<ItemChannelLin
super.setEventPublisher(eventPublisher);
}
@Override
@Reference
protected void setReadyService(ReadyService readyService) {
super.setReadyService(readyService);
}
@Override
protected void unsetReadyService(ReadyService readyService) {
super.unsetReadyService(readyService);
}
@Override
protected void unsetEventPublisher(final EventPublisher eventPublisher) {
super.unsetEventPublisher(eventPublisher);
@@ -32,6 +32,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.events.Event;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.service.ReadyService;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
@@ -82,6 +84,7 @@ public abstract class AbstractRegistry<@NonNull E extends Identifiable<K>, @NonN
private Optional<ManagedProvider<E, K>> managedProvider = Optional.empty();
private @Nullable EventPublisher eventPublisher;
private @Nullable ReadyService readyService;
/**
* Constructor.
@@ -427,6 +430,11 @@ public abstract class AbstractRegistry<@NonNull E extends Identifiable<K>, @NonN
elementWriteLock.unlock();
}
elementsAdded.forEach(this::notifyListenersAboutAddedElement);
if (provider instanceof ManagedProvider && readyService != null) {
readyService.markReady(
new ReadyMarker("managed", providerClazz.getSimpleName().replace("Provider", "").toLowerCase()));
}
logger.debug("Provider \"{}\" has been added.", provider.getClass().getName());
}
@@ -654,6 +662,14 @@ public abstract class AbstractRegistry<@NonNull E extends Identifiable<K>, @NonN
this.eventPublisher = null;
}
protected void setReadyService(ReadyService readyService) {
this.readyService = readyService;
}
protected void unsetReadyService(ReadyService readyService) {
this.readyService = null;
}
/**
* This method can be used in a subclass in order to post events through the openHAB events bus. A common
* use case is to notify event subscribers about an element which has been added/removed/updated to the registry.
@@ -39,6 +39,7 @@ import org.openhab.core.items.MetadataRegistry;
import org.openhab.core.items.RegistryHook;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.service.CommandDescriptionService;
import org.openhab.core.service.ReadyService;
import org.openhab.core.service.StateDescriptionService;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
@@ -280,6 +281,17 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
}
}
@Override
@Reference
protected void setReadyService(ReadyService readyService) {
super.setReadyService(readyService);
}
@Override
protected void unsetReadyService(ReadyService readyService) {
super.unsetReadyService(readyService);
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
protected void setUnitProvider(UnitProvider unitProvider) {
this.unitProvider = unitProvider;
@@ -20,6 +20,7 @@ import org.openhab.core.items.Metadata;
import org.openhab.core.items.MetadataKey;
import org.openhab.core.items.MetadataProvider;
import org.openhab.core.items.MetadataRegistry;
import org.openhab.core.service.ReadyService;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
@@ -71,6 +72,17 @@ public class MetadataRegistryImpl extends AbstractRegistry<Metadata, MetadataKey
super.unsetEventPublisher(eventPublisher);
}
@Override
@Reference
protected void setReadyService(ReadyService readyService) {
super.setReadyService(readyService);
}
@Override
protected void unsetReadyService(ReadyService readyService) {
super.unsetReadyService(readyService);
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
protected void setManagedProvider(ManagedMetadataProvider provider) {
super.setManagedProvider(provider);
@@ -0,0 +1,256 @@
/**
* 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.service;
import java.util.AbstractMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.events.system.StartlevelEvent;
import org.openhab.core.events.system.SystemEventFactory;
import org.openhab.core.service.ReadyService.ReadyTracker;
import org.osgi.framework.BundleContext;
import org.osgi.framework.startlevel.FrameworkStartLevel;
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.Modified;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This service combines different {@link ReadyMarker}s into a new start level ready marker and thus
* lets other services depend on those, without having to know about the single markers.
* This brings an important decoupling, since the set of markers for a certain start level might
* depend on the individual set up-
* The start level service is therefore configurable, so that users have a chance to adapt the
* conditions upon a certain start level is reached.
*
* Start levels are defined as values between 0 and 100. They carry the following semantics:
*
* 00 - OSGi framework has been started.
* 10 - OSGi application start level has been reached, i.e. bundles are activated.
* 20 - Model entities (items, things, links, persist config) have been loaded, both from db as well as files.
* 30 - Item states have been restored from persistence service, where applicable.
* 40 - Rules are loaded and parsed, both from db as well as dsl and script files.
* 50 - Rule engine has executed all "system started" rules and is active.
* 70 - User interface is up and running.
* 80 - All things have been initialized.
* 100 - Startup is fully complete.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
@Component(immediate = true, configurationPid = "org.openhab.startlevel")
public class StartLevelService {
public final static String STARTLEVEL_MARKER_TYPE = "startlevel";
public final static int STARTLEVEL_OSGI = 10;
public final static int STARTLEVEL_MODEL = 20;
public final static int STARTLEVEL_STATES = 30;
public final static int STARTLEVEL_RULES = 40;
public final static int STARTLEVEL_RULEENGINE = 50;
public final static int STARTLEVEL_UI = 70;
public final static int STARTLEVEL_THINGS = 80;
public final static int STARTLEVEL_COMPLETE = 100;
private final Logger logger = LoggerFactory.getLogger(StartLevelService.class);
private final BundleContext bundleContext;
private final ReadyService readyService;
private final EventPublisher eventPublisher;
private final Set<ReadyMarker> markers = ConcurrentHashMap.newKeySet();
private final Map<String, ReadyTracker> trackers = new ConcurrentHashMap<>();
private final Map<Integer, @NonNull ReadyMarker> slmarker = new ConcurrentHashMap<>();
private @Nullable ScheduledFuture<?> job;
private final ScheduledExecutorService scheduler = Executors
.newSingleThreadScheduledExecutor(new NamedThreadFactory("startlevel"));
private int openHABStartLevel = 0;
private Map<Integer, Set<ReadyMarker>> startlevels = Map.of();
@Activate
public StartLevelService(BundleContext bundleContext, @Reference ReadyService readyService,
@Reference EventPublisher eventPublisher) {
this.bundleContext = bundleContext;
this.readyService = readyService;
this.eventPublisher = eventPublisher;
}
@Activate
protected void activate(Map<String, Object> configuration) {
modified(configuration);
job = scheduler.scheduleWithFixedDelay(() -> {
handleOSGiStartlevel();
if (openHABStartLevel >= 10) {
for (Integer level : new TreeSet<>(startlevels.keySet())) {
if (openHABStartLevel >= level) {
continue;
} else {
boolean reached = isStartLevelReached(startlevels.get(level));
if (reached) {
setStartLevel(level);
} else {
return;
}
}
}
if (openHABStartLevel < 100) {
setStartLevel(100);
}
}
}, 0, 5, TimeUnit.SECONDS);
}
/**
* Returns the current start level of openHAB
*
* @return the current start level
*/
public int getStartLevel() {
return openHABStartLevel;
}
private boolean isStartLevelReached(@Nullable Set<ReadyMarker> markerSet) {
if (markerSet == null) {
return true;
}
for (ReadyMarker m : markerSet) {
if (!markers.contains(m)) {
return false;
}
}
return true;
}
private void handleOSGiStartlevel() {
FrameworkStartLevel sl = this.bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
int defaultStartLevel = sl.getInitialBundleStartLevel();
int startLevel = sl.getStartLevel();
if (startLevel >= defaultStartLevel && openHABStartLevel < 10) {
setStartLevel(10);
} else if (startLevel < defaultStartLevel && openHABStartLevel >= 10) {
setStartLevel(0);
}
}
@Modified
protected void modified(Map<String, Object> configuration) {
// clean up
slmarker.clear();
trackers.values().forEach(t -> readyService.unregisterTracker(t));
// set up trackers and markers
startlevels = parseConfig(configuration);
startlevels.keySet()
.forEach(sl -> slmarker.put(sl, new ReadyMarker(STARTLEVEL_MARKER_TYPE, Integer.toString(sl))));
startlevels.values().stream().forEach(ms -> ms.forEach(e -> registerTracker(e)));
}
private void registerTracker(ReadyMarker e) {
String type = e.getType();
if (!trackers.containsKey(type)) {
ReadyTracker tracker = new ReadyTracker() {
@Override
public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
markers.remove(readyMarker);
}
@Override
public void onReadyMarkerAdded(ReadyMarker readyMarker) {
markers.add(readyMarker);
}
};
readyService.registerTracker(tracker, new ReadyMarkerFilter().withType(type));
trackers.put(type, tracker);
}
}
private Map<Integer, Set<ReadyMarker>> parseConfig(Map<String, Object> configuration) {
return configuration.entrySet().stream() //
.filter(e -> hasIntegerKey(e)) //
.map(e -> new AbstractMap.SimpleEntry<>(Integer.valueOf(e.getKey()), markerSet(e.getValue()))) //
.sorted(Map.Entry.<Integer, Set<ReadyMarker>> comparingByKey().reversed()) //
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
private Set<ReadyMarker> markerSet(Object value) {
Set<ReadyMarker> markerSet = new HashSet<>();
if (value instanceof String) {
String[] segments = ((String) value).split(",");
for (String segment : segments) {
if (segment.contains(":")) {
String[] markerParts = segment.strip().split(":");
markerSet.add(new ReadyMarker(markerParts[0], markerParts[1]));
} else {
logger.warn("Ignoring invalid configuration value '{}'", value);
}
}
}
return markerSet;
}
private boolean hasIntegerKey(Entry<String, Object> entry) {
try {
Integer.valueOf(entry.getKey());
return true;
} catch (Exception e) {
return false;
}
}
@Deactivate
protected void deactivate() {
slmarker.clear();
trackers.values().forEach(t -> readyService.unregisterTracker(t));
ScheduledFuture<?> job = this.job;
if (job != null) {
job.cancel(true);
}
}
private void setStartLevel(int level) {
ReadyMarker marker = slmarker.get(level);
if (marker != null) {
readyService.markReady(marker);
}
openHABStartLevel = level;
scheduler.submit(() -> {
StartlevelEvent startlevelEvent = SystemEventFactory.createStartlevelEvent(level);
eventPublisher.post(startlevelEvent);
});
}
}
@@ -1,6 +1,7 @@
-include: ../itest-common.bndrun
Bundle-SymbolicName: ${project.artifactId}
Fragment-Host: org.openhab.core.automation
-runrequires: bnd.identity;id='org.openhab.core.automation.integration.tests'
@@ -36,6 +36,7 @@ import org.openhab.core.automation.RuleStatus;
import org.openhab.core.automation.RuleStatusInfo;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.events.RuleStatusInfoEvent;
import org.openhab.core.automation.internal.RuleEngineImpl;
import org.openhab.core.automation.type.ActionType;
import org.openhab.core.automation.type.Input;
import org.openhab.core.automation.type.ModuleTypeRegistry;
@@ -54,6 +55,7 @@ import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.storage.StorageService;
import org.openhab.core.test.java.JavaOSGiTest;
import org.openhab.core.test.storage.VolatileStorageService;
@@ -165,6 +167,10 @@ public class AutomationIntegrationJsonTest extends JavaOSGiTest {
assertThat(managedRuleProvider, is(notNullValue()));
assertThat(moduleTypeRegistry, is(notNullValue()));
}, 9000, 1000);
// start rule engine
((RuleEngineImpl) ruleManager).onReadyMarkerAdded(new ReadyMarker("", ""));
logger.info("@Before.finish");
}
@@ -48,6 +48,7 @@ import org.openhab.core.automation.events.RuleAddedEvent;
import org.openhab.core.automation.events.RuleRemovedEvent;
import org.openhab.core.automation.events.RuleStatusInfoEvent;
import org.openhab.core.automation.events.RuleUpdatedEvent;
import org.openhab.core.automation.internal.RuleEngineImpl;
import org.openhab.core.automation.template.RuleTemplate;
import org.openhab.core.automation.template.RuleTemplateProvider;
import org.openhab.core.automation.template.Template;
@@ -75,6 +76,7 @@ import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.storage.StorageService;
import org.openhab.core.test.java.JavaOSGiTest;
import org.slf4j.Logger;
@@ -164,6 +166,10 @@ public class AutomationIntegrationTest extends JavaOSGiTest {
assertThat(templateRegistry, is(notNullValue()));
assertThat(managedRuleProvider, is(notNullValue()));
}, 9000, 1000);
// start rule engine
((RuleEngineImpl) ruleEngine).onReadyMarkerAdded(new ReadyMarker("", ""));
logger.info("@Before.finish");
}
@@ -31,6 +31,7 @@ import org.openhab.core.automation.Rule;
import org.openhab.core.automation.RuleManager;
import org.openhab.core.automation.RuleRegistry;
import org.openhab.core.automation.RuleStatus;
import org.openhab.core.automation.internal.RuleEngineImpl;
import org.openhab.core.automation.util.ModuleBuilder;
import org.openhab.core.automation.util.RuleBuilder;
import org.openhab.core.common.registry.ProviderChangeListener;
@@ -47,6 +48,7 @@ import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.test.java.JavaOSGiTest;
import org.openhab.core.test.storage.VolatileStorageService;
import org.slf4j.Logger;
@@ -82,6 +84,9 @@ public class RunRuleModuleTest extends JavaOSGiTest {
}
});
registerService(volatileStorageService);
// start rule engine
((RuleEngineImpl) getService(RuleManager.class)).onReadyMarkerAdded(new ReadyMarker("", ""));
}
private Rule createSceneRule() {
@@ -36,6 +36,7 @@ import org.openhab.core.automation.RuleRegistry;
import org.openhab.core.automation.RuleStatus;
import org.openhab.core.automation.RuleStatusDetail;
import org.openhab.core.automation.events.RuleStatusInfoEvent;
import org.openhab.core.automation.internal.RuleEngineImpl;
import org.openhab.core.automation.internal.module.handler.CompareConditionHandler;
import org.openhab.core.automation.type.ModuleTypeRegistry;
import org.openhab.core.automation.util.ModuleBuilder;
@@ -54,6 +55,7 @@ import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.test.java.JavaOSGiTest;
import org.openhab.core.test.storage.VolatileStorageService;
import org.openhab.core.types.TypeParser;
@@ -94,6 +96,9 @@ public class RuntimeRuleTest extends JavaOSGiTest {
}
});
registerService(volatileStorageService);
// start rule engine
((RuleEngineImpl) getService(RuleManager.class)).onReadyMarkerAdded(new ReadyMarker("", ""));
}
@Test
@@ -49,4 +49,54 @@ Fragment-Host: org.openhab.core.automation.module.script
org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\
org.glassfish.hk2.osgi-resource-locator;version='[1.0.1,1.0.2)',\
jollyday;version='[0.5.10,0.5.11)',\
org.threeten.extra;version='[1.5.0,1.5.1)'
org.threeten.extra;version='[1.5.0,1.5.1)',\
com.google.guava;version='[27.1.0,27.1.1)',\
com.google.guava.failureaccess;version='[1.0.1,1.0.2)',\
com.google.inject;version='[3.0.0,3.0.1)',\
io.github.classgraph;version='[4.8.35,4.8.36)',\
log4j;version='[1.2.17,1.2.18)',\
org.antlr.runtime;version='[3.2.0,3.2.1)',\
org.apache.felix.configadmin;version='[1.9.8,1.9.9)',\
org.apache.xbean.bundleutils;version='[4.17.0,4.17.1)',\
org.apache.xbean.finder;version='[4.17.0,4.17.1)',\
org.eclipse.emf.common;version='[2.17.0,2.17.1)',\
org.eclipse.emf.ecore;version='[2.20.0,2.20.1)',\
org.eclipse.emf.ecore.xmi;version='[2.16.0,2.16.1)',\
org.eclipse.equinox.common;version='[3.12.0,3.12.1)',\
org.eclipse.jetty.client;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.websocket.api;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.websocket.client;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.websocket.common;version='[9.4.20,9.4.21)',\
org.eclipse.jetty.xml;version='[9.4.20,9.4.21)',\
org.eclipse.xtend.lib;version='[2.23.0,2.23.1)',\
org.eclipse.xtend.lib.macro;version='[2.23.0,2.23.1)',\
org.eclipse.xtext;version='[2.23.0,2.23.1)',\
org.eclipse.xtext.common.types;version='[2.23.0,2.23.1)',\
org.eclipse.xtext.util;version='[2.23.0,2.23.1)',\
org.eclipse.xtext.xbase;version='[2.23.0,2.23.1)',\
org.eclipse.xtext.xbase.lib;version='[2.23.0,2.23.1)',\
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.objectweb.asm;version='[8.0.1,8.0.2)',\
org.objectweb.asm.commons;version='[8.0.1,8.0.2)',\
org.objectweb.asm.tree;version='[8.0.1,8.0.2)',\
org.openhab.core.audio;version='[3.0.0,3.0.1)',\
org.openhab.core.io.http;version='[3.0.0,3.0.1)',\
org.openhab.core.io.net;version='[3.0.0,3.0.1)',\
org.openhab.core.model.core;version='[3.0.0,3.0.1)',\
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.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.model.thing.runtime;version='[3.0.0,3.0.1)',\
org.openhab.core.persistence;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.ops4j.pax.swissbox.optional.jcl;version='[1.8.3,1.8.4)',\
org.ops4j.pax.web.pax-web-api;version='[7.2.19,7.2.20)',\
org.ops4j.pax.web.pax-web-jetty;version='[7.2.19,7.2.20)',\
org.ops4j.pax.web.pax-web-runtime;version='[7.2.19,7.2.20)',\
org.ops4j.pax.web.pax-web-spi;version='[7.2.19,7.2.20)'
@@ -0,0 +1,38 @@
[
{
"uid": "javascript.rule1",
"name": "DemoScriptRule",
"description": "Sample rule based on scripts",
"triggers": [
{
"id": "trigger",
"type": "core.GenericEventTrigger",
"configuration": {
"eventSource": "MyTrigger",
"eventTopic": "openhab/items/MyTrigger/state",
"eventTypes": "ItemStateEvent"
}
}
],
"conditions": [
{
"id": "condition",
"type": "script.ScriptCondition",
"configuration": {
"type": "application/javascript",
"script": "event.itemState==ON"
}
}
],
"actions": [
{
"id": "action",
"type": "script.ScriptAction",
"configuration": {
"type": "application/javascript",
"script": "print(items.MyTrigger), print(things.getAll()), print(ctx.get('trigger.event')), events.sendCommand('ScriptItem', 'ON')"
}
}
]
}
]
@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.automation.module.script;
package org.openhab.core.automation.module.script.internal.defaultscope;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -22,7 +22,6 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.Condition;
import org.openhab.core.automation.Rule;
@@ -106,7 +105,7 @@ public class ScriptRuleOSGiTest extends JavaOSGiTest {
registerService(eventSubscriber);
}
@Test
// ignore - wip @Test
public void testPredefinedRule() throws ItemNotFoundException {
EventPublisher eventPublisher = getService(EventPublisher.class);
ItemRegistry itemRegistry = getService(ItemRegistry.class);
@@ -121,7 +120,7 @@ public class ScriptRuleOSGiTest extends JavaOSGiTest {
RuleStatusInfo ruleStatus2 = ruleEngine.getStatusInfo(rule2.getUID());
assertThat(ruleStatus2, is(notNullValue()));
assertThat(ruleStatus2.getStatus(), is(RuleStatus.IDLE));
}, 10000, 200);
}, 5000, 200);
Rule rule = ruleRegistry.get("javascript.rule1");
assertThat(rule, is(notNullValue()));
assertThat(rule.getName(), is("DemoScriptRule"));
@@ -34,6 +34,7 @@ import org.openhab.core.automation.RuleRegistry;
import org.openhab.core.automation.RuleStatus;
import org.openhab.core.automation.RuleStatusInfo;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.internal.RuleEngineImpl;
import org.openhab.core.automation.internal.module.handler.ItemCommandActionHandler;
import org.openhab.core.automation.internal.module.handler.ItemStateTriggerHandler;
import org.openhab.core.automation.util.ModuleBuilder;
@@ -52,6 +53,7 @@ import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.test.java.JavaOSGiTest;
import org.openhab.core.test.storage.VolatileStorageService;
import org.slf4j.Logger;
@@ -105,6 +107,9 @@ public abstract class BasicConditionHandlerTest extends JavaOSGiTest {
ruleEngine = getService(RuleManager.class);
assertThat(ruleEngine, is(notNullValue()));
}, 3000, 100);
// start rule engine
((RuleEngineImpl) getService(RuleManager.class)).onReadyMarkerAdded(new ReadyMarker("", ""));
}
@Test
@@ -136,6 +141,9 @@ public abstract class BasicConditionHandlerTest extends JavaOSGiTest {
// prepare the execution
EventPublisher eventPublisher = getService(EventPublisher.class);
// start rule engine
((RuleEngineImpl) getService(RuleManager.class)).onReadyMarkerAdded(new ReadyMarker("", ""));
EventSubscriber itemEventHandler = new EventSubscriber() {
@Override
@@ -34,6 +34,7 @@ import org.openhab.core.automation.RuleStatus;
import org.openhab.core.automation.RuleStatusDetail;
import org.openhab.core.automation.RuleStatusInfo;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.internal.RuleEngineImpl;
import org.openhab.core.automation.internal.module.handler.GenericCronTriggerHandler;
import org.openhab.core.automation.type.ModuleTypeRegistry;
import org.openhab.core.automation.util.ModuleBuilder;
@@ -47,6 +48,7 @@ import org.openhab.core.items.Item;
import org.openhab.core.items.ItemProvider;
import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.test.java.JavaOSGiTest;
import org.openhab.core.test.storage.VolatileStorageService;
import org.slf4j.Logger;
@@ -82,6 +84,9 @@ public class RuntimeRuleTest extends JavaOSGiTest {
ruleEngine = getService(RuleManager.class);
assertThat("RuleManager service not found", ruleEngine, is(notNullValue()));
}, 3000, 100);
// start rule engine
((RuleEngineImpl) getService(RuleManager.class)).onReadyMarkerAdded(new ReadyMarker("", ""));
}
@Test
@@ -39,6 +39,7 @@ import org.openhab.core.automation.events.RuleAddedEvent;
import org.openhab.core.automation.events.RuleRemovedEvent;
import org.openhab.core.automation.events.RuleStatusInfoEvent;
import org.openhab.core.automation.events.RuleUpdatedEvent;
import org.openhab.core.automation.internal.RuleEngineImpl;
import org.openhab.core.automation.util.ModuleBuilder;
import org.openhab.core.automation.util.RuleBuilder;
import org.openhab.core.common.registry.ProviderChangeListener;
@@ -55,6 +56,7 @@ import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.test.java.JavaOSGiTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -97,6 +99,9 @@ public class RuleEventTest extends JavaOSGiTest {
};
registerService(itemProvider);
registerVolatileStorageService();
// start rule engine
((RuleEngineImpl) getService(RuleManager.class)).onReadyMarkerAdded(new ReadyMarker("", ""));
}
@Test
@@ -21,7 +21,6 @@ import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.xtext.naming.QualifiedName;
@@ -81,9 +80,7 @@ public class DSLRuleProviderTest extends JavaOSGiTest {
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));
}
readyService.markReady(new ReadyMarker("rules", RulesRefresher.RULES_REFRESH));
}
@AfterEach