diff --git a/bundles/org.openhab.core.model.script/bnd.bnd b/bundles/org.openhab.core.model.script/bnd.bnd index a03f2dda384..4d49f171a14 100644 --- a/bundles/org.openhab.core.model.script/bnd.bnd +++ b/bundles/org.openhab.core.model.script/bnd.bnd @@ -19,6 +19,7 @@ Export-Package: org.openhab.core.model.script,\ org.openhab.core.model.script.validation Import-Package: \ org.openhab.core.audio,\ + org.openhab.core.automation,\ org.openhab.core.automation.module.script.action,\ org.openhab.core.automation.module.script.rulesupport.shared,\ org.openhab.core.common.registry,\ diff --git a/bundles/org.openhab.core.model.script/pom.xml b/bundles/org.openhab.core.model.script/pom.xml index b666248ae38..c63df47a5cc 100644 --- a/bundles/org.openhab.core.model.script/pom.xml +++ b/bundles/org.openhab.core.model.script/pom.xml @@ -50,6 +50,11 @@ org.openhab.core.io.net ${project.version} + + org.openhab.core.bundles + org.openhab.core.automation + ${project.version} + org.openhab.core.bundles org.openhab.core.automation.module.script diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/ScriptServiceUtil.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/ScriptServiceUtil.java index 0ba56d99ec0..fd0348bd8d8 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/ScriptServiceUtil.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/ScriptServiceUtil.java @@ -16,8 +16,12 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.automation.RuleManager; +import org.openhab.core.automation.RuleRegistry; import org.openhab.core.events.EventPublisher; import org.openhab.core.items.ItemRegistry; +import org.openhab.core.items.MetadataRegistry; import org.openhab.core.model.core.ModelRepository; import org.openhab.core.model.script.engine.ScriptEngine; import org.openhab.core.model.script.engine.action.ActionService; @@ -50,6 +54,9 @@ public class ScriptServiceUtil { private final ThingRegistry thingRegistry; private final EventPublisher eventPublisher; private final ModelRepository modelRepository; + private final MetadataRegistry metadataRegistry; + private final RuleRegistry ruleRegistry; + private volatile @Nullable RuleManager ruleManager; private final Scheduler scheduler; private final AtomicReference scriptEngine = new AtomicReference<>(); @@ -59,11 +66,14 @@ public class ScriptServiceUtil { @Activate public ScriptServiceUtil(final @Reference ItemRegistry itemRegistry, final @Reference ThingRegistry thingRegistry, final @Reference EventPublisher eventPublisher, final @Reference ModelRepository modelRepository, + final @Reference MetadataRegistry metadataRegistry, final @Reference RuleRegistry ruleRegistry, final @Reference Scheduler scheduler) { this.itemRegistry = itemRegistry; this.thingRegistry = thingRegistry; this.eventPublisher = eventPublisher; this.modelRepository = modelRepository; + this.metadataRegistry = metadataRegistry; + this.ruleRegistry = ruleRegistry; this.scheduler = scheduler; if (instance != null) { @@ -73,6 +83,15 @@ public ScriptServiceUtil(final @Reference ItemRegistry itemRegistry, final @Refe logger.debug("ScriptServiceUtil started"); } + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) + void setRuleManager(RuleManager ruleManager) { + this.ruleManager = ruleManager; + } + + void unsetRuleManager(RuleManager ruleManager) { + this.ruleManager = null; + } + @Deactivate public void deactivate() { logger.debug("ScriptServiceUtil stopped"); @@ -91,6 +110,10 @@ public ItemRegistry getItemRegistryInstance() { return itemRegistry; } + public static ThingRegistry getThingRegistry() { + return getInstance().thingRegistry; + } + public ThingRegistry getThingRegistryInstance() { return thingRegistry; } @@ -107,6 +130,30 @@ public ModelRepository getModelRepositoryInstance() { return modelRepository; } + public static MetadataRegistry getMetadataRegistry() { + return getInstance().metadataRegistry; + } + + public MetadataRegistry getMetadataRegistryInstance() { + return metadataRegistry; + } + + public static RuleRegistry getRuleRegistry() { + return getInstance().ruleRegistry; + } + + public RuleRegistry getRuleRegistryInstance() { + return ruleRegistry; + } + + public @Nullable static RuleManager getRuleManager() { + return getInstance().ruleManager; + } + + public @Nullable RuleManager getRuleManagerInstance() { + return ruleManager; + } + public static Scheduler getScheduler() { return getInstance().scheduler; } diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/ItemExtensions.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/ItemExtensions.java new file mode 100644 index 00000000000..390a26496b1 --- /dev/null +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/ItemExtensions.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2010-2026 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 java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.items.Item; +import org.openhab.core.items.Metadata; + +/** + * {@link ItemExtensions} provides DSL access to things like OSGi instances, system registries and the ability to run + * other + * rules. + * + * @author Ravi Nadahar - Initial contribution + */ +@NonNullByDefault +public class ItemExtensions { + + @NonNullByDefault({}) + public static void addMetadata(Item item, String namespace, String value) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null"); + } + Items.addMetadata(item.getName(), namespace, value, (String) null); + } + + @NonNullByDefault({}) + public static void addMetadata(Item item, String namespace, String value, Object... configuration) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null"); + } + Items.addMetadata(item.getName(), namespace, value, parseObjectArray(configuration)); + } + + @NonNullByDefault({}) + public static void addMetadata(Item item, String namespace, String value, + @Nullable Map<@NonNull String, @NonNull Object> configuration) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null"); + } + Items.addMetadata(item.getName(), namespace, value, configuration); + } + + @NonNullByDefault({}) + public static @Nullable Metadata getMetadata(Item item, String namespace) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null"); + } + return Items.getMetadata(item.getName(), namespace); + } + + @NonNullByDefault({}) + public static @Nullable Metadata removeMetadata(Item item, String namespace) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null"); + } + return Items.removeMetadata(item.getName(), namespace); + } + + @NonNullByDefault({}) + public static @Nullable Metadata updateMetadata(Item item, String namespace, String value) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null"); + } + return Items.updateMetadata(item.getName(), namespace, value); + } + + @NonNullByDefault({}) + public static @Nullable Metadata updateMetadata(Item item, String namespace, String value, + Object... configuration) { + return Items.updateMetadata(item.getName(), namespace, value, parseObjectArray(configuration)); + } + + @NonNullByDefault({}) + public static @Nullable Metadata updateMetadata(Item item, String namespace, String value, + @Nullable Map<@NonNull String, @NonNull Object> configuration) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null"); + } + return Items.updateMetadata(item.getName(), namespace, value); + } + + private static Map parseObjectArray(Object @Nullable [] objects) throws IllegalArgumentException { + if (objects == null || objects.length == 0) { + return Map.of(); + } + if ((objects.length % 2) != 0) { + throw new IllegalArgumentException("There must be an even number of objects (" + objects.length + ')'); + } + Map result = new LinkedHashMap<>(); + for (int i = 0; i < objects.length; i += 2) { + if (objects[i] instanceof String key) { + result.put(key, objects[i + 1]); + } else { + throw new IllegalArgumentException("Keys must be strings: " + objects[i]); + } + } + return result; + } +} diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Items.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Items.java new file mode 100644 index 00000000000..d54e2766e0d --- /dev/null +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Items.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2010-2026 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 java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.registry.ManagedProvider; +import org.openhab.core.items.Item; +import org.openhab.core.items.Metadata; +import org.openhab.core.items.MetadataKey; +import org.openhab.core.items.MetadataProvider; +import org.openhab.core.model.script.ScriptServiceUtil; + +/** + * {@link Items} provides DSL access to things like OSGi instances, system registries and the ability to run other + * rules. + * + * @author Ravi Nadahar - Initial contribution + */ +@NonNullByDefault +public class Items { + + public static @Nullable Item getItem(String itemName) { + return ScriptServiceUtil.getItemRegistry().get(itemName); + } + + /** + * + * @param itemName + * @param namespace + * @param value + * @throws IllegalArgumentException If either value is {@code null} or {@code namespace} or + * {@code itemName} is invalid. + * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is + * not a {@link ManagedProvider}. + * @throws IllegalStateException If no ManagedProvider is available. + */ + @NonNullByDefault({}) + public static void addMetadata(String itemName, String namespace, String value) { + addMetadata(itemName, namespace, value, (String) null); + } + + @NonNullByDefault({}) + public static void addMetadata(String itemName, String namespace, String value, Object... configuration) { + addMetadata(itemName, namespace, value, parseObjectArray(configuration)); + } + + /** + * + * @param itemName + * @param namespace + * @param value + * @param configuration + * @throws IllegalArgumentException If {@code namespace}, {@code itemName} or {@code value} is {@code null} or if + * {@code namespace} or {@code itemName} is invalid. + * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is + * not a {@link ManagedProvider}. + * @throws IllegalStateException If no ManagedProvider is available. + */ + @NonNullByDefault({}) + public static void addMetadata(String itemName, String namespace, String value, + @Nullable Map<@NonNull String, @NonNull Object> configuration) { + if (itemName == null) { + throw new IllegalArgumentException("itemName cannot be null"); + } + if (namespace == null) { + throw new IllegalArgumentException("namespace cannot be null"); + } + if (value == null) { + throw new IllegalArgumentException("value cannot be null"); + } + ScriptServiceUtil.getMetadataRegistry() + .add(new Metadata(new MetadataKey(namespace, itemName), value, configuration)); + } + + @NonNullByDefault({}) + public static @Nullable Metadata getMetadata(String itemName, String namespace) { + if (itemName == null) { + throw new IllegalArgumentException("itemName cannot be null"); + } + if (namespace == null) { + throw new IllegalArgumentException("namespace cannot be null"); + } + return ScriptServiceUtil.getMetadataRegistry().get(new MetadataKey(namespace, itemName)); + } + + @NonNullByDefault({}) + public static @Nullable Metadata removeMetadata(String itemName, String namespace) { + if (itemName == null) { + throw new IllegalArgumentException("itemName cannot be null"); + } + if (namespace == null) { + throw new IllegalArgumentException("namespace cannot be null"); + } + return ScriptServiceUtil.getMetadataRegistry().remove(new MetadataKey(namespace, itemName)); + } + + /** + * + * @param namespace + * @param itemName + * @param value + * @throws IllegalArgumentException If either value is {@code null} or {@code namespace} or + * {@code itemName} is invalid. + * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is + * not a {@link ManagedProvider}. + * @throws IllegalStateException If no ManagedProvider is available. + */ + @NonNullByDefault({}) + public static @Nullable Metadata updateMetadata(String itemName, String namespace, String value) { + return updateMetadata(itemName, namespace, value, (Map) null); + } + + @NonNullByDefault({}) + public static @Nullable Metadata updateMetadata(String itemName, String namespace, String value, + Object... configuration) { + return updateMetadata(itemName, namespace, value, parseObjectArray(configuration)); + } + + /** + * + * @param itemName + * @param namespace + * @param value + * @param configuration + * @throws IllegalArgumentException If {@code namespace}, {@code itemName} or {@code value} is {@code null} or if + * {@code namespace} or {@code itemName} is invalid. + * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is + * not a {@link ManagedProvider}. + * @throws IllegalStateException If no ManagedProvider is available. + */ + @NonNullByDefault({}) + public static @Nullable Metadata updateMetadata(String itemName, String namespace, String value, + @Nullable Map<@NonNull String, @NonNull Object> configuration) { + if (itemName == null) { + throw new IllegalArgumentException("itemName cannot be null"); + } + if (namespace == null) { + throw new IllegalArgumentException("namespace cannot be null"); + } + if (value == null) { + throw new IllegalArgumentException("value cannot be null"); + } + return ScriptServiceUtil.getMetadataRegistry() + .update(new Metadata(new MetadataKey(namespace, itemName), value, configuration)); + } + + private static Map parseObjectArray(Object @Nullable [] objects) throws IllegalArgumentException { + if (objects == null || objects.length == 0) { + return Map.of(); + } + if ((objects.length % 2) != 0) { + throw new IllegalArgumentException("There must be an even number of objects (" + objects.length + ')'); + } + Map result = new LinkedHashMap<>(); + for (int i = 0; i < objects.length; i += 2) { + if (objects[i] instanceof String key) { + result.put(key, objects[i + 1]); + } else { + throw new IllegalArgumentException("Keys must be strings: " + objects[i]); + } + } + return result; + } +} diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Ping.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Ping.java index e252bf1c0fb..ed55132aa40 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Ping.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Ping.java @@ -32,15 +32,15 @@ public class Ping { * is specified (which is the default when configuring just the host), a * regular ping is issued. If other ports are specified we try open a new * Socket with the given timeout. - * + * * @param host * @param port * @param timeout - * + * * @return true when host is reachable on port within the given * timeout and false in all other * cases. - * + * * @throws IOException * @throws SocketTimeoutException */ @@ -53,10 +53,10 @@ public static boolean checkVitality(String host, int port, int timeout) throws I } else { SocketAddress socketAddress = new InetSocketAddress(host, port); - Socket socket = new Socket(); - socket.connect(socketAddress, timeout); - success = true; - socket.close(); + try (Socket socket = new Socket()) { + socket.connect(socketAddress, timeout); + success = true; + } } } diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Registries.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Registries.java new file mode 100644 index 00000000000..7a7e53c1f2e --- /dev/null +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Registries.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2010-2026 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.automation.RuleRegistry; +import org.openhab.core.items.ItemRegistry; +import org.openhab.core.items.MetadataRegistry; +import org.openhab.core.model.script.ScriptServiceUtil; +import org.openhab.core.model.script.engine.action.ActionDoc; +import org.openhab.core.thing.ThingRegistry; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +/** + * {@link Registries} provides DSL access to things like OSGi instances, system registries and the ability to run other + * rules. + * + * @author Ravi Nadahar - Initial contribution + */ +@NonNullByDefault +public class Registries { + + /** + * Retrieve an OSGi instance of the specified {@link Class}, if it exists. + * + * @param the class type. + * @param clazz the class of the instance to get. + * @return The instance or {@code null} if the instance wasn't found. + */ + @ActionDoc(text = "acquire an OSGi instance") + public static @Nullable T getInstance(Class clazz) { + Bundle bundle = FrameworkUtil.getBundle(clazz); + if (bundle != null) { + BundleContext bc = bundle.getBundleContext(); + if (bc != null) { + ServiceReference ref = bc.getServiceReference(clazz); + if (ref != null) { + return bc.getService(ref); + } + } + } + return null; + } + + /** + * @return The {@link ThingRegistry}. + */ + @ActionDoc(text = "get the thing registry") + public static ThingRegistry getThingRegistry() { + return ScriptServiceUtil.getThingRegistry(); + } + + /** + * @return The {@link MetadataRegistry}. + */ + @ActionDoc(text = "get the metadata registry") + public static MetadataRegistry getMetadataRegistry() { + return ScriptServiceUtil.getMetadataRegistry(); + } + + /** + * @return The {@link ItemRegistry}. + */ + @ActionDoc(text = "get the item registry") + public static ItemRegistry getItemRegistry() { + return ScriptServiceUtil.getItemRegistry(); + } + + /** + * @return The {@link RuleRegistry}. + */ + @ActionDoc(text = "get the rule registry") + public static RuleRegistry getRuleRegistry() { + return ScriptServiceUtil.getRuleRegistry(); + } +} diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/RuleExtensions.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/RuleExtensions.java new file mode 100644 index 00000000000..d0efe331b83 --- /dev/null +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/RuleExtensions.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2010-2026 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 java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.automation.Rule; +import org.openhab.core.automation.RuleManager; +import org.openhab.core.model.script.ScriptServiceUtil; + +/** + * {@link RuleExtensions} provides DSL access to things like OSGi instances, system registries and the ability to run + * other + * rules. + * + * @author Ravi Nadahar - Initial contribution + */ +@NonNullByDefault +public class RuleExtensions { + + /** + * Run the rule with the specified UID. + * + * @param ruleUID the UID of the rule to run. + * @return A copy of the rule context, including possible return values. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + public static Map run(Rule rule) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + String ruleUID = rule.getUID(); + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID); + } + + public static Map run(Rule rule, boolean considerConditions) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + String ruleUID = rule.getUID(); + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID, considerConditions, null); + } + + /** + * Run the rule with the specified UID with the specified context. + * + * @param ruleUID the UID of the rule to run. + * @param context the {@link Map} of {@link String} and {@link Object} pairs that constitutes the context. + * @return A copy of the rule context, including possible return values. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + public static Map run(Rule rule, Map context) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + String ruleUID = rule.getUID(); + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID, false, context); + } + + /** + * Run the rule with the specified UID with the specified context, while optionally taking conditions into + * account. + * + * @param ruleUID the UID of the rule to run. + * @param considerConditions {@code true} to not run the rule if its conditions don't qualify. + * @param context the pairs of {@link String}s and {@link Object}s that constitutes the context. Must be in pairs, + * the first is the key, the second is the value. + * @return A copy of the rule context, including possible return values. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + public static Map run(Rule rule, boolean considerConditions, Object... context) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + String ruleUID = rule.getUID(); + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID, considerConditions, parseObjectArray(context)); + } + + /** + * Run the rule with the specified UID with the specified context, while optionally taking conditions into + * account. + * + * @param ruleUID the UID of the rule to run. + * @param considerConditions {@code true} to not run the rule if its conditions don't qualify. + * @param context the {@link Map} of {@link String} and {@link Object} pairs that constitutes the context. + * @return A copy of the rule context, including possible return values. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + public static Map run(Rule rule, boolean considerConditions, + @Nullable Map context) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + String ruleUID = rule.getUID(); + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID, considerConditions, context); + } + + /** + * Check whether the specified rule is enabled. + * + * @param ruleUID the UID of the rule to check. + * @return {@code true} if the rule is enabled, {@code false} otherwise. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + public static boolean isEnabled(Rule rule) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + String ruleUID = rule.getUID(); + Boolean result = ruleManager.isEnabled(ruleUID); + if (result == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return result.booleanValue(); + } + + /** + * Set whether the specified rule is enabled. + * + * @param ruleUID the UID of the rule to enable or disable. + * @param enabled {@code true} to enable the rule, {@code false} to disable the rule. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + public static void setEnabled(Rule rule, boolean enabled) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + ruleManager.setEnabled(rule.getUID(), enabled); + } + + private static Map parseObjectArray(Object @Nullable [] objects) throws IllegalArgumentException { + if (objects == null || objects.length == 0) { + return Map.of(); + } + if ((objects.length % 2) != 0) { + throw new IllegalArgumentException("There must be an even number of objects (" + objects.length + ')'); + } + Map result = new LinkedHashMap<>(); + for (int i = 0; i < objects.length; i += 2) { + if (objects[i] instanceof String key) { + result.put(key, objects[i + 1]); + } else { + throw new IllegalArgumentException("Keys must be strings: " + objects[i]); + } + } + return result; + } +} diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Rules.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Rules.java new file mode 100644 index 00000000000..4d98c70f2a0 --- /dev/null +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Rules.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2010-2026 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 java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.automation.Rule; +import org.openhab.core.automation.RuleManager; +import org.openhab.core.model.script.ScriptServiceUtil; +import org.openhab.core.model.script.engine.action.ActionDoc; + +/** + * {@link Rules} provides DSL access to things like OSGi instances, system registries and the ability to run other + * rules. + * + * @author Ravi Nadahar - Initial contribution + */ +@NonNullByDefault +public class Rules { + + public static @Nullable Rule getRule(String ruleUID) { + return ScriptServiceUtil.getRuleRegistry().get(ruleUID); + } + + /** + * Run the rule with the specified UID. + * + * @param ruleUID the UID of the rule to run. + * @return A copy of the rule context, including possible return values. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + @ActionDoc(text = "run the rule with the specified UID") + public static Map runRule(String ruleUID) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID); + } + + @ActionDoc(text = "run the rule with the specified UID and condition evaluation setting") + public static Map runRule(String ruleUID, boolean considerConditions) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID, considerConditions, null); + } + + /** + * Run the rule with the specified UID with the specified context. + * + * @param ruleUID the UID of the rule to run. + * @param context the {@link Map} of {@link String} and {@link Object} pairs that constitutes the context. + * @return A copy of the rule context, including possible return values. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + @ActionDoc(text = "run the rule with the specified UID and context") + public static Map runRule(String ruleUID, Map context) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID, false, context); + } + + /** + * Run the rule with the specified UID with the specified context, while optionally taking conditions into + * account. + * + * @param ruleUID the UID of the rule to run. + * @param considerConditions {@code true} to not run the rule if its conditions don't qualify. + * @param context the pairs of {@link String}s and {@link Object}s that constitutes the context. Must be in pairs, + * the first is the key, the second is the value. + * @return A copy of the rule context, including possible return values. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + @ActionDoc(text = "run the rule with the specified UID, condition evaluation setting and context") + public static Map runRule(String ruleUID, boolean considerConditions, Object... context) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID, considerConditions, parseObjectArray(context)); + } + + /** + * Run the rule with the specified UID with the specified context, while optionally taking conditions into + * account. + * + * @param ruleUID the UID of the rule to run. + * @param considerConditions {@code true} to not run the rule if its conditions don't qualify. + * @param context the {@link Map} of {@link String} and {@link Object} pairs that constitutes the context. + * @return A copy of the rule context, including possible return values. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + @ActionDoc(text = "run the rule with the specified UID, condition evaluation setting and context") + public static Map runRule(String ruleUID, boolean considerConditions, Map context) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + if (ruleManager.getStatus(ruleUID) == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return ruleManager.runNow(ruleUID, considerConditions, context); + } + + /** + * Check whether the specified rule is enabled. + * + * @param ruleUID the UID of the rule to check. + * @return {@code true} if the rule is enabled, {@code false} otherwise. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + @ActionDoc(text = "check whether the specified rule rule is enabled") + public static boolean isRuleEnabled(String ruleUID) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + Boolean result = ruleManager.isEnabled(ruleUID); + if (result == null) { + throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); + } + return result.booleanValue(); + } + + /** + * Set whether the specified rule is enabled. + * + * @param ruleUID the UID of the rule to enable or disable. + * @param enabled {@code true} to enable the rule, {@code false} to disable the rule. + * @throws IllegalArgumentException If a rule with the specified UID doesn't exist. + * @throws IllegalStateException If no {@link RuleManager} instance exists. + */ + @ActionDoc(text = "set whether the specified rule is enabled") + public static void setRuleEnabled(String ruleUID, boolean enabled) { + RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); + if (ruleManager == null) { + throw new IllegalStateException("RuleManager doesn't exist"); + } + ruleManager.setEnabled(ruleUID, enabled); + } + + /** + * @return The {@link RuleManager} or {@code null}. + */ + @ActionDoc(text = "get the rule manager") + public static @Nullable RuleManager getRuleManager() { + return ScriptServiceUtil.getRuleManager(); + } + + private static Map parseObjectArray(Object @Nullable [] objects) throws IllegalArgumentException { + if (objects == null || objects.length == 0) { + return Map.of(); + } + if ((objects.length % 2) != 0) { + throw new IllegalArgumentException("There must be an even number of objects (" + objects.length + ')'); + } + Map result = new LinkedHashMap<>(); + for (int i = 0; i < objects.length; i += 2) { + if (objects[i] instanceof String key) { + result.put(key, objects[i + 1]); + } else { + throw new IllegalArgumentException("Keys must be strings: " + objects[i]); + } + } + return result; + } +} diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImplicitlyImportedTypes.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImplicitlyImportedTypes.java index d8c68693d23..93405f09f1b 100644 --- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImplicitlyImportedTypes.java +++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImplicitlyImportedTypes.java @@ -32,8 +32,13 @@ import org.openhab.core.model.script.actions.CoreUtil; import org.openhab.core.model.script.actions.Exec; import org.openhab.core.model.script.actions.HTTP; +import org.openhab.core.model.script.actions.ItemExtensions; +import org.openhab.core.model.script.actions.Items; import org.openhab.core.model.script.actions.Log; import org.openhab.core.model.script.actions.Ping; +import org.openhab.core.model.script.actions.Registries; +import org.openhab.core.model.script.actions.RuleExtensions; +import org.openhab.core.model.script.actions.Rules; import org.openhab.core.model.script.actions.ScriptExecution; import org.openhab.core.model.script.actions.Transformation; import org.openhab.core.model.script.engine.IActionServiceProvider; @@ -76,6 +81,8 @@ protected List> getExtensionClasses() { result.add(Ping.class); result.add(Transformation.class); result.add(ScriptExecution.class); + result.add(ItemExtensions.class); + result.add(RuleExtensions.class); result.add(URLEncoder.class); result.addAll(getActionClasses()); @@ -92,6 +99,9 @@ protected List> getStaticImportClasses() { result.add(Ping.class); result.add(Transformation.class); result.add(ScriptExecution.class); + result.add(Items.class); + result.add(Registries.class); + result.add(Rules.class); result.add(URLEncoder.class); result.add(CoreUtil.class);