Raise minimum JDK version to 17 (#13276)

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K 2022-12-19 20:39:42 +01:00 committed by GitHub
parent 4d6d6443ef
commit 9f3b8e1c04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 17 additions and 1022 deletions

View File

@ -395,7 +395,6 @@
/bundles/org.openhab.persistence.rrd4j/ @openhab/add-ons-maintainers
/bundles/org.openhab.transform.bin2json/ @paulianttila
/bundles/org.openhab.transform.exec/ @openhab/add-ons-maintainers
/bundles/org.openhab.transform.javascript/ @openhab/add-ons-maintainers
/bundles/org.openhab.transform.jinja/ @jochen314
/bundles/org.openhab.transform.jsonpath/ @clinique
/bundles/org.openhab.transform.map/ @openhab/add-ons-maintainers

View File

@ -1966,11 +1966,6 @@
<artifactId>org.openhab.transform.exec</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.transform.javascript</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.transform.jinja</artifactId>

View File

@ -109,9 +109,9 @@ public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService impl
String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
bridgeHandler.getThing().getThings().stream()
.filter(t -> t.getConfiguration().getProperties().getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID)
.toString().equals(enoceanId))
bridgeHandler
.getThing().getThings().stream().filter(t -> t.getConfiguration().getProperties()
.getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID).toString().equals(enoceanId))
.findFirst().ifPresentOrElse(t -> {
// If repeated learn is not allowed => send teach out
// otherwise do nothing

View File

@ -73,8 +73,8 @@ final class ChannelSceneSilentmode extends ChannelHandlerTemplate {
LOGGER.debug("handleCommand({},{},{},{}) called.", channelUID, channelId, command, thisBridgeHandler);
Command newValue = null;
do { // just for common exit
assert thisBridgeHandler.bridgeParameters.scenes
.getChannel().existingScenes != null : "VeluxBridgeHandler.existingScenes not initialized.";
assert thisBridgeHandler.bridgeParameters.scenes.getChannel().existingScenes != null
: "VeluxBridgeHandler.existingScenes not initialized.";
if (!ThingConfiguration.exists(thisBridgeHandler, channelUID, VeluxBindingProperties.PROPERTY_SCENE_NAME)) {
LOGGER.trace("handleCommand(): aborting processing as scene name is not set.");
break;

View File

@ -1,13 +0,0 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -1,78 +0,0 @@
# JavaScript Transformation Service
Transform an input to an output using JavaScript.
It expects the transformation rule to be read from a file which is stored under the `transform` folder.
To organize the various transformations, one should use subfolders.
Simple transformation rules can also be given as an inline script.
Inline script should be start by `|` character following the JavaScript.
Beware that complex inline script could cause issues to e.g. item file parsing.
## Examples
Let's assume we have received a string containing `foo bar baz` and we're looking for a length of the last word (`baz`).
transform/getValue.js:
```
(function(i) {
var array = i.split(" ");
return array[array.length - 1].length;
})(input)
```
JavaScript transformation syntax also support additional parameters which can be passed to the script.
This can prevent redundancy when transformation is needed for several use cases, but with small adaptations.
Additional parameters can be passed to the script via [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) query syntax.
As `input` name is reserved for transformed data, it can't be used in query parameters.
Also `?` and `&` characters are reserved, but if they need to passed as additional data, they can be escaped according to URI syntax.
transform/scale.js:
```
(function(data, cf, d) {
return parseFloat(data) * parseFloat(cf) / parseFloat(d);
})(input, correctionFactor, divider)
```
`transform/scale.js?correctionFactor=1.1&divider=10`
Following example will return value `23.54` when `input` data is `214`.
### Inline script example:
Normally JavaScript transformation is given by filename, e.g. `JS(transform/getValue.js)`.
Inline script can be given by `|` character following the JavaScript, e.g. `JS(| input / 10)`.
## Test JavaScript
You can use online JavaScript testers to validate your script.
E.g. https://www.webtoolkitonline.com/javascript-tester.html
`Input` variable need to be replaced by the test string, e.g. earlier test string `foo bar baz`
```
(function(i) {
var array = i.split(" ");
return array[array.length - 1].length;
})("foo bar baz")
```
When you press execute button, tester will show the result returned by the script or error if script contains any.
## Usage as a Profile
The functionality of this `TransformationService` can be used in a `Profile` on an `ItemChannelLink` too.
To do so, it can be configured in the `.items` file as follows:
```java
String <itemName> { channel="<channelUID>"[profile="transform:JS", function="<filename>", sourceFormat="<valueFormat>"]}
```
The Javascript file (from within the `transform` folder) to be used has to be set in the `function` parameter.
The parameter `sourceFormat` is optional and can be used to format the input value **before** the transformation, i.e. `%.3f`.
If omitted the default is `%s`, so the input value will be put into the transformation without any format changes.
Please note: This profile is a one-way transformation, i.e. only values from a device towards the item are changed, the other direction is left untouched.

View File

@ -1,4 +0,0 @@
(function(i) {
var array = i.split(" ");
return array[array.length - 1].length;
})(input)

View File

@ -1,4 +0,0 @@
(function(i) {
var array = i.split(" ");
return array[array.length - 1].length;
})(input)

View File

@ -1,3 +0,0 @@
(function(i, a, b) {
return b;
})(input, a, test)

View File

@ -1,3 +0,0 @@
(function(data, cf, d) {
return parseFloat(data) * parseFloat(cf) / parseFloat(d);
})(input, correctionFactor, divider)

View File

@ -1,3 +0,0 @@
(function(i, a, b) {
return parseInt(i) + parseInt(a) + parseInt(b);
})(input, a, b)

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.transform.javascript-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-transformation-javascript" description="Javascript Transformation" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="75">mvn:org.openhab.addons.bundles/org.openhab.transform.javascript/${project.version}</bundle>
</feature>
</features>

View File

@ -1,133 +0,0 @@
/**
* Copyright (c) 2010-2022 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;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.cache.ExpiringCacheMap;
import org.openhab.core.transform.TransformationException;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
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)
public class JavaScriptEngineManager {
private final Logger logger = LoggerFactory.getLogger(JavaScriptEngineManager.class);
private final ScriptEngineManager manager = new ScriptEngineManager();
/* keep memory foot print low. max 2 concurrent threads are estimated */
private final Map<String, CompiledScript> compiledScriptMap = new ConcurrentHashMap<>(4, 0.5f, 2);
private final ExpiringCacheMap<String, CompiledScript> cacheForInlineScripts = new ExpiringCacheMap<>(
Duration.ofDays(1));
/**
* Get a pre compiled script {@link CompiledScript} from cache. If it is not in the cache, then load it from
* storage and put a pre compiled version into the cache.
*
* @param filename name of the JavaScript file to load
* @return a pre compiled script {@link CompiledScript}
* @throws TransformationException if compile of JavaScript failed
*/
protected CompiledScript getCompiledScriptByFilename(final String filename) throws TransformationException {
synchronized (compiledScriptMap) {
CompiledScript compiledScript = compiledScriptMap.get(filename);
if (compiledScript != null) {
logger.debug("Loading JavaScript {} from cache.", filename);
return compiledScript;
} else {
final String path = TransformationScriptWatcher.TRANSFORM_FOLDER + File.separator + filename;
logger.debug("Loading script {} from storage ", path);
try (final Reader reader = new InputStreamReader(new FileInputStream(path))) {
final ScriptEngine engine = manager.getEngineByName("javascript");
final CompiledScript cScript = ((Compilable) engine).compile(reader);
logger.debug("Putting compiled JavaScript {} to cache.", cScript);
compiledScriptMap.put(filename, cScript);
return cScript;
} catch (IOException | ScriptException e) {
throw new TransformationException("An error occurred while loading JavaScript. " + e.getMessage(),
e);
}
}
}
}
/**
* Get a pre compiled script {@link CompiledScript} from cache. If it is not in the cache, then compile
* it and put a pre compiled version into the cache.
*
* @param script JavaScript which should be returned as a pre compiled
* @return a pre compiled script {@link CompiledScript}
* @throws TransformationException if compile of JavaScript failed
*/
protected CompiledScript getCompiledScriptByInlineScript(final String script) throws TransformationException {
synchronized (cacheForInlineScripts) {
try {
final String hash = calcHash(script);
final CompiledScript compiledScript = cacheForInlineScripts.get(hash);
if (compiledScript != null) {
logger.debug("Loading JavaScript from cache.");
return compiledScript;
} else {
logger.debug("Compiling script {}", script);
final ScriptEngine engine = manager.getEngineByName("javascript");
final CompiledScript cScript = ((Compilable) engine).compile(script);
cacheForInlineScripts.put(hash, () -> cScript);
return cScript;
}
} catch (ScriptException | NoSuchAlgorithmException e) {
throw new TransformationException("An error occurred while compiling JavaScript. " + e.getMessage(), e);
}
}
}
/**
* remove a pre compiled script from cache.
*
* @param fileName name of the script file to remove
*/
protected void removeFromCache(String fileName) {
logger.debug("Removing JavaScript {} from cache.", fileName);
compiledScriptMap.remove(fileName);
}
private String calcHash(final String script) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(script.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
}
}

View File

@ -1,204 +0,0 @@
/**
* Copyright (c) 2010-2022 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;
import java.io.File;
import java.io.FilenameFilter;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
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;
import org.slf4j.LoggerFactory;
/**
* The implementation of {@link TransformationService} which transforms the
* input by Java Script.
*
* @author Pauli Anttila - Initial contribution
* @author Thomas Kordelle - pre compiled scripts
*/
@NonNullByDefault
@Component(service = { TransformationService.class, ConfigOptionProvider.class }, property = { "openhab.transform=JS" })
public class JavaScriptTransformationService implements TransformationService, ConfigOptionProvider {
private final Logger logger = LoggerFactory.getLogger(JavaScriptTransformationService.class);
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 static final String SCRIPT_DATA_WORD = "input";
private final JavaScriptEngineManager manager;
@Activate
public JavaScriptTransformationService(final @Reference JavaScriptEngineManager manager) {
this.manager = manager;
}
/**
* Transforms the input <code>source</code> by Java Script. If script is a filename, it expects the
* transformation rule to be read from a file which is stored under the
* 'configurations/transform' folder. To organize the various
* transformations one should use subfolders.
*
* @param filenameOrInlineScript parameter can be 1) the name of the file which contains the Java script
* transformation rule. Filename can also include additional
* variables in URI query variable format which will be injected
* to script engine. 2) inline script when starting with '|' character.
* Transformation service inject input (source) to 'input' variable.
* @param source the input to transform
*/
@Override
public @Nullable String transform(String filenameOrInlineScript, String source) throws TransformationException {
final long startTime = System.currentTimeMillis();
logger.debug("about to transform '{}' by the JavaScript '{}'", source, filenameOrInlineScript);
Map<String, String> vars = Collections.emptyMap();
String result = "";
CompiledScript cScript;
if (filenameOrInlineScript.startsWith("|")) {
// inline java script
cScript = manager.getCompiledScriptByInlineScript(filenameOrInlineScript.substring(1));
} else {
String filename = filenameOrInlineScript;
if (filename.contains("?")) {
String[] parts = filename.split("\\?");
if (parts.length > 2) {
throw new TransformationException("Questionmark should be defined only once in the filename");
}
filename = parts[0];
try {
vars = splitQuery(parts[1]);
} catch (IllegalArgumentException e) {
throw new TransformationException("Illegal filename syntax");
}
if (isReservedWordUsed(vars)) {
throw new TransformationException(
"'" + SCRIPT_DATA_WORD + "' word is reserved and can't be used in additional parameters");
}
}
cScript = manager.getCompiledScriptByFilename(filename);
}
try {
final Bindings bindings = cScript.getEngine().createBindings();
bindings.put(SCRIPT_DATA_WORD, source);
vars.forEach((k, v) -> bindings.put(k, v));
result = String.valueOf(cScript.eval(bindings));
return result;
} catch (ScriptException e) {
throw new TransformationException("An error occurred while executing script. " + e.getMessage(), e);
} finally {
logger.trace("JavaScript execution elapsed {} ms. Result: {}", System.currentTimeMillis() - startTime,
result);
}
}
private boolean isReservedWordUsed(Map<String, String> map) {
for (String key : map.keySet()) {
if (SCRIPT_DATA_WORD.equals(key)) {
return true;
}
}
return false;
}
private Map<String, String> splitQuery(@Nullable String query) throws IllegalArgumentException {
Map<String, String> result = new LinkedHashMap<>();
if (query != null) {
String[] pairs = query.split("&");
for (String pair : pairs) {
String[] keyval = pair.split("=");
if (keyval.length != 2) {
throw new IllegalArgumentException();
} else {
result.put(URLDecoder.decode(keyval[0], StandardCharsets.UTF_8),
URLDecoder.decode(keyval[1], StandardCharsets.UTF_8));
}
}
}
return 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;
}
}
}

View File

@ -1,84 +0,0 @@
/**
* Copyright (c) 2010-2022 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;
import static java.nio.file.StandardWatchEventKinds.*;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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
public class TransformationScriptWatcher extends AbstractWatchService {
public static final String TRANSFORM_FOLDER = OpenHAB.getConfigFolder() + File.separator
+ TransformationService.TRANSFORM_FOLDER_NAME;
private final Logger logger = LoggerFactory.getLogger(TransformationScriptWatcher.class);
private final JavaScriptEngineManager manager;
@Activate
public TransformationScriptWatcher(final @Reference JavaScriptEngineManager manager) {
super(TRANSFORM_FOLDER);
this.manager = manager;
}
@Override
public void activate() {
super.activate();
}
@Override
protected boolean watchSubDirectories() {
return true;
}
@Override
protected Kind<?>[] getWatchEventKinds(Path directory) {
return new Kind<?>[] { ENTRY_DELETE, ENTRY_MODIFY };
}
@SuppressWarnings("unchecked")
@Override
protected void processWatchEvent(WatchEvent<?> event, Kind<?> kind, Path path) {
logger.debug("New watch event {} for path {}.", kind, path);
if (kind == OVERFLOW) {
return;
}
final WatchEvent<Path> ev = (WatchEvent<Path>) event;
final Path filename = ev.context();
logger.debug("Reloading javascript file {}.", filename);
manager.removeFromCache(filename.toString());
}
}

View File

@ -1,136 +0,0 @@
/**
* Copyright (c) 2010-2022 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 an 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;
}
}
}

View File

@ -1,71 +0,0 @@
/**
* Copyright (c) 2010-2022 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;
}
}

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="profile:transform:JS">
<parameter name="function" type="text" required="true">
<label>JavaScript Filename or Inline Script</label>
<description>Filename of the JavaScript in the transform folder or inline script starting with "|" character. The
state will be available in the variable "input".</description>
<limitToOptions>false</limitToOptions>
</parameter>
<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>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -1,8 +0,0 @@
profile.config.transform.JS.function.label = JavaScript Filename
profile.config.transform.JS.function.description = Filename of the JavaScript in the transform folder. The state will be available in the variable \"input\".
profile.config.transform.JS.sourceFormat.label = State Formatter
profile.config.transform.JS.sourceFormat.description = How to format the state on the channel before transforming it, i.e. %s or %.1f °C (default is %s).
# profile type
profile-type.transform.JS.label = JS

View File

@ -1,8 +0,0 @@
profile.config.transform.JS.function.label = Dateiname
profile.config.transform.JS.function.description = Datei mit dem JavaScript Code. Der Item State wird in der Variable `input` an das Skript übergeben.
profile.config.transform.JS.sourceFormat.label = State Format
profile.config.transform.JS.sourceFormat.description = Format, welches auf den State des Channels angewendet.wird, bevor das Mapping erfolgt (z.B. %s oder %.1f °C, Standard ist %s).
# profile type
profile-type.transform.JS.label = JavaScript

View File

@ -1,8 +0,0 @@
profile.config.transform.JS.function.label = JavaScript-tiedostonimi
profile.config.transform.JS.function.description = JavaScriptin tiedostonimi muunnoskansiossa. Tila on käytettävissä muuttujassa "input".
profile.config.transform.JS.sourceFormat.label = Tilan muotoilija
profile.config.transform.JS.sourceFormat.description = Miten kanavan tila muotoillaan ennen muunnosta, esim. %s tai %.1f °C (oletus on %s).
# profile type
profile-type.transform.JS.label = JS

View File

@ -1,8 +0,0 @@
profile.config.transform.JS.function.label = Nom du fichier JavaScript
profile.config.transform.JS.function.description = Nom du fichier JavaScript dans le dossier transform. L'état sera disponible dans la variable "input".
profile.config.transform.JS.sourceFormat.label = Formatage de l'état
profile.config.transform.JS.sourceFormat.description = Comment formater l'état du canal avant de le transformer, par exemple %s ou %.1f °C (par défaut %s).
# profile type
profile-type.transform.JS.label = JS

View File

@ -1,8 +0,0 @@
profile.config.transform.JS.function.label = Javascript fájl neve
profile.config.transform.JS.function.description = A javascript átalakító neve a mappában. Az átalakítandó érték elérhető lesz az "input" nevű változóban.
profile.config.transform.JS.sourceFormat.label = Formázandó állapotérték
profile.config.transform.JS.sourceFormat.description = Hogyan formázzuk az állapot csatornát, mielőtt átalakítjuk. Pl.\: %s vagy %.1f °C (alapértelmezetten %s).
# profile type
profile-type.transform.JS.label = JS

View File

@ -1,193 +0,0 @@
/**
* Copyright (c) 2010-2022 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;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.core.transform.TransformationException;
import org.osgi.framework.BundleContext;
/**
* @author Pauli Anttila - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class JavaScriptTransformationServiceTest {
private static final boolean NASHORN_AVAILABLE = isNashornAvailable();
private static final String BASE_FOLDER = "target";
private static final String SRC_FOLDER = "conf";
private static final String CONFIG_FOLDER = BASE_FOLDER + File.separator + SRC_FOLDER;
private @Mock BundleContext bundleContext;
private TestableJavaScriptTransformationService processor;
private class TestableJavaScriptTransformationService extends JavaScriptTransformationService {
public TestableJavaScriptTransformationService(JavaScriptEngineManager manager) {
super(manager);
}
};
/**
* Returns if the Nashorn JavaScript engine is available based on the Java specification version property.
* Nashorn has been removed from JDK 15 and onwards.
*
* @return {@code true} if Nashorn is available, {@code false} otherwise
*/
private static boolean isNashornAvailable() {
try {
String javaVersion = System.getProperty("java.specification.version");
return javaVersion == null ? false : Long.parseLong(javaVersion) < 15;
} catch (NumberFormatException e) {
return false;
}
}
@BeforeEach
public void setUp() throws IOException {
assumeTrue(NASHORN_AVAILABLE);
JavaScriptEngineManager manager = new JavaScriptEngineManager();
processor = new TestableJavaScriptTransformationService(manager);
copyDirectory(SRC_FOLDER, CONFIG_FOLDER);
}
@AfterEach
public void tearDown() throws IOException {
Path path = Path.of(CONFIG_FOLDER);
if (Files.exists(path)) {
try (Stream<Path> walk = Files.walk(path)) {
walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}
}
}
private void copyDirectory(String from, String to) throws IOException {
Files.walk(Paths.get(from)).forEach(fromPath -> {
Path toPath = Paths.get(to, fromPath.toString().substring(from.length()));
try {
Files.copy(fromPath, toPath);
} catch (IOException e) {
}
});
}
@Test
public void testInlineScript() throws Exception {
final String DATA = "100";
final String SCRIPT = "| input / 10";
String transformedResponse = processor.transform(SCRIPT, DATA);
assertEquals("10.0", transformedResponse);
}
@Test
public void testInlineScriptIncludingPipe() throws Exception {
final String DATA = "1";
final String SCRIPT = "| false || (input == '1')";
String transformedResponse = processor.transform(SCRIPT, DATA);
assertEquals("true", transformedResponse);
}
@Test
public void testReadmeExampleWithoutSubFolder() throws Exception {
final String DATA = "foo bar baz";
final String SCRIPT = "readme.js";
String transformedResponse = processor.transform(SCRIPT, DATA);
assertEquals("3", transformedResponse);
}
@Test
public void testReadmeExampleWithSubFolders() throws Exception {
final String DATA = "foo bar baz";
final String SCRIPT = "js/readme/readme.js";
String transformedResponse = processor.transform(SCRIPT, DATA);
assertEquals("3", transformedResponse);
}
@Test
public void testReadmeScaleExample() throws Exception {
final String DATA = "214";
final String SCRIPT = "scale.js?correctionFactor=1.1&divider=10.js";
String transformedResponse = processor.transform(SCRIPT, DATA);
assertEquals("23.54", transformedResponse);
}
@Test
public void testAdditionalVariables() throws Exception {
final String DATA = "100";
final String SCRIPT = "sum.js?a=10&b=1";
String transformedResponse = processor.transform(SCRIPT, DATA);
assertEquals("111", transformedResponse);
}
@Test
public void testIllegalVariableName() throws Exception {
final String DATA = "100";
final String SCRIPT = "sum.js?a=10&input=fail&b=1";
Exception exception = assertThrows(TransformationException.class, () -> processor.transform(SCRIPT, DATA));
assertEquals("'input' word is reserved and can't be used in additional parameters", exception.getMessage());
}
@Test
public void testIllegalQuestionmarkSequence() throws Exception {
final String DATA = "100";
final String SCRIPT = "sum.js?a=1&test=ab?d&b=2";
Exception exception = assertThrows(TransformationException.class, () -> processor.transform(SCRIPT, DATA));
assertEquals("Questionmark should be defined only once in the filename", exception.getMessage());
}
@Test
public void testIllegalAmbersandSequence() throws Exception {
final String DATA = "foo";
final String SCRIPT = "returntest.js?a=1&test=ab&d&b=2";
Exception exception = assertThrows(TransformationException.class, () -> processor.transform(SCRIPT, DATA));
assertEquals("Illegal filename syntax", exception.getMessage());
}
@Test
public void testEncodedSpecialCharacters() throws Exception {
final String DATA = "100";
final String SCRIPT = "returntest.js?a=1&test=ab%3Fd%26f&b=2";
String transformedResponse = processor.transform(SCRIPT, DATA);
assertEquals("ab?d&f", transformedResponse);
}
}

View File

@ -34,7 +34,6 @@
<!-- transformations -->
<module>org.openhab.transform.bin2json</module>
<module>org.openhab.transform.exec</module>
<module>org.openhab.transform.javascript</module>
<module>org.openhab.transform.jinja</module>
<module>org.openhab.transform.jsonpath</module>
<module>org.openhab.transform.map</module>

View File

@ -19,7 +19,7 @@ Test-Cases: ${classes;CONCRETE;PUBLIC;NAMED;*Test}
-runsystempackages: sun.reflect
-runfw: org.eclipse.osgi
-runee: JavaSE-11
-runee: JavaSE-17
# An unused random HTTP port is used during tests to prevent resource conflicts
# This property is set by the build-helper-maven-plugin in the itests pom.xml
@ -39,7 +39,6 @@ Export-Package:
bnd.identity;id='junit-jupiter-engine'
-runproperties: \
nashorn.args=--no-deprecation-warning,\
org.ops4j.pax.logging.DefaultServiceLog.level=WARN
-runvm.java9plus: \

21
pom.xml
View File

@ -63,7 +63,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<oh.java.version>11</oh.java.version>
<oh.java.version>17</oh.java.version>
<maven.compiler.source>${oh.java.version}</maven.compiler.source>
<maven.compiler.target>${oh.java.version}</maven.compiler.target>
<maven.compiler.compilerVersion>${oh.java.version}</maven.compiler.compilerVersion>
@ -77,7 +77,8 @@
<netty.version>4.1.72.Final</netty.version>
<okhttp.version>3.14.9</okhttp.version>
<sat.version>0.13.0</sat.version>
<spotless.version>2.0.3</spotless.version>
<spotless.version>2.28.0</spotless.version>
<spotless.eclipse.version>4.21.0</spotless.eclipse.version>
<bnd.importpackage/>
<bnd.exportpackage/>
@ -311,12 +312,12 @@ Import-Package: \\
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId>
<version>2.11.1</version>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
<version>3.28.0</version>
<version>3.30.0</version>
</dependency>
</dependencies>
</plugin>
@ -384,7 +385,7 @@ Import-Package: \\
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M7</version>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
@ -479,7 +480,7 @@ Import-Package: \\
<java>
<eclipse>
<file>openhab_codestyle.xml</file>
<version>4.13.0</version>
<version>${spotless.eclipse.version}</version>
</eclipse>
<removeUnusedImports/>
<importOrder>
@ -507,7 +508,7 @@ Import-Package: \\
<files>
<file>openhab_wst_xml_files.prefs</file>
</files>
<version>4.13.0</version>
<version>${spotless.eclipse.version}</version>
</eclipseWtp>
<trimTrailingWhitespace/>
<endWithNewline/>
@ -522,7 +523,7 @@ Import-Package: \\
<files>
<file>openhab_wst_feature_file.prefs</file>
</files>
<version>4.13.0</version>
<version>${spotless.eclipse.version}</version>
</eclipseWtp>
<trimTrailingWhitespace/>
<endWithNewline/>
@ -537,7 +538,7 @@ Import-Package: \\
<files>
<file>openhab_wst_pom_file.prefs</file>
</files>
<version>4.13.0</version>
<version>${spotless.eclipse.version}</version>
</eclipseWtp>
<trimTrailingWhitespace/>
<endWithNewline/>
@ -599,7 +600,7 @@ Import-Package: \\
<configuration>
<rules>
<requireJavaVersion>
<version>[11.0,18.0)</version>
<version>[17.0,18.0)</version>
</requireJavaVersion>
</rules>
</configuration>