-
-
Notifications
You must be signed in to change notification settings - Fork 463
[DSL] Add extra commands/abilities to DSL scripts/rules #5481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
1c38954
ee8ae1e
28509b4
bee6c3f
b951020
96fa0c9
acf9ebb
7a3e584
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| /* | ||
| * Copyright (c) 2010-2026 Contributors to the openHAB project | ||
| * | ||
| * See the NOTICE file(s) distributed with this work for additional | ||
| * information. | ||
| * | ||
| * This program and the accompanying materials are made available under the | ||
| * terms of the Eclipse Public License 2.0 which is available at | ||
| * http://www.eclipse.org/legal/epl-2.0 | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| */ | ||
| package org.openhab.core.model.script.actions; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| import org.eclipse.jdt.annotation.NonNullByDefault; | ||
| import org.eclipse.jdt.annotation.Nullable; | ||
| import org.openhab.core.automation.RuleManager; | ||
| import org.openhab.core.automation.RuleRegistry; | ||
| import org.openhab.core.items.ItemRegistry; | ||
| import org.openhab.core.items.MetadataRegistry; | ||
| import org.openhab.core.model.script.ScriptServiceUtil; | ||
| import org.openhab.core.model.script.engine.action.ActionDoc; | ||
| import org.openhab.core.thing.ThingRegistry; | ||
| import org.osgi.framework.Bundle; | ||
| import org.osgi.framework.BundleContext; | ||
| import org.osgi.framework.FrameworkUtil; | ||
| import org.osgi.framework.ServiceReference; | ||
|
|
||
| /** | ||
| * {@link System} provides DSL access to things like OSGi instances, system registries and the ability to run other | ||
| * rules. | ||
| * | ||
| * @author Ravi Nadahar - Initial contribution | ||
| */ | ||
| @NonNullByDefault | ||
| public class System { | ||
|
|
||
| /** | ||
| * Retrieve an OSGi instance of the specified {@link Class}, if it exists. | ||
| * | ||
| * @param <T> the class type. | ||
| * @param clazz the class of the instance to get. | ||
| * @return The instance or {@code null} if the instance wasn't found. | ||
| */ | ||
| @ActionDoc(text = "acquire an OSGi instance") | ||
| public static @Nullable <T> T getInstance(Class<T> clazz) { | ||
| Bundle bundle = FrameworkUtil.getBundle(clazz); | ||
| if (bundle != null) { | ||
| BundleContext bc = bundle.getBundleContext(); | ||
| if (bc != null) { | ||
| ServiceReference<T> ref = bc.getServiceReference(clazz); | ||
| if (ref != null) { | ||
| return bc.getService(ref); | ||
| } | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * 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. | ||
| */ | ||
| @ActionDoc(text = "run the rule with the specified UID") | ||
| public static Map<String, Object> runRule(String ruleUID) { | ||
| RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); | ||
| 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 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. | ||
| */ | ||
| @ActionDoc(text = "run the rule with the specified UID and context") | ||
| public static Map<String, Object> runRule(String ruleUID, Map<String, Object> context) { | ||
| RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); | ||
| if (ruleManager.getStatus(ruleUID) == null) { | ||
| throw new IllegalArgumentException("Rule with UID '" + ruleUID + "' doesn't exist"); | ||
| } | ||
| return ruleManager.runNow(ruleUID, false, context); | ||
|
Nadahar marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| /** | ||
| * 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. | ||
| */ | ||
| @ActionDoc(text = "run the rule with the specified UID, condition evaluation setting and context") | ||
| public static Map<String, Object> runRule(String ruleUID, boolean considerConditions, Map<String, Object> context) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems weird to make considerConditions the second argument given there is a version of the method that includes the context but doesn't take the condition boolean. That is how it appears in JS. I don't know the other languages. As an end user who knows nothing of the RuleManager, I would expect the actions to be:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I followed the parameter order of the underlying methods. I'd say that it's not a big deal either way, but that perhaps it would be useful with a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When it comes to JS, that's an entirely different thing. It allows that you only specify some parameters, the rest are Here, there is no such thing, you must have an exact signature match, so the order doesn't have any significance, unless you use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we really wanted to go down the road of type-unsafety, I could also let the last parameter be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm certainly not against it.
I just brought up the JS as an example of following a pattern. Given It's not about technical stuff. It's about making decisions that allow users who are not as familiar with the source code to correctly guess what is correct. Of course, if you add the newly proposed method there is no more pattern and the order doesn't matter as much. |
||
| RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); | ||
| 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. | ||
| */ | ||
| @ActionDoc(text = "check whether the specified rule rule is enabled") | ||
| public static boolean isRuleEnabled(String ruleUID) { | ||
| RuleManager ruleManager = ScriptServiceUtil.getRuleManager(); | ||
| 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. | ||
| */ | ||
| @ActionDoc(text = "set whether the specified rule is enabled") | ||
| public static void setRuleEnabled(String ruleUID, boolean enabled) { | ||
| ScriptServiceUtil.getRuleManager().setEnabled(ruleUID, enabled); | ||
| } | ||
|
|
||
| /** | ||
| * @return The {@link ThingRegistry}. | ||
| */ | ||
| @ActionDoc(text = "get the thing registry") | ||
| public static ThingRegistry getThingRegistry() { | ||
| return ScriptServiceUtil.getThingRegistry(); | ||
| } | ||
|
|
||
| /** | ||
| * @return The {@link MetadataRegistry}. | ||
| */ | ||
| @ActionDoc(text = "get the metadata registry") | ||
| public static MetadataRegistry getMetadataRegistry() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even once you gain access to the MetadataRegistry, creating and adding or modifying is a pain because it requires creating a bunch of custom classes. Maybe not for this PR but eventually adding some helper methods like are here for rules would be beneficial.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could add more "helper tools" if somebody just give me a hint of what to make (a reference to something else that does something similar) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an example, JS provides a In addition it provides access to those methods on the Item itself. The methods include:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those methods are quite similar to those I've already created. I could certainly add variants that accepts When in comes to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unfortunate that they have called one method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think that is not as useful in DSL as it is in JS.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've already done that, but it's done as "extensions", so it becomes:
Given that Items are magically available in DSL, this should make it very easy to manipulate Item metadata. |
||
| return ScriptServiceUtil.getMetadataRegistry(); | ||
| } | ||
|
|
||
| /** | ||
| * @return The {@link ItemRegistry}. | ||
| */ | ||
| @ActionDoc(text = "get the item registry") | ||
| public static ItemRegistry getItemRegistry() { | ||
| return ScriptServiceUtil.getItemRegistry(); | ||
| } | ||
|
|
||
| /** | ||
| * @return The {@link RuleRegistry}. | ||
| */ | ||
| @ActionDoc(text = "get the rule registry") | ||
| public static RuleRegistry getRuleRegistry() { | ||
| return ScriptServiceUtil.getRuleRegistry(); | ||
| } | ||
|
|
||
| /** | ||
| * @return The {@link RuleManager}. | ||
| */ | ||
| @ActionDoc(text = "get the rule manager") | ||
| public static RuleManager getRuleManager() { | ||
| return ScriptServiceUtil.getRuleManager(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"System" is a pretty generic and almost meaningless name. Would something like "Registries" be feasible? Everything here either exposes or lets users interact with one of the registries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name can be anything, but it does handle more than registries. It also provides OSGi instances and some rule manipulation methods. I chose system because I couldn't really find anything "suitable".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also considered names like "OSGi", "Core", "OpenHAB", but they can all be considered "wrong" or misunderstood. The users don't actually have to see this name though. Look at
ScriptExecutionthat creates timers for example. My guess is that nobody knows/cares, because they don't have to qualify the method with the class name.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about "Services" or "Utility"? They don't mean much more than "System", but it's hard to find something that means much and also fits the methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about "Helper"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of the JSR223 languages have to deal with those class names. It's really only Rules DSL that doesn't. But since this is all for the benefit of Rules DSL in the first place maybe it doesn't matter.
Which brings up something I decided not to mention. Would it make more sense to split this into separate classes, one for rules, one for metadata, one for things and one for Items? Then the naming becomes obvious and clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could be done, but I'm not sure if there's much to gain by doing it. It would mean more classes that had to be added to the "implicit DSL context", but whether that has any practical consequences I don't know. Probably not.