Add a registry for transformation configurations and allow editing them (#2821)

Signed-off-by: Jan N. Klug <github@klug.nrw>
pull/2910/head
J-N-K 2022-04-11 08:18:14 +02:00 committed by GitHub
parent 90f6a95251
commit 53dcf48a17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1264 additions and 15 deletions

View File

@ -238,6 +238,12 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.io.rest.transform</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.io.rest.ui</artifactId>

View File

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

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.reactor.bundles</artifactId>
<version>3.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.core.io.rest.transform</artifactId>
<name>openHAB Core :: Bundles :: Transformation REST Interface</name>
<dependencies>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.transform</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.io.rest</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.io.rest.transform;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.transform.TransformationConfiguration;
/**
* The {@link TransformationConfigurationDTO} wraps a {@link TransformationConfiguration}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class TransformationConfigurationDTO {
public String uid;
public String label;
public String type;
public @Nullable String language;
public String content;
public boolean editable = false;
public TransformationConfigurationDTO(TransformationConfiguration transformationConfiguration) {
this.uid = transformationConfiguration.getUID();
this.label = transformationConfiguration.getLabel();
this.type = transformationConfiguration.getType();
this.content = transformationConfiguration.getContent();
this.language = transformationConfiguration.getLanguage();
}
}

View File

@ -0,0 +1,192 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.io.rest.transform.internal;
import java.util.Objects;
import java.util.stream.Stream;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.auth.Role;
import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.io.rest.RESTResource;
import org.openhab.core.io.rest.Stream2JSONInputStream;
import org.openhab.core.io.rest.transform.TransformationConfigurationDTO;
import org.openhab.core.transform.ManagedTransformationConfigurationProvider;
import org.openhab.core.transform.TransformationConfiguration;
import org.openhab.core.transform.TransformationConfigurationRegistry;
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.jaxrs.whiteboard.JaxrsWhiteboardConstants;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* The {@link TransformationConfigurationResource} is a REST resource for handling transformation configurations
*
* @author Jan N. Klug - Initial contribution
*/
@Component(immediate = true)
@JaxrsResource
@JaxrsName(TransformationConfigurationResource.PATH_TRANSFORMATIONS)
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
@JSONRequired
@Path(TransformationConfigurationResource.PATH_TRANSFORMATIONS)
@RolesAllowed({ Role.ADMIN })
@SecurityRequirement(name = "oauth2", scopes = { "admin" })
@Tag(name = TransformationConfigurationResource.PATH_TRANSFORMATIONS)
@NonNullByDefault
public class TransformationConfigurationResource implements RESTResource {
public static final String PATH_TRANSFORMATIONS = "transformations";
private final Logger logger = LoggerFactory.getLogger(TransformationConfigurationResource.class);
private final TransformationConfigurationRegistry transformationConfigurationRegistry;
private final ManagedTransformationConfigurationProvider managedTransformationConfigurationProvider;
private @Context @NonNullByDefault({}) UriInfo uriInfo;
@Activate
public TransformationConfigurationResource(
final @Reference TransformationConfigurationRegistry transformationConfigurationRegistry,
final @Reference ManagedTransformationConfigurationProvider managedTransformationConfigurationProvider) {
this.transformationConfigurationRegistry = transformationConfigurationRegistry;
this.managedTransformationConfigurationProvider = managedTransformationConfigurationProvider;
}
@GET
@Path("configurations")
@Produces(MediaType.APPLICATION_JSON)
@Operation(operationId = "getTransformationConfigurations", summary = "Get a list of all transformation configurations", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = TransformationConfigurationDTO.class)))) })
public Response getTransformationConfigurations() {
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
Stream<TransformationConfigurationDTO> stream = transformationConfigurationRegistry.stream()
.map(TransformationConfigurationDTO::new).peek(c -> c.editable = isEditable(c.uid));
return Response.ok(new Stream2JSONInputStream(stream)).build();
}
@GET
@Path("configurations/{uid}")
@Produces(MediaType.APPLICATION_JSON)
@Operation(operationId = "getTransformationConfiguration", summary = "Get a single transformation configuration", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = TransformationConfiguration.class))),
@ApiResponse(responseCode = "404", description = "Not found") })
public Response getTransformationConfiguration(
@PathParam("uid") @Parameter(description = "Configuration UID") String uid) {
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
TransformationConfiguration configuration = transformationConfigurationRegistry.get(uid);
if (configuration == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(configuration).build();
}
@PUT
@Path("configurations/{uid}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
@Operation(operationId = "putTransformationConfiguration", summary = "Get a single transformation configuration", responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "400", description = "Bad Request (content missing or invalid)"),
@ApiResponse(responseCode = "405", description = "Configuration not editable") })
public Response putTransformationConfiguration(
@PathParam("uid") @Parameter(description = "Configuration UID") String uid,
@Parameter(description = "configuration", required = true) @Nullable TransformationConfigurationDTO newConfiguration) {
logger.debug("Received HTTP PUT request at '{}'", uriInfo.getPath());
TransformationConfiguration oldConfiguration = transformationConfigurationRegistry.get(uid);
if (oldConfiguration != null && !isEditable(uid)) {
return Response.status(Response.Status.METHOD_NOT_ALLOWED).build();
}
if (newConfiguration == null) {
return Response.status(Response.Status.BAD_REQUEST).entity("Content missing.").build();
}
if (!uid.equals(newConfiguration.uid)) {
return Response.status(Response.Status.BAD_REQUEST).entity("UID of configuration and path not matching.")
.build();
}
TransformationConfiguration transformationConfiguration = new TransformationConfiguration(newConfiguration.uid,
newConfiguration.label, newConfiguration.type, newConfiguration.language, newConfiguration.content);
try {
if (oldConfiguration != null) {
managedTransformationConfigurationProvider.update(transformationConfiguration);
} else {
managedTransformationConfigurationProvider.add(transformationConfiguration);
}
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST).entity(Objects.requireNonNullElse(e.getMessage(), ""))
.build();
}
return Response.ok().build();
}
@DELETE
@Path("configurations/{uid}")
@Produces(MediaType.TEXT_PLAIN)
@Operation(operationId = "deleteTransformationConfiguration", summary = "Get a single transformation configuration", responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "UID not found"),
@ApiResponse(responseCode = "405", description = "Configuration not editable") })
public Response deleteTransformationConfiguration(
@PathParam("uid") @Parameter(description = "Configuration UID") String uid) {
logger.debug("Received HTTP DELETE request at '{}'", uriInfo.getPath());
TransformationConfiguration oldConfiguration = transformationConfigurationRegistry.get(uid);
if (oldConfiguration == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
if (!isEditable(uid)) {
return Response.status(Response.Status.METHOD_NOT_ALLOWED).build();
}
managedTransformationConfigurationProvider.remove(uid);
return Response.ok().build();
}
private boolean isEditable(String uid) {
return managedTransformationConfigurationProvider.get(uid) != null;
}
}

View File

@ -20,6 +20,12 @@
<artifactId>org.openhab.core.config.core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -49,6 +49,8 @@ import org.slf4j.LoggerFactory;
* under the 'transform' folder within the configuration path. To organize the various
* transformations one might use subfolders.
*
* @deprecated use the {@link TransformationConfigurationRegistry} instead
*
* @author Gaël L'hopital - Initial contribution
* @author Kai Kreuzer - File caching mechanism
* @author Markus Rathgeb - Add locale provider support

View File

@ -0,0 +1,156 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.OpenHAB;
import org.openhab.core.common.registry.ProviderChangeListener;
import org.openhab.core.service.AbstractWatchService;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link FileTransformationConfigurationProvider} implements a {@link TransformationConfigurationProvider} for
* supporting configurations stored in configuration files
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@Component(service = TransformationConfigurationProvider.class, immediate = true)
public class FileTransformationConfigurationProvider extends AbstractWatchService
implements TransformationConfigurationProvider {
private static final WatchEvent.Kind<?>[] WATCH_EVENTS = { StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY };
private static final Set<String> IGNORED_EXTENSIONS = Set.of("txt");
private static final Pattern FILENAME_PATTERN = Pattern
.compile("(?<filename>.+?)(_(?<language>[a-z]{2}))?\\.(?<extension>[^.]*)$");
private static final Path TRANSFORMATION_PATH = Path.of(OpenHAB.getConfigFolder(),
TransformationService.TRANSFORM_FOLDER_NAME);
private final Logger logger = LoggerFactory.getLogger(FileTransformationConfigurationProvider.class);
private final Set<ProviderChangeListener<TransformationConfiguration>> listeners = ConcurrentHashMap.newKeySet();
private final Map<Path, TransformationConfiguration> transformationConfigurations = new ConcurrentHashMap<>();
private final Path transformationPath;
public FileTransformationConfigurationProvider() {
this(TRANSFORMATION_PATH);
}
// constructor package private used for testing
FileTransformationConfigurationProvider(Path transformationPath) {
super(transformationPath.toString());
this.transformationPath = transformationPath;
// read initial contents
try {
Files.walk(transformationPath).filter(Files::isRegularFile)
.forEach(f -> processPath(StandardWatchEventKinds.ENTRY_CREATE, f));
} catch (IOException e) {
logger.warn("Could not list files in '{}', transformation configurations might be missing: {}",
transformationPath, e.getMessage());
}
}
@Override
public void addProviderChangeListener(ProviderChangeListener<TransformationConfiguration> listener) {
listeners.add(listener);
}
@Override
public void removeProviderChangeListener(ProviderChangeListener<TransformationConfiguration> listener) {
listeners.remove(listener);
}
@Override
public Collection<TransformationConfiguration> getAll() {
return transformationConfigurations.values();
}
@Override
protected boolean watchSubDirectories() {
return true;
}
@Override
protected WatchEvent.Kind<?> @Nullable [] getWatchEventKinds(Path directory) {
return WATCH_EVENTS;
}
@Override
protected void processWatchEvent(WatchEvent<?> event, WatchEvent.Kind<?> kind, Path path) {
processPath(kind, path);
}
private void processPath(WatchEvent.Kind<?> kind, Path path) {
if (!Files.isRegularFile(path)) {
logger.trace("Skipping {} event for '{}' - not a regular file", kind, path);
return;
}
if (StandardWatchEventKinds.ENTRY_DELETE.equals(kind)) {
TransformationConfiguration oldElement = transformationConfigurations.remove(path);
if (oldElement != null) {
logger.trace("Removed configuration from file '{}", path);
listeners.forEach(listener -> listener.removed(this, oldElement));
}
} else if (StandardWatchEventKinds.ENTRY_CREATE.equals(kind)
|| StandardWatchEventKinds.ENTRY_MODIFY.equals(kind)) {
try {
String fileName = path.getFileName().toString();
Matcher m = FILENAME_PATTERN.matcher(fileName);
if (!m.matches()) {
logger.debug("Skipping {} event for '{}' - no file extensions found or remaining filename empty",
kind, path);
return;
}
String fileExtension = m.group("extension");
if (IGNORED_EXTENSIONS.contains(fileExtension)) {
logger.debug("Skipping {} event for '{}' - file extension '{}' is ignored", kind, path,
fileExtension);
return;
}
String content = new String(Files.readAllBytes(path));
String uid = transformationPath.relativize(path).toString();
TransformationConfiguration newElement = new TransformationConfiguration(uid, uid, fileExtension,
m.group("language"), content);
TransformationConfiguration oldElement = transformationConfigurations.put(path, newElement);
if (oldElement == null) {
logger.trace("Added new configuration from file '{}'", path);
listeners.forEach(listener -> listener.added(this, newElement));
} else {
logger.trace("Updated new configuration from file '{}'", path);
listeners.forEach(listener -> listener.updated(this, oldElement, newElement));
}
} catch (IOException e) {
logger.warn("Skipping {} event for '{}' - failed to read content: {}", kind, path, e.getMessage());
}
}
}
}

View File

@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform;
import java.util.Objects;
import java.util.regex.Matcher;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.AbstractManagedProvider;
import org.openhab.core.storage.StorageService;
import org.openhab.core.transform.ManagedTransformationConfigurationProvider.PersistedTransformationConfiguration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link ManagedTransformationConfigurationProvider} implements a {@link TransformationConfigurationProvider} for
* managed configurations stored in a JSON database
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@Component(service = { TransformationConfigurationProvider.class,
ManagedTransformationConfigurationProvider.class }, immediate = true)
public class ManagedTransformationConfigurationProvider
extends AbstractManagedProvider<TransformationConfiguration, String, PersistedTransformationConfiguration>
implements TransformationConfigurationProvider {
@Activate
public ManagedTransformationConfigurationProvider(final @Reference StorageService storageService) {
super(storageService);
}
@Override
protected String getStorageName() {
return TransformationConfiguration.class.getName();
}
@Override
protected String keyToString(String key) {
return key;
}
@Override
protected @Nullable TransformationConfiguration toElement(String key,
PersistedTransformationConfiguration persistableElement) {
return new TransformationConfiguration(persistableElement.uid, persistableElement.label,
persistableElement.type, persistableElement.language, persistableElement.content);
}
@Override
protected PersistedTransformationConfiguration toPersistableElement(TransformationConfiguration element) {
return new PersistedTransformationConfiguration(element);
}
@Override
public void add(TransformationConfiguration element) {
checkConfiguration(element);
super.add(element);
}
@Override
public @Nullable TransformationConfiguration update(TransformationConfiguration element) {
checkConfiguration(element);
return super.update(element);
}
private static void checkConfiguration(TransformationConfiguration element) {
Matcher matcher = TransformationConfigurationRegistry.CONFIG_UID_PATTERN.matcher(element.getUID());
if (!matcher.matches()) {
throw new IllegalArgumentException(
"The transformation configuration UID '" + element.getUID() + "' is invalid.");
}
if (!Objects.equals(element.getLanguage(), matcher.group("language"))) {
throw new IllegalArgumentException("The transformation configuration UID '" + element.getUID()
+ "' contains(misses) a language, but it is not set (set).");
}
if (!Objects.equals(element.getType(), matcher.group("type"))) {
throw new IllegalArgumentException("The transformation configuration UID '" + element.getUID()
+ "' is not matching the type '" + element.getType() + "'.");
}
}
public static class PersistedTransformationConfiguration {
public @NonNullByDefault({}) String uid;
public @NonNullByDefault({}) String label;
public @NonNullByDefault({}) String type;
public @Nullable String language;
public @NonNullByDefault({}) String content;
protected PersistedTransformationConfiguration() {
// default constructor for deserialization
}
public PersistedTransformationConfiguration(TransformationConfiguration configuration) {
this.uid = configuration.getUID();
this.label = configuration.getLabel();
this.type = configuration.getType();
this.language = configuration.getLanguage();
this.content = configuration.getContent();
}
}
}

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.Identifiable;
/**
* The {@link TransformationConfiguration} encapsulates a transformation configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class TransformationConfiguration implements Identifiable<String> {
private final String uid;
private final String label;
private final String type;
private final @Nullable String language;
private final String content;
/**
* @param uid the configuration UID. The format is config:&lt;type&gt;:&lt;name&gt;[:&lt;locale&gt;]. For backward
* compatibility also filenames are allowed.
* @param type the type of the configuration (file extension for file-based providers)
* @param language the language of this configuration (<code>null</code> if not set)
* @param content the content of this configuration
*/
public TransformationConfiguration(String uid, String label, String type, @Nullable String language,
String content) {
this.uid = uid;
this.label = label;
this.type = type;
this.content = content;
this.language = language;
}
@Override
public String getUID() {
return uid;
}
public String getLabel() {
return label;
}
public String getType() {
return type;
}
public @Nullable String getLanguage() {
return language;
}
public String getContent() {
return content;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TransformationConfiguration that = (TransformationConfiguration) o;
return uid.equals(that.uid) && label.equals(that.label) && type.equals(that.type)
&& Objects.equals(language, that.language) && content.equals(that.content);
}
@Override
public int hashCode() {
return Objects.hash(uid, label, type, language, content);
}
@Override
public String toString() {
return "TransformationConfiguration{uid='" + uid + "', label='" + label + "', type='" + type + "', language='"
+ language + "', content='" + content + "'}";
}
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.common.registry.Provider;
/**
* The {@link TransformationConfigurationProvider} is implemented by providers for transformation configurations
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public interface TransformationConfigurationProvider extends Provider<TransformationConfiguration> {
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform;
import java.util.Collection;
import java.util.Locale;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.Registry;
/**
* The {@link TransformationConfigurationRegistry} is the interface for the transformation configuration registry
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public interface TransformationConfigurationRegistry extends Registry<TransformationConfiguration, String> {
Pattern CONFIG_UID_PATTERN = Pattern.compile("config:(?<type>\\w+):(?<name>\\w+)(:(?<language>\\w+))?");
/**
* Get a localized version of the configuration for a given UID
*
* @param uid the configuration UID
* @param locale a locale (system locale is used if <code>null</code>)
* @return the requested {@link TransformationConfiguration} (or <code>null</code> if not found).
*/
@Nullable
TransformationConfiguration get(String uid, @Nullable Locale locale);
/**
* Get all configurations which match the given types
*
* @param types a {@link Collection} of configuration types
* @return a {@link Collection} of {@link TransformationConfiguration}s
*/
Collection<TransformationConfiguration> getConfigurations(Collection<String> types);
}

View File

@ -12,6 +12,8 @@
*/
package org.openhab.core.transform.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
@ -23,17 +25,18 @@ import org.slf4j.LoggerFactory;
* @author Thomas Eichstaedt-Engelen - Initial contribution
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public final class TransformationActivator implements BundleActivator {
private final Logger logger = LoggerFactory.getLogger(TransformationActivator.class);
private static BundleContext context;
private static @Nullable BundleContext context;
/**
* Called whenever the OSGi framework starts our bundle
*/
@Override
public void start(BundleContext bc) throws Exception {
public void start(@Nullable BundleContext bc) throws Exception {
context = bc;
logger.debug("Transformation Service has been started.");
}
@ -42,7 +45,7 @@ public final class TransformationActivator implements BundleActivator {
* Called whenever the OSGi framework stops our bundle
*/
@Override
public void stop(BundleContext bc) throws Exception {
public void stop(@Nullable BundleContext bc) throws Exception {
context = null;
logger.debug("Transformation Service has been stopped.");
}
@ -52,7 +55,7 @@ public final class TransformationActivator implements BundleActivator {
*
* @return the bundle context
*/
public static BundleContext getContext() {
public static @Nullable BundleContext getContext() {
return context;
}
}

View File

@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform.internal;
import java.util.Collection;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.AbstractRegistry;
import org.openhab.core.common.registry.Provider;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.transform.ManagedTransformationConfigurationProvider;
import org.openhab.core.transform.TransformationConfiguration;
import org.openhab.core.transform.TransformationConfigurationProvider;
import org.openhab.core.transform.TransformationConfigurationRegistry;
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 TransformationConfigurationRegistryImpl} implements the {@link TransformationConfigurationRegistry}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@Component(immediate = true)
public class TransformationConfigurationRegistryImpl
extends AbstractRegistry<TransformationConfiguration, String, TransformationConfigurationProvider>
implements TransformationConfigurationRegistry {
private static final Pattern FILENAME_PATTERN = Pattern
.compile("(?<filename>.+)(_(?<language>[a-z]{2}))?\\.(?<extension>[^.]*)$");
private final LocaleProvider localeProvider;
@Activate
public TransformationConfigurationRegistryImpl(@Reference LocaleProvider localeProvider) {
super(TransformationConfigurationProvider.class);
this.localeProvider = localeProvider;
}
@Override
public @Nullable TransformationConfiguration get(String uid, @Nullable Locale locale) {
TransformationConfiguration configuration = null;
String language = Objects.requireNonNullElse(locale, localeProvider.getLocale()).getLanguage();
Matcher uidMatcher = CONFIG_UID_PATTERN.matcher(uid);
if (uidMatcher.matches()) {
// try to get localized version of the uid if no locale information is present
if (uidMatcher.group("language") == null) {
configuration = get(uid + ":" + language);
}
} else {
// check if legacy configuration and try to get localized version
uidMatcher = FILENAME_PATTERN.matcher(uid);
if (uidMatcher.matches() && uidMatcher.group("language") == null) {
// try to get a localized version
String localizedUid = uidMatcher.group("filename") + "_" + language + "."
+ uidMatcher.group("extension");
configuration = get(localizedUid);
}
}
return (configuration != null) ? configuration : get(uid);
}
@Override
public Collection<TransformationConfiguration> getConfigurations(Collection<String> types) {
return getAll().stream().filter(e -> types.contains(e.getType())).collect(Collectors.toList());
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
protected void setManagedProvider(ManagedTransformationConfigurationProvider provider) {
super.setManagedProvider(provider);
}
protected void unsetManagedProvider(ManagedTransformationConfigurationProvider provider) {
super.unsetManagedProvider(provider);
}
@Override
protected void addProvider(Provider<TransformationConfiguration> provider) {
// overridden to make method available for testing
super.addProvider(provider);
}
}

View File

@ -0,0 +1,167 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.core.common.registry.ProviderChangeListener;
/**
* The {@link FileTransformationConfigurationProviderTest} includes tests for the
* {@link FileTransformationConfigurationProvider}
*
* @author Jan N. Klug - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class FileTransformationConfigurationProviderTest {
private static final String FOO_TYPE = "foo";
private static final String INITIAL_CONTENT = "initial";
private static final String INITIAL_FILENAME = INITIAL_CONTENT + "." + FOO_TYPE;
private static final TransformationConfiguration INITIAL_CONFIGURATION = new TransformationConfiguration(
INITIAL_FILENAME, INITIAL_FILENAME, FOO_TYPE, null, INITIAL_CONTENT);
private static final String ADDED_CONTENT = "added";
private static final String ADDED_FILENAME = ADDED_CONTENT + "." + FOO_TYPE;
private @Mock @NonNullByDefault({}) WatchEvent<String> watchEventMock;
private @Mock @NonNullByDefault({}) ProviderChangeListener<@NonNull TransformationConfiguration> listenerMock;
private @NonNullByDefault({}) FileTransformationConfigurationProvider provider;
private @NonNullByDefault({}) Path targetPath;
@BeforeEach
public void setup() throws IOException {
// create directory
targetPath = Files.createTempDirectory("fileTest");
// set initial content
Files.write(targetPath.resolve(INITIAL_FILENAME), INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
provider = new FileTransformationConfigurationProvider(targetPath);
provider.addProviderChangeListener(listenerMock);
}
@AfterEach
public void tearDown() throws IOException {
try (Stream<Path> walk = Files.walk(targetPath)) {
walk.map(Path::toFile).forEach(File::delete);
}
Files.deleteIfExists(targetPath);
}
@Test
public void testInitialConfigurationIsPresent() {
// assert that initial configuration is present
assertThat(provider.getAll(), contains(INITIAL_CONFIGURATION));
}
@Test
public void testAddingConfigurationIsPropagated() throws IOException {
Path path = targetPath.resolve(ADDED_FILENAME);
Files.write(path, ADDED_CONTENT.getBytes(StandardCharsets.UTF_8));
TransformationConfiguration addedConfiguration = new TransformationConfiguration(ADDED_FILENAME, ADDED_FILENAME,
FOO_TYPE, null, ADDED_CONTENT);
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_CREATE, path);
// assert registry is notified and internal cache updated
Mockito.verify(listenerMock).added(provider, addedConfiguration);
assertThat(provider.getAll(), hasItem(addedConfiguration));
}
@Test
public void testUpdatingConfigurationIsPropagated() throws IOException {
Path path = targetPath.resolve(INITIAL_FILENAME);
Files.write(path, "updated".getBytes(StandardCharsets.UTF_8));
TransformationConfiguration updatedConfiguration = new TransformationConfiguration(INITIAL_FILENAME,
INITIAL_FILENAME, FOO_TYPE, null, "updated");
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_MODIFY, path);
Mockito.verify(listenerMock).updated(provider, INITIAL_CONFIGURATION, updatedConfiguration);
assertThat(provider.getAll(), contains(updatedConfiguration));
assertThat(provider.getAll(), not(contains(INITIAL_CONFIGURATION)));
}
@Test
public void testDeletingConfigurationIsPropagated() {
Path path = targetPath.resolve(INITIAL_FILENAME);
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_DELETE, path);
Mockito.verify(listenerMock).removed(provider, INITIAL_CONFIGURATION);
assertThat(provider.getAll(), not(contains(INITIAL_CONFIGURATION)));
}
@Test
public void testLanguageIsProperlyParsed() throws IOException {
String fileName = "test_de." + FOO_TYPE;
Path path = targetPath.resolve(fileName);
Files.write(path, INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
TransformationConfiguration expected = new TransformationConfiguration(fileName, fileName, FOO_TYPE, "de",
INITIAL_CONTENT);
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_CREATE, path);
assertThat(provider.getAll(), hasItem(expected));
}
@Test
public void testMissingExtensionIsIgnored() throws IOException {
Path path = targetPath.resolve("extensionMissing");
Files.write(path, INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_CREATE, path);
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_MODIFY, path);
Mockito.verify(listenerMock, never()).added(any(), any());
Mockito.verify(listenerMock, never()).updated(any(), any(), any());
}
@Test
public void testIgnoredExtensionIsIgnored() throws IOException {
Path path = targetPath.resolve("extensionIgnore.txt");
Files.write(path, INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_CREATE, path);
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_MODIFY, path);
Mockito.verify(listenerMock, never()).added(any(), any());
Mockito.verify(listenerMock, never()).updated(any(), any(), any());
}
}

View File

@ -0,0 +1,126 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.core.common.registry.ProviderChangeListener;
import org.openhab.core.test.storage.VolatileStorageService;
/**
* The {@link ManagedTransformationConfigurationProviderTest} includes tests for the
* {@link org.openhab.core.transform.ManagedTransformationConfigurationProvider}
*
* @author Jan N. Klug - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class ManagedTransformationConfigurationProviderTest {
private @Mock @NonNullByDefault({}) ProviderChangeListener<@NonNull TransformationConfiguration> listenerMock;
private @NonNullByDefault({}) ManagedTransformationConfigurationProvider provider;
@BeforeEach
public void setup() {
VolatileStorageService storageService = new VolatileStorageService();
provider = new ManagedTransformationConfigurationProvider(storageService);
provider.addProviderChangeListener(listenerMock);
}
@Test
public void testValidConfigurationsAreAdded() {
TransformationConfiguration withoutLanguage = new TransformationConfiguration("config:foo:identifier", "",
"foo", null, "content");
provider.add(withoutLanguage);
TransformationConfiguration withLanguage = new TransformationConfiguration("config:foo:identifier:de", "",
"foo", "de", "content");
provider.add(withLanguage);
Mockito.verify(listenerMock).added(provider, withoutLanguage);
Mockito.verify(listenerMock).added(provider, withLanguage);
}
@Test
public void testValidConfigurationsIsUpdated() {
TransformationConfiguration configuration = new TransformationConfiguration("config:foo:identifier", "", "foo",
null, "content");
TransformationConfiguration updatedConfiguration = new TransformationConfiguration("config:foo:identifier", "",
"foo", null, "updated");
provider.add(configuration);
provider.update(updatedConfiguration);
Mockito.verify(listenerMock).added(provider, configuration);
Mockito.verify(listenerMock).updated(provider, configuration, updatedConfiguration);
}
@Test
public void testUidFormatValidation() {
TransformationConfiguration inValidUid = new TransformationConfiguration("invalid:foo:identifier", "", "foo",
null, "content");
assertThrows(IllegalArgumentException.class, () -> provider.add(inValidUid));
}
@Test
public void testLanguageValidations() {
TransformationConfiguration languageMissingInUid = new TransformationConfiguration("config:foo:identifier", "",
"foo", "de", "content");
assertThrows(IllegalArgumentException.class, () -> provider.add(languageMissingInUid));
TransformationConfiguration languageMissingInConfiguration = new TransformationConfiguration(
"config:foo:identifier:de", "", "foo", null, "content");
assertThrows(IllegalArgumentException.class, () -> provider.add(languageMissingInConfiguration));
TransformationConfiguration languageNotMatching = new TransformationConfiguration("config:foo:identifier:en",
"", "foo", "de", "content");
assertThrows(IllegalArgumentException.class, () -> provider.add(languageNotMatching));
}
@Test
public void testTypeValidation() {
TransformationConfiguration typeNotMatching = new TransformationConfiguration("config:foo:identifier", "",
"bar", null, "content");
assertThrows(IllegalArgumentException.class, () -> provider.add(typeNotMatching));
}
@Test
public void testSerializationDeserializationResultsInSameConfiguration() {
TransformationConfiguration configuration = new TransformationConfiguration("config:foo:identifier", "", "foo",
null, "content");
provider.add(configuration);
TransformationConfiguration configuration1 = provider.get("config:foo:identifier");
assertThat(configuration, is(configuration1));
}
}

View File

@ -12,7 +12,9 @@
*/
package org.openhab.core.transform.actions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
@ -27,15 +29,13 @@ public class TransformationTest {
@Test
public void testTransform() {
String result = Transformation.transform("UnknownTransformation", "function", "test");
assertEquals("test", result);
assertThat(result, is("test"));
}
@Test
public void testTransformRaw() {
try {
Transformation.transformRaw("UnknownTransformation", "function", "test");
} catch (TransformationException e) {
assertEquals("No transformation service 'UnknownTransformation' could be found.", e.getMessage());
}
TransformationException e = assertThrows(TransformationException.class,
() -> Transformation.transformRaw("UnknownTransformation", "function", "test"));
assertThat(e.getMessage(), is("No transformation service 'UnknownTransformation' could be found."));
}
}

View File

@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.transform.internal;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.transform.ManagedTransformationConfigurationProvider;
import org.openhab.core.transform.TransformationConfiguration;
/**
* The {@link TransformationConfigurationRegistryImplTest} includes tests for the
* {@link TransformationConfigurationRegistryImpl}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class TransformationConfigurationRegistryImplTest {
private static final String SERVICE = "foo";
private static final String MANAGED_WITHOUT_LANGUAGE_UID = "config:" + SERVICE + ":managed";
private static final String MANAGED_WITH_EN_LANGUAGE_UID = "config:" + SERVICE + ":managed:en";
private static final String MANAGED_WITH_DE_LANGUAGE_UID = "config:" + SERVICE + ":managed:de";
private static final TransformationConfiguration MANAGED_WITHOUT_LANGUAGE = new TransformationConfiguration(
MANAGED_WITHOUT_LANGUAGE_UID, "", SERVICE, null, MANAGED_WITHOUT_LANGUAGE_UID);
private static final TransformationConfiguration MANAGED_WITH_EN_LANGUAGE = new TransformationConfiguration(
MANAGED_WITH_EN_LANGUAGE_UID, "", SERVICE, "en", MANAGED_WITH_EN_LANGUAGE_UID);
private static final TransformationConfiguration MANAGED_WITH_DE_LANGUAGE = new TransformationConfiguration(
MANAGED_WITH_DE_LANGUAGE_UID, "", SERVICE, "de", MANAGED_WITH_DE_LANGUAGE_UID);
private static final String FILE_WITHOUT_LANGUAGE_UID = "foo/FILE." + SERVICE;
private static final String FILE_WITH_EN_LANGUAGE_UID = "foo/FILE_en." + SERVICE;
private static final String FILE_WITH_DE_LANGUAGE_UID = "foo/FILE_de." + SERVICE;
private static final TransformationConfiguration FILE_WITHOUT_LANGUAGE = new TransformationConfiguration(
FILE_WITHOUT_LANGUAGE_UID, "", SERVICE, null, FILE_WITHOUT_LANGUAGE_UID);
private static final TransformationConfiguration FILE_WITH_EN_LANGUAGE = new TransformationConfiguration(
FILE_WITH_EN_LANGUAGE_UID, "", SERVICE, "en", FILE_WITH_EN_LANGUAGE_UID);
private static final TransformationConfiguration FILE_WITH_DE_LANGUAGE = new TransformationConfiguration(
FILE_WITH_DE_LANGUAGE_UID, "", SERVICE, "de", FILE_WITH_DE_LANGUAGE_UID);
private @Mock @NonNullByDefault({}) LocaleProvider localeProviderMock;
private @Mock @NonNullByDefault({}) ManagedTransformationConfigurationProvider providerMock;
private @NonNullByDefault({}) TransformationConfigurationRegistryImpl registry;
@BeforeEach
public void setup() {
Mockito.when(localeProviderMock.getLocale()).thenReturn(Locale.US);
registry = new TransformationConfigurationRegistryImpl(localeProviderMock);
registry.addProvider(providerMock);
registry.added(providerMock, MANAGED_WITHOUT_LANGUAGE);
registry.added(providerMock, MANAGED_WITH_EN_LANGUAGE);
registry.added(providerMock, MANAGED_WITH_DE_LANGUAGE);
registry.added(providerMock, FILE_WITHOUT_LANGUAGE);
registry.added(providerMock, FILE_WITH_EN_LANGUAGE);
registry.added(providerMock, FILE_WITH_DE_LANGUAGE);
}
@Test
public void testManagedReturnsCorrectLanguage() {
// language contained in uid, default requested (explicit uid takes precedence)
assertThat(registry.get(MANAGED_WITH_DE_LANGUAGE_UID, null), is(MANAGED_WITH_DE_LANGUAGE));
// language contained in uid, other requested (explicit uid takes precedence)
assertThat(registry.get(MANAGED_WITH_DE_LANGUAGE_UID, Locale.FRANCE), is(MANAGED_WITH_DE_LANGUAGE));
// no language in uid, default requested
assertThat(registry.get(MANAGED_WITHOUT_LANGUAGE_UID, null), is(MANAGED_WITH_EN_LANGUAGE));
// no language in uid, other requested
assertThat(registry.get(MANAGED_WITHOUT_LANGUAGE_UID, Locale.GERMANY), is(MANAGED_WITH_DE_LANGUAGE));
// no language in uid, unknown requested
assertThat(registry.get(MANAGED_WITHOUT_LANGUAGE_UID, Locale.FRANCE), is(MANAGED_WITHOUT_LANGUAGE));
}
@Test
public void testFileReturnsCorrectLanguage() {
// language contained in uid, default requested (explicit uid takes precedence)
assertThat(registry.get(FILE_WITH_DE_LANGUAGE_UID, null), is(FILE_WITH_DE_LANGUAGE));
// language contained in uid, other requested (explicit uid takes precedence)
assertThat(registry.get(FILE_WITH_DE_LANGUAGE_UID, Locale.FRANCE), is(FILE_WITH_DE_LANGUAGE));
// no language in uid, default requested
assertThat(registry.get(FILE_WITHOUT_LANGUAGE_UID, null), is(FILE_WITH_EN_LANGUAGE));
// no language in uid, other requested
assertThat(registry.get(FILE_WITHOUT_LANGUAGE_UID, Locale.GERMANY), is(FILE_WITH_DE_LANGUAGE));
// no language in uid, unknown requested
assertThat(registry.get(FILE_WITHOUT_LANGUAGE_UID, Locale.FRANCE), is(FILE_WITHOUT_LANGUAGE));
}
}

View File

@ -67,6 +67,7 @@
<module>org.openhab.core.io.rest.sitemap</module>
<module>org.openhab.core.io.rest.sse</module>
<module>org.openhab.core.io.rest.swagger</module>
<module>org.openhab.core.io.rest.transform</module>
<module>org.openhab.core.io.rest.ui</module>
<module>org.openhab.core.io.rest.voice</module>
<module>org.openhab.core.io.transport.mdns</module>

View File

@ -157,6 +157,11 @@
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.audio/${project.version}</bundle>
</feature>
<feature name="openhab-core-io-rest-transform" version="${project.version}">
<feature>openhab-core-base</feature>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.transform/${project.version}</bundle>
</feature>
<feature name="openhab-core-io-rest-voice" version="${project.version}">
<feature>openhab-core-base</feature>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.voice/${project.version}</bundle>
@ -379,12 +384,13 @@
<feature>openhab-core-automation-module-media</feature>
<feature>openhab-core-io-console-karaf</feature>
<feature>openhab-core-io-http-auth</feature>
<feature>openhab-core-io-rest-auth</feature>
<feature>openhab-core-io-rest-sitemap</feature>
<feature>openhab-core-io-rest-audio</feature>
<feature>openhab-core-io-rest-voice</feature>
<feature>openhab-core-io-rest-swagger</feature>
<feature>openhab-core-io-rest-auth</feature>
<feature>openhab-core-io-rest-mdns</feature>
<feature>openhab-core-io-rest-sitemap</feature>
<feature>openhab-core-io-rest-swagger</feature>
<feature>openhab-core-io-rest-transform</feature>
<feature>openhab-core-io-rest-voice</feature>
<feature>openhab-core-model-lsp</feature>
<feature>openhab-core-model-item</feature>
<feature>openhab-core-model-persistence</feature>