Add dynamic scripting-language transformation service (#3487)
* Add dynamic scripting language transformation service This replaced SCRIPT transformation with one specific to each language e.g. JS, RB, GROOVY, etc. Co-authored-by: Jan N. Klug <github@klug.nrw> Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>pull/3548/head
parent
fa37a467ac
commit
fbaf992666
|
@ -12,8 +12,13 @@
|
|||
*/
|
||||
package org.openhab.core.automation.module.script;
|
||||
|
||||
import static org.openhab.core.automation.module.script.profile.ScriptProfileFactory.PROFILE_CONFIG_URI_PREFIX;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -34,11 +39,15 @@ import javax.script.ScriptException;
|
|||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.module.script.internal.ScriptEngineFactoryHelper;
|
||||
import org.openhab.core.automation.module.script.profile.ScriptProfile;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.common.registry.RegistryChangeListener;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionBuilder;
|
||||
import org.openhab.core.config.core.ConfigDescriptionProvider;
|
||||
import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||
import org.openhab.core.config.core.ConfigOptionProvider;
|
||||
import org.openhab.core.config.core.ConfigParser;
|
||||
import org.openhab.core.config.core.ParameterOption;
|
||||
import org.openhab.core.transform.Transformation;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
|
@ -48,8 +57,6 @@ import org.osgi.service.component.annotations.Activate;
|
|||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -59,35 +66,50 @@ import org.slf4j.LoggerFactory;
|
|||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@Component(service = { TransformationService.class, ScriptTransformationService.class,
|
||||
ConfigOptionProvider.class }, property = { "openhab.transform=SCRIPT" })
|
||||
@NonNullByDefault
|
||||
public class ScriptTransformationService
|
||||
implements TransformationService, RegistryChangeListener<Transformation>, ConfigOptionProvider {
|
||||
@Component(factory = "org.openhab.core.automation.module.script.transformation.factory", service = {
|
||||
TransformationService.class, ScriptTransformationService.class, ConfigOptionProvider.class,
|
||||
ConfigDescriptionProvider.class })
|
||||
public class ScriptTransformationService implements TransformationService, ConfigOptionProvider,
|
||||
ConfigDescriptionProvider, RegistryChangeListener<Transformation> {
|
||||
public static final String SCRIPT_TYPE_PROPERTY_NAME = "openhab.transform.script.scriptType";
|
||||
public static final String OPENHAB_TRANSFORMATION_SCRIPT = "openhab-transformation-script-";
|
||||
private static final String PROFILE_CONFIG_URI = "profile:transform:SCRIPT";
|
||||
public static final String SUPPORTED_CONFIGURATION_TYPE = "script";
|
||||
|
||||
private static final Pattern SCRIPT_CONFIG_PATTERN = Pattern
|
||||
.compile("(?<scriptType>.*?):(?<scriptUid>.*?)(\\?(?<params>.*?))?");
|
||||
private static final URI CONFIG_DESCRIPTION_TEMPLATE_URI = URI.create(PROFILE_CONFIG_URI_PREFIX + "SCRIPT");
|
||||
|
||||
private static final Pattern INLINE_SCRIPT_CONFIG_PATTERN = Pattern.compile("\\|(?<inlineScript>.+)");
|
||||
|
||||
private static final Pattern SCRIPT_CONFIG_PATTERN = Pattern.compile("(?<scriptUid>.+?)(\\?(?<params>.*?))?");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ScriptTransformationService.class);
|
||||
|
||||
private final ScheduledExecutorService scheduler = ThreadPoolManager
|
||||
.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
|
||||
|
||||
private final String scriptType;
|
||||
private final URI profileConfigUri;
|
||||
|
||||
private final Map<String, ScriptRecord> scriptCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final TransformationRegistry transformationRegistry;
|
||||
private final Map<String, String> supportedScriptTypes = new ConcurrentHashMap<>();
|
||||
|
||||
private final ScriptEngineManager scriptEngineManager;
|
||||
private final ConfigDescriptionRegistry configDescRegistry;
|
||||
|
||||
@Activate
|
||||
public ScriptTransformationService(@Reference TransformationRegistry transformationRegistry,
|
||||
@Reference ScriptEngineManager scriptEngineManager) {
|
||||
@Reference ConfigDescriptionRegistry configDescRegistry, @Reference ScriptEngineManager scriptEngineManager,
|
||||
Map<String, Object> config) {
|
||||
String scriptType = ConfigParser.valueAs(config.get(SCRIPT_TYPE_PROPERTY_NAME), String.class);
|
||||
if (scriptType == null) {
|
||||
throw new IllegalStateException(
|
||||
"'" + SCRIPT_TYPE_PROPERTY_NAME + "' must not be null in service configuration");
|
||||
}
|
||||
|
||||
this.transformationRegistry = transformationRegistry;
|
||||
this.configDescRegistry = configDescRegistry;
|
||||
this.scriptEngineManager = scriptEngineManager;
|
||||
this.scriptType = scriptType;
|
||||
this.profileConfigUri = URI.create(PROFILE_CONFIG_URI_PREFIX + scriptType.toUpperCase());
|
||||
transformationRegistry.addRegistryChangeListener(this);
|
||||
}
|
||||
|
||||
|
@ -101,28 +123,34 @@ public class ScriptTransformationService
|
|||
|
||||
@Override
|
||||
public @Nullable String transform(String function, String source) throws TransformationException {
|
||||
Matcher configMatcher = SCRIPT_CONFIG_PATTERN.matcher(function);
|
||||
if (!configMatcher.matches()) {
|
||||
throw new TransformationException("Script Type must be prepended to transformation UID.");
|
||||
String scriptUid;
|
||||
String inlineScript = null;
|
||||
String params = null;
|
||||
|
||||
Matcher configMatcher = INLINE_SCRIPT_CONFIG_PATTERN.matcher(function);
|
||||
if (configMatcher.matches()) {
|
||||
inlineScript = configMatcher.group("inlineScript");
|
||||
// prefix with | to avoid clashing with a real filename
|
||||
scriptUid = "|" + Integer.toString(inlineScript.hashCode());
|
||||
} else {
|
||||
configMatcher = SCRIPT_CONFIG_PATTERN.matcher(function);
|
||||
if (!configMatcher.matches()) {
|
||||
throw new TransformationException("Invalid syntax for the script transformation: '" + function + "'");
|
||||
}
|
||||
scriptUid = configMatcher.group("scriptUid");
|
||||
params = configMatcher.group("params");
|
||||
}
|
||||
String scriptType = configMatcher.group("scriptType");
|
||||
String scriptUid = configMatcher.group("scriptUid");
|
||||
|
||||
ScriptRecord scriptRecord = scriptCache.computeIfAbsent(scriptUid, k -> new ScriptRecord());
|
||||
scriptRecord.lock.lock();
|
||||
try {
|
||||
if (scriptRecord.script.isBlank()) {
|
||||
if (scriptUid.startsWith("|")) {
|
||||
// inline script -> strip inline-identifier
|
||||
scriptRecord.script = scriptUid.substring(1);
|
||||
if (inlineScript != null) {
|
||||
scriptRecord.script = inlineScript;
|
||||
} else {
|
||||
// get script from transformation registry
|
||||
Transformation transformation = transformationRegistry.get(scriptUid);
|
||||
if (transformation != null) {
|
||||
if (!SUPPORTED_CONFIGURATION_TYPE.equals(transformation.getType())) {
|
||||
throw new TransformationException("Configuration does not have correct type 'script' but '"
|
||||
+ transformation.getType() + "'.");
|
||||
}
|
||||
scriptRecord.script = transformation.getConfiguration().getOrDefault(Transformation.FUNCTION,
|
||||
"");
|
||||
}
|
||||
|
@ -160,7 +188,6 @@ public class ScriptTransformationService
|
|||
ScriptContext executionContext = engine.getContext();
|
||||
executionContext.setAttribute("input", source, ScriptContext.ENGINE_SCOPE);
|
||||
|
||||
String params = configMatcher.group("params");
|
||||
if (params != null) {
|
||||
for (String param : params.split("&")) {
|
||||
String[] splitString = param.split("=");
|
||||
|
@ -169,7 +196,9 @@ public class ScriptTransformationService
|
|||
"Parameter '{}' does not consist of two parts for configuration UID {}, skipping.",
|
||||
param, scriptUid);
|
||||
} else {
|
||||
executionContext.setAttribute(splitString[0], splitString[1], ScriptContext.ENGINE_SCOPE);
|
||||
param = URLDecoder.decode(splitString[0], StandardCharsets.UTF_8);
|
||||
String value = URLDecoder.decode(splitString[1], StandardCharsets.UTF_8);
|
||||
executionContext.setAttribute(param, value, ScriptContext.ENGINE_SCOPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -208,6 +237,44 @@ public class ScriptTransformationService
|
|||
clearCache(element.getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable String context,
|
||||
@Nullable Locale locale) {
|
||||
if (!uri.equals(profileConfigUri)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ScriptProfile.CONFIG_TO_HANDLER_SCRIPT.equals(param) || ScriptProfile.CONFIG_TO_ITEM_SCRIPT.equals(param)) {
|
||||
return transformationRegistry.getTransformations(List.of(scriptType.toLowerCase())).stream()
|
||||
.map(c -> new ParameterOption(c.getUID(), c.getLabel())).collect(Collectors.toList());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigDescription> getConfigDescriptions(@Nullable Locale locale) {
|
||||
ConfigDescription configDescription = getConfigDescription(profileConfigUri, locale);
|
||||
if (configDescription != null) {
|
||||
return List.of(configDescription);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConfigDescription getConfigDescription(URI uri, @Nullable Locale locale) {
|
||||
if (!uri.equals(profileConfigUri)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ConfigDescription template = configDescRegistry.getConfigDescription(CONFIG_DESCRIPTION_TEMPLATE_URI, locale);
|
||||
if (template == null) {
|
||||
return null;
|
||||
}
|
||||
return ConfigDescriptionBuilder.create(uri).withParameters(template.getParameters())
|
||||
.withParameterGroups(template.getParameterGroups()).build();
|
||||
}
|
||||
|
||||
private void clearCache(String uid) {
|
||||
ScriptRecord scriptRecord = scriptCache.remove(uid);
|
||||
if (scriptRecord != null) {
|
||||
|
@ -243,38 +310,6 @@ public class ScriptTransformationService
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable String context,
|
||||
@Nullable Locale locale) {
|
||||
if (PROFILE_CONFIG_URI.equals(uri.toString())) {
|
||||
if (ScriptProfile.CONFIG_TO_HANDLER_SCRIPT.equals(param)
|
||||
|| ScriptProfile.CONFIG_TO_ITEM_SCRIPT.equals(param)) {
|
||||
return transformationRegistry.getTransformations(List.of(SUPPORTED_CONFIGURATION_TYPE)).stream()
|
||||
.map(c -> new ParameterOption(c.getUID(), c.getLabel())).collect(Collectors.toList());
|
||||
}
|
||||
if (ScriptProfile.CONFIG_SCRIPT_LANGUAGE.equals(param)) {
|
||||
return supportedScriptTypes.entrySet().stream().map(e -> new ParameterOption(e.getKey(), e.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* As {@link ScriptEngineFactory}s are added/removed, this method will cache all available script types
|
||||
*/
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setScriptEngineFactory(ScriptEngineFactory engineFactory) {
|
||||
Map.Entry<String, String> parameterOption = ScriptEngineFactoryHelper.getParameterOption(engineFactory);
|
||||
if (parameterOption != null) {
|
||||
supportedScriptTypes.put(parameterOption.getKey(), parameterOption.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void unsetScriptEngineFactory(ScriptEngineFactory engineFactory) {
|
||||
supportedScriptTypes.remove(ScriptEngineFactoryHelper.getPreferredMimeType(engineFactory));
|
||||
}
|
||||
|
||||
private static class ScriptRecord {
|
||||
public String script = "";
|
||||
public @Nullable ScriptEngineContainer scriptEngineContainer;
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2023 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.core.automation.module.script;
|
||||
|
||||
import java.util.Dictionary;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.automation.module.script.internal.ScriptEngineFactoryHelper;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.osgi.service.component.ComponentFactory;
|
||||
import org.osgi.service.component.ComponentInstance;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
|
||||
/**
|
||||
* The {@link ScriptTransformationServiceFactory} registers a {@link ScriptTransformationService}
|
||||
* for each newly added script engine.
|
||||
*
|
||||
* @author Jimmy Tanagra - Initial contribution
|
||||
*/
|
||||
@Component(immediate = true, service = { ScriptTransformationServiceFactory.class })
|
||||
@NonNullByDefault
|
||||
public class ScriptTransformationServiceFactory {
|
||||
|
||||
private final ComponentFactory<ScriptTransformationService> scriptTransformationFactory;
|
||||
|
||||
private final Map<ScriptEngineFactory, ComponentInstance<ScriptTransformationService>> scriptTransformations = new ConcurrentHashMap<>();
|
||||
|
||||
@Activate
|
||||
public ScriptTransformationServiceFactory(
|
||||
@Reference(target = "(component.factory=org.openhab.core.automation.module.script.transformation.factory)") ComponentFactory<ScriptTransformationService> factory) {
|
||||
this.scriptTransformationFactory = factory;
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
scriptTransformations.values().forEach(this::unregisterService);
|
||||
scriptTransformations.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* As {@link ScriptEngineFactory}s are added/removed, this method will cache all available script types
|
||||
* and registers a transformation service for the script engine.
|
||||
*/
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setScriptEngineFactory(ScriptEngineFactory engineFactory) {
|
||||
Optional<String> scriptType = ScriptEngineFactoryHelper.getPreferredExtension(engineFactory);
|
||||
if (scriptType.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scriptTransformations.computeIfAbsent(engineFactory, factory -> {
|
||||
ScriptEngine scriptEngine = engineFactory.createScriptEngine(scriptType.get());
|
||||
if (scriptEngine == null) {
|
||||
return null;
|
||||
}
|
||||
String languageName = ScriptEngineFactoryHelper.getLanguageName(scriptEngine.getFactory());
|
||||
Dictionary<String, Object> properties = new Hashtable<>();
|
||||
properties.put(TransformationService.SERVICE_PROPERTY_NAME, scriptType.get().toUpperCase());
|
||||
properties.put(TransformationService.SERVICE_PROPERTY_LABEL, "SCRIPT " + languageName);
|
||||
properties.put(ScriptTransformationService.SCRIPT_TYPE_PROPERTY_NAME, scriptType.get());
|
||||
return scriptTransformationFactory.newInstance(properties);
|
||||
});
|
||||
}
|
||||
|
||||
public void unsetScriptEngineFactory(ScriptEngineFactory engineFactory) {
|
||||
ComponentInstance<ScriptTransformationService> toBeUnregistered = scriptTransformations.remove(engineFactory);
|
||||
if (toBeUnregistered != null) {
|
||||
unregisterService(toBeUnregistered);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterService(ComponentInstance<ScriptTransformationService> instance) {
|
||||
instance.getInstance().deactivate();
|
||||
instance.dispose();
|
||||
}
|
||||
}
|
|
@ -13,8 +13,10 @@
|
|||
package org.openhab.core.automation.module.script.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
|
@ -67,4 +69,10 @@ public class ScriptEngineFactoryHelper {
|
|||
factory.getLanguageName().substring(0, 1).toUpperCase() + factory.getLanguageName().substring(1),
|
||||
factory.getLanguageVersion());
|
||||
}
|
||||
|
||||
public static Optional<String> getPreferredExtension(ScriptEngineFactory factory) {
|
||||
// return an Optional because GenericScriptEngineFactory has no scriptTypes
|
||||
return factory.getScriptTypes().stream().filter(type -> !type.contains("/"))
|
||||
.min(Comparator.comparing(String::length));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ import org.slf4j.LoggerFactory;
|
|||
@NonNullByDefault
|
||||
public class ScriptProfile implements StateProfile {
|
||||
|
||||
public static final String CONFIG_SCRIPT_LANGUAGE = "scriptLanguage";
|
||||
public static final String CONFIG_TO_ITEM_SCRIPT = "toItemScript";
|
||||
public static final String CONFIG_TO_HANDLER_SCRIPT = "toHandlerScript";
|
||||
|
||||
|
@ -54,14 +53,15 @@ public class ScriptProfile implements StateProfile {
|
|||
private final List<Class<? extends Command>> acceptedCommandTypes;
|
||||
private final List<Class<? extends Command>> handlerAcceptedCommandTypes;
|
||||
|
||||
private final String scriptLanguage;
|
||||
private final String toItemScript;
|
||||
private final String toHandlerScript;
|
||||
private final ProfileTypeUID profileTypeUID;
|
||||
|
||||
private final boolean isConfigured;
|
||||
|
||||
public ScriptProfile(ProfileCallback callback, ProfileContext profileContext,
|
||||
public ScriptProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback, ProfileContext profileContext,
|
||||
TransformationService transformationService) {
|
||||
this.profileTypeUID = profileTypeUID;
|
||||
this.callback = callback;
|
||||
this.transformationService = transformationService;
|
||||
|
||||
|
@ -69,19 +69,11 @@ public class ScriptProfile implements StateProfile {
|
|||
this.acceptedDataTypes = profileContext.getAcceptedDataTypes();
|
||||
this.handlerAcceptedCommandTypes = profileContext.getHandlerAcceptedCommandTypes();
|
||||
|
||||
this.scriptLanguage = ConfigParser.valueAsOrElse(profileContext.getConfiguration().get(CONFIG_SCRIPT_LANGUAGE),
|
||||
String.class, "");
|
||||
this.toItemScript = ConfigParser.valueAsOrElse(profileContext.getConfiguration().get(CONFIG_TO_ITEM_SCRIPT),
|
||||
String.class, "");
|
||||
this.toHandlerScript = ConfigParser
|
||||
.valueAsOrElse(profileContext.getConfiguration().get(CONFIG_TO_HANDLER_SCRIPT), String.class, "");
|
||||
|
||||
if (scriptLanguage.isBlank()) {
|
||||
logger.error("Script language is not defined. Profile will discard all states and commands.");
|
||||
isConfigured = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (toItemScript.isBlank() && toHandlerScript.isBlank()) {
|
||||
logger.error(
|
||||
"Neither 'toItem' nor 'toHandler' script defined. Profile will discard all states and commands.");
|
||||
|
@ -94,7 +86,7 @@ public class ScriptProfile implements StateProfile {
|
|||
|
||||
@Override
|
||||
public ProfileTypeUID getProfileTypeUID() {
|
||||
return ScriptProfileFactory.SCRIPT_PROFILE_UID;
|
||||
return profileTypeUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -149,7 +141,7 @@ public class ScriptProfile implements StateProfile {
|
|||
private @Nullable String executeScript(String script, Type input) {
|
||||
if (!script.isBlank()) {
|
||||
try {
|
||||
return transformationService.transform(scriptLanguage + ":" + script, input.toFullString());
|
||||
return transformationService.transform(script, input.toFullString());
|
||||
} catch (TransformationException e) {
|
||||
if (e.getCause() instanceof ScriptException) {
|
||||
logger.error("Failed to process script '{}': {}", script, e.getCause().getMessage());
|
||||
|
|
|
@ -14,7 +14,9 @@ package org.openhab.core.automation.module.script.profile;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
@ -28,48 +30,58 @@ 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.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
|
||||
/**
|
||||
* The {@link ScriptProfileFactory} creates {@link ScriptProfile} instances
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@Component(service = { ScriptProfileFactory.class, ProfileFactory.class, ProfileTypeProvider.class })
|
||||
@NonNullByDefault
|
||||
@Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
|
||||
public class ScriptProfileFactory implements ProfileFactory, ProfileTypeProvider {
|
||||
public static final String PROFILE_CONFIG_URI_PREFIX = "profile:transform:";
|
||||
|
||||
public static final ProfileTypeUID SCRIPT_PROFILE_UID = new ProfileTypeUID(
|
||||
TransformationService.TRANSFORM_PROFILE_SCOPE, "SCRIPT");
|
||||
|
||||
private static final ProfileType PROFILE_TYPE_SCRIPT = ProfileTypeBuilder.newState(SCRIPT_PROFILE_UID, "Script")
|
||||
.build();
|
||||
|
||||
private final ScriptTransformationService transformationService;
|
||||
|
||||
@Activate
|
||||
public ScriptProfileFactory(final @Reference ScriptTransformationService transformationService) {
|
||||
this.transformationService = transformationService;
|
||||
}
|
||||
private final Map<String, ServiceRecord> services = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
|
||||
ProfileContext profileContext) {
|
||||
if (SCRIPT_PROFILE_UID.equals(profileTypeUID)) {
|
||||
return new ScriptProfile(callback, profileContext, transformationService);
|
||||
}
|
||||
return null;
|
||||
String serviceId = profileTypeUID.getId();
|
||||
ScriptTransformationService transformationService = services.get(serviceId).service();
|
||||
return new ScriptProfile(profileTypeUID, callback, profileContext, transformationService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
|
||||
return Set.of(SCRIPT_PROFILE_UID);
|
||||
return services.keySet().stream()
|
||||
.map(id -> new ProfileTypeUID(TransformationService.TRANSFORM_PROFILE_SCOPE, id)).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
|
||||
return Set.of(PROFILE_TYPE_SCRIPT);
|
||||
return getSupportedProfileTypeUIDs().stream().map(uid -> {
|
||||
String id = uid.getId();
|
||||
String label = services.get(id).serviceLabel();
|
||||
return ProfileTypeBuilder.newState(uid, label).build();
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
public void bindScriptTransformationService(ScriptTransformationService service, Map<String, Object> properties) {
|
||||
String serviceId = (String) properties.get(TransformationService.SERVICE_PROPERTY_NAME);
|
||||
String serviceLabel = (String) properties.get(TransformationService.SERVICE_PROPERTY_LABEL);
|
||||
services.put(serviceId, new ServiceRecord(service, serviceLabel));
|
||||
}
|
||||
|
||||
public void unbindScriptTransformationService(ScriptTransformationService service, Map<String, Object> properties) {
|
||||
String serviceId = (String) properties.get(TransformationService.SERVICE_PROPERTY_NAME);
|
||||
services.remove(serviceId);
|
||||
}
|
||||
|
||||
private record ServiceRecord(ScriptTransformationService service, String serviceLabel) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
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:SCRIPT">
|
||||
<parameter name="scriptLanguage" type="text" required="true">
|
||||
<label>Script Language</label>
|
||||
<description>MIME-type ("application/vnd.openhab.dsl.rule") of the scripting language</description>
|
||||
</parameter>
|
||||
<parameter name="toItemScript" type="text">
|
||||
<label>Thing To Item Transformation</label>
|
||||
<description>The Script for transforming state updates and commands from the Thing handler to the item. The script
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
profile.system.script.scriptLanguage.label = Script Language
|
||||
profile.system.script.scriptLanguage.description = MIME-type ("application/vnd.openhab.dsl.rule") of the scripting language
|
||||
profile.system.script.toItemScript.label = Thing To Item Transformation
|
||||
profile.system.script.toItemScript.description = The Script for transforming state updates and commands from the Thing handler to the item. The script may return null to discard the updates/commands and not pass them through.
|
||||
profile.system.script.toHandlerScript.label = Item To Thing Transformation
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
profile.system.script.scriptLanguage.label = Script-sprog
|
||||
profile.system.script.scriptLanguage.description = MIME-type ("application/vnd.openhab.dsl.rule") for script-sproget
|
||||
profile.system.script.toItemScript.label = Til item-script
|
||||
profile.system.script.toItemScript.description = Scriptet til at transformere tilstande og kommandoer fra handler til item.
|
||||
profile.system.script.toHandlerScript.label = Til handler-script
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
profile.system.script.scriptLanguage.label = Skriptsprache
|
||||
profile.system.script.scriptLanguage.description = MIME-Typ ("application/vnd.openhab.dsl.rule") der Skriptsprache.
|
||||
profile.system.script.toItemScript.label = Transformation Thing -> Item
|
||||
profile.system.script.toItemScript.description = Das Skript für die Transformtion von States und Commands vom Thing zum Item.
|
||||
profile.system.script.toHandlerScript.label = Transformation Item -> Thing
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
profile.system.script.scriptLanguage.label = Skriptin kieli
|
||||
profile.system.script.scriptLanguage.description = Skriptin kielen MIME-tyyppi ("application/vnd.openhab.dsl.rule")
|
||||
profile.system.script.toItemScript.label = Item-skriptiksi
|
||||
profile.system.script.toItemScript.description = Skripti, jota käytetään tilojen ja komentojen muuntoon käsittelijästä itemiksi.
|
||||
profile.system.script.toHandlerScript.label = Käsittelijäskriptiksi
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
profile.system.script.scriptLanguage.label = שפת תסריט
|
||||
profile.system.script.scriptLanguage.description = סוג MIME ("application/vnd.openhab.dsl.rule") של שפת הסקריפט
|
||||
profile.system.script.toItemScript.label = המרת Thing לפריט
|
||||
profile.system.script.toItemScript.description = הסקריפט להפיכת עדכוני מצב ופקודות מהמטפל ב-Thing לפריט. הסקריפט עשוי לחזור null כדי למחוק את העדכונים/פקודות ולא להעביר אותם.
|
||||
profile.system.script.toHandlerScript.label = שינוי פריט ל-thing
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
profile.system.script.scriptLanguage.label = Linguaggio Script
|
||||
profile.system.script.scriptLanguage.description = Tipo MIME ("application/vnd.openhab.dsl.rule") del linguaggio di scripting
|
||||
profile.system.script.toItemScript.label = Trasformazione da Thing a Item
|
||||
profile.system.script.toItemScript.description = Lo script per trasformare gli aggiornamenti dello stato e i comandi dal gestore Thing all'Item. Lo script può restituire null per scartare gli aggiornamenti/comandi e non passarli.
|
||||
profile.system.script.toHandlerScript.label = Trasformazione da Item a Thing
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
profile.system.script.scriptLanguage.label = Język Skryptu
|
||||
profile.system.script.scriptLanguage.description = MIME-Typ języka skryptu ("application/vnd.openhab.dsl.rule")
|
||||
profile.system.script.toItemScript.label = Skrypt transformacji z kanału do elementu
|
||||
profile.system.script.toItemScript.description = Skrypt do transformacji stanu lub wartości z kanału do elementu. Skrypt może zwrócić stan lub wartość *null* aby pominąć transformację i nie przekazać jej do elementu.
|
||||
profile.system.script.toHandlerScript.label = Skrypt transformacji z elementu do kanału
|
||||
|
|
|
@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.*;
|
|||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -36,6 +37,7 @@ 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.config.core.ConfigDescriptionRegistry;
|
||||
import org.openhab.core.transform.Transformation;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationRegistry;
|
||||
|
@ -50,7 +52,7 @@ import org.openhab.core.transform.TransformationRegistry;
|
|||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class ScriptTransformationServiceTest {
|
||||
private static final String SCRIPT_LANGUAGE = "customDsl";
|
||||
private static final String SCRIPT_UID = "scriptUid";
|
||||
private static final String SCRIPT_UID = "scriptUid." + SCRIPT_LANGUAGE;
|
||||
private static final String INVALID_SCRIPT_UID = "invalidScriptUid";
|
||||
|
||||
private static final String INLINE_SCRIPT = "|inlineScript";
|
||||
|
@ -59,7 +61,7 @@ public class ScriptTransformationServiceTest {
|
|||
private static final String SCRIPT_OUTPUT = "output";
|
||||
|
||||
private static final Transformation TRANSFORMATION_CONFIGURATION = new Transformation(SCRIPT_UID, "label",
|
||||
ScriptTransformationService.SUPPORTED_CONFIGURATION_TYPE, Map.of(Transformation.FUNCTION, SCRIPT));
|
||||
SCRIPT_LANGUAGE, Map.of(Transformation.FUNCTION, SCRIPT));
|
||||
private static final Transformation INVALID_TRANSFORMATION_CONFIGURATION = new Transformation(INVALID_SCRIPT_UID,
|
||||
"label", "invalid", Map.of(Transformation.FUNCTION, SCRIPT));
|
||||
|
||||
|
@ -73,7 +75,10 @@ public class ScriptTransformationServiceTest {
|
|||
|
||||
@BeforeEach
|
||||
public void setUp() throws ScriptException {
|
||||
service = new ScriptTransformationService(transformationRegistry, scriptEngineManager);
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(ScriptTransformationService.SCRIPT_TYPE_PROPERTY_NAME, SCRIPT_LANGUAGE);
|
||||
service = new ScriptTransformationService(transformationRegistry, mock(ConfigDescriptionRegistry.class),
|
||||
scriptEngineManager, properties);
|
||||
|
||||
when(scriptEngineManager.createScriptEngine(eq(SCRIPT_LANGUAGE), any())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManager.isSupported(anyString()))
|
||||
|
@ -96,14 +101,14 @@ public class ScriptTransformationServiceTest {
|
|||
|
||||
@Test
|
||||
public void success() throws TransformationException {
|
||||
String returnValue = Objects.requireNonNull(service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"));
|
||||
String returnValue = Objects.requireNonNull(service.transform(SCRIPT_UID, "input"));
|
||||
|
||||
assertThat(returnValue, is(SCRIPT_OUTPUT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptExecutionParametersAreInjectedIntoEngineContext() throws TransformationException {
|
||||
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID + "?param1=value1¶m2=value2", "input");
|
||||
service.transform(SCRIPT_UID + "?param1=value1¶m2=value2", "input");
|
||||
|
||||
verify(scriptContext).setAttribute(eq("input"), eq("input"), eq(ScriptContext.ENGINE_SCOPE));
|
||||
verify(scriptContext).setAttribute(eq("param1"), eq("value1"), eq(ScriptContext.ENGINE_SCOPE));
|
||||
|
@ -111,6 +116,16 @@ public class ScriptTransformationServiceTest {
|
|||
verifyNoMoreInteractions(scriptContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptExecutionParametersAreDecoded() throws TransformationException {
|
||||
service.transform(SCRIPT_UID + "?param1=%26amp;¶m2=%3dvalue", "input");
|
||||
|
||||
verify(scriptContext).setAttribute(eq("input"), eq("input"), eq(ScriptContext.ENGINE_SCOPE));
|
||||
verify(scriptContext).setAttribute(eq("param1"), eq("&"), eq(ScriptContext.ENGINE_SCOPE));
|
||||
verify(scriptContext).setAttribute(eq("param2"), eq("=value"), eq(ScriptContext.ENGINE_SCOPE));
|
||||
verifyNoMoreInteractions(scriptContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptSetAttributesBeforeCompiling() throws TransformationException, ScriptException {
|
||||
abstract class CompilableScriptEngine implements ScriptEngine, Compilable {
|
||||
|
@ -122,7 +137,7 @@ public class ScriptTransformationServiceTest {
|
|||
|
||||
InOrder inOrder = inOrder(scriptContext, scriptEngine);
|
||||
|
||||
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID + "?param1=value1", "input");
|
||||
service.transform(SCRIPT_UID + "?param1=value1", "input");
|
||||
|
||||
inOrder.verify(scriptContext, times(2)).setAttribute(anyString(), anyString(), eq(ScriptContext.ENGINE_SCOPE));
|
||||
inOrder.verify((Compilable) scriptEngine).compile(SCRIPT);
|
||||
|
@ -132,7 +147,7 @@ public class ScriptTransformationServiceTest {
|
|||
|
||||
@Test
|
||||
public void invalidScriptExecutionParametersAreDiscarded() throws TransformationException {
|
||||
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID + "?param1=value1&invalid", "input");
|
||||
service.transform(SCRIPT_UID + "?param1=value1&invalid", "input");
|
||||
|
||||
verify(scriptContext).setAttribute(eq("input"), eq("input"), eq(ScriptContext.ENGINE_SCOPE));
|
||||
verify(scriptContext).setAttribute(eq("param1"), eq("value1"), eq(ScriptContext.ENGINE_SCOPE));
|
||||
|
@ -141,41 +156,25 @@ public class ScriptTransformationServiceTest {
|
|||
|
||||
@Test
|
||||
public void scriptsAreCached() throws TransformationException {
|
||||
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input");
|
||||
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input");
|
||||
service.transform(SCRIPT_UID, "input");
|
||||
service.transform(SCRIPT_UID, "input");
|
||||
|
||||
verify(transformationRegistry).get(SCRIPT_UID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptCacheInvalidatedAfterChange() throws TransformationException {
|
||||
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input");
|
||||
service.transform(SCRIPT_UID, "input");
|
||||
service.updated(TRANSFORMATION_CONFIGURATION, TRANSFORMATION_CONFIGURATION);
|
||||
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input");
|
||||
service.transform(SCRIPT_UID, "input");
|
||||
|
||||
verify(transformationRegistry, times(2)).get(SCRIPT_UID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noScriptTypeThrowsException() {
|
||||
TransformationException e = assertThrows(TransformationException.class,
|
||||
() -> service.transform(SCRIPT_UID, "input"));
|
||||
|
||||
assertThat(e.getMessage(), is("Script Type must be prepended to transformation UID."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownScriptTypeThrowsException() {
|
||||
TransformationException e = assertThrows(TransformationException.class,
|
||||
() -> service.transform("foo" + ":" + SCRIPT_UID, "input"));
|
||||
|
||||
assertThat(e.getMessage(), is("Script type 'foo' is not supported by any available script engine."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownScriptUidThrowsException() {
|
||||
TransformationException e = assertThrows(TransformationException.class,
|
||||
() -> service.transform(SCRIPT_LANGUAGE + ":" + "foo", "input"));
|
||||
() -> service.transform("foo", "input"));
|
||||
|
||||
assertThat(e.getMessage(), is("Could not get script for UID 'foo'."));
|
||||
}
|
||||
|
@ -185,24 +184,16 @@ public class ScriptTransformationServiceTest {
|
|||
when(scriptEngine.eval(SCRIPT)).thenThrow(new ScriptException("exception"));
|
||||
|
||||
TransformationException e = assertThrows(TransformationException.class,
|
||||
() -> service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input"));
|
||||
() -> service.transform(SCRIPT_UID, "input"));
|
||||
|
||||
assertThat(e.getMessage(), is("Failed to execute script."));
|
||||
assertThat(e.getCause(), instanceOf(ScriptException.class));
|
||||
assertThat(e.getCause().getMessage(), is("exception"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidConfigurationTypeThrowsTransformationException() {
|
||||
TransformationException e = assertThrows(TransformationException.class,
|
||||
() -> service.transform(SCRIPT_LANGUAGE + ":" + INVALID_SCRIPT_UID, "input"));
|
||||
|
||||
assertThat(e.getMessage(), is("Configuration does not have correct type 'script' but 'invalid'."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inlineScriptProperlyProcessed() throws TransformationException, ScriptException {
|
||||
service.transform(SCRIPT_LANGUAGE + ":" + INLINE_SCRIPT, "input");
|
||||
service.transform(INLINE_SCRIPT, "input");
|
||||
|
||||
verify(scriptEngine).eval(INLINE_SCRIPT.substring(1));
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
package org.openhab.core.automation.module.script.profile;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_SCRIPT_LANGUAGE;
|
||||
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_TO_HANDLER_SCRIPT;
|
||||
import static org.openhab.core.automation.module.script.profile.ScriptProfile.CONFIG_TO_ITEM_SCRIPT;
|
||||
|
||||
|
@ -42,6 +42,7 @@ import org.openhab.core.library.types.PercentType;
|
|||
import org.openhab.core.test.java.JavaTest;
|
||||
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.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
import org.openhab.core.types.Command;
|
||||
|
@ -67,11 +68,12 @@ public class ScriptProfileTest extends JavaTest {
|
|||
|
||||
@Test
|
||||
public void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptDefined() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL").build();
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().build();
|
||||
|
||||
setupInterceptedLogger(ScriptProfile.class, LogLevel.ERROR);
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock);
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(OnOffType.ON);
|
||||
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
|
||||
|
@ -86,40 +88,16 @@ public class ScriptProfileTest extends JavaTest {
|
|||
"Neither 'toItem' nor 'toHandler' script defined. Profile will discard all states and commands.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptLanguageDefined()
|
||||
throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(DecimalType.class))
|
||||
.withAcceptedDataTypes(List.of(PercentType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(HSBType.class)).build();
|
||||
|
||||
setupInterceptedLogger(ScriptProfile.class, LogLevel.ERROR);
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(OnOffType.ON);
|
||||
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
|
||||
scriptProfile.onCommandFromItem(OnOffType.ON);
|
||||
|
||||
verify(transformationServiceMock, never()).transform(any(), any());
|
||||
verify(profileCallback, never()).handleCommand(any());
|
||||
verify(profileCallback, never()).sendUpdate(any());
|
||||
verify(profileCallback, never()).sendCommand(any());
|
||||
|
||||
assertLogMessage(ScriptProfile.class, LogLevel.ERROR,
|
||||
"Script language is not defined. Profile will discard all states and commands.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptExecutionErrorForwardsNoValueToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL")
|
||||
.withToItemScript("inScript").withToHandlerScript("outScript").build();
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any()))
|
||||
.thenThrow(new TransformationException("intentional failure"));
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock);
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(OnOffType.ON);
|
||||
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
|
||||
|
@ -133,12 +111,13 @@ public class ScriptProfileTest extends JavaTest {
|
|||
|
||||
@Test
|
||||
public void scriptExecutionResultNullForwardsNoValueToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL")
|
||||
.withToItemScript("inScript").withToHandlerScript("outScript").build();
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(null);
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock);
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(OnOffType.ON);
|
||||
scriptProfile.onStateUpdateFromHandler(OnOffType.ON);
|
||||
|
@ -152,14 +131,15 @@ public class ScriptProfileTest extends JavaTest {
|
|||
|
||||
@Test
|
||||
public void scriptExecutionResultForwardsTransformedValueToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL")
|
||||
.withToItemScript("inScript").withToHandlerScript("outScript")
|
||||
.withAcceptedCommandTypes(List.of(OnOffType.class)).withAcceptedDataTypes(List.of(OnOffType.class))
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(OnOffType.class))
|
||||
.withAcceptedDataTypes(List.of(OnOffType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(OnOffType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock);
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
|
@ -173,14 +153,14 @@ public class ScriptProfileTest extends JavaTest {
|
|||
|
||||
@Test
|
||||
public void onlyToItemScriptDoesNotForwardOutboundCommands() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL")
|
||||
.withToItemScript("inScript").withAcceptedCommandTypes(List.of(OnOffType.class))
|
||||
.withAcceptedDataTypes(List.of(OnOffType.class))
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withAcceptedCommandTypes(List.of(OnOffType.class)).withAcceptedDataTypes(List.of(OnOffType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(DecimalType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock);
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
|
@ -194,14 +174,14 @@ public class ScriptProfileTest extends JavaTest {
|
|||
|
||||
@Test
|
||||
public void onlyToHandlerScriptDoesNotForwardInboundCommands() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL")
|
||||
.withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(DecimalType.class))
|
||||
.withAcceptedDataTypes(List.of(DecimalType.class))
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToHandlerScript("outScript")
|
||||
.withAcceptedCommandTypes(List.of(DecimalType.class)).withAcceptedDataTypes(List.of(DecimalType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(OnOffType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock);
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
|
@ -215,14 +195,15 @@ public class ScriptProfileTest extends JavaTest {
|
|||
|
||||
@Test
|
||||
public void incompatibleStateOrCommandNotForwardedToCallback() throws TransformationException {
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withScriptLanguage("customDSL")
|
||||
.withToItemScript("inScript").withToHandlerScript("outScript")
|
||||
.withAcceptedCommandTypes(List.of(DecimalType.class)).withAcceptedDataTypes(List.of(PercentType.class))
|
||||
ProfileContext profileContext = ProfileContextBuilder.create().withToItemScript("inScript")
|
||||
.withToHandlerScript("outScript").withAcceptedCommandTypes(List.of(DecimalType.class))
|
||||
.withAcceptedDataTypes(List.of(PercentType.class))
|
||||
.withHandlerAcceptedCommandTypes(List.of(HSBType.class)).build();
|
||||
|
||||
when(transformationServiceMock.transform(any(), any())).thenReturn(OnOffType.OFF.toString());
|
||||
|
||||
ScriptProfile scriptProfile = new ScriptProfile(profileCallback, profileContext, transformationServiceMock);
|
||||
ScriptProfile scriptProfile = new ScriptProfile(mock(ProfileTypeUID.class), profileCallback, profileContext,
|
||||
transformationServiceMock);
|
||||
|
||||
scriptProfile.onCommandFromHandler(DecimalType.ZERO);
|
||||
scriptProfile.onStateUpdateFromHandler(DecimalType.ZERO);
|
||||
|
@ -244,11 +225,6 @@ public class ScriptProfileTest extends JavaTest {
|
|||
return new ProfileContextBuilder();
|
||||
}
|
||||
|
||||
public ProfileContextBuilder withScriptLanguage(String scriptLanguage) {
|
||||
configuration.put(CONFIG_SCRIPT_LANGUAGE, scriptLanguage);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProfileContextBuilder withToItemScript(String toItem) {
|
||||
configuration.put(CONFIG_TO_ITEM_SCRIPT, toItem);
|
||||
return this;
|
||||
|
|
|
@ -117,8 +117,9 @@ public class TransformationResource implements RESTResource {
|
|||
try {
|
||||
Collection<ServiceReference<TransformationService>> refs = bundleContext
|
||||
.getServiceReferences(TransformationService.class, null);
|
||||
Stream<String> services = refs.stream().map(ref -> (String) ref.getProperty("openhab.transform"))
|
||||
.filter(Objects::nonNull).map(Objects::requireNonNull);
|
||||
Stream<String> services = refs.stream()
|
||||
.map(ref -> (String) ref.getProperty(TransformationService.SERVICE_PROPERTY_NAME))
|
||||
.filter(Objects::nonNull).map(Objects::requireNonNull).sorted();
|
||||
return Response.ok(new Stream2JSONInputStream(services)).build();
|
||||
} catch (InvalidSyntaxException e) {
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
|
||||
|
|
|
@ -61,7 +61,7 @@ public class TransformationHelper {
|
|||
public static @Nullable TransformationService getTransformationService(@Nullable BundleContext context,
|
||||
String transformationType) {
|
||||
if (context != null) {
|
||||
String filter = "(openhab.transform=" + transformationType + ")";
|
||||
String filter = "(" + TransformationService.SERVICE_PROPERTY_NAME + "=" + transformationType + ")";
|
||||
try {
|
||||
Collection<ServiceReference<TransformationService>> refs = context
|
||||
.getServiceReferences(TransformationService.class, filter);
|
||||
|
|
|
@ -32,8 +32,10 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||
@NonNullByDefault
|
||||
public interface TransformationService {
|
||||
|
||||
public static final String TRANSFORM_FOLDER_NAME = "transform";
|
||||
public static final String TRANSFORM_PROFILE_SCOPE = "transform";
|
||||
String SERVICE_PROPERTY_NAME = "openhab.transform";
|
||||
String SERVICE_PROPERTY_LABEL = "openhab.transform.label";
|
||||
String TRANSFORM_FOLDER_NAME = "transform";
|
||||
String TRANSFORM_PROFILE_SCOPE = "transform";
|
||||
|
||||
/**
|
||||
* Transforms the input <code>source</code> by means of the given <code>function</code> and returns the transformed
|
||||
|
|
Loading…
Reference in New Issue