diff --git a/bundles/org.openhab.core.model.rule/bnd.bnd b/bundles/org.openhab.core.model.rule/bnd.bnd index f43eae7b46c..0d7459ca75c 100644 --- a/bundles/org.openhab.core.model.rule/bnd.bnd +++ b/bundles/org.openhab.core.model.rule/bnd.bnd @@ -27,6 +27,7 @@ Import-Package: \ org.openhab.core.model.items,\ org.openhab.core.model.script,\ org.openhab.core.model.script.engine.action,\ + org.openhab.core.model.script.helper,\ org.openhab.core.persistence,\ org.openhab.core.persistence.extensions,\ org.openhab.core.service,\ diff --git a/bundles/org.openhab.core.model.script/bnd.bnd b/bundles/org.openhab.core.model.script/bnd.bnd index 4d49f171a14..d8826f1d533 100644 --- a/bundles/org.openhab.core.model.script/bnd.bnd +++ b/bundles/org.openhab.core.model.script/bnd.bnd @@ -5,6 +5,7 @@ Export-Package: org.openhab.core.model.script,\ org.openhab.core.model.script.engine.action,\ org.openhab.core.model.script.extension,\ org.openhab.core.model.script.formatting,\ + org.openhab.core.model.script.helper,\ org.openhab.core.model.script.interpreter,\ org.openhab.core.model.script.jvmmodel,\ org.openhab.core.model.script.lib,\ @@ -25,6 +26,7 @@ Import-Package: \ org.openhab.core.common.registry,\ org.openhab.core.ephemeris,\ org.openhab.core.events,\ + org.openhab.core.i18n,\ org.openhab.core.io.console,\ org.openhab.core.io.console.extensions,\ org.openhab.core.io.net.exec,\ @@ -42,6 +44,7 @@ Import-Package: \ org.openhab.core.thing,\ org.openhab.core.thing.binding,\ org.openhab.core.thing.events,\ + org.openhab.core.thing.link,\ org.openhab.core.transform,\ org.openhab.core.transform.actions,\ org.openhab.core.types,\ 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..9173f1f5842 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 @@ -12,18 +12,32 @@ */ package org.openhab.core.model.script; +import java.time.ZoneId; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; 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.i18n.LocaleProvider; +import org.openhab.core.i18n.TimeZoneProvider; 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; import org.openhab.core.scheduler.Scheduler; import org.openhab.core.thing.ThingRegistry; import org.openhab.core.thing.binding.ThingActions; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -38,6 +52,7 @@ * * @author Davy Vanherbergen - Initial contribution * @author Kai Kreuzer - renamed and removed interface + * @author Ravi Nadahar - added additional registries for retrieval */ @Component(immediate = true, service = ScriptServiceUtil.class) public class ScriptServiceUtil { @@ -50,6 +65,12 @@ public class ScriptServiceUtil { private final ThingRegistry thingRegistry; private final EventPublisher eventPublisher; private final ModelRepository modelRepository; + private final MetadataRegistry metadataRegistry; + private final RuleRegistry ruleRegistry; + private final ItemChannelLinkRegistry itemChannelLinkRegistry; + private final TimeZoneProvider timeZoneProvider; + private final LocaleProvider localeProvider; + private volatile @Nullable RuleManager ruleManager; private final Scheduler scheduler; private final AtomicReference scriptEngine = new AtomicReference<>(); @@ -59,11 +80,19 @@ 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 ItemChannelLinkRegistry itemChannelLinkRegistry, + final @Reference TimeZoneProvider timeZoneProvider, final @Reference LocaleProvider localeProvider, final @Reference Scheduler scheduler) { this.itemRegistry = itemRegistry; this.thingRegistry = thingRegistry; this.eventPublisher = eventPublisher; this.modelRepository = modelRepository; + this.metadataRegistry = metadataRegistry; + this.ruleRegistry = ruleRegistry; + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + this.timeZoneProvider = timeZoneProvider; + this.localeProvider = localeProvider; this.scheduler = scheduler; if (instance != null) { @@ -73,6 +102,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"); @@ -83,6 +121,9 @@ private static ScriptServiceUtil getInstance() { return instance; } + /** + * @return The {@link ItemRegistry}. + */ public static ItemRegistry getItemRegistry() { return getInstance().itemRegistry; } @@ -91,14 +132,27 @@ public ItemRegistry getItemRegistryInstance() { return itemRegistry; } + /** + * @return The {@link ThingRegistry} instance. + */ + public static ThingRegistry getThingRegistry() { + return getInstance().thingRegistry; + } + public ThingRegistry getThingRegistryInstance() { return thingRegistry; } + /** + * @return The {@link EventPublisher} instance. + */ public static EventPublisher getEventPublisher() { return getInstance().eventPublisher; } + /** + * @return The {@link ModelRepository} instance. + */ public static ModelRepository getModelRepository() { return getInstance().modelRepository; } @@ -107,6 +161,96 @@ public ModelRepository getModelRepositoryInstance() { return modelRepository; } + /** + * @return The {@link MetadataRegistry} instance. + */ + public static MetadataRegistry getMetadataRegistry() { + return getInstance().metadataRegistry; + } + + public MetadataRegistry getMetadataRegistryInstance() { + return metadataRegistry; + } + + /** + * @return The {@link RuleRegistry} instance. + */ + public static RuleRegistry getRuleRegistry() { + return getInstance().ruleRegistry; + } + + public RuleRegistry getRuleRegistryInstance() { + return ruleRegistry; + } + + /** + * @return The {@link ItemChannelLinkRegistry} instance. + */ + public static ItemChannelLinkRegistry getItemChannelLinkRegistry() { + return getInstance().itemChannelLinkRegistry; + } + + public ItemChannelLinkRegistry getItemChannelLinkRegistryInstance() { + return itemChannelLinkRegistry; + } + + /** + * @return The currently openHAB configured {@link TimeZone}. + */ + public static TimeZone getTimeZone() { + return TimeZone.getTimeZone(getInstance().timeZoneProvider.getTimeZone()); + } + + /** + * @return The currently openHAB configured {@link ZoneId}. + */ + public static ZoneId getZoneId() { + return getInstance().timeZoneProvider.getTimeZone(); + } + + /** + * @return The {@link TimeZoneProvider} instance. + */ + public static TimeZoneProvider getTimeZoneProvider() { + return getInstance().timeZoneProvider; + } + + public TimeZoneProvider getTimeZoneProviderInstance() { + return timeZoneProvider; + } + + /** + * @return The currently openHAB configured {@link Locale}. + */ + public static Locale getLocale() { + return getInstance().localeProvider.getLocale(); + } + + /** + * @return The {@link LocaleProvider} instance. + */ + public static LocaleProvider getLocaleProvide() { + return getInstance().localeProvider; + } + + public LocaleProvider getLocaleProviderInstance() { + return localeProvider; + } + + /** + * @return The {@link RuleManager} / rule engine instance or {@code null} if it doesn't exist. + */ + public @Nullable static RuleManager getRuleManager() { + return getInstance().ruleManager; + } + + public @Nullable RuleManager getRuleManagerInstance() { + return ruleManager; + } + + /** + * @return The {@link Scheduler} instance. + */ public static Scheduler getScheduler() { return getInstance().scheduler; } @@ -119,12 +263,18 @@ public static ScriptEngine getScriptEngine() { return getInstance().scriptEngine.get(); } + /** + * @return A {@link List} of currently registered {@link ActionService} instances. + */ public static List getActionServices() { - return getInstance().actionServices; + return List.copyOf(getInstance().actionServices); } + /** + * @return A {@link List} of currently registered {@link ThingActions} instances. + */ public static List getThingActions() { - return getInstance().thingActions; + return List.copyOf(getInstance().thingActions); } public List 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; } }