diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java index 38b474ab431..c5f22eb8d81 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationService.java @@ -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 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.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 @@ * * @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, 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 { + 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("(?.*?):(?.*?)(\\?(?.*?))?"); + 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("\\|(?.+)"); + + private static final Pattern SCRIPT_CONFIG_PATTERN = Pattern.compile("(?.+?)(\\?(?.*?))?"); 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 scriptCache = new ConcurrentHashMap<>(); private final TransformationRegistry transformationRegistry; - private final Map 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 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 void deactivate() { @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 void deactivate() { 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 void deactivate() { "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 void updated(Transformation oldElement, Transformation element) { clearCache(element.getUID()); } + @Override + public @Nullable Collection 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 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 @@ private void disposeScriptEngine(ScriptEngine scriptEngine) { } } - @Override - public @Nullable Collection 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 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; diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationServiceFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationServiceFactory.java new file mode 100644 index 00000000000..fdcd21adc52 --- /dev/null +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptTransformationServiceFactory.java @@ -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 scriptTransformationFactory; + + private final Map> scriptTransformations = new ConcurrentHashMap<>(); + + @Activate + public ScriptTransformationServiceFactory( + @Reference(target = "(component.factory=org.openhab.core.automation.module.script.transformation.factory)") ComponentFactory 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 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 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 toBeUnregistered = scriptTransformations.remove(engineFactory); + if (toBeUnregistered != null) { + unregisterService(toBeUnregistered); + } + } + + private void unregisterService(ComponentInstance instance) { + instance.getInstance().deactivate(); + instance.dispose(); + } +} diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java index 12ffaeb4c93..6acbfeac2d0 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryHelper.java @@ -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 static String getLanguageName(javax.script.ScriptEngineFactory factory) { factory.getLanguageName().substring(0, 1).toUpperCase() + factory.getLanguageName().substring(1), factory.getLanguageVersion()); } + + public static Optional getPreferredExtension(ScriptEngineFactory factory) { + // return an Optional because GenericScriptEngineFactory has no scriptTypes + return factory.getScriptTypes().stream().filter(type -> !type.contains("/")) + .min(Comparator.comparing(String::length)); + } } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java index 68d405b021e..cb2634afc67 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfile.java @@ -41,7 +41,6 @@ @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> acceptedCommandTypes; private final List> 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 ScriptProfile(ProfileCallback callback, ProfileContext profileContext, 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 ScriptProfile(ProfileCallback callback, ProfileContext profileContext, @Override public ProfileTypeUID getProfileTypeUID() { - return ScriptProfileFactory.SCRIPT_PROFILE_UID; + return profileTypeUID; } @Override @@ -149,7 +141,7 @@ public void onStateUpdateFromHandler(State state) { 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()); diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfileFactory.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfileFactory.java index adbf5c9710f..6e46afa5f3a 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfileFactory.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/profile/ScriptProfileFactory.java @@ -14,7 +14,9 @@ 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.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 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 getSupportedProfileTypeUIDs() { - return Set.of(SCRIPT_PROFILE_UID); + return services.keySet().stream() + .map(id -> new ProfileTypeUID(TransformationService.TRANSFORM_PROFILE_SCOPE, id)).toList(); } @Override public Collection 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 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 properties) { + String serviceId = (String) properties.get(TransformationService.SERVICE_PROPERTY_NAME); + services.remove(serviceId); + } + + private record ServiceRecord(ScriptTransformationService service, String serviceLabel) { } } diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml index cbbfad5bb02..6e1670c5295 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/config/script-profile.xml @@ -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"> - - - MIME-type ("application/vnd.openhab.dsl.rule") of the scripting language - The Script for transforming state updates and commands from the Thing handler to the item. The script diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties index ba168b38021..8c0323b52b6 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile.properties @@ -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 diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_da.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_da.properties index 6e1e3e64cc6..b484a6da08d 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_da.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_da.properties @@ -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 diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties index a645a911802..fdd90001389 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_de.properties @@ -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 diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties index b7ee809f7a0..454984afb8d 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_fi.properties @@ -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 diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties index 3274b2e7c51..79b93afbb94 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_he.properties @@ -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 diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties index ba5bdca5c3b..810fcea2836 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_it.properties @@ -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 diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties index 77de1604dd5..7320cb082ce 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties +++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_pl.properties @@ -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 diff --git a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java index 832b444dc27..b73010e6aa8 100644 --- a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java +++ b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/ScriptTransformationServiceTest.java @@ -19,6 +19,7 @@ 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.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 @@ @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 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 void setUp() throws ScriptException { @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 void scriptExecutionParametersAreInjectedIntoEngineContext() throws Trans 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 @@ abstract class CompilableScriptEngine implements ScriptEngine, Compilable { 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 @@ abstract class CompilableScriptEngine implements ScriptEngine, Compilable { @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 void invalidScriptExecutionParametersAreDiscarded() throws Transformation @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 void scriptExceptionResultsInTransformationException() throws ScriptExcep 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)); } diff --git a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java index 8b26a0c0ed2..11111ec4499 100644 --- a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java +++ b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/profile/ScriptProfileTest.java @@ -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.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 void setUp() throws TransformationException { @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 void testScriptNotExecutedAndNoValueForwardedToCallbackIfNoScriptDefined( "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 void scriptExecutionErrorForwardsNoValueToCallback() throws Transformatio @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 void scriptExecutionResultNullForwardsNoValueToCallback() throws Transfor @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 void scriptExecutionResultForwardsTransformedValueToCallback() throws Tra @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 void onlyToItemScriptDoesNotForwardOutboundCommands() throws Transformati @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 void onlyToHandlerScriptDoesNotForwardInboundCommands() throws Transforma @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 static ProfileContextBuilder create() { 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; diff --git a/bundles/org.openhab.core.io.rest.transform/src/main/java/org/openhab/core/io/rest/transform/internal/TransformationResource.java b/bundles/org.openhab.core.io.rest.transform/src/main/java/org/openhab/core/io/rest/transform/internal/TransformationResource.java index fb4197b0792..9e9942864ca 100644 --- a/bundles/org.openhab.core.io.rest.transform/src/main/java/org/openhab/core/io/rest/transform/internal/TransformationResource.java +++ b/bundles/org.openhab.core.io.rest.transform/src/main/java/org/openhab/core/io/rest/transform/internal/TransformationResource.java @@ -117,8 +117,9 @@ public Response getTransformationServices() { try { Collection> refs = bundleContext .getServiceReferences(TransformationService.class, null); - Stream services = refs.stream().map(ref -> (String) ref.getProperty("openhab.transform")) - .filter(Objects::nonNull).map(Objects::requireNonNull); + Stream 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(); diff --git a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java index 04eb909b43e..e39202afe96 100644 --- a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java +++ b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationHelper.java @@ -61,7 +61,7 @@ public static boolean isTransform(String pattern) { 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> refs = context .getServiceReferences(TransformationService.class, filter); diff --git a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationService.java b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationService.java index 60be09cdbe7..f018a3a4aee 100644 --- a/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationService.java +++ b/bundles/org.openhab.core.transform/src/main/java/org/openhab/core/transform/TransformationService.java @@ -32,8 +32,10 @@ @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 source by means of the given function and returns the transformed