[js] Added 'ConfigOptionProvider' to provide filenames ending with '.js' in Profile configuration ()

* Added ConfigOptionProvider to provide file names ending with '.js' in Profile configuration

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp 2021-01-04 18:32:02 +01:00 committed by GitHub
parent ceb119b02c
commit 25947bfa9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 113 additions and 54 deletions

@ -19,6 +19,7 @@ transform/getValue.js:
```
## Test JavaScript
You can use online JavaScript testers to validate your script.
E.g. https://www.webtoolkitonline.com/javascript-tester.html

@ -35,8 +35,8 @@ import org.slf4j.LoggerFactory;
/**
* Simple cache for compiled JavaScript files.
*
* @author Thomas Kordelle - Initial contribution
* @author Thomas Kordelle - pre compiled scripts
*
*/
@NonNullByDefault
@Component(service = JavaScriptEngineManager.class)

@ -12,14 +12,26 @@
*/
package org.openhab.transform.javascript.internal;
import java.io.File;
import java.io.FilenameFilter;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigOptionProvider;
import org.openhab.core.config.core.ParameterOption;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
@ -33,21 +45,24 @@ import org.slf4j.LoggerFactory;
* @author Thomas Kordelle - pre compiled scripts
*/
@NonNullByDefault
@Component(property = { "openhab.transform=JS" })
public class JavaScriptTransformationService implements TransformationService {
@Component(service = { TransformationService.class, ConfigOptionProvider.class }, property = { "openhab.transform=JS" })
public class JavaScriptTransformationService implements TransformationService, ConfigOptionProvider {
private Logger logger = LoggerFactory.getLogger(JavaScriptTransformationService.class);
private @NonNullByDefault({}) JavaScriptEngineManager manager;
private final Logger logger = LoggerFactory.getLogger(JavaScriptTransformationService.class);
@Reference
public void setJavaScriptEngineManager(JavaScriptEngineManager manager) {
private static final char EXTENSION_SEPARATOR = '.';
private static final String PROFILE_CONFIG_URI = "profile:transform:JS";
private static final String CONFIG_PARAM_FUNCTION = "function";
private static final String[] FILE_NAME_EXTENSIONS = { "js" };
private final JavaScriptEngineManager manager;
@Activate
public JavaScriptTransformationService(final @Reference JavaScriptEngineManager manager) {
this.manager = manager;
}
public void unsetJavaScriptEngineManager(JavaScriptEngineManager manager) {
this.manager = null;
}
/**
* Transforms the input <code>source</code> by Java Script. It expects the
* transformation rule to be read from a file which is stored under the
@ -83,4 +98,47 @@ public class JavaScriptTransformationService implements TransformationService {
result);
}
}
@Override
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable String context,
@Nullable Locale locale) {
if (PROFILE_CONFIG_URI.equals(uri.toString())) {
switch (param) {
case CONFIG_PARAM_FUNCTION:
return getFilenames(FILE_NAME_EXTENSIONS).stream().map(f -> new ParameterOption(f, f))
.collect(Collectors.toList());
}
}
return null;
}
/**
* Returns a list of all files with the given extensions in the transformation folder
*/
private List<String> getFilenames(String[] validExtensions) {
File path = new File(TransformationScriptWatcher.TRANSFORM_FOLDER + File.separator);
return Arrays.asList(path.listFiles(new FileExtensionsFilter(validExtensions))).stream().map(f -> f.getName())
.collect(Collectors.toList());
}
private class FileExtensionsFilter implements FilenameFilter {
private final String[] validExtensions;
public FileExtensionsFilter(String[] validExtensions) {
this.validExtensions = validExtensions;
}
@Override
public boolean accept(@Nullable File dir, @Nullable String name) {
if (name != null) {
for (String extension : validExtensions) {
if (name.toLowerCase().endsWith(EXTENSION_SEPARATOR + extension)) {
return true;
}
}
}
return false;
}
}
}

@ -22,6 +22,7 @@ import java.nio.file.WatchEvent.Kind;
import org.openhab.core.OpenHAB;
import org.openhab.core.service.AbstractWatchService;
import org.openhab.core.transform.TransformationService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@ -29,30 +30,23 @@ import org.osgi.service.component.annotations.Reference;
* The {@link TransformationScriptWatcher} watches the transformation directory for files. If a deleted/modified file is
* detected, the script is passed to the {@link JavaScriptEngineManager}.
*
* @author Thomas Kordelle - Initial contribution
* @author Thomas Kordelle - pre compiled scripts
*
*/
@Component()
@Component
public class TransformationScriptWatcher extends AbstractWatchService {
public static final String TRANSFORM_FOLDER = OpenHAB.getConfigFolder() + File.separator
+ TransformationService.TRANSFORM_FOLDER_NAME;
private JavaScriptEngineManager manager;
private final JavaScriptEngineManager manager;
public TransformationScriptWatcher() {
@Activate
public TransformationScriptWatcher(final @Reference JavaScriptEngineManager manager) {
super(TRANSFORM_FOLDER);
}
@Reference
public void setJavaScriptEngineManager(JavaScriptEngineManager manager) {
this.manager = manager;
}
public void unsetJavaScriptEngineManager(JavaScriptEngineManager manager) {
this.manager = null;
}
@Override
public void activate() {
super.activate();

@ -13,6 +13,7 @@
package org.openhab.transform.javascript.internal.profiles;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
@ -30,29 +31,26 @@ import org.slf4j.LoggerFactory;
/**
* Profile to offer the JavascriptTransformationservice on a ItemChannelLink
*
* @author Stefan Triller - initial contribution
*
* @author Stefan Triller - Initial contribution
*/
@NonNullByDefault
public class JavascriptTransformationProfile implements StateProfile {
public class JavaScriptTransformationProfile implements StateProfile {
private final Logger logger = LoggerFactory.getLogger(JavaScriptTransformationProfile.class);
public static final ProfileTypeUID PROFILE_TYPE_UID = new ProfileTypeUID(
TransformationService.TRANSFORM_PROFILE_SCOPE, "JS");
private final Logger logger = LoggerFactory.getLogger(JavascriptTransformationProfile.class);
private static final String FUNCTION_PARAM = "function";
private static final String SOURCE_FORMAT_PARAM = "sourceFormat";
private final TransformationService service;
private final ProfileCallback callback;
private static final String FUNCTION_PARAM = "function";
private static final String SOURCE_FORMAT_PARAM = "sourceFormat";
private final @Nullable String function;
private final @Nullable String sourceFormat;
@NonNullByDefault({})
private final String function;
@NonNullByDefault({})
private final String sourceFormat;
public JavascriptTransformationProfile(ProfileCallback callback, ProfileContext context,
public JavaScriptTransformationProfile(ProfileCallback callback, ProfileContext context,
TransformationService service) {
this.service = service;
this.callback = callback;
@ -116,15 +114,23 @@ public class JavascriptTransformationProfile implements StateProfile {
}
private Type transformState(Type state) {
String result = state.toFullString();
try {
result = TransformationHelper.transform(service, function, sourceFormat, state.toFullString());
} catch (TransformationException e) {
logger.warn("Could not transform state '{}' with function '{}' and format '{}'", state, function,
sourceFormat);
String localFunction = function, localSourceFormat = sourceFormat;
if (localFunction != null && localSourceFormat != null) {
String result = state.toFullString();
try {
result = TransformationHelper.transform(service, localFunction, localSourceFormat, result);
} catch (TransformationException e) {
logger.warn("Could not transform state '{}' with function '{}' and format '{}'", state, function,
sourceFormat);
}
StringType resultType = new StringType(result);
logger.debug("Transformed '{}' into '{}'", state, resultType);
return resultType;
} else {
logger.warn(
"Please specify a function and a source format for this Profile in the '{}' and '{}' parameters. Returning the original state now.",
FUNCTION_PARAM, SOURCE_FORMAT_PARAM);
return state;
}
StringType resultType = new StringType(result);
logger.debug("Transformed '{}' into '{}'", state, resultType);
return resultType;
}
}

@ -27,37 +27,37 @@ import org.openhab.core.thing.profiles.ProfileTypeBuilder;
import org.openhab.core.thing.profiles.ProfileTypeProvider;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.transform.TransformationService;
import org.openhab.transform.javascript.internal.JavaScriptTransformationService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Profilefactory that creates the transformation profile for the javascript transformation service
*
* @author Stefan Triller - initial contribution
* {@link ProfileFactory} that creates the transformation profile for the {@link JavaScriptTransformationService}
*
* @author Stefan Triller - Initial contribution
*/
@NonNullByDefault
@Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
public class JavascriptTransformationProfileFactory implements ProfileFactory, ProfileTypeProvider {
public class JavaScriptTransformationProfileFactory implements ProfileFactory, ProfileTypeProvider {
@NonNullByDefault({})
private TransformationService service;
@Override
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
return Arrays.asList(ProfileTypeBuilder.newState(JavascriptTransformationProfile.PROFILE_TYPE_UID,
JavascriptTransformationProfile.PROFILE_TYPE_UID.getId()).build());
return Arrays.asList(ProfileTypeBuilder.newState(JavaScriptTransformationProfile.PROFILE_TYPE_UID,
JavaScriptTransformationProfile.PROFILE_TYPE_UID.getId()).build());
}
@Override
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
ProfileContext profileContext) {
return new JavascriptTransformationProfile(callback, profileContext, service);
return new JavaScriptTransformationProfile(callback, profileContext, service);
}
@Override
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
return Arrays.asList(JavascriptTransformationProfile.PROFILE_TYPE_UID);
return Arrays.asList(JavaScriptTransformationProfile.PROFILE_TYPE_UID);
}
@Reference(target = "(openhab.transform=JS)")

@ -9,8 +9,9 @@
<label>JavaScript Filename</label>
<description>Filename of the JavaScript in the transform folder. The state will be available in the variable
\"input\".</description>
<limitToOptions>false</limitToOptions>
</parameter>
<parameter name="sourceFormat" type="text" required="false">
<parameter name="sourceFormat" type="text">
<label>State Formatter</label>
<description>How to format the state on the channel before transforming it, i.e. %s or %.1f °C (default is %s)</description>
<advanced>true</advanced>