From d276000d4aa9f8bc1778daaf95c7fb3642507a07 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Tue, 20 Aug 2024 00:39:13 +1000 Subject: [PATCH] [exec] Support transformation chaining and refactor using ChannelTransformation (#17292) * [exec] Support transformation chaining and refactor using ChannelTransformation Signed-off-by: Jimmy Tanagra Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.exec/README.md | 9 ++- .../exec/internal/handler/ExecHandler.java | 72 +++---------------- .../resources/OH-INF/i18n/exec.properties | 2 +- .../resources/OH-INF/thing/thing-types.xml | 8 ++- 4 files changed, 23 insertions(+), 68 deletions(-) diff --git a/bundles/org.openhab.binding.exec/README.md b/bundles/org.openhab.binding.exec/README.md index 4d82de4216a..4ba708c8270 100644 --- a/bundles/org.openhab.binding.exec/README.md +++ b/bundles/org.openhab.binding.exec/README.md @@ -36,13 +36,20 @@ It is not advised to run the virtual machine as superuser/root. The "command" Thing requires the command to execute on the shell. Optionally one can specify: -- `transform` - A [transformation](https://www.openhab.org/docs/configuration/transformations.html) to apply on the execution result string. +- `transform` - [Transformations](/docs/configuration/transformations.html) to apply on the execution result string. - `interval` - An interval, in seconds, the command will be repeatedly executed. Default is 60 seconds, set to 0 to avoid automatic repetition. - `timeout` - A time-out, in seconds, the execution of the command will time out, and lastly, - `autorun` - A boolean parameter to make the command execute immediately every time the input channel is sent a different openHAB command. If choosing autorun, you may wish to also set `interval=0`. Note that sending the same command a second time will not trigger execution. For each shell command, a separate Thing has to be defined. +### Transformations + +Transformations can be chained in the UI by listing each transformation on a separate line, or by separating them with the mathematical intersection character "∩". +Transformations are defined using this syntax: `TYPE(FUNCTION)`, e.g.: `JSONPATH($.path)`. +The syntax: `TYPE:FUNCTION` is also supported, e.g.: `JSONPATH:$.path`. +Please note that if the transformation failed or returned `null`, the original data will be passed through. + ```java Thing exec:command:uniquename [command="/command/to/execute here", interval=15, timeout=5, autorun=false] ``` diff --git a/bundles/org.openhab.binding.exec/src/main/java/org/openhab/binding/exec/internal/handler/ExecHandler.java b/bundles/org.openhab.binding.exec/src/main/java/org/openhab/binding/exec/internal/handler/ExecHandler.java index 30629851626..bf3e54ff553 100644 --- a/bundles/org.openhab.binding.exec/src/main/java/org/openhab/binding/exec/internal/handler/ExecHandler.java +++ b/bundles/org.openhab.binding.exec/src/main/java/org/openhab/binding/exec/internal/handler/ExecHandler.java @@ -23,11 +23,10 @@ import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.IllegalFormatException; +import java.util.List; import java.util.Objects; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -41,9 +40,7 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseThingHandler; -import org.openhab.core.transform.TransformationException; -import org.openhab.core.transform.TransformationHelper; -import org.openhab.core.transform.TransformationService; +import org.openhab.core.thing.binding.generic.ChannelTransformation; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.util.StringUtils; @@ -85,14 +82,13 @@ public class ExecHandler extends BaseThingHandler { public static final String TRANSFORM = "transform"; public static final String AUTORUN = "autorun"; - // RegEx to extract a parse a function String '(.*?)\((.*)\)' - private static final Pattern EXTRACT_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\)"); - private @Nullable ScheduledFuture executionJob; private @Nullable String lastInput; private static Runtime rt = Runtime.getRuntime(); + private @Nullable ChannelTransformation channelTransformation; + public ExecHandler(Thing thing, ExecWhitelistWatchService execWhitelistWatchService) { super(thing); this.bundleContext = FrameworkUtil.getBundle(ExecHandler.class).getBundleContext(); @@ -128,6 +124,8 @@ public class ExecHandler extends BaseThingHandler { @Override public void initialize() { + channelTransformation = new ChannelTransformation((List) getConfig().get(TRANSFORM)); + if (executionJob == null || executionJob.isCancelled()) { if ((getConfig().get(INTERVAL)) != null && ((BigDecimal) getConfig().get(INTERVAL)).intValue() > 0) { int pollingInterval = ((BigDecimal) getConfig().get(INTERVAL)).intValue(); @@ -144,6 +142,7 @@ public class ExecHandler extends BaseThingHandler { executionJob.cancel(true); executionJob = null; } + channelTransformation = null; } public void execute() { @@ -291,10 +290,9 @@ public class ExecHandler extends BaseThingHandler { outputBuilder.append(errorBuilder.toString()); String transformedResponse = Objects.requireNonNull(StringUtils.chomp(outputBuilder.toString())); - String transformation = (String) getConfig().get(TRANSFORM); - if (transformation != null && transformation.length() > 0) { - transformedResponse = transformResponse(transformedResponse, transformation); + if (channelTransformation != null) { + transformedResponse = channelTransformation.apply(transformedResponse).orElse(transformedResponse); } updateState(OUTPUT, new StringType(transformedResponse)); @@ -304,58 +302,6 @@ public class ExecHandler extends BaseThingHandler { } } - protected @Nullable String transformResponse(String response, String transformation) { - String transformedResponse; - - try { - String[] parts = splitTransformationConfig(transformation); - String transformationType = parts[0]; - String transformationFunction = parts[1]; - - TransformationService transformationService = TransformationHelper.getTransformationService(bundleContext, - transformationType); - if (transformationService != null) { - transformedResponse = transformationService.transform(transformationFunction, response); - } else { - transformedResponse = response; - logger.warn("Couldn't transform response because transformationService of type '{}' is unavailable", - transformationType); - } - } catch (TransformationException te) { - logger.warn("An exception occurred while transforming '{}' with '{}' : '{}'", response, transformation, - te.getMessage()); - - // in case of an error we return the response without any transformation - transformedResponse = response; - } - - logger.debug("Transformed response is '{}'", transformedResponse); - return transformedResponse; - } - - /** - * Splits a transformation configuration string into its two parts - the - * transformation type and the function/pattern to apply. - * - * @param transformation the string to split - * @return a string array with exactly two entries for the type and the function - */ - protected String[] splitTransformationConfig(String transformation) { - Matcher matcher = EXTRACT_FUNCTION_PATTERN.matcher(transformation); - - if (!matcher.matches()) { - throw new IllegalArgumentException("given transformation function '" + transformation - + "' does not follow the expected pattern '()'"); - } - matcher.reset(); - - matcher.find(); - String type = matcher.group(1); - String pattern = matcher.group(2); - - return new String[] { type, pattern }; - } - /** * Transforms the command string into an array. * Either invokes the shell and passes using the "c" option diff --git a/bundles/org.openhab.binding.exec/src/main/resources/OH-INF/i18n/exec.properties b/bundles/org.openhab.binding.exec/src/main/resources/OH-INF/i18n/exec.properties index 052d3a3742a..fc5d0535cbd 100644 --- a/bundles/org.openhab.binding.exec/src/main/resources/OH-INF/i18n/exec.properties +++ b/bundles/org.openhab.binding.exec/src/main/resources/OH-INF/i18n/exec.properties @@ -19,7 +19,7 @@ thing-type.config.exec.command.interval.description = Interval, in seconds, the thing-type.config.exec.command.timeout.label = Timeout thing-type.config.exec.command.timeout.description = Time out, in seconds, the execution of the command will time out thing-type.config.exec.command.transform.label = Transform -thing-type.config.exec.command.transform.description = The transformation to apply on the execution result, e.g. REGEX((.*)) +thing-type.config.exec.command.transform.description = The transformation to apply on the execution result, e.g. REGEX((.*)). You can chain transformations by listing each transformation on a separate line, or by separating them with the intersection character ∩. # channel types diff --git a/bundles/org.openhab.binding.exec/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.exec/src/main/resources/OH-INF/thing/thing-types.xml index ebcaad830ab..7b797c94543 100644 --- a/bundles/org.openhab.binding.exec/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.exec/src/main/resources/OH-INF/thing/thing-types.xml @@ -21,10 +21,12 @@ The command to execute - + - The transformation to apply on the execution result, e.g. REGEX((.*)) - REGEX((.*)) + The transformation to apply on the execution result, e.g. REGEX((.*)). + You can chain transformations by + listing each transformation on a separate line, or + by separating them with the intersection character ∩.