getActionServiceInstances() {
@@ -162,4 +312,38 @@ public void unsetScriptEngine(ScriptEngine scriptEngine) {
// uninjected as a callback from the script engine, not via DS as it is a circular dependency...
this.scriptEngine.compareAndSet(scriptEngine, null);
}
+
+ /**
+ * Retrieve an OSGi instance of the specified {@link Class}, if it exists. The reference to the instance is
+ * unreserved, which means that the instance might stop being valid at any time, for example if the service
+ * or the containing bundle is stopped.
+ *
+ * Returning an unreserved service isn't kosher in the world of OSGi, but the only alternative is that the scripts
+ * that retrieve the instance are responsible for unregistering the reservation after use. That isn't a reasonable
+ * thing to expect from user scripts. The chance that a service is stopped while OH is running is quite small, so
+ * on balance, returning an unreserved service instance seems like the best way to do it. It isn't much different
+ * from returning an instance to a registry that is reserved by {@link ScriptServiceUtil} - if the
+ * {@link ScriptServiceUtil} itself is stopped, the instance might become invalid while the script is using it.
+ *
+ * @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.
+ */
+ 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) {
+ T result = bc.getService(ref);
+ if (result != null) {
+ bc.ungetService(ref);
+ }
+ return result;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Log.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Log.java
index 505b64cee36..922dc39a61a 100644
--- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Log.java
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Log.java
@@ -28,56 +28,128 @@ public class Log {
/**
* Creates the Log-Entry format with level DEBUG and logs under the loggers name
* org.openhab.core.model.script.<loggerName>
- *
+ *
+ * @param loggerName the name of the Logger which is prefixed with org.openhab.core.model.script.
+ * @param format the Log-Statement which can contain a placeholder '{}'
+ * @param arg the argument to replace the placeholder contained in format
+ *
+ * @see Logger
+ *
+ * @implNote This overload exists to avoid confusion when mapping a single argument to Varargs.
+ */
+ public static void logDebug(String loggerName, Object format, Object arg) {
+ LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName))
+ .debug(format instanceof String formatStr ? formatStr : format == null ? null : format.toString(), arg);
+ }
+
+ /**
+ * Creates the Log-Entry format with level DEBUG and logs under the loggers name
+ * org.openhab.core.model.script.<loggerName>
+ *
* @param loggerName the name of the Logger which is prefixed with org.openhab.core.model.script.
* @param format the Log-Statement which can contain placeholders '{}'
* @param args the arguments to replace the placeholders contained in format
- *
+ *
+ * @see Logger
+ */
+ public static void logDebug(String loggerName, Object format, Object... args) {
+ LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).debug(
+ format instanceof String formatStr ? formatStr : format == null ? null : format.toString(), args);
+ }
+
+ /**
+ * Creates the Log-Entry format with level INFO and logs under the loggers name
+ * org.openhab.core.model.script.<loggerName>
+ *
+ * @param loggerName the name of the Logger which is prefixed with org.openhab.core.model.script.
+ * @param format the Log-Statement which can contain a placeholder '{}'
+ * @param arg the argument to replace the placeholder contained in format
+ *
* @see Logger
+ *
+ * @implNote This overload exists to avoid confusion when mapping a single argument to Varargs.
*/
- public static void logDebug(String loggerName, String format, Object... args) {
- LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).debug(format, args);
+ public static void logInfo(String loggerName, Object format, Object arg) {
+ LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName))
+ .info(format instanceof String formatStr ? formatStr : format == null ? null : format.toString(), arg);
}
/**
* Creates the Log-Entry format with level INFO and logs under the loggers name
* org.openhab.core.model.script.<loggerName>
- *
+ *
* @param loggerName the name of the Logger which is prefixed with org.openhab.core.model.script.
* @param format the Log-Statement which can contain placeholders '{}'
* @param args the arguments to replace the placeholders contained in format
- *
+ *
* @see Logger
*/
- public static void logInfo(String loggerName, String format, Object... args) {
- LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).info(format, args);
+ public static void logInfo(String loggerName, Object format, Object... args) {
+ LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName))
+ .info(format instanceof String formatStr ? formatStr : format == null ? null : format.toString(), args);
}
/**
* Creates the Log-Entry format with level WARN and logs under the loggers name
* org.openhab.core.model.script.<loggerName>
- *
+ *
+ * @param loggerName the name of the Logger which is prefixed with org.openhab.core.model.script.
+ * @param format the Log-Statement which can contain a placeholder '{}'
+ * @param arg the argument to replace the placeholder contained in format
+ *
+ * @see Logger
+ *
+ * @implNote This overload exists to avoid confusion when mapping a single argument to Varargs.
+ */
+ public static void logWarn(String loggerName, Object format, Object arg) {
+ LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName))
+ .warn(format instanceof String formatStr ? formatStr : format == null ? null : format.toString(), arg);
+ }
+
+ /**
+ * Creates the Log-Entry format with level WARN and logs under the loggers name
+ * org.openhab.core.model.script.<loggerName>
+ *
* @param loggerName the name of the Logger which is prefixed with org.openhab.core.model.script.
* @param format the Log-Statement which can contain placeholders '{}'
* @param args the arguments to replace the placeholders contained in format
- *
+ *
+ * @see Logger
+ */
+ public static void logWarn(String loggerName, Object format, Object... args) {
+ LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName))
+ .warn(format instanceof String formatStr ? formatStr : format == null ? null : format.toString(), args);
+ }
+
+ /**
+ * Creates the Log-Entry format with level ERROR and logs under the loggers name
+ * org.openhab.core.model.script.<loggerName>
+ *
+ * @param loggerName the name of the Logger which is prefixed with org.openhab.core.model.script.
+ * @param format the Log-Statement which can contain a placeholder '{}'
+ * @param arg the argument to replace the placeholder contained in format
+ *
* @see Logger
+ *
+ * @implNote This overload exists to avoid confusion when mapping a single argument to Varargs.
*/
- public static void logWarn(String loggerName, String format, Object... args) {
- LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).warn(format, args);
+ public static void logError(String loggerName, Object format, Object arg) {
+ LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName))
+ .error(format instanceof String formatStr ? formatStr : format == null ? null : format.toString(), arg);
}
/**
* Creates the Log-Entry format with level ERROR and logs under the loggers name
* org.openhab.core.model.script.<loggerName>
- *
+ *
* @param loggerName the name of the Logger which is prefixed with org.openhab.core.model.script.
* @param format the Log-Statement which can contain placeholders '{}'
* @param args the arguments to replace the placeholders contained in format
- *
+ *
* @see Logger
*/
- public static void logError(String loggerName, String format, Object... args) {
- LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).error(format, args);
+ public static void logError(String loggerName, Object format, Object... args) {
+ LoggerFactory.getLogger(LOGGER_NAME_PREFIX.concat(loggerName)).error(
+ format instanceof String formatStr ? formatStr : format == null ? null : format.toString(), args);
}
}
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/Things.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Things.java
index 7d9afcc0258..4bf0617c579 100644
--- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Things.java
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/actions/Things.java
@@ -13,7 +13,10 @@
package org.openhab.core.model.script.actions;
import org.openhab.core.model.script.internal.engine.action.ThingActionService;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingActions;
/**
@@ -45,4 +48,30 @@ public static ThingStatusInfo getThingStatusInfo(String thingUid) {
public static ThingActions getActions(String scope, String thingUid) {
return ThingActionService.getActions(scope, thingUid);
}
+
+ /**
+ * Gets the actions instance of a certain scope for a given {@link Thing}.
+ *
+ * @param thing the {@link Thing}.
+ * @param scope the action scope.
+ *
+ * @return The {@link ThingActions} instance or {@code null}.
+ */
+ public static ThingActions getActions(Thing thing, String scope) {
+ return ThingActionService.getActions(thing, scope);
+ }
+
+ /**
+ * Get a {@link Thing} from the {@link ThingRegistry}.
+ *
+ * @param registry the {@link ThingRegistry}.
+ * @param thingUid the Thing UID string.
+ * @return The {@link Thing} instance of {@code null}.
+ */
+ public static Thing get(ThingRegistry registry, String thingUid) {
+ if (registry == null || thingUid == null) {
+ return null;
+ }
+ return registry.get(new ThingUID(thingUid));
+ }
}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/engine/action/ThingActionService.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/engine/action/ThingActionService.java
index 90f75a3a402..57e8098e4e3 100644
--- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/engine/action/ThingActionService.java
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/internal/engine/action/ThingActionService.java
@@ -56,8 +56,12 @@ public Class> getActionClass() {
}
public static @Nullable ThingStatusInfo getThingStatusInfo(String thingUid) {
+ ThingRegistry registry = thingRegistry;
+ if (registry == null) {
+ return null;
+ }
ThingUID uid = new ThingUID(thingUid);
- Thing thing = thingRegistry.get(uid);
+ Thing thing = registry.get(uid);
if (thing != null) {
return thing.getStatusInfo();
@@ -75,8 +79,12 @@ public Class> getActionClass() {
* @return actions the actions instance or null, if not available
*/
public static @Nullable ThingActions getActions(String scope, String thingUid) {
+ ThingRegistry registry = thingRegistry;
+ if (registry == null) {
+ return null;
+ }
ThingUID uid = new ThingUID(thingUid);
- Thing thing = thingRegistry.get(uid);
+ Thing thing = registry.get(uid);
if (thing != null) {
ThingHandler handler = thing.getHandler();
if (handler != null) {
@@ -86,6 +94,24 @@ public Class> getActionClass() {
return null;
}
+ /**
+ * Gets an actions instance of a certain scope for a given {@link Thing}.
+ *
+ * @param thing the {@link Thing}.
+ * @param scope the action scope.
+ *
+ * @return actions the actions instance or null, if not available.
+ */
+ public static @Nullable ThingActions getActions(@Nullable Thing thing, String scope) {
+ if (thing != null) {
+ ThingHandler handler = thing.getHandler();
+ if (handler != null) {
+ return THING_ACTIONS_MAP.get(getKey(scope, thing.getUID().getAsString()));
+ }
+ }
+ return null;
+ }
+
@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE)
public void addThingActions(ThingActions thingActions) {
String key = getKey(thingActions);
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Channels.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Channels.java
new file mode 100644
index 00000000000..e3ff5c7f189
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Channels.java
@@ -0,0 +1,576 @@
+/*
+ * 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.lib;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+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.config.core.Configuration;
+import org.openhab.core.items.Item;
+import org.openhab.core.model.script.ScriptServiceUtil;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.link.ItemChannelLink;
+
+/**
+ * {@link Channels} provides DSL access to channel manipulation.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class Channels {
+
+ /**
+ * Get the {@link ItemChannelLink}s that are linked to the specified item.
+ *
+ * @param itemName the name of the item.
+ * @return The {@link Set} of {@link ItemChannelLink}s.
+ */
+ public static Set getLinks(@Nullable String itemName) {
+ if (itemName == null || itemName.isBlank()) {
+ return Set.of();
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getLinks(itemName);
+ }
+
+ /**
+ * Get the {@link ItemChannelLink}s that are linked to the specified item.
+ *
+ * @param item the {@link Item}.
+ * @return The {@link Set} of {@link ItemChannelLink}s.
+ */
+ public static Set getLinks(@Nullable Item item) {
+ if (item == null) {
+ return Set.of();
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getLinks(item.getName());
+ }
+
+ /**
+ * Get the {@link ItemChannelLink}s that are linked to the specified channel.
+ *
+ * @param channelUid the UID of the channel.
+ * @return The {@link Set} of {@link ItemChannelLink}s.
+ */
+ public static Set getChannelLinks(@Nullable String channelUid) {
+ if (channelUid == null || channelUid.isBlank()) {
+ return Set.of();
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getLinks(new ChannelUID(channelUid));
+ }
+
+ /**
+ * Get the {@link ItemChannelLink}s that are linked to the specified channel.
+ *
+ * @param channelUid the {@link ChannelUID} of the channel.
+ * @return The {@link Set} of {@link ItemChannelLink}s.
+ */
+ public static Set getChannelLinks(@Nullable ChannelUID channelUid) {
+ if (channelUid == null) {
+ return Set.of();
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getLinks(channelUid);
+ }
+
+ /**
+ * Get the {@link ChannelUID}s of the channels that are bound to the specified {@link Item}.
+ *
+ * @param itemName the name of the item.
+ * @return The {@link Set} of {@link ChannelUID}s of the bound channels.
+ */
+ public static Set getBoundChannels(@Nullable String itemName) {
+ if (itemName == null) {
+ return Set.of();
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getBoundChannels(itemName);
+ }
+
+ /**
+ * Get the {@link ChannelUID}s of the channels that are bound to the specified {@link Item}.
+ *
+ * @param item the {@link Item}.
+ * @return The {@link Set} of {@link ChannelUID}s of the bound channels.
+ * @throws IllegalArgumentException If {@code item} is {@code null}.
+ */
+ @NonNullByDefault({})
+ public static @NonNull Set<@NonNull ChannelUID> getBoundChannels(Item item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getBoundChannels(item.getName());
+ }
+
+ /**
+ * Get the {@link Thing}s that are bound to the specified {@link Item}.
+ *
+ * @param itemName the name of the item.
+ * @return The {@link Set} of bound {@link Thing}s.
+ */
+ public static Set getBoundThings(@Nullable String itemName) {
+ if (itemName == null) {
+ return Set.of();
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getBoundThings(itemName);
+ }
+
+ /**
+ * Get the {@link Thing}s that are bound to the specified {@link Item}.
+ *
+ * @param item the {@link Item}.
+ * @return The {@link Set} of bound {@link Thing}s.
+ * @throws IllegalArgumentException If {@code item} is {@code null}.
+ */
+ @NonNullByDefault({})
+ public static @NonNull Set<@NonNull Thing> getBoundThings(Item item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getBoundThings(item.getName());
+ }
+
+ /**
+ * Get the names of all the items that are linked to a specific channel.
+ *
+ * @param channelUid the UID of the channel.
+ * @return The {@link Set} of names of the linked items.
+ */
+ public static Set getLinkedItemNames(@Nullable String channelUid) {
+ if (channelUid == null || channelUid.isBlank()) {
+ return Set.of();
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getLinkedItemNames(new ChannelUID(channelUid));
+ }
+
+ /**
+ * Get the names of all the items that are linked to a specific channel.
+ *
+ * @param channelUid the {@link ChannelUID} of the channel.
+ * @return The {@link Set} of names of the linked items.
+ * @throws IllegalArgumentException If {@code channelUid} is {@code null}.
+ */
+ @NonNullByDefault({})
+ public static @NonNull Set<@NonNull String> getLinkedItemNames(ChannelUID channelUid) {
+ if (channelUid == null) {
+ throw new IllegalArgumentException("channelUid cannot be null");
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getLinkedItemNames(channelUid);
+ }
+
+ /**
+ * Get all {@link Item}s that are linked to a specific channel.
+ *
+ * @param channelUid the UID of the channel.
+ * @return The {@link Set} of linked {@link Item}s.
+ */
+ public static Set- getLinkedItems(@Nullable String channelUid) {
+ if (channelUid == null || channelUid.isBlank()) {
+ return Set.of();
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getLinkedItems(new ChannelUID(channelUid));
+ }
+
+ /**
+ * Get all {@link Item}s that are linked to a specific channel.
+ *
+ * @param channelUid the {@link ChannelUID} of the channel.
+ * @return The {@link Set} of linked {@link Item}s.
+ * @throws IllegalArgumentException If {@code channelUid} is {@code null}.
+ */
+ @NonNullByDefault({})
+ public static @NonNull Set<@NonNull Item> getLinkedItems(ChannelUID channelUid) {
+ if (channelUid == null) {
+ throw new IllegalArgumentException("channelUid cannot be null");
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().getLinkedItems(channelUid);
+ }
+
+ /**
+ * Check if the specified item has at least one link.
+ *
+ * @param itemName the name of the item to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isLinked(@Nullable String itemName) {
+ if (itemName == null || itemName.isBlank()) {
+ return false;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().isLinked(itemName);
+ }
+
+ /**
+ * Check if the specified item has at least one link.
+ *
+ * @param item the {@link Item} to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isLinked(@Nullable Item item) {
+ if (item == null) {
+ return false;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().isLinked(item.getName());
+ }
+
+ /**
+ * Check if the specified item and channel are linked.
+ *
+ * @param itemName the name of the item to check.
+ * @param channelUid the UID of the channel to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isLinked(@Nullable String itemName, @Nullable String channelUid) {
+ if (itemName == null || itemName.isBlank() || channelUid == null || channelUid.isBlank()) {
+ return false;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().isLinked(itemName, new ChannelUID(channelUid));
+ }
+
+ /**
+ * Check if the specified item and channel are linked.
+ *
+ * @param item the {@link Item} to check.
+ * @param channelUid the UID of the channel to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isLinked(@Nullable Item item, @Nullable String channelUid) {
+ if (item == null || channelUid == null || channelUid.isBlank()) {
+ return false;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().isLinked(item.getName(), new ChannelUID(channelUid));
+ }
+
+ /**
+ * Check if the specified item and channel are linked.
+ *
+ * @param item the {@link Item} to check.
+ * @param channelUid the {@link ChannelUID} of the channel to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isLinked(@Nullable Item item, @Nullable ChannelUID channelUid) {
+ if (item == null || channelUid == null) {
+ return false;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().isLinked(item.getName(), channelUid);
+ }
+
+ /**
+ * Check if the specified channel has at least one link.
+ *
+ * @param channelUid the UID of the channel to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isChannelLinked(@Nullable String channelUid) {
+ if (channelUid == null || channelUid.isBlank()) {
+ return false;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().isLinked(new ChannelUID(channelUid));
+ }
+
+ /**
+ * Check if the specified channel has at least one link.
+ *
+ * @param channelUid the {@link ChannelUID} of the channel to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isChannelLinked(@Nullable ChannelUID channelUid) {
+ if (channelUid == null) {
+ return false;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().isLinked(channelUid);
+ }
+
+ /**
+ * Get an existing {@link ItemChannelLink} for the specified item and channel.
+ *
+ * @param item the {@link Item}.
+ * @param channelUid the {@link ChannelUID} of the channel.
+ * @return The {@link ItemChannelLink} or {@code null} if none were found.
+ * @throws IllegalArgumentException If {@code item} or {@code channelUid} is {@code null}.
+ */
+ @NonNullByDefault({})
+ public static @Nullable ItemChannelLink getLink(Item item, ChannelUID channelUid) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ if (channelUid == null) {
+ throw new IllegalArgumentException("channelUid cannot be null");
+ }
+ return getLink(item.getName(), channelUid.getAsString());
+ }
+
+ /**
+ * Get an existing {@link ItemChannelLink} for the specified item and channel.
+ *
+ * @param item the {@link Item}.
+ * @param channelUid the UID of the channel.
+ * @return The {@link ItemChannelLink} or {@code null} if none were found.
+ * @throws IllegalArgumentException If {@code item} is {@code null}.
+ */
+ @NonNullByDefault({})
+ public static @Nullable ItemChannelLink getLink(Item item, @Nullable String channelUid) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return getLink(item.getName(), channelUid);
+ }
+
+ /**
+ * Get an existing {@link ItemChannelLink} for the specified item and channel.
+ *
+ * @param itemName the name of the item.
+ * @param channelUid the UID of the channel.
+ * @return The {@link ItemChannelLink} or {@code null} if none were found.
+ */
+ public static @Nullable ItemChannelLink getLink(@Nullable String itemName, @Nullable String channelUid) {
+ if (itemName == null || channelUid == null) {
+ return null;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().get(itemName + " -> " + channelUid);
+ }
+
+ /**
+ * Add a new {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @return The newly created {@link ItemChannelLink}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, the {@link ItemChannelLink} already exists,
+ * or {@code channelUid} is invalid.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ @NonNullByDefault({})
+ public static ItemChannelLink addItemChannelLink(Item item, String channelUid) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry()
+ .add(new ItemChannelLink(item.getName(), new ChannelUID(channelUid)));
+ }
+
+ /**
+ * Add a new {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @param configProperties the pairs of {@link String}s and {@link Object}s that constitutes the configuration.
+ * properties for the link. Must be in pairs, the first is the key, the second is the value.
+ * @return The newly created {@link ItemChannelLink}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, {@code channelUid} is invalid, the
+ * {@link ItemChannelLink} already exists, or if there is an odd number of {@code configProperties}, or
+ * if any of the keys aren't {@link String}s.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static ItemChannelLink addItemChannelLink(Item item, String channelUid, Object... configProperties) {
+ Map<@Nullable String, @Nullable Object> props = Map.copyOf(parseObjectArray(configProperties));
+ return addItemChannelLink(item, channelUid, props);
+ }
+
+ /**
+ * Add a new {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @param configProperties the map of configuration properties for the link.
+ * @return The newly created {@link ItemChannelLink}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, the {@link ItemChannelLink} already exists,
+ * or {@code channelUid} is invalid.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ @NonNullByDefault({})
+ public static ItemChannelLink addItemChannelLink(Item item, String channelUid,
+ @Nullable Map<@Nullable String, @Nullable Object> configProperties) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().add(
+ new ItemChannelLink(item.getName(), new ChannelUID(channelUid), new Configuration(configProperties)));
+ }
+
+ /**
+ * Add or replace a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @return The old {@link ItemChannelLink} if one existed, or {@code null}.
+ * @throws IllegalArgumentException If {@code item} is {@code null} or {@code channelUid} is invalid.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ @NonNullByDefault({})
+ public static @Nullable ItemChannelLink replaceItemChannelLink(Item item, String channelUid) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry()
+ .update(new ItemChannelLink(item.getName(), new ChannelUID(channelUid)));
+ }
+
+ /**
+ * Add or replace a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @param configProperties the pairs of {@link String}s and {@link Object}s that constitutes the configuration.
+ * properties for the link. Must be in pairs, the first is the key, the second is the value.
+ * @return The old {@link ItemChannelLink} if one existed, or {@code null}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, {@code channelUid} is invalid, or if there is
+ * an odd number of {@code configProperties}, or if any of the keys aren't {@link String}s.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static @Nullable ItemChannelLink replaceItemChannelLink(Item item, String channelUid,
+ Object... configProperties) {
+ Map<@Nullable String, @Nullable Object> props = Map.copyOf(parseObjectArray(configProperties));
+ return replaceItemChannelLink(item, channelUid, props);
+ }
+
+ /**
+ * Add or replace a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @param configProperties the map of configuration properties for the link.
+ * @return The old {@link ItemChannelLink} if one existed, or {@code null}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, or {@code channelUid} is invalid.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ @NonNullByDefault({})
+ public static @Nullable ItemChannelLink replaceItemChannelLink(Item item, String channelUid,
+ @Nullable Map<@Nullable String, @Nullable Object> configProperties) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().update(
+ new ItemChannelLink(item.getName(), new ChannelUID(channelUid), new Configuration(configProperties)));
+ }
+
+ /**
+ * Remove a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} of the link to remove.
+ * @param channelUid the UID of the channel of the link to remove.
+ * @return The removed {@link ItemChannelLink} or {@code null} if none existed.
+ * @throws IllegalArgumentException If {@code item} or {@code channelUid} is {@code null}.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ @NonNullByDefault({})
+ public static @Nullable ItemChannelLink removeItemChannelLink(Item item, ChannelUID channelUid) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ if (channelUid == null) {
+ throw new IllegalArgumentException("channelUid cannot be null");
+ }
+ return removeItemChannelLink(item.getName(), channelUid.getAsString());
+ }
+
+ /**
+ * Remove a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} of the link to remove.
+ * @param channelUid the UID of the channel of the link to remove.
+ * @return The removed {@link ItemChannelLink} or {@code null} if none existed.
+ * @throws IllegalArgumentException If {@code item} is {@code null}.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ @NonNullByDefault({})
+ public static @Nullable ItemChannelLink removeItemChannelLink(Item item, @Nullable String channelUid) {
+ if (item == null) {
+ throw new IllegalArgumentException("item cannot be null");
+ }
+ return removeItemChannelLink(item.getName(), channelUid);
+ }
+
+ /**
+ * Remove a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param itemName the name of the item of the link to remove.
+ * @param channelUid the UID of the channel of the link to remove.
+ * @return The removed {@link ItemChannelLink} or {@code null} if none existed.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static @Nullable ItemChannelLink removeItemChannelLink(@Nullable String itemName,
+ @Nullable String channelUid) {
+ if (itemName == null || channelUid == null) {
+ return null;
+ }
+ return ScriptServiceUtil.getItemChannelLinkRegistry().remove(itemName + " -> " + channelUid);
+ }
+
+ /**
+ * Remove all managed links related to the specified item.
+ *
+ * @param itemName the name of the item.
+ * @return The number of removed links.
+ */
+ public static int removeLinksForItem(@Nullable String itemName) {
+ if (itemName == null || itemName.isBlank()) {
+ return 0;
+ }
+
+ return ScriptServiceUtil.getItemChannelLinkRegistry().removeLinksForItem(itemName);
+ }
+
+ /**
+ * Remove all managed links related to the specified item.
+ *
+ * @param item the {@link Item}.
+ * @return The number of removed links.
+ */
+ public static int removeLinksForItem(@Nullable Item item) {
+ if (item == null) {
+ return 0;
+ }
+
+ return ScriptServiceUtil.getItemChannelLinkRegistry().removeLinksForItem(item.getName());
+ }
+
+ /**
+ * Remove all orphaned (item or channel missing) managed links.
+ *
+ * @return The number of removed links.
+ */
+ public static int removeOrphanedItemChannelLinks() {
+ return ScriptServiceUtil.getItemChannelLinkRegistry().purge();
+ }
+
+ /**
+ * Transforms pairs of {@link Object}s into a {@link Map}. The former of each pair (the key) must be a
+ * {@link String}.
+ *
+ * @param objects the array of {@link Object}s to transform.
+ * @return The resulting {@link Map}.
+ * @throws IllegalArgumentException If there is an odd number of objects, or if any of the keys aren't
+ * {@link String}s.
+ */
+ 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/lib/ItemExtensions.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/ItemExtensions.java
new file mode 100644
index 00000000000..561b66c62a5
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/ItemExtensions.java
@@ -0,0 +1,380 @@
+/*
+ * 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.lib;
+
+import java.util.Map;
+import java.util.Set;
+
+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.MetadataProvider;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.link.ItemChannelLink;
+
+/**
+ * {@link ItemExtensions} provides DSL {@link Item} extensions.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class ItemExtensions {
+
+ /**
+ * Add metadata to an item.
+ *
+ * @param item the {@link Item}.
+ * @param namespace the metadata namespace.
+ * @param value the metadata value.
+ * @throws IllegalArgumentException If {@code namespace} or {@code value} is {@code null}, if {@code namespace} is
+ * invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no {@code ManagedProvider} is available.
+ */
+ public static void addMetadata(Item item, String namespace, String value) {
+ Items.addMetadata(item.getName(), namespace, value, (String) null);
+ }
+
+ /**
+ * Add metadata to an item.
+ *
+ * @param item the {@link Item}.
+ * @param namespace the metadata namespace.
+ * @param value the metadata value.
+ * @param configProperties the pairs of {@link String}s and {@link Object}s that constitutes the configuration.
+ * @throws IllegalArgumentException If {@code namespace} or {@code value} is {@code null}, if {@code namespace} is
+ * invalid, or if there is an odd number of {@code configProperties}, or if any of the keys aren't
+ * {@link String}s.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no {@code ManagedProvider} is available.
+ */
+ public static void addMetadata(Item item, String namespace, String value, Object... configProperties) {
+ Items.addMetadata(item.getName(), namespace, value, configProperties);
+ }
+
+ /**
+ * Add metadata to an item.
+ *
+ * @param item the {@link Item}.
+ * @param namespace the metadata namespace.
+ * @param value the metadata value.
+ * @param configuration the {@link Map} of configuration properties that make up the configuration.
+ * @throws IllegalArgumentException If {@code namespace} or {@code value} is {@code null}, or if {@code namespace}
+ * is invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no {@code ManagedProvider} is available.
+ */
+ public static void addMetadata(Item item, String namespace, String value,
+ @Nullable Map configuration) {
+ Items.addMetadata(item.getName(), namespace, value, configuration);
+ }
+
+ /**
+ * Get item metadata for the specified namespace.
+ *
+ * @param item the {@link Item}.
+ * @param namespace the metadata namespace.
+ * @return The matching {@link Metadata} or {@code null}.
+ */
+ public static @Nullable Metadata getMetadata(Item item, String namespace) {
+ return Items.getMetadata(item.getName(), namespace);
+ }
+
+ /**
+ * Remove metadata from an item for the specified namespace.
+ *
+ * @param item the {@link Item}.
+ * @param namespace the metadata namespace.
+ * @return The removed {@link Metadata} or {@code null} if no such metadata existed.
+ */
+ public static @Nullable Metadata removeMetadata(Item item, String namespace) {
+ return Items.removeMetadata(item.getName(), namespace);
+ }
+
+ /**
+ * Update item metadata for the specified namespace.
+ *
+ * @param item the {@link Item}.
+ * @param namespace the metadata namespace.
+ * @param value the new metadata value.
+ * @return The old {@link Metadata} or {@code null} if no previous metadata existed.
+ * @throws IllegalArgumentException If {@code namespace} or {@code value} is {@code null}, or if {@code namespace}
+ * is invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no {@code ManagedProvider} is available.
+ */
+ public static @Nullable Metadata updateMetadata(Item item, String namespace, String value) {
+ return Items.updateMetadata(item.getName(), namespace, value);
+ }
+
+ /**
+ * Update item metadata for the specified namespace.
+ *
+ * @param item the {@link Item}.
+ * @param namespace the metadata namespace.
+ * @param value the new metadata value.
+ * @return The old {@link Metadata} or {@code null} if no previous metadata existed.
+ * @throws IllegalArgumentException If {@code namespace} or {@code value} is {@code null}, or is {@code namespace}
+ * is invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no {@code ManagedProvider} is available.
+ */
+ public static @Nullable Metadata updateMetadata(Item item, String namespace, String value,
+ Object... configuration) {
+ return Items.updateMetadata(item.getName(), namespace, value, configuration);
+ }
+
+ /**
+ * Update item metadata for the specified namespace.
+ *
+ * @param item the {@link Item}.
+ * @param namespace the metadata namespace.
+ * @param value the new metadata value.
+ * @param configuration the {@link Map} of configuration properties that make up the configuration.
+ * @return The old {@link Metadata} or {@code null} if no previous metadata existed.
+ * @throws IllegalArgumentException If {@code namespace} or {@code value} is {@code null}, or if {@code namespace}
+ * is invalid.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no {@code ManagedProvider} is available.
+ */
+ public static @Nullable Metadata updateMetadata(Item item, String namespace, String value,
+ @Nullable Map configuration) {
+ return Items.updateMetadata(item.getName(), namespace, value, configuration);
+ }
+
+ /**
+ * Get the {@link ItemChannelLink}s that are linked to the specified item.
+ *
+ * @param item the {@link Item}.
+ * @return The {@link Set} of {@link ItemChannelLink}s.
+ */
+ public static Set getChannelLinks(@Nullable Item item) {
+ return Channels.getLinks(item);
+ }
+
+ /**
+ * Get the {@link ChannelUID}s of the channels that are bound to the specified {@link Item}.
+ *
+ * @param item the {@link Item}.
+ * @return The {@link Set} of {@link ChannelUID}s of the bound channels.
+ */
+ public static Set getBoundChannels(Item item) {
+ return Channels.getBoundChannels(item);
+ }
+
+ /**
+ * Get the {@link Thing}s that are bound to the specified {@link Item}.
+ *
+ * @param item the {@link Item}.
+ * @return The {@link Set} of bound {@link Thing}s.
+ * @throws IllegalArgumentException If {@code item} is {@code null}.
+ */
+ public static Set getBoundThings(Item item) {
+ return Channels.getBoundThings(item);
+ }
+
+ /**
+ * Check if the specified item has at least one link.
+ *
+ * @param item the {@link Item} to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isLinked(@Nullable Item item) {
+ return Channels.isLinked(item);
+ }
+
+ /**
+ * Check if the specified item and channel are linked.
+ *
+ * @param item the {@link Item} to check.
+ * @param channelUid the UID of the channel to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isLinked(@Nullable Item item, @Nullable String channelUid) {
+ return Channels.isLinked(item, channelUid);
+ }
+
+ /**
+ * Check if the specified item and channel are linked.
+ *
+ * @param item the {@link Item} to check.
+ * @param channelUid the {@link ChannelUID} of the channel to check.
+ * @return {@code true} if a link exists, {@code false} otherwise.
+ */
+ public static boolean isLinked(@Nullable Item item, @Nullable ChannelUID channelUid) {
+ return Channels.isLinked(item, channelUid);
+ }
+
+ /**
+ * Get an existing {@link ItemChannelLink} for the specified item and channel.
+ *
+ * @param item the {@link Item}.
+ * @param channelUid the {@link ChannelUID} of the channel.
+ * @return The {@link ItemChannelLink} or {@code null} if none were found.
+ * @throws IllegalArgumentException If {@code item} or {@code channelUid} is {@code null}.
+ */
+ public static @Nullable ItemChannelLink getChannelLink(Item item, ChannelUID channelUid) {
+ return Channels.getLink(item, channelUid);
+ }
+
+ /**
+ * Get an existing {@link ItemChannelLink} for the specified item and channel.
+ *
+ * @param item the {@link Item}.
+ * @param channelUid the UID of the channel.
+ * @return The {@link ItemChannelLink} or {@code null} if none were found.
+ * @throws IllegalArgumentException If {@code item} is {@code null}.
+ */
+ public static @Nullable ItemChannelLink getChannelLink(Item item, @Nullable String channelUid) {
+ return Channels.getLink(item, channelUid);
+ }
+
+ /**
+ * Add a new {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @return The newly created {@link ItemChannelLink}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, the {@link ItemChannelLink} already exists,
+ * or {@code channelUid} is invalid.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ @NonNullByDefault({})
+ public static ItemChannelLink addChannelLink(Item item, String channelUid) {
+ return Channels.addItemChannelLink(item, channelUid);
+ }
+
+ /**
+ * Add a new {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @param configProperties the pairs of {@link String}s and {@link Object}s that constitutes the configuration
+ * properties for the link. Must be in pairs, the first is the key, the second is the value.
+ * @return The newly created {@link ItemChannelLink}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, {@code channelUid} is invalid, the
+ * {@link ItemChannelLink} already exists, or if there is an odd number of {@code configProperties}, or
+ * if any of the keys aren't {@link String}s.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static ItemChannelLink addChannelLink(Item item, String channelUid, Object... configProperties) {
+ return Channels.addItemChannelLink(item, channelUid, configProperties);
+ }
+
+ /**
+ * Add a new {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @param configProperties the map of configuration properties for the link.
+ * @return The newly created {@link ItemChannelLink}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, the {@link ItemChannelLink} already exists,
+ * or {@code channelUid} is invalid.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static ItemChannelLink addChannelLink(Item item, String channelUid,
+ @Nullable Map<@Nullable String, @Nullable Object> configProperties) {
+ return Channels.addItemChannelLink(item, channelUid, configProperties);
+ }
+
+ /**
+ * Add or replace a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @return The old {@link ItemChannelLink} if one existed, or {@code null}.
+ * @throws IllegalArgumentException If {@code item} is {@code null} or {@code channelUid} is invalid.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static @Nullable ItemChannelLink replaceChannelLink(Item item, String channelUid) {
+ return Channels.replaceItemChannelLink(item, channelUid);
+ }
+
+ /**
+ * Add or replace a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @param configProperties the pairs of {@link String}s and {@link Object}s that constitutes the configuration
+ * properties for the link. Must be in pairs, the first is the key, the second is the value.
+ * @return The old {@link ItemChannelLink} if one existed, or {@code null}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, {@code channelUid} is invalid, or if there is
+ * an odd number of {@code configProperties}, or if any of the keys aren't {@link String}s.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static @Nullable ItemChannelLink replaceChannelLink(Item item, String channelUid,
+ Object... configProperties) {
+ return Channels.replaceItemChannelLink(item, channelUid, configProperties);
+ }
+
+ /**
+ * Add or replace a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} to link.
+ * @param channelUid the UID of the channel to link.
+ * @param configProperties the map of configuration properties for the link.
+ * @return The old {@link ItemChannelLink} if one existed, or {@code null}.
+ * @throws IllegalArgumentException If {@code item} is {@code null}, or {@code channelUid} is invalid.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static @Nullable ItemChannelLink replaceChannelLink(Item item, String channelUid,
+ @Nullable Map<@Nullable String, @Nullable Object> configProperties) {
+ return Channels.replaceItemChannelLink(item, channelUid, configProperties);
+ }
+
+ /**
+ * Remove a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} of the link to remove.
+ * @param channelUid the UID of the channel of the link to remove.
+ * @return The removed {@link ItemChannelLink} or {@code null} if none existed.
+ * @throws IllegalArgumentException If {@code item} or {@code channelUid} is {@code null}.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static @Nullable ItemChannelLink removeChannelLink(Item item, ChannelUID channelUid) {
+ return Channels.removeItemChannelLink(item, channelUid);
+ }
+
+ /**
+ * Remove a {@link ItemChannelLink} between an existing {@link Item} and a {@link Channel}.
+ *
+ * @param item the {@link Item} of the link to remove.
+ * @param channelUid the UID of the channel of the link to remove.
+ * @return The removed {@link ItemChannelLink} or {@code null} if none existed.
+ * @throws IllegalArgumentException If {@code item} is {@code null}.
+ * @throws IllegalStateException If a {@link ManagedProvider} isn't available.
+ */
+ public static @Nullable ItemChannelLink removeChannelLink(Item item, @Nullable String channelUid) {
+ return Channels.removeItemChannelLink(item, channelUid);
+ }
+
+ /**
+ * Remove all managed links related to the specified item.
+ *
+ * @param item the {@link Item}.
+ * @return The number of removed links.
+ */
+ public static int removeChannelLinks(@Nullable Item item) {
+ return Channels.removeLinksForItem(item);
+ }
+}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Items.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Items.java
new file mode 100644
index 00000000000..6a6078dc59f
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Items.java
@@ -0,0 +1,305 @@
+/*
+ * 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.lib;
+
+import java.util.Collection;
+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 item and metadata manipulation.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class Items {
+
+ /**
+ * Check whether a named item exists.
+ *
+ * @param itemName the item name.
+ * @return {@code true} if the item exists, {@code false} if it doesn't.
+ */
+ public static boolean exists(String itemName) {
+ return ScriptServiceUtil.getItemRegistry().get(itemName) != null;
+ }
+
+ /**
+ * Get an item by name.
+ *
+ * @param itemName the item name.
+ * @return The {@link Item} or {@code null} if it doesn't exist.
+ */
+ public static @Nullable Item get(String itemName) {
+ return ScriptServiceUtil.getItemRegistry().get(itemName);
+ }
+
+ /**
+ * Get all items.
+ *
+ * @return The {@link Collection} of {@link Item}s.
+ */
+ public static Collection
- getAll() {
+ return ScriptServiceUtil.getItemRegistry().getAll();
+ }
+
+ /**
+ * Get all items that match a pattern using {@code ?} and {@code *}.
+ *
+ * @param pattern the pattern.
+ * @return The {@link Collection} of matching {@link Item}s.
+ */
+ public static Collection
- getByPattern(String pattern) {
+ return ScriptServiceUtil.getItemRegistry().getItems(pattern);
+ }
+
+ /**
+ * Get all items that have all the specified tags.
+ *
+ * @param tags the tags.
+ * @return The {@link Collection} of matching {@link Item}s.
+ */
+ public static Collection
- getByTag(String... tags) {
+ return ScriptServiceUtil.getItemRegistry().getItemsByTag(tags);
+ }
+
+ /**
+ * Get all items of the specified type.
+ *
+ * Types includes: {@code Call}, {@code Color}, {@code Contact}, {@code DateTime}, {@code Dimmer}, {@code Image},
+ * {@code Location}, {@code Number}, {@code Player}, {@code Rollershutter}, {@code String} and {@code Switch}.
+ *
+ * @param type the type.
+ * @return The {@link Collection} of matching {@link Item}s.
+ */
+ public static Collection- getOfType(String type) {
+ return ScriptServiceUtil.getItemRegistry().getItemsOfType(type);
+ }
+
+ /**
+ * Get all items of the specified type that also have all the specified tags.
+ *
+ * Types includes: {@code Call}, {@code Color}, {@code Contact}, {@code DateTime}, {@code Dimmer}, {@code Image},
+ * {@code Location}, {@code Number}, {@code Player}, {@code Rollershutter}, {@code String} and {@code Switch}.
+ *
+ * @param type the type.
+ * @param tags the tags.
+ * @return The {@link Collection} of matching {@link Item}s.
+ */
+ public static Collection- getByTagAndType(String type, String... tags) {
+ return ScriptServiceUtil.getItemRegistry().getItemsByTagAndType(type, tags);
+ }
+
+ /**
+ * Get item metadata for the specified namespace.
+ *
+ * @param itemName the item name.
+ * @param namespace the metadata namespace.
+ * @return The matching {@link Metadata} or {@code null}.
+ */
+ @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));
+ }
+
+ /**
+ * Add metadata to an item.
+ *
+ * @param itemName the item name.
+ * @param namespace the metadata namespace.
+ * @param value the metadata value.
+ * @throws IllegalArgumentException If {@code value} is {@code null}, {@code namespace} is {@code null}, 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 {@code ManagedProvider} is available.
+ */
+ public static void addMetadata(String itemName, String namespace, String value) {
+ addMetadata(itemName, namespace, value, (String) null);
+ }
+
+ /**
+ * Add metadata to an item.
+ *
+ * @param itemName the item name.
+ * @param namespace the metadata namespace.
+ * @param value the metadata value.
+ * @param configProperties the pairs of {@link String}s and {@link Object}s that constitutes the configuration.
+ * @throws IllegalArgumentException If {@code namespace}, {@code itemName} or {@code value} is {@code null}, if
+ * {@code namespace} or {@code itemName} is invalid, or if there is an odd number of
+ * {@code configProperties}, or if any of the keys aren't {@link String}s.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no {@code ManagedProvider} is available.
+ */
+ public static void addMetadata(String itemName, String namespace, String value, Object... configProperties) {
+ addMetadata(itemName, namespace, value, parseObjectArray(configProperties));
+ }
+
+ /**
+ * Add metadata to an item.
+ *
+ * @param itemName the item name.
+ * @param namespace the metadata namespace.
+ * @param value the metadata value.
+ * @param configuration the {@link Map} of configuration properties that make up the 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 {@code 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));
+ }
+
+ /**
+ * Remove metadata from an item for the specified namespace.
+ *
+ * @param itemName the item name.
+ * @param namespace the metadata namespace.
+ * @return The removed {@link Metadata} or {@code null} if no such metadata existed.
+ */
+ @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));
+ }
+
+ /**
+ * Update item metadata for the specified namespace.
+ *
+ * @param itemName the item name.
+ * @param namespace the metadata namespace.
+ * @param value the new metadata value.
+ * @return The old {@link Metadata} or {@code null} if no previous metadata existed.
+ * @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 {@code ManagedProvider} is available.
+ */
+ public static @Nullable Metadata updateMetadata(String itemName, String namespace, String value) {
+ return updateMetadata(itemName, namespace, value, (Map) null);
+ }
+
+ /**
+ * Update item metadata for the specified namespace.
+ *
+ * @param itemName the item name.
+ * @param namespace the metadata namespace.
+ * @param value the new metadata value.
+ * @param configProperties the pairs of {@link String}s and {@link Object}s that constitutes the configuration.
+ * @return The old {@link Metadata} or {@code null} if no previous metadata existed.
+ * @throws IllegalArgumentException If {@code namespace}, {@code itemName} or {@code value} is {@code null}, if
+ * {@code namespace} or {@code itemName} is invalid, or if there is an odd number of
+ * {@code configProperties}, or if any of the keys aren't {@link String}s.
+ * @throws UnsupportedOperationException If the metadata namespace has a reserved {@link MetadataProvider} that is
+ * not a {@link ManagedProvider}.
+ * @throws IllegalStateException If no {@code ManagedProvider} is available.
+ */
+ public static @Nullable Metadata updateMetadata(String itemName, String namespace, String value,
+ Object... configProperties) {
+ return updateMetadata(itemName, namespace, value, parseObjectArray(configProperties));
+ }
+
+ /**
+ * Update item metadata for the specified namespace.
+ *
+ * @param itemName the item name.
+ * @param namespace the metadata namespace.
+ * @param value the new metadata value.
+ * @param configuration the {@link Map} of configuration properties that make up the configuration.
+ * @return The old {@link Metadata} or {@code null} if no previous metadata existed.
+ * @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 {@code 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));
+ }
+
+ /**
+ * Transforms pairs of {@link Object}s into a {@link Map}. The former of each pair (the key) must be a
+ * {@link String}.
+ *
+ * @param objects the array of {@link Object}s to transform.
+ * @return The resulting {@link Map}.
+ * @throws IllegalArgumentException If there is an odd number of objects, or if any of the keys aren't
+ * {@link String}s.
+ */
+ 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/lib/RuleExtensions.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/RuleExtensions.java
new file mode 100644
index 00000000000..778fe02a1c9
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/RuleExtensions.java
@@ -0,0 +1,121 @@
+/*
+ * 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.lib;
+
+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;
+
+/**
+ * {@link ItemExtensions} provides DSL {@link Rule} extensions.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class RuleExtensions {
+
+ /**
+ * Run the specified rule.
+ *
+ * @param rule the {@link Rule} to run.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If the specified rule isn't registered.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule) {
+ return Rules.runRule(rule.getUID());
+ }
+
+ /**
+ * Run the specified rule while optionally taking conditions into account.
+ *
+ * @param rule the {@link Rule} to run.
+ * @param considerConditions {@code true} to not run the rule if its conditions don't qualify.
+ * @return A copy of the rule context, including possible return values.
+ * @throws IllegalArgumentException If the specified rule isn't registered.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule, boolean considerConditions) {
+ return Rules.runRule(rule.getUID(), considerConditions);
+ }
+
+ /**
+ * Run the specified rule with the specified context.
+ *
+ * @param rule the {@link 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 the specified rule isn't registered.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule, Map context) {
+ return Rules.runRule(rule.getUID(), context);
+ }
+
+ /**
+ * Run the specified rule with the specified context, while optionally taking conditions into account.
+ *
+ * @param rule the {@link 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 the specified rule isn't registered.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule, boolean considerConditions, Object... context) {
+ return Rules.runRule(rule.getUID(), considerConditions, context);
+ }
+
+ /**
+ * Run the specified rule with the specified context, while optionally taking conditions into account.
+ *
+ * @param rule the {@link 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 the specified rule isn't registered.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static Map run(Rule rule, boolean considerConditions,
+ @Nullable Map context) {
+ return Rules.runRule(rule.getUID(), considerConditions, context);
+ }
+
+ /**
+ * Check whether the specified rule is enabled.
+ *
+ * @param rule the {@link Rule} to check.
+ * @return {@code true} if the rule is enabled, {@code false} otherwise.
+ * @throws IllegalArgumentException If the specified rule isn't registered.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static boolean isEnabled(Rule rule) {
+ return Rules.isRuleEnabled(rule.getUID());
+ }
+
+ /**
+ * Set whether the specified rule is enabled.
+ *
+ * @param rule the {@link Rule} to enable or disable.
+ * @param enabled {@code true} to enable the rule, {@code false} to disable the rule.
+ * @throws IllegalArgumentException If the specified rule isn't registered.
+ * @throws IllegalStateException If no {@link RuleManager} instance exists.
+ */
+ public static void setEnabled(Rule rule, boolean enabled) {
+ Rules.setRuleEnabled(rule.getUID(), enabled);
+ }
+}
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Rules.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Rules.java
new file mode 100644
index 00000000000..bc22e540d94
--- /dev/null
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/lib/Rules.java
@@ -0,0 +1,216 @@
+/*
+ * 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.lib;
+
+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 Rules} provides DSL access to rule operations.
+ *
+ * @author Ravi Nadahar - Initial contribution
+ */
+@NonNullByDefault
+public class Rules {
+
+ /**
+ * Get a rule by UID.
+ *
+ * @param ruleUid
+ * @return The {@link Rule} or {@code null} if no matching rule exists.
+ */
+ 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.
+ */
+ 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);
+ }
+
+ /**
+ * Run the rule with the specified UID 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.
+ * @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 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public static Map runRule(String ruleUid, boolean considerConditions,
+ @Nullable 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.
+ */
+ 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.
+ */
+ 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}.
+ */
+ public static @Nullable RuleManager getRuleManager() {
+ return ScriptServiceUtil.getRuleManager();
+ }
+
+ /**
+ * Transforms pairs of {@link Object}s into a {@link Map}. The former of each pair (the key) must be a
+ * {@link String}.
+ *
+ * @param objects the array of {@link Object}s to transform.
+ * @return The resulting {@link Map}.
+ * @throws IllegalArgumentException If there is an odd number of objects, or if any of the keys aren't
+ * {@link String}s.
+ */
+ 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/ActionClassLoader.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ActionClassLoader.java
index bf9bb596c1a..5ac7bf9d014 100644
--- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ActionClassLoader.java
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ActionClassLoader.java
@@ -31,7 +31,11 @@ public ActionClassLoader(ClassLoader cl) {
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
try {
- return getParent().loadClass(name);
+ ClassLoader parent = getParent();
+ if (parent == null) {
+ throw new ClassNotFoundException("Unable to load '" + name + "' because parent is null");
+ }
+ return parent.loadClass(name);
} catch (ClassNotFoundException e) {
for (ActionService actionService : ScriptServiceUtil.getActionServices()) {
if (actionService.getActionClassName().equals(name)) {
@@ -43,7 +47,7 @@ public Class> loadClass(String name) throws ClassNotFoundException {
return actions.getClass();
}
}
+ throw e;
}
- throw new ClassNotFoundException();
}
}
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..262b5d5df6e 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
@@ -28,6 +28,7 @@
import org.openhab.core.library.unit.MetricPrefix;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
+import org.openhab.core.model.script.ScriptServiceUtil;
import org.openhab.core.model.script.actions.BusEvent;
import org.openhab.core.model.script.actions.CoreUtil;
import org.openhab.core.model.script.actions.Exec;
@@ -39,7 +40,9 @@
import org.openhab.core.model.script.engine.IActionServiceProvider;
import org.openhab.core.model.script.engine.IThingActionsProvider;
import org.openhab.core.model.script.engine.action.ActionService;
+import org.openhab.core.model.script.lib.ItemExtensions;
import org.openhab.core.model.script.lib.NumberExtensions;
+import org.openhab.core.model.script.lib.RuleExtensions;
import org.openhab.core.thing.binding.ThingActions;
import com.google.inject.Inject;
@@ -76,6 +79,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 +97,7 @@ protected List> getStaticImportClasses() {
result.add(Ping.class);
result.add(Transformation.class);
result.add(ScriptExecution.class);
+ result.add(ScriptServiceUtil.class);
result.add(URLEncoder.class);
result.add(CoreUtil.class);
diff --git a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImportSectionNamespaceScopeProvider.java b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImportSectionNamespaceScopeProvider.java
index c0a78b64ab4..815411660ed 100644
--- a/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImportSectionNamespaceScopeProvider.java
+++ b/bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/scoping/ScriptImportSectionNamespaceScopeProvider.java
@@ -29,7 +29,14 @@ public class ScriptImportSectionNamespaceScopeProvider extends XImportSectionNam
"library", "types");
public static final QualifiedName CORE_LIBRARY_ITEMS_PACKAGE = QualifiedName.create("org", "openhab", "core",
"library", "items");
+ public static final QualifiedName CORE_TYPES_TIMESERIES_CLASS = QualifiedName.create("org", "openhab", "core",
+ "types", "TimeSeries");
public static final QualifiedName CORE_ITEMS_PACKAGE = QualifiedName.create("org", "openhab", "core", "items");
+ public static final QualifiedName CORE_THING_PACKAGE = QualifiedName.create("org", "openhab", "core", "thing");
+ public static final QualifiedName CORE_THING_LINK_ITEMCHANNELLINK_CLASS = QualifiedName.create("org", "openhab",
+ "core", "thing", "link", "ItemChannelLink");
+ public static final QualifiedName CORE_THING_LINK_ITEMCHANNELLINKREGISTRY_CLASS = QualifiedName.create("org",
+ "openhab", "core", "thing", "link", "ItemChannelLinkRegistry");
public static final QualifiedName CORE_PERSISTENCE_PACKAGE = QualifiedName.create("org", "openhab", "core",
"persistence");
public static final QualifiedName CORE_PERSISTENCE_RIEMANNTYPE_CLASS = QualifiedName.create("org", "openhab",
@@ -37,8 +44,18 @@ public class ScriptImportSectionNamespaceScopeProvider extends XImportSectionNam
public static final QualifiedName MODEL_SCRIPT_ACTIONS_PACKAGE = QualifiedName.create("org", "openhab", "core",
"model", "script", "actions");
public static final QualifiedName TIME_PACKAGE = QualifiedName.create("java", "time");
- public static final QualifiedName CHRONOUNIT_CLASS = QualifiedName.create("java", "time", "temporal", "ChronoUnit");
+ public static final QualifiedName TIME_FORMAT_PACKAGE = QualifiedName.create("java", "time", "format");
+ public static final QualifiedName TIME_TEMPORAL_PACKAGE = QualifiedName.create("java", "time", "temporal");
+ public static final QualifiedName UTIL_REGEX_PACKAGE = QualifiedName.create("java", "util", "regex");
+ public static final QualifiedName UTIL_LOCALE_CLASS = QualifiedName.create("java", "util", "Locale");
+ public static final QualifiedName UTIL_TIMEZONE_CLASS = QualifiedName.create("java", "util", "TimeZone");
public static final QualifiedName QUANTITY_PACKAGE = QualifiedName.create("javax", "measure", "quantity");
+ public static final QualifiedName CHANNELS_CLASS = QualifiedName.create("org", "openhab", "core", "model", "script",
+ "lib", "Channels");
+ public static final QualifiedName ITEMS_CLASS = QualifiedName.create("org", "openhab", "core", "model", "script",
+ "lib", "Items");
+ public static final QualifiedName RULES_CLASS = QualifiedName.create("org", "openhab", "core", "model", "script",
+ "lib", "Rules");
@Override
protected List getImplicitImports(boolean ignoreCase) {
@@ -46,13 +63,24 @@ protected List getImplicitImports(boolean ignoreCase) {
implicitImports.add(doCreateImportNormalizer(CORE_LIBRARY_UNITS_PACKAGE, true, false));
implicitImports.add(doCreateImportNormalizer(CORE_LIBRARY_TYPES_PACKAGE, true, false));
implicitImports.add(doCreateImportNormalizer(CORE_LIBRARY_ITEMS_PACKAGE, true, false));
+ implicitImports.add(doCreateImportNormalizer(CORE_TYPES_TIMESERIES_CLASS, false, false));
implicitImports.add(doCreateImportNormalizer(CORE_ITEMS_PACKAGE, true, false));
+ implicitImports.add(doCreateImportNormalizer(CORE_THING_PACKAGE, true, false));
+ implicitImports.add(doCreateImportNormalizer(CORE_THING_LINK_ITEMCHANNELLINK_CLASS, false, false));
+ implicitImports.add(doCreateImportNormalizer(CORE_THING_LINK_ITEMCHANNELLINKREGISTRY_CLASS, false, false));
implicitImports.add(doCreateImportNormalizer(CORE_PERSISTENCE_PACKAGE, true, false));
implicitImports.add(doCreateImportNormalizer(CORE_PERSISTENCE_RIEMANNTYPE_CLASS, false, false));
implicitImports.add(doCreateImportNormalizer(MODEL_SCRIPT_ACTIONS_PACKAGE, true, false));
implicitImports.add(doCreateImportNormalizer(TIME_PACKAGE, true, false));
- implicitImports.add(doCreateImportNormalizer(CHRONOUNIT_CLASS, false, false));
+ implicitImports.add(doCreateImportNormalizer(TIME_FORMAT_PACKAGE, true, false));
+ implicitImports.add(doCreateImportNormalizer(TIME_TEMPORAL_PACKAGE, true, false));
+ implicitImports.add(doCreateImportNormalizer(UTIL_REGEX_PACKAGE, true, false));
+ implicitImports.add(doCreateImportNormalizer(UTIL_LOCALE_CLASS, false, false));
+ implicitImports.add(doCreateImportNormalizer(UTIL_TIMEZONE_CLASS, false, false));
implicitImports.add(doCreateImportNormalizer(QUANTITY_PACKAGE, true, false));
+ implicitImports.add(doCreateImportNormalizer(CHANNELS_CLASS, false, false));
+ implicitImports.add(doCreateImportNormalizer(ITEMS_CLASS, false, false));
+ implicitImports.add(doCreateImportNormalizer(RULES_CLASS, false, false));
return implicitImports;
}
}