* Added ConfigOptionProvider to provide file names ending with '.js' in Profile configuration
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
```
## Test JavaScript
+
You can use online JavaScript testers to validate your script.
E.g. https://www.webtoolkitonline.com/javascript-tester.html
/**
* Simple cache for compiled JavaScript files.
*
+ * @author Thomas Kordelle - Initial contribution
* @author Thomas Kordelle - pre compiled scripts
- *
*/
@NonNullByDefault
@Component(service = JavaScriptEngineManager.class)
*/
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;
* @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) {
- this.manager = manager;
- }
+ private static final char EXTENSION_SEPARATOR = '.';
- public void unsetJavaScriptEngineManager(JavaScriptEngineManager manager) {
- this.manager = null;
+ 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;
}
/**
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;
+ }
+ }
}
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;
* 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();
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.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;
+import org.openhab.core.thing.profiles.ProfileTypeUID;
+import org.openhab.core.thing.profiles.StateProfile;
+import org.openhab.core.transform.TransformationException;
+import org.openhab.core.transform.TransformationHelper;
+import org.openhab.core.transform.TransformationService;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.Type;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Profile to offer the JavascriptTransformationservice on a ItemChannelLink
+ *
+ * @author Stefan Triller - Initial contribution
+ */
+@NonNullByDefault
+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 static final String FUNCTION_PARAM = "function";
+ private static final String SOURCE_FORMAT_PARAM = "sourceFormat";
+
+ private final TransformationService service;
+ private final ProfileCallback callback;
+
+ private final @Nullable String function;
+ private final @Nullable String sourceFormat;
+
+ public JavaScriptTransformationProfile(ProfileCallback callback, ProfileContext context,
+ TransformationService service) {
+ this.service = service;
+ this.callback = callback;
+
+ Object paramFunction = context.getConfiguration().get(FUNCTION_PARAM);
+ Object paramSource = context.getConfiguration().get(SOURCE_FORMAT_PARAM);
+
+ logger.debug("Profile configured with '{}'='{}', '{}'={}", FUNCTION_PARAM, paramFunction, SOURCE_FORMAT_PARAM,
+ paramSource);
+ // SOURCE_FORMAT_PARAM is an advanced parameter and we assume "%s" if it is not set
+ if (paramSource == null) {
+ paramSource = "%s";
+ }
+ if (paramFunction instanceof String && paramSource instanceof String) {
+ function = (String) paramFunction;
+ sourceFormat = (String) paramSource;
+ } else {
+ logger.error("Parameter '{}' and '{}' have to be Strings. Profile will be inactive.", FUNCTION_PARAM,
+ SOURCE_FORMAT_PARAM);
+ function = null;
+ sourceFormat = null;
+ }
+ }
+
+ @Override
+ public ProfileTypeUID getProfileTypeUID() {
+ return PROFILE_TYPE_UID;
+ }
+
+ @Override
+ public void onStateUpdateFromItem(State state) {
+ }
+
+ @Override
+ public void onCommandFromItem(Command command) {
+ callback.handleCommand(command);
+ }
+
+ @Override
+ public void onCommandFromHandler(Command command) {
+ if (function == null || sourceFormat == null) {
+ logger.warn(
+ "Please specify a function and a source format for this Profile in the '{}', and '{}' parameters. Returning the original command now.",
+ FUNCTION_PARAM, SOURCE_FORMAT_PARAM);
+ callback.sendCommand(command);
+ return;
+ }
+ callback.sendCommand((Command) transformState(command));
+ }
+
+ @Override
+ public void onStateUpdateFromHandler(State state) {
+ if (function == null || sourceFormat == null) {
+ 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);
+ callback.sendUpdate(state);
+ return;
+ }
+ callback.sendUpdate((State) transformState(state));
+ }
+
+ private Type transformState(Type state) {
+ 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;
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.transform.javascript.internal.profiles;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.profiles.Profile;
+import org.openhab.core.thing.profiles.ProfileCallback;
+import org.openhab.core.thing.profiles.ProfileContext;
+import org.openhab.core.thing.profiles.ProfileFactory;
+import org.openhab.core.thing.profiles.ProfileType;
+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;
+
+/**
+ * {@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 {
+
+ @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());
+ }
+
+ @Override
+ public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
+ ProfileContext profileContext) {
+ return new JavaScriptTransformationProfile(callback, profileContext, service);
+ }
+
+ @Override
+ public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
+ return Arrays.asList(JavaScriptTransformationProfile.PROFILE_TYPE_UID);
+ }
+
+ @Reference(target = "(openhab.transform=JS)")
+ public void addTransformationService(TransformationService service) {
+ this.service = service;
+ }
+
+ public void removeTransformationService(TransformationService service) {
+ this.service = null;
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 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.transform.javascript.internal.profiles;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.profiles.ProfileCallback;
-import org.openhab.core.thing.profiles.ProfileContext;
-import org.openhab.core.thing.profiles.ProfileTypeUID;
-import org.openhab.core.thing.profiles.StateProfile;
-import org.openhab.core.transform.TransformationException;
-import org.openhab.core.transform.TransformationHelper;
-import org.openhab.core.transform.TransformationService;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.State;
-import org.openhab.core.types.Type;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Profile to offer the JavascriptTransformationservice on a ItemChannelLink
- *
- * @author Stefan Triller - initial contribution
- *
- */
-@NonNullByDefault
-public class JavascriptTransformationProfile implements StateProfile {
-
- public static final ProfileTypeUID PROFILE_TYPE_UID = new ProfileTypeUID(
- TransformationService.TRANSFORM_PROFILE_SCOPE, "JS");
-
- private final Logger logger = LoggerFactory.getLogger(JavascriptTransformationProfile.class);
-
- private final TransformationService service;
- private final ProfileCallback callback;
-
- private static final String FUNCTION_PARAM = "function";
- private static final String SOURCE_FORMAT_PARAM = "sourceFormat";
-
- @NonNullByDefault({})
- private final String function;
- @NonNullByDefault({})
- private final String sourceFormat;
-
- public JavascriptTransformationProfile(ProfileCallback callback, ProfileContext context,
- TransformationService service) {
- this.service = service;
- this.callback = callback;
-
- Object paramFunction = context.getConfiguration().get(FUNCTION_PARAM);
- Object paramSource = context.getConfiguration().get(SOURCE_FORMAT_PARAM);
-
- logger.debug("Profile configured with '{}'='{}', '{}'={}", FUNCTION_PARAM, paramFunction, SOURCE_FORMAT_PARAM,
- paramSource);
- // SOURCE_FORMAT_PARAM is an advanced parameter and we assume "%s" if it is not set
- if (paramSource == null) {
- paramSource = "%s";
- }
- if (paramFunction instanceof String && paramSource instanceof String) {
- function = (String) paramFunction;
- sourceFormat = (String) paramSource;
- } else {
- logger.error("Parameter '{}' and '{}' have to be Strings. Profile will be inactive.", FUNCTION_PARAM,
- SOURCE_FORMAT_PARAM);
- function = null;
- sourceFormat = null;
- }
- }
-
- @Override
- public ProfileTypeUID getProfileTypeUID() {
- return PROFILE_TYPE_UID;
- }
-
- @Override
- public void onStateUpdateFromItem(State state) {
- }
-
- @Override
- public void onCommandFromItem(Command command) {
- callback.handleCommand(command);
- }
-
- @Override
- public void onCommandFromHandler(Command command) {
- if (function == null || sourceFormat == null) {
- logger.warn(
- "Please specify a function and a source format for this Profile in the '{}', and '{}' parameters. Returning the original command now.",
- FUNCTION_PARAM, SOURCE_FORMAT_PARAM);
- callback.sendCommand(command);
- return;
- }
- callback.sendCommand((Command) transformState(command));
- }
-
- @Override
- public void onStateUpdateFromHandler(State state) {
- if (function == null || sourceFormat == null) {
- 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);
- callback.sendUpdate(state);
- return;
- }
- callback.sendUpdate((State) transformState(state));
- }
-
- 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);
- }
- StringType resultType = new StringType(result);
- logger.debug("Transformed '{}' into '{}'", state, resultType);
- return resultType;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 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.transform.javascript.internal.profiles;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.thing.profiles.Profile;
-import org.openhab.core.thing.profiles.ProfileCallback;
-import org.openhab.core.thing.profiles.ProfileContext;
-import org.openhab.core.thing.profiles.ProfileFactory;
-import org.openhab.core.thing.profiles.ProfileType;
-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.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
- *
- */
-@NonNullByDefault
-@Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
-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());
- }
-
- @Override
- public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
- ProfileContext profileContext) {
- return new JavascriptTransformationProfile(callback, profileContext, service);
- }
-
- @Override
- public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
- return Arrays.asList(JavascriptTransformationProfile.PROFILE_TYPE_UID);
- }
-
- @Reference(target = "(openhab.transform=JS)")
- public void addTransformationService(TransformationService service) {
- this.service = service;
- }
-
- public void removeTransformationService(TransformationService service) {
- this.service = null;
- }
-}
<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>
+++ /dev/null
-Bundle resources go in here!