Add event information in rules for time, manual and RunRuleAction trigger (#2965)

* Add event information for time triggers

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K
2023-05-28 10:15:16 +02:00
committed by GitHub
parent d34b9164f7
commit 88bb76c21c
9 changed files with 246 additions and 9 deletions
@@ -64,12 +64,14 @@ import org.openhab.core.automation.dto.RuleDTO;
import org.openhab.core.automation.dto.RuleDTOMapper;
import org.openhab.core.automation.dto.TriggerDTO;
import org.openhab.core.automation.dto.TriggerDTOMapper;
import org.openhab.core.automation.events.AutomationEventFactory;
import org.openhab.core.automation.rest.internal.dto.EnrichedRuleDTO;
import org.openhab.core.automation.rest.internal.dto.EnrichedRuleDTOMapper;
import org.openhab.core.automation.util.ModuleBuilder;
import org.openhab.core.automation.util.RuleBuilder;
import org.openhab.core.config.core.ConfigUtil;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.Event;
import org.openhab.core.io.rest.DTOMapper;
import org.openhab.core.io.rest.JSONResponse;
import org.openhab.core.io.rest.RESTConstants;
@@ -331,7 +333,14 @@ public class RuleResource implements RESTResource {
ruleUID);
return Response.status(Status.NOT_FOUND).build();
} else {
ruleManager.runNow(ruleUID, false, context);
if (context == null || context.isEmpty()) {
// only add event to context if no context given, otherwise it might interfere with the intention of the
// provided context
Event event = AutomationEventFactory.createExecutionEvent(ruleUID, null, "manual");
ruleManager.runNow(ruleUID, false, Map.of("event", event));
} else {
ruleManager.runNow(ruleUID, false, context);
}
return Response.ok().build();
}
}
@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.automation.events;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.events.AbstractEventFactory;
import org.openhab.core.events.Event;
import org.openhab.core.events.EventFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is a factory that creates Timer and Execution Events.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@Component(service = EventFactory.class, immediate = true)
public class AutomationEventFactory extends AbstractEventFactory {
private static final String MODULE_IDENTIFIER = "{moduleId}";
private static final String TIMER_EVENT_TOPIC = "openhab/timer/" + MODULE_IDENTIFIER + "/triggered";
private static final String EXECUTION_EVENT_TOPIC = "openhab/execution/" + MODULE_IDENTIFIER + "/triggered";
private final Logger logger = LoggerFactory.getLogger(AutomationEventFactory.class);
private static final Set<String> SUPPORTED_TYPES = Set.of(TimerEvent.TYPE, ExecutionEvent.TYPE);
public AutomationEventFactory() {
super(SUPPORTED_TYPES);
}
@Override
protected Event createEventByType(String eventType, String topic, String payload, @Nullable String source)
throws Exception {
logger.trace("creating ruleEvent of type: {}", eventType);
if (TimerEvent.TYPE.equals(eventType)) {
return createTimerEvent(topic, payload, Objects.requireNonNullElse(source, "<unknown>"));
} else if (ExecutionEvent.TYPE.equals(eventType)) {
if (source == null) {
throw new IllegalArgumentException("'source' must not be null for execution events");
}
return createExecutionEvent(topic, payload, source);
}
throw new IllegalArgumentException("The event type '" + eventType + "' is not supported by this factory.");
}
private Event createTimerEvent(String topic, String payload, String source) {
return new TimerEvent(topic, payload, source);
}
private Event createExecutionEvent(String topic, String payload, String source) {
return new ExecutionEvent(topic, payload, source);
}
/**
* Creates a {@link TimerEvent}
*
* @param moduleId the module type id of this event
* @param label The label (or id) of this object
* @param configuration the configuration of the trigger
* @return the created event
*/
public static TimerEvent createTimerEvent(String moduleId, @Nullable String label,
Map<String, Object> configuration) {
String topic = TIMER_EVENT_TOPIC.replace(MODULE_IDENTIFIER, moduleId);
String payload = serializePayload(configuration);
return new TimerEvent(topic, payload, label);
}
/**
* Creates an {@link ExecutionEvent}
*
* @param moduleId the module type id of this event
* @param payload A map with additional information like preceding events when rules are called from other rules
* (optional)
* @param source The source of this event (e.g. "script" or "manual")
* @return the created event
*/
public static ExecutionEvent createExecutionEvent(String moduleId, @Nullable Map<String, Object> payload,
String source) {
String topic = EXECUTION_EVENT_TOPIC.replace(MODULE_IDENTIFIER, moduleId);
String serializedPayload = serializePayload(Objects.requireNonNullElse(payload, Map.of()));
return new ExecutionEvent(topic, serializedPayload, source);
}
}
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.automation.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.events.AbstractEvent;
/**
* An {@link ExecutionEvent} is only used to notify rules when a script or the REST API trigger the run.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class ExecutionEvent extends AbstractEvent {
public static final String TYPE = ExecutionEvent.class.getSimpleName();
/**
* Constructs a new rule execution event
*
* @param topic the topic of the event
* @param payload the payload of the event
* @param source the source of the event
*/
public ExecutionEvent(String topic, String payload, String source) {
super(topic, payload, source);
}
@Override
public String getType() {
return TYPE;
}
@Override
public String toString() {
return "Execution triggered by " + getSource();
}
}
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.automation.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.events.AbstractEvent;
/**
* An {@link TimerEvent} is only used to notify rules when timer triggers fire.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class TimerEvent extends AbstractEvent {
public static final String TYPE = TimerEvent.class.getSimpleName();
/**
* Constructs a new timer event
*
* @param topic the topic of the event
* @param payload the payload of the event (contains trigger configuration)
* @param source the source of the event
*/
public TimerEvent(String topic, String payload, @Nullable String source) {
super(topic, payload, source);
}
@Override
public String getType() {
return TYPE;
}
@Override
public String toString() {
return "Timer " + getSource() + " triggered.";
}
}
@@ -14,12 +14,16 @@ package org.openhab.core.automation.internal.module.handler;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.ModuleHandlerCallback;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.events.AutomationEventFactory;
import org.openhab.core.automation.events.TimerEvent;
import org.openhab.core.automation.handler.BaseTriggerModuleHandler;
import org.openhab.core.automation.handler.TimeBasedTriggerHandler;
import org.openhab.core.automation.handler.TriggerHandlerCallback;
@@ -114,7 +118,9 @@ public class DateTimeTriggerHandler extends BaseTriggerModuleHandler
public void run() {
ModuleHandlerCallback callback = this.callback;
if (callback instanceof TriggerHandlerCallback triggerHandlerCallback) {
triggerHandlerCallback.triggered(module);
TimerEvent event = AutomationEventFactory.createTimerEvent(module.getTypeUID(),
Objects.requireNonNullElse(module.getLabel(), module.getId()), Map.of(CONFIG_ITEM_NAME, itemName));
triggerHandlerCallback.triggered(module, Map.of("event", event));
} else {
logger.debug("Tried to trigger, but callback isn't available!");
}
@@ -12,10 +12,15 @@
*/
package org.openhab.core.automation.internal.module.handler;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.ModuleHandlerCallback;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.events.AutomationEventFactory;
import org.openhab.core.automation.events.TimerEvent;
import org.openhab.core.automation.handler.BaseTriggerModuleHandler;
import org.openhab.core.automation.handler.TimeBasedTriggerHandler;
import org.openhab.core.automation.handler.TriggerHandlerCallback;
@@ -80,7 +85,10 @@ public class GenericCronTriggerHandler extends BaseTriggerModuleHandler
@Override
public void run() {
if (callback != null) {
((TriggerHandlerCallback) callback).triggered(module);
TimerEvent event = AutomationEventFactory.createTimerEvent(module.getTypeUID(),
Objects.requireNonNullElse(module.getLabel(), module.getId()),
Map.of(CFG_CRON_EXPRESSION, expression));
((TriggerHandlerCallback) callback).triggered(module, Map.of("event", event));
} else {
logger.debug("Tried to trigger, but callback isn't available!");
}
@@ -12,14 +12,17 @@
*/
package org.openhab.core.automation.internal.module.handler;
import java.util.HashMap;
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.Action;
import org.openhab.core.automation.events.AutomationEventFactory;
import org.openhab.core.automation.handler.BaseActionModuleHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,6 +67,7 @@ public class RunRuleActionHandler extends BaseActionModuleHandler {
* the UIDs of the rules to be executed.
*/
private final List<String> ruleUIDs;
private final String moduleId;
/**
* boolean to express if the conditions should be considered, defaults to
@@ -84,16 +88,22 @@ public class RunRuleActionHandler extends BaseActionModuleHandler {
throw new IllegalArgumentException("'ruleUIDs' property must not be null.");
}
if (config.get(CONSIDER_CONDITIONS_KEY) != null && config.get(CONSIDER_CONDITIONS_KEY) instanceof Boolean) {
this.considerConditions = ((Boolean) config.get(CONSIDER_CONDITIONS_KEY)).booleanValue();
this.considerConditions = (Boolean) config.get(CONSIDER_CONDITIONS_KEY);
}
this.moduleId = module.getId();
}
@Override
public @Nullable Map<String, Object> execute(Map<String, Object> context) {
// execute each rule after the other; at the moment synchronously
Object previousEvent = context.get("event");
Event event = AutomationEventFactory.createExecutionEvent(moduleId,
previousEvent instanceof Event ? Map.of("previous", previousEvent) : null, "runRuleAction");
Map<String, Object> newContext = new HashMap<>(context);
newContext.put("event", event);
for (String uid : ruleUIDs) {
if (callback != null) {
callback.runNow(uid, considerConditions, context);
callback.runNow(uid, considerConditions, newContext);
} else {
logger.warn("Action is not applied to {} because rule engine is not available.", uid);
}
@@ -13,11 +13,15 @@
package org.openhab.core.automation.internal.module.handler;
import java.text.MessageFormat;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.ModuleHandlerCallback;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.events.AutomationEventFactory;
import org.openhab.core.automation.events.TimerEvent;
import org.openhab.core.automation.handler.BaseTriggerModuleHandler;
import org.openhab.core.automation.handler.TimeBasedTriggerHandler;
import org.openhab.core.automation.handler.TriggerHandlerCallback;
@@ -46,13 +50,14 @@ public class TimeOfDayTriggerHandler extends BaseTriggerModuleHandler
public static final String CFG_TIME = "time";
private final CronScheduler scheduler;
private final String time;
private final String expression;
private @Nullable ScheduledCompletableFuture<?> schedule;
public TimeOfDayTriggerHandler(Trigger module, CronScheduler scheduler) {
super(module);
this.scheduler = scheduler;
String time = module.getConfiguration().get(CFG_TIME).toString();
this.time = module.getConfiguration().get(CFG_TIME).toString();
this.expression = buildExpressionFromConfigurationTime(time);
}
@@ -83,7 +88,9 @@ public class TimeOfDayTriggerHandler extends BaseTriggerModuleHandler
@Override
public void run() {
if (callback != null) {
((TriggerHandlerCallback) callback).triggered(module);
TimerEvent event = AutomationEventFactory.createTimerEvent(module.getTypeUID(),
Objects.requireNonNullElse(module.getLabel(), module.getId()), Map.of(CFG_TIME, time));
((TriggerHandlerCallback) callback).triggered(module, Map.of("event", event));
} else {
logger.debug("Tried to trigger, but callback isn't available!");
}
@@ -22,8 +22,7 @@ import com.google.gson.Gson;
/**
* The {@link AbstractEventFactory} defines an abstract implementation of the {@link EventFactory} interface. Subclasses
* must implement the abstract method {@link #createEventByType(String, String, String, String)} in order to create
* event
* instances based on the event type.
* event instances based on the event type.
*
* @author Stefan Bußweiler - Initial contribution
*/