]> git.basschouten.com Git - openhab-addons.git/commitdiff
[exec] Support transformation chaining and refactor using ChannelTransformation ...
authorjimtng <2554958+jimtng@users.noreply.github.com>
Mon, 19 Aug 2024 14:39:13 +0000 (00:39 +1000)
committerGitHub <noreply@github.com>
Mon, 19 Aug 2024 14:39:13 +0000 (16:39 +0200)
* [exec] Support transformation chaining and refactor using ChannelTransformation

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
bundles/org.openhab.binding.exec/README.md
bundles/org.openhab.binding.exec/src/main/java/org/openhab/binding/exec/internal/handler/ExecHandler.java
bundles/org.openhab.binding.exec/src/main/resources/OH-INF/i18n/exec.properties
bundles/org.openhab.binding.exec/src/main/resources/OH-INF/thing/thing-types.xml

index 4d82de4216a02373eb8b5d6aac09dfbbc132c2f3..4ba708c8270a4db1a9e1babe8dc5cc4772d4840c 100644 (file)
@@ -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]
 ```
index 3062985162688b2db6667302f9f7f203e741678c..bf3e54ff553591115e87cad7be15bb6012ed1f01 100644 (file)
@@ -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 <code>'(.*?)\((.*)\)'</code>
-    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<String>) 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 '<function>(<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
index 052d3a3742a7888b3e601d2361984c5a0ae4dcbd..fc5d0535cbd9410d4a6a4f60a85f84d2aa08875f 100644 (file)
@@ -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
 
index ebcaad830ab8ea12d035143e81432d96accad79b..7b797c94543de5d9907ff6cdf92888a4f3e13e44 100644 (file)
                                <label>Command</label>
                                <description>The command to execute</description>
                        </parameter>
-                       <parameter name="transform" type="text" required="false">
+                       <parameter name="transform" type="text" required="false" multiple="true">
                                <label>Transform</label>
-                               <description>The transformation to apply on the execution result, e.g. REGEX((.*))</description>
-                               <default>REGEX((.*))</default>
+                               <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 ∩.</description>
                        </parameter>
                        <parameter name="interval" type="integer" required="false">
                                <label>Interval</label>