added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

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

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.transform.map.internal;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import org.openhab.core.transform.AbstractFileTransformationService;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationService;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* The implementation of {@link TransformationService} which simply maps strings to other strings
*
* @author Kai Kreuzer - Initial contribution and API
* @author Gaël L'hopital - Make it localizable
*/
@Component(immediate = true, service = TransformationService.class, property = { "smarthome.transform=MAP" })
public class MapTransformationService extends AbstractFileTransformationService<Properties> {
private final Logger logger = LoggerFactory.getLogger(MapTransformationService.class);
/**
* <p>
* Transforms the input <code>source</code> by mapping it to another string. It expects the mappings to be read from
* a file which is stored under the 'configurations/transform' folder. This file should be in property syntax, i.e.
* simple lines with "key=value" pairs. To organize the various transformations one might use subfolders.
*
* @param properties the list of properties which contains the key value pairs for the mapping.
* @param source the input to transform
*/
@Override
protected String internalTransform(Properties properties, String source) throws TransformationException {
String target = properties.getProperty(source);
if (target == null) {
target = properties.getProperty("");
if (target == null) {
throw new TransformationException("Target value not found in map for '" + source + "'");
}
}
logger.debug("Transformation resulted in '{}'", target);
return target;
}
@Override
protected Properties internalLoadTransform(String filename) throws TransformationException {
Properties result = new Properties();
try (FileReader reader = new FileReader(filename)) {
result.load(reader);
return result;
} catch (IOException e) {
throw new TransformationException("An error occurred while opening file.", e);
}
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.transform.map.internal.profiles;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.thing.profiles.StateProfile;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationHelper;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Profile to offer the MapTransformationservice on a ItemChannelLink
*
* @author Stefan Triller - initial contribution
*
*/
@NonNullByDefault
public class MapTransformationProfile implements StateProfile {
public static final ProfileTypeUID PROFILE_TYPE_UID = new ProfileTypeUID(
TransformationService.TRANSFORM_PROFILE_SCOPE, "MAP");
private final Logger logger = LoggerFactory.getLogger(MapTransformationProfile.class);
private final TransformationService service;
private final ProfileCallback callback;
private static final String FUNCTION_PARAM = "function";
private static final String SOURCE_FORMAT_PARAM = "sourceFormat";
@NonNullByDefault({})
private final String function;
@NonNullByDefault({})
private final String sourceFormat;
public MapTransformationProfile(ProfileCallback callback, ProfileContext context, TransformationService service) {
this.service = service;
this.callback = callback;
Object paramFunction = context.getConfiguration().get(FUNCTION_PARAM);
Object paramSource = context.getConfiguration().get(SOURCE_FORMAT_PARAM);
logger.debug("Profile configured with '{}'='{}', '{}'={}", FUNCTION_PARAM, paramFunction, SOURCE_FORMAT_PARAM,
paramSource);
// SOURCE_FORMAT_PARAM is an advanced parameter and we assume "%s" if it is not set
if (paramSource == null) {
paramSource = "%s";
}
if (paramFunction instanceof String && paramSource instanceof String) {
function = (String) paramFunction;
sourceFormat = (String) paramSource;
} else {
logger.error("Parameter '{}' and '{}' have to be Strings. Profile will be inactive.", FUNCTION_PARAM,
SOURCE_FORMAT_PARAM);
function = null;
sourceFormat = null;
}
}
@Override
public ProfileTypeUID getProfileTypeUID() {
return PROFILE_TYPE_UID;
}
@Override
public void onStateUpdateFromItem(State state) {
callback.handleUpdate(state);
}
@Override
public void onCommandFromItem(Command command) {
callback.handleCommand(command);
}
@Override
public void onCommandFromHandler(Command command) {
if (function == null || sourceFormat == null) {
logger.warn(
"Please specify a function and a source format for this Profile in the '{}', and '{}' parameters, e.g \"translation.map\" and \"%s\". Returning the original command now.",
FUNCTION_PARAM, SOURCE_FORMAT_PARAM);
callback.sendCommand(command);
return;
}
callback.sendCommand((Command) transformState(command));
}
@Override
public void onStateUpdateFromHandler(State state) {
if (function == null || sourceFormat == null) {
logger.warn(
"Please specify a function and a source format for this Profile in the '{}' and '{}' parameters, e.g \"translation.map\" and \"%s\". Returning the original state now.",
FUNCTION_PARAM, SOURCE_FORMAT_PARAM);
callback.sendUpdate(state);
return;
}
callback.sendUpdate((State) transformState(state));
}
private Type transformState(Type state) {
String result = state.toFullString();
try {
result = TransformationHelper.transform(service, function, sourceFormat, state.toFullString());
if (result != null && result.isEmpty()) {
// map transformation service returns an empty string if the entry is not found in the map, we will use
// the original value
result = state.toFullString();
}
} catch (TransformationException e) {
logger.warn("Could not transform state '{}' with function '{}' and format '{}'", state, function,
sourceFormat);
}
StringType resultType = new StringType(result);
logger.debug("Transformed '{}' into '{}'", state, resultType);
return resultType;
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.transform.map.internal.profiles;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.profiles.Profile;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileFactory;
import org.openhab.core.thing.profiles.ProfileType;
import org.openhab.core.thing.profiles.ProfileTypeBuilder;
import org.openhab.core.thing.profiles.ProfileTypeProvider;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.transform.TransformationService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Profilefactory that creates the transformation profile for the map transformation service
*
* @author Stefan Triller - initial contribution
*
*/
@NonNullByDefault
@Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
public class MapTransformationProfileFactory implements ProfileFactory, ProfileTypeProvider {
@NonNullByDefault({})
private TransformationService service;
@Override
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
return Arrays.asList(ProfileTypeBuilder
.newState(MapTransformationProfile.PROFILE_TYPE_UID, MapTransformationProfile.PROFILE_TYPE_UID.getId())
.build());
}
@Override
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
ProfileContext profileContext) {
return new MapTransformationProfile(callback, profileContext, service);
}
@Override
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
return Arrays.asList(MapTransformationProfile.PROFILE_TYPE_UID);
}
@Reference(target = "(smarthome.transform=MAP)")
public void addTransformationService(TransformationService service) {
this.service = service;
}
public void removeTransformationService(TransformationService service) {
this.service = null;
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="profile:transform:MAP">
<parameter name="function" type="text" required="true">
<label>Filename</label>
<description>Filename containing the mapping information.</description>
</parameter>
<parameter name="sourceFormat" type="text" required="false">
<label>State Formatter</label>
<description>How to format the state on the channel before transforming it, i.e. %s or %.1f °C (default is %s)</description>
<advanced>true</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1 @@
Bundle resources go in here!

View File

@@ -0,0 +1,179 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.transform.map.internal;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.Callable;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.osgi.framework.BundleContext;
/**
* @author Gaël L'hopital - Initial contribution
*/
public class MapTransformationServiceTest {
private static final String SOURCE_CLOSED = "CLOSED";
private static final String SOURCE_UNKNOWN = "UNKNOWN";
private static final String EXISTING_FILENAME_DE = "map/doorstatus_de.map";
private static final String SHOULD_BE_LOCALIZED_FILENAME = "map/doorstatus.map";
private static final String DEFAULTED_FILENAME = "map/doorstatus_defaulted.map";
private static final String INEXISTING_FILENAME = "map/de.map";
private static final String BASE_FOLDER = "target";
private static final String SRC_FOLDER = "conf";
private static final String CONFIG_FOLDER = BASE_FOLDER + File.separator + SRC_FOLDER;
private static final String USED_FILENAME = CONFIG_FOLDER + File.separator + "transform/" + EXISTING_FILENAME_DE;
@Mock
private BundleContext bundleContext;
private TestableMapTransformationService processor;
private class TestableMapTransformationService extends MapTransformationService {
@Override
protected String getSourcePath() {
return BASE_FOLDER + File.separator + super.getSourcePath();
}
@Override
protected Locale getLocale() {
return Locale.US;
}
@Override
public void activate(BundleContext context) {
super.activate(context);
}
@Override
public void deactivate() {
super.deactivate();
}
};
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
processor = new TestableMapTransformationService();
processor.activate(bundleContext);
FileUtils.copyDirectory(new File(SRC_FOLDER), new File(CONFIG_FOLDER));
}
@After
public void tearDown() throws IOException {
processor.deactivate();
FileUtils.deleteDirectory(new File(CONFIG_FOLDER));
}
@Test
public void testTransformByMap() throws Exception {
// Test that we find a translation in an existing file
String transformedResponse = processor.transform(EXISTING_FILENAME_DE, SOURCE_CLOSED);
Assert.assertEquals("zu", transformedResponse);
Properties properties = new Properties();
try (FileReader reader = new FileReader(USED_FILENAME); FileWriter writer = new FileWriter(USED_FILENAME)) {
properties.load(reader);
properties.setProperty(SOURCE_CLOSED, "changevalue");
properties.store(writer, "");
// This tests that the requested transformation file has been removed from
// the cache
waitForAssert(new Callable<Void>() {
@Override
public Void call() throws Exception {
final String transformedResponse = processor.transform(EXISTING_FILENAME_DE, SOURCE_CLOSED);
Assert.assertEquals("changevalue", transformedResponse);
return null;
}
}, 10000, 100);
properties.setProperty(SOURCE_CLOSED, "zu");
properties.store(writer, "");
waitForAssert(new Callable<Void>() {
@Override
public Void call() throws Exception {
final String transformedResponse = processor.transform(EXISTING_FILENAME_DE, SOURCE_CLOSED);
Assert.assertEquals("zu", transformedResponse);
return null;
}
}, 10000, 100);
} catch (IOException e1) {
e1.printStackTrace(System.err);
}
// Checks that an unknown input in an existing file give the expected
// transformed response that shall be empty string (Issue #1107) if not found in the file
transformedResponse = processor.transform(EXISTING_FILENAME_DE, SOURCE_UNKNOWN);
Assert.assertEquals("", transformedResponse);
// Test that an inexisting file raises correct exception as expected
try {
transformedResponse = processor.transform(INEXISTING_FILENAME, SOURCE_CLOSED);
fail();
} catch (Exception e) {
// That's what we expect.
}
// Test that we find a localized version of desired file
transformedResponse = processor.transform(SHOULD_BE_LOCALIZED_FILENAME, SOURCE_CLOSED);
// as we don't know the real locale at the moment the
// test is run, we test that the string has actually been transformed
Assert.assertNotEquals(SOURCE_CLOSED, transformedResponse);
transformedResponse = processor.transform(SHOULD_BE_LOCALIZED_FILENAME, SOURCE_CLOSED);
Assert.assertNotEquals(SOURCE_CLOSED, transformedResponse);
}
@Test
public void testTransformByMapWithDefault() throws Exception {
// Standard behaviour with no default value
String transformedResponse = processor.transform(SHOULD_BE_LOCALIZED_FILENAME, "toBeDefaulted");
Assert.assertEquals("", transformedResponse);
// Modified behaviour with a file containing default value definition
transformedResponse = processor.transform(DEFAULTED_FILENAME, "toBeDefaulted");
Assert.assertEquals("Default Value", transformedResponse);
}
protected void waitForAssert(Callable<Void> assertion, int timeout, int sleepTime) throws Exception {
int waitingTime = 0;
while (waitingTime < timeout) {
try {
assertion.call();
return;
} catch (AssertionError error) {
waitingTime += sleepTime;
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
assertion.call();
}
}