Skip to content

Commit b82379c

Browse files
authored
[exec] Support transformation chaining and refactor using ChannelTransformation (openhab#17292)
* [exec] Support transformation chaining and refactor using ChannelTransformation Signed-off-by: Jimmy Tanagra <[email protected]>
1 parent 95a964c commit b82379c

File tree

4 files changed

+23
-68
lines changed

4 files changed

+23
-68
lines changed

bundles/org.openhab.binding.exec/README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,20 @@ It is not advised to run the virtual machine as superuser/root.
3636
The "command" Thing requires the command to execute on the shell.
3737
Optionally one can specify:
3838

39-
- `transform` - A [transformation](https://www.openhab.org/docs/configuration/transformations.html) to apply on the execution result string.
39+
- `transform` - [Transformations](/docs/configuration/transformations.html) to apply on the execution result string.
4040
- `interval` - An interval, in seconds, the command will be repeatedly executed. Default is 60 seconds, set to 0 to avoid automatic repetition.
4141
- `timeout` - A time-out, in seconds, the execution of the command will time out, and lastly,
4242
- `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.
4343

4444
For each shell command, a separate Thing has to be defined.
4545

46+
### Transformations
47+
48+
Transformations can be chained in the UI by listing each transformation on a separate line, or by separating them with the mathematical intersection character "∩".
49+
Transformations are defined using this syntax: `TYPE(FUNCTION)`, e.g.: `JSONPATH($.path)`.
50+
The syntax: `TYPE:FUNCTION` is also supported, e.g.: `JSONPATH:$.path`.
51+
Please note that if the transformation failed or returned `null`, the original data will be passed through.
52+
4653
```java
4754
Thing exec:command:uniquename [command="/command/to/execute here", interval=15, timeout=5, autorun=false]
4855
```

bundles/org.openhab.binding.exec/src/main/java/org/openhab/binding/exec/internal/handler/ExecHandler.java

+9-63
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@
2323
import java.util.Calendar;
2424
import java.util.Date;
2525
import java.util.IllegalFormatException;
26+
import java.util.List;
2627
import java.util.Objects;
2728
import java.util.concurrent.ScheduledFuture;
2829
import java.util.concurrent.TimeUnit;
29-
import java.util.regex.Matcher;
30-
import java.util.regex.Pattern;
3130
import java.util.regex.PatternSyntaxException;
3231

3332
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -41,9 +40,7 @@
4140
import org.openhab.core.thing.Thing;
4241
import org.openhab.core.thing.ThingStatus;
4342
import org.openhab.core.thing.binding.BaseThingHandler;
44-
import org.openhab.core.transform.TransformationException;
45-
import org.openhab.core.transform.TransformationHelper;
46-
import org.openhab.core.transform.TransformationService;
43+
import org.openhab.core.thing.binding.generic.ChannelTransformation;
4744
import org.openhab.core.types.Command;
4845
import org.openhab.core.types.RefreshType;
4946
import org.openhab.core.util.StringUtils;
@@ -85,14 +82,13 @@ public class ExecHandler extends BaseThingHandler {
8582
public static final String TRANSFORM = "transform";
8683
public static final String AUTORUN = "autorun";
8784

88-
// RegEx to extract a parse a function String <code>'(.*?)\((.*)\)'</code>
89-
private static final Pattern EXTRACT_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\)");
90-
9185
private @Nullable ScheduledFuture<?> executionJob;
9286
private @Nullable String lastInput;
9387

9488
private static Runtime rt = Runtime.getRuntime();
9589

90+
private @Nullable ChannelTransformation channelTransformation;
91+
9692
public ExecHandler(Thing thing, ExecWhitelistWatchService execWhitelistWatchService) {
9793
super(thing);
9894
this.bundleContext = FrameworkUtil.getBundle(ExecHandler.class).getBundleContext();
@@ -128,6 +124,8 @@ public void handleCommand(ChannelUID channelUID, Command command) {
128124

129125
@Override
130126
public void initialize() {
127+
channelTransformation = new ChannelTransformation((List<String>) getConfig().get(TRANSFORM));
128+
131129
if (executionJob == null || executionJob.isCancelled()) {
132130
if ((getConfig().get(INTERVAL)) != null && ((BigDecimal) getConfig().get(INTERVAL)).intValue() > 0) {
133131
int pollingInterval = ((BigDecimal) getConfig().get(INTERVAL)).intValue();
@@ -144,6 +142,7 @@ public void dispose() {
144142
executionJob.cancel(true);
145143
executionJob = null;
146144
}
145+
channelTransformation = null;
147146
}
148147

149148
public void execute() {
@@ -291,10 +290,9 @@ public void execute() {
291290
outputBuilder.append(errorBuilder.toString());
292291

293292
String transformedResponse = Objects.requireNonNull(StringUtils.chomp(outputBuilder.toString()));
294-
String transformation = (String) getConfig().get(TRANSFORM);
295293

296-
if (transformation != null && transformation.length() > 0) {
297-
transformedResponse = transformResponse(transformedResponse, transformation);
294+
if (channelTransformation != null) {
295+
transformedResponse = channelTransformation.apply(transformedResponse).orElse(transformedResponse);
298296
}
299297

300298
updateState(OUTPUT, new StringType(transformedResponse));
@@ -304,58 +302,6 @@ public void execute() {
304302
}
305303
}
306304

307-
protected @Nullable String transformResponse(String response, String transformation) {
308-
String transformedResponse;
309-
310-
try {
311-
String[] parts = splitTransformationConfig(transformation);
312-
String transformationType = parts[0];
313-
String transformationFunction = parts[1];
314-
315-
TransformationService transformationService = TransformationHelper.getTransformationService(bundleContext,
316-
transformationType);
317-
if (transformationService != null) {
318-
transformedResponse = transformationService.transform(transformationFunction, response);
319-
} else {
320-
transformedResponse = response;
321-
logger.warn("Couldn't transform response because transformationService of type '{}' is unavailable",
322-
transformationType);
323-
}
324-
} catch (TransformationException te) {
325-
logger.warn("An exception occurred while transforming '{}' with '{}' : '{}'", response, transformation,
326-
te.getMessage());
327-
328-
// in case of an error we return the response without any transformation
329-
transformedResponse = response;
330-
}
331-
332-
logger.debug("Transformed response is '{}'", transformedResponse);
333-
return transformedResponse;
334-
}
335-
336-
/**
337-
* Splits a transformation configuration string into its two parts - the
338-
* transformation type and the function/pattern to apply.
339-
*
340-
* @param transformation the string to split
341-
* @return a string array with exactly two entries for the type and the function
342-
*/
343-
protected String[] splitTransformationConfig(String transformation) {
344-
Matcher matcher = EXTRACT_FUNCTION_PATTERN.matcher(transformation);
345-
346-
if (!matcher.matches()) {
347-
throw new IllegalArgumentException("given transformation function '" + transformation
348-
+ "' does not follow the expected pattern '<function>(<pattern>)'");
349-
}
350-
matcher.reset();
351-
352-
matcher.find();
353-
String type = matcher.group(1);
354-
String pattern = matcher.group(2);
355-
356-
return new String[] { type, pattern };
357-
}
358-
359305
/**
360306
* Transforms the command string into an array.
361307
* Either invokes the shell and passes using the "c" option

bundles/org.openhab.binding.exec/src/main/resources/OH-INF/i18n/exec.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ thing-type.config.exec.command.interval.description = Interval, in seconds, the
1919
thing-type.config.exec.command.timeout.label = Timeout
2020
thing-type.config.exec.command.timeout.description = Time out, in seconds, the execution of the command will time out
2121
thing-type.config.exec.command.transform.label = Transform
22-
thing-type.config.exec.command.transform.description = The transformation to apply on the execution result, e.g. REGEX((.*))
22+
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 ∩.
2323

2424
# channel types
2525

bundles/org.openhab.binding.exec/src/main/resources/OH-INF/thing/thing-types.xml

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
<label>Command</label>
2222
<description>The command to execute</description>
2323
</parameter>
24-
<parameter name="transform" type="text" required="false">
24+
<parameter name="transform" type="text" required="false" multiple="true">
2525
<label>Transform</label>
26-
<description>The transformation to apply on the execution result, e.g. REGEX((.*))</description>
27-
<default>REGEX((.*))</default>
26+
<description>The transformation to apply on the execution result, e.g. REGEX((.*)).
27+
You can chain transformations by
28+
listing each transformation on a separate line, or
29+
by separating them with the intersection character ∩.</description>
2830
</parameter>
2931
<parameter name="interval" type="integer" required="false">
3032
<label>Interval</label>

0 commit comments

Comments
 (0)