Enhance transformation configuration (#3036)

* Enhance transformation configuration

This is needed to give the users the best experience when editing transformations in UI.

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K 2022-07-14 21:00:53 +02:00 committed by GitHub
parent 2909945998
commit 0b7fc242d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 540 additions and 379 deletions

View File

@ -29,9 +29,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.common.registry.RegistryChangeListener;
import org.openhab.core.transform.TransformationConfiguration;
import org.openhab.core.transform.TransformationConfigurationRegistry;
import org.openhab.core.transform.Transformation;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationRegistry;
import org.openhab.core.transform.TransformationService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
@ -48,8 +48,7 @@ import org.slf4j.LoggerFactory;
*/
@Component(service = TransformationService.class, property = { "openhab.transform=SCRIPT" })
@NonNullByDefault
public class ScriptTransformationService
implements TransformationService, RegistryChangeListener<TransformationConfiguration> {
public class ScriptTransformationService implements TransformationService, RegistryChangeListener<Transformation> {
public static final String OPENHAB_TRANSFORMATION_SCRIPT = "openhab-transformation-script-";
public static final String SUPPORTED_CONFIGURATION_TYPE = "script";
@ -65,21 +64,20 @@ public class ScriptTransformationService
private final Map<String, CompiledScript> compiledScripts = new HashMap<>();
private final Map<String, String> scriptCache = new HashMap<>();
private final TransformationConfigurationRegistry transformationConfigurationRegistry;
private final TransformationRegistry transformationRegistry;
private final ScriptEngineManager scriptEngineManager;
@Activate
public ScriptTransformationService(
@Reference TransformationConfigurationRegistry transformationConfigurationRegistry,
public ScriptTransformationService(@Reference TransformationRegistry transformationRegistry,
@Reference ScriptEngineManager scriptEngineManager) {
this.transformationConfigurationRegistry = transformationConfigurationRegistry;
this.transformationRegistry = transformationRegistry;
this.scriptEngineManager = scriptEngineManager;
transformationConfigurationRegistry.addRegistryChangeListener(this);
transformationRegistry.addRegistryChangeListener(this);
}
@Deactivate
public void deactivate() {
transformationConfigurationRegistry.removeRegistryChangeListener(this);
transformationRegistry.removeRegistryChangeListener(this);
// cleanup script engines
scriptEngineContainers.values().stream().map(ScriptEngineContainer::getScriptEngine)
@ -98,14 +96,13 @@ public class ScriptTransformationService
String script = scriptCache.get(scriptUid);
if (script == null) {
TransformationConfiguration transformationConfiguration = transformationConfigurationRegistry
.get(scriptUid);
if (transformationConfiguration != null) {
if (!SUPPORTED_CONFIGURATION_TYPE.equals(transformationConfiguration.getType())) {
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 '"
+ transformationConfiguration.getType() + "'.");
+ transformation.getType() + "'.");
}
script = transformationConfiguration.getContent();
script = transformation.getConfiguration().get(Transformation.FUNCTION);
}
if (script == null) {
throw new TransformationException("Could not get script for UID '" + scriptUid + "'.");
@ -164,17 +161,17 @@ public class ScriptTransformationService
}
@Override
public void added(TransformationConfiguration element) {
public void added(Transformation element) {
clearCache(element.getUID());
}
@Override
public void removed(TransformationConfiguration element) {
public void removed(Transformation element) {
clearCache(element.getUID());
}
@Override
public void updated(TransformationConfiguration oldElement, TransformationConfiguration element) {
public void updated(Transformation oldElement, Transformation element) {
clearCache(element.getUID());
}

View File

@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.util.Map;
import java.util.Objects;
import javax.script.ScriptContext;
@ -33,9 +34,9 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.core.transform.TransformationConfiguration;
import org.openhab.core.transform.TransformationConfigurationRegistry;
import org.openhab.core.transform.Transformation;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationRegistry;
/**
* The {@link ScriptTransformationServiceTest} holds tests for the {@link ScriptTransformationService}
@ -53,12 +54,12 @@ public class ScriptTransformationServiceTest {
private static final String SCRIPT = "script";
private static final String SCRIPT_OUTPUT = "output";
private static final TransformationConfiguration TRANSFORMATION_CONFIGURATION = new TransformationConfiguration(
SCRIPT_UID, "label", ScriptTransformationService.SUPPORTED_CONFIGURATION_TYPE, null, SCRIPT);
private static final TransformationConfiguration INVALID_TRANSFORMATION_CONFIGURATION = new TransformationConfiguration(
INVALID_SCRIPT_UID, "label", "invalid", null, SCRIPT);
private static final Transformation TRANSFORMATION_CONFIGURATION = new Transformation(SCRIPT_UID, "label",
ScriptTransformationService.SUPPORTED_CONFIGURATION_TYPE, Map.of(Transformation.FUNCTION, SCRIPT));
private static final Transformation INVALID_TRANSFORMATION_CONFIGURATION = new Transformation(INVALID_SCRIPT_UID,
"label", "invalid", Map.of(Transformation.FUNCTION, SCRIPT));
private @Mock @NonNullByDefault({}) TransformationConfigurationRegistry transformationConfigurationRegistry;
private @Mock @NonNullByDefault({}) TransformationRegistry transformationRegistry;
private @Mock @NonNullByDefault({}) ScriptEngineManager scriptEngineManager;
private @Mock @NonNullByDefault({}) ScriptEngineContainer scriptEngineContainer;
private @Mock @NonNullByDefault({}) ScriptEngine scriptEngine;
@ -68,7 +69,7 @@ public class ScriptTransformationServiceTest {
@BeforeEach
public void setUp() throws ScriptException {
service = new ScriptTransformationService(transformationConfigurationRegistry, scriptEngineManager);
service = new ScriptTransformationService(transformationRegistry, scriptEngineManager);
when(scriptEngineManager.createScriptEngine(eq(SCRIPT_LANGUAGE), any())).thenReturn(scriptEngineContainer);
when(scriptEngineManager.isSupported(anyString()))
@ -77,7 +78,7 @@ public class ScriptTransformationServiceTest {
when(scriptEngine.eval(SCRIPT)).thenReturn("output");
when(scriptEngine.getContext()).thenReturn(scriptContext);
when(transformationConfigurationRegistry.get(anyString())).thenAnswer(arguments -> {
when(transformationRegistry.get(anyString())).thenAnswer(arguments -> {
String scriptUid = arguments.getArgument(0);
if (SCRIPT_UID.equals(scriptUid)) {
return TRANSFORMATION_CONFIGURATION;
@ -120,7 +121,7 @@ public class ScriptTransformationServiceTest {
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input");
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input");
verify(transformationConfigurationRegistry).get(SCRIPT_UID);
verify(transformationRegistry).get(SCRIPT_UID);
}
@Test
@ -129,7 +130,7 @@ public class ScriptTransformationServiceTest {
service.updated(TRANSFORMATION_CONFIGURATION, TRANSFORMATION_CONFIGURATION);
service.transform(SCRIPT_LANGUAGE + ":" + SCRIPT_UID, "input");
verify(transformationConfigurationRegistry, times(2)).get(SCRIPT_UID);
verify(transformationRegistry, times(2)).get(SCRIPT_UID);
}
@Test

View File

@ -1,40 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.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,39 @@
/**
* 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 java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.transform.Transformation;
/**
* The {@link TransformationDTO} wraps a {@link Transformation}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class TransformationDTO {
public String uid;
public String label;
public String type;
public Map<String, String> configuration;
public boolean editable = false;
public TransformationDTO(Transformation transformation) {
this.uid = transformation.getUID();
this.label = transformation.getLabel();
this.type = transformation.getType();
this.configuration = transformation.getConfiguration();
}
}

View File

@ -34,10 +34,10 @@ 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.openhab.core.io.rest.transform.TransformationDTO;
import org.openhab.core.transform.ManagedTransformationProvider;
import org.openhab.core.transform.Transformation;
import org.openhab.core.transform.TransformationRegistry;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@ -59,99 +59,97 @@ 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
* The {@link TransformationResource} is a REST resource for handling transformations
*
* @author Jan N. Klug - Initial contribution
*/
@Component(immediate = true)
@JaxrsResource
@JaxrsName(TransformationConfigurationResource.PATH_TRANSFORMATIONS)
@JaxrsName(TransformationResource.PATH_TRANSFORMATIONS)
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
@JSONRequired
@Path(TransformationConfigurationResource.PATH_TRANSFORMATIONS)
@Path(TransformationResource.PATH_TRANSFORMATIONS)
@RolesAllowed({ Role.ADMIN })
@SecurityRequirement(name = "oauth2", scopes = { "admin" })
@Tag(name = TransformationConfigurationResource.PATH_TRANSFORMATIONS)
@Tag(name = TransformationResource.PATH_TRANSFORMATIONS)
@NonNullByDefault
public class TransformationConfigurationResource implements RESTResource {
public class TransformationResource 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 final Logger logger = LoggerFactory.getLogger(TransformationResource.class);
private final TransformationRegistry transformationRegistry;
private final ManagedTransformationProvider managedTransformationProvider;
private @Context @NonNullByDefault({}) UriInfo uriInfo;
@Activate
public TransformationConfigurationResource(
final @Reference TransformationConfigurationRegistry transformationConfigurationRegistry,
final @Reference ManagedTransformationConfigurationProvider managedTransformationConfigurationProvider) {
this.transformationConfigurationRegistry = transformationConfigurationRegistry;
this.managedTransformationConfigurationProvider = managedTransformationConfigurationProvider;
public TransformationResource(final @Reference TransformationRegistry transformationRegistry,
final @Reference ManagedTransformationProvider managedTransformationProvider) {
this.transformationRegistry = transformationRegistry;
this.managedTransformationProvider = managedTransformationProvider;
}
@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() {
@Operation(operationId = "getTransformations", summary = "Get a list of all transformations", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = TransformationDTO.class)))) })
public Response getTransformations() {
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
Stream<TransformationConfigurationDTO> stream = transformationConfigurationRegistry.stream()
.map(TransformationConfigurationDTO::new).peek(c -> c.editable = isEditable(c.uid));
Stream<TransformationDTO> stream = transformationRegistry.stream().map(TransformationDTO::new)
.peek(c -> c.editable = isEditable(c.uid));
return Response.ok(new Stream2JSONInputStream(stream)).build();
}
@GET
@Path("configurations/{uid}")
@Path("{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))),
@Operation(operationId = "getTransformation", summary = "Get a single transformation", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = Transformation.class))),
@ApiResponse(responseCode = "404", description = "Not found") })
public Response getTransformationConfiguration(
@PathParam("uid") @Parameter(description = "Configuration UID") String uid) {
public Response getTransformation(@PathParam("uid") @Parameter(description = "Transformation UID") String uid) {
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
TransformationConfiguration configuration = transformationConfigurationRegistry.get(uid);
if (configuration == null) {
Transformation transformation = transformationRegistry.get(uid);
if (transformation == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(configuration).build();
TransformationDTO dto = new TransformationDTO(transformation);
dto.editable = isEditable(uid);
return Response.ok(dto).build();
}
@PUT
@Path("configurations/{uid}")
@Path("{uid}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
@Operation(operationId = "putTransformationConfiguration", summary = "Get a single transformation configuration", responses = {
@Operation(operationId = "putTransformation", summary = "Put a single transformation", 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) {
@ApiResponse(responseCode = "405", description = "Transformation not editable") })
public Response putTransformation(@PathParam("uid") @Parameter(description = "Transformation UID") String uid,
@Parameter(description = "transformation", required = true) @Nullable TransformationDTO newTransformation) {
logger.debug("Received HTTP PUT request at '{}'", uriInfo.getPath());
TransformationConfiguration oldConfiguration = transformationConfigurationRegistry.get(uid);
if (oldConfiguration != null && !isEditable(uid)) {
Transformation oldTransformation = transformationRegistry.get(uid);
if (oldTransformation != null && !isEditable(uid)) {
return Response.status(Response.Status.METHOD_NOT_ALLOWED).build();
}
if (newConfiguration == null) {
if (newTransformation == 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.")
if (!uid.equals(newTransformation.uid)) {
return Response.status(Response.Status.BAD_REQUEST).entity("UID of transformation and path not matching.")
.build();
}
TransformationConfiguration transformationConfiguration = new TransformationConfiguration(newConfiguration.uid,
newConfiguration.label, newConfiguration.type, newConfiguration.language, newConfiguration.content);
Transformation transformation = new Transformation(newTransformation.uid, newTransformation.label,
newTransformation.type, newTransformation.configuration);
try {
if (oldConfiguration != null) {
managedTransformationConfigurationProvider.update(transformationConfiguration);
if (oldTransformation != null) {
managedTransformationProvider.update(transformation);
} else {
managedTransformationConfigurationProvider.add(transformationConfiguration);
managedTransformationProvider.add(transformation);
}
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST).entity(Objects.requireNonNullElse(e.getMessage(), ""))
@ -162,18 +160,17 @@ public class TransformationConfigurationResource implements RESTResource {
}
@DELETE
@Path("configurations/{uid}")
@Path("{uid}")
@Produces(MediaType.TEXT_PLAIN)
@Operation(operationId = "deleteTransformationConfiguration", summary = "Get a single transformation configuration", responses = {
@Operation(operationId = "deleteTransformation", summary = "Get a single transformation", 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) {
@ApiResponse(responseCode = "405", description = "Transformation not editable") })
public Response deleteTransformation(@PathParam("uid") @Parameter(description = "Transformation UID") String uid) {
logger.debug("Received HTTP DELETE request at '{}'", uriInfo.getPath());
TransformationConfiguration oldConfiguration = transformationConfigurationRegistry.get(uid);
if (oldConfiguration == null) {
Transformation oldTransformation = transformationRegistry.get(uid);
if (oldTransformation == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
@ -181,12 +178,12 @@ public class TransformationConfigurationResource implements RESTResource {
return Response.status(Response.Status.METHOD_NOT_ALLOWED).build();
}
managedTransformationConfigurationProvider.remove(uid);
managedTransformationProvider.remove(uid);
return Response.ok().build();
}
private boolean isEditable(String uid) {
return managedTransformationConfigurationProvider.get(uid) != null;
return managedTransformationProvider.get(uid) != null;
}
}

View File

@ -26,6 +26,7 @@ import org.openhab.core.config.core.ConfigurableService;
import org.openhab.core.storage.Storage;
import org.openhab.core.storage.StorageService;
import org.openhab.core.storage.json.internal.migration.BridgeImplTypeMigrator;
import org.openhab.core.storage.json.internal.migration.PersistedTransformationTypeMigrator;
import org.openhab.core.storage.json.internal.migration.ThingImplTypeMigrator;
import org.openhab.core.storage.json.internal.migration.TypeMigrator;
import org.osgi.framework.Constants;
@ -54,7 +55,9 @@ public class JsonStorageService implements StorageService {
* Contains a map of needed migrations, key is the storage name
*/
private static final Map<String, List<TypeMigrator>> MIGRATORS = Map.of( //
"org.openhab.core.thing.Thing", List.of(new BridgeImplTypeMigrator(), new ThingImplTypeMigrator()));
"org.openhab.core.thing.Thing", List.of(new BridgeImplTypeMigrator(), new ThingImplTypeMigrator()), //
"org.openhab.core.transform.TransformationConfiguration",
List.of(new PersistedTransformationTypeMigrator()));
private final Logger logger = LoggerFactory.getLogger(JsonStorageService.class);

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.storage.json.internal.migration;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* The {@link PersistedTransformationTypeMigrator} implements a {@link TypeMigrator} for stored things
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class PersistedTransformationTypeMigrator implements TypeMigrator {
@Override
public String getOldType() {
return "org.openhab.core.transform.ManagedTransformationConfigurationProvider$PersistedTransformationConfiguration";
}
@Override
public String getNewType() {
return "org.openhab.core.transform.ManagedTransformationProvider$PersistedTransformation";
}
@Override
public JsonElement migrate(JsonElement oldValue) throws TypeMigrationException {
JsonObject newValue = oldValue.deepCopy().getAsJsonObject();
JsonObject configuration = new JsonObject();
configuration.addProperty("function", newValue.remove("content").getAsString());
newValue.remove("language");
newValue.add("configuration", configuration);
return newValue;
}
}

View File

@ -0,0 +1,100 @@
/**
* 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.storage.json.internal;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.nio.file.Path;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.core.config.core.OrderingMapSerializer;
import org.openhab.core.config.core.OrderingSetSerializer;
import org.openhab.core.storage.json.internal.migration.PersistedTransformationTypeMigrator;
import org.openhab.core.storage.json.internal.migration.TypeMigrationException;
import org.openhab.core.storage.json.internal.migration.TypeMigrator;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
/**
* The {@link PersistedTransformationMigratorTest} contains tests for the ThingImpl and BridgeImpl migrators
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class PersistedTransformationMigratorTest {
private final Gson internalMapper = new GsonBuilder() //
.registerTypeHierarchyAdapter(Map.class, new OrderingMapSerializer())//
.registerTypeHierarchyAdapter(Set.class, new OrderingSetSerializer())//
.registerTypeHierarchyAdapter(Map.class, new StorageEntryMapDeserializer()) //
.setPrettyPrinting() //
.create();
private @NonNullByDefault({}) Map<String, StorageEntry> inputMap;
private @NonNullByDefault({}) Map<String, StorageEntry> resultMap;
@BeforeEach
public void setup() throws FileNotFoundException {
inputMap = readDatabase(Path.of("src/test/resources/transformMigration-input.json"));
resultMap = readDatabase(Path.of("src/test/resources/transformMigration-result.json"));
assertThat(inputMap.size(), is(2));
assertThat(resultMap.size(), is(2));
}
private static Stream<Arguments> typeMigrationsSource() {
return Stream.of(Arguments.of("config:map:2480cdc5d0:de", new PersistedTransformationTypeMigrator()),
Arguments.of("config:script:testTransCfg", new PersistedTransformationTypeMigrator()));
}
@ParameterizedTest
@MethodSource("typeMigrationsSource")
public void typeMigration(String thingUid, TypeMigrator migrator) throws TypeMigrationException {
StorageEntry inputEntry = inputMap.get(thingUid);
StorageEntry resultEntry = resultMap.get(thingUid);
assertThat(inputEntry.getEntityClassName(), is(migrator.getOldType()));
JsonElement entityValue = (JsonElement) inputEntry.getValue();
JsonElement newEntityValue = migrator.migrate(entityValue);
assertThat(newEntityValue, is(resultEntry.getValue()));
}
@SuppressWarnings("unchecked")
private Map<String, StorageEntry> readDatabase(Path path) throws FileNotFoundException {
final Map<String, StorageEntry> map = new ConcurrentHashMap<>();
FileReader reader = new FileReader(path.toFile());
Map<String, StorageEntry> loadedMap = internalMapper.fromJson(reader, map.getClass());
if (loadedMap != null && !loadedMap.isEmpty()) {
map.putAll(loadedMap);
}
return map;
}
}

View File

@ -0,0 +1,21 @@
{
"config:map:2480cdc5d0:de": {
"class": "org.openhab.core.transform.ManagedTransformationConfigurationProvider$PersistedTransformationConfiguration",
"value": {
"uid": "config:map:2480cdc5d0:de",
"label": "MAP DE 2",
"type": "map",
"language": "de",
"content": "dcyxc"
}
},
"config:script:testTransCfg": {
"class": "org.openhab.core.transform.ManagedTransformationConfigurationProvider$PersistedTransformationConfiguration",
"value": {
"uid": "config:script:testTransCfg",
"label": "Test Transformation X",
"type": "script",
"content": "if (input \u003d\u003d \u0027test\u0027) {\n console.log(\"Test!\")\n}"
}
}
}

View File

@ -0,0 +1,20 @@
{
"config:map:2480cdc5d0:de": {
"class": "org.openhab.core.transform.ManagedTransformationProvider$PersistedTransformation",
"value": {
"uid": "config:map:2480cdc5d0:de",
"label": "MAP DE 2",
"type": "map",
"configuration": { "function": "dcyxc" }
}
},
"config:script:testTransCfg": {
"class": "org.openhab.core.transform.ManagedTransformationProvider$PersistedTransformation",
"value": {
"uid": "config:script:testTransCfg",
"label": "Test Transformation X",
"type": "script",
"configuration": { "function": "if (input \u003d\u003d \u0027test\u0027) {\n console.log(\"Test!\")\n}" }
}
}
}

View File

@ -49,7 +49,7 @@ 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
* @deprecated use the {@link TransformationRegistry} instead
*
* @author Gaël L'hopital - Initial contribution
* @author Kai Kreuzer - File caching mechanism

View File

@ -12,6 +12,8 @@
*/
package org.openhab.core.transform;
import static org.openhab.core.transform.Transformation.FUNCTION;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
@ -35,15 +37,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link FileTransformationConfigurationProvider} implements a {@link TransformationConfigurationProvider} for
* supporting configurations stored in configuration files
* The {@link FileTransformationProvider} implements a {@link TransformationProvider} for
* supporting transformations stored in configuration files
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@Component(service = TransformationConfigurationProvider.class, immediate = true)
public class FileTransformationConfigurationProvider extends AbstractWatchService
implements TransformationConfigurationProvider {
@Component(service = TransformationProvider.class, immediate = true)
public class FileTransformationProvider extends AbstractWatchService implements TransformationProvider {
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");
@ -52,18 +53,18 @@ public class FileTransformationConfigurationProvider extends AbstractWatchServic
private static final Path TRANSFORMATION_PATH = Path.of(OpenHAB.getConfigFolder(),
TransformationService.TRANSFORM_FOLDER_NAME);
private final Logger logger = LoggerFactory.getLogger(FileTransformationConfigurationProvider.class);
private final Logger logger = LoggerFactory.getLogger(FileTransformationProvider.class);
private final Set<ProviderChangeListener<TransformationConfiguration>> listeners = ConcurrentHashMap.newKeySet();
private final Map<Path, TransformationConfiguration> transformationConfigurations = new ConcurrentHashMap<>();
private final Set<ProviderChangeListener<Transformation>> listeners = ConcurrentHashMap.newKeySet();
private final Map<Path, Transformation> transformationConfigurations = new ConcurrentHashMap<>();
private final Path transformationPath;
public FileTransformationConfigurationProvider() {
public FileTransformationProvider() {
this(TRANSFORMATION_PATH);
}
// constructor package private used for testing
FileTransformationConfigurationProvider(Path transformationPath) {
FileTransformationProvider(Path transformationPath) {
super(transformationPath.toString());
this.transformationPath = transformationPath;
@ -78,17 +79,17 @@ public class FileTransformationConfigurationProvider extends AbstractWatchServic
}
@Override
public void addProviderChangeListener(ProviderChangeListener<TransformationConfiguration> listener) {
public void addProviderChangeListener(ProviderChangeListener<Transformation> listener) {
listeners.add(listener);
}
@Override
public void removeProviderChangeListener(ProviderChangeListener<TransformationConfiguration> listener) {
public void removeProviderChangeListener(ProviderChangeListener<Transformation> listener) {
listeners.remove(listener);
}
@Override
public Collection<TransformationConfiguration> getAll() {
public Collection<Transformation> getAll() {
return transformationConfigurations.values();
}
@ -109,7 +110,7 @@ public class FileTransformationConfigurationProvider extends AbstractWatchServic
private void processPath(WatchEvent.Kind<?> kind, Path path) {
if (StandardWatchEventKinds.ENTRY_DELETE.equals(kind)) {
TransformationConfiguration oldElement = transformationConfigurations.remove(path);
Transformation oldElement = transformationConfigurations.remove(path);
if (oldElement != null) {
logger.trace("Removed configuration from file '{}", path);
listeners.forEach(listener -> listener.removed(this, oldElement));
@ -135,9 +136,8 @@ public class FileTransformationConfigurationProvider extends AbstractWatchServic
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);
Transformation newElement = new Transformation(uid, uid, fileExtension, Map.of(FUNCTION, content));
Transformation oldElement = transformationConfigurations.put(path, newElement);
if (oldElement == null) {
logger.trace("Added new configuration from file '{}'", path);
listeners.forEach(listener -> listener.added(this, newElement));

View File

@ -1,114 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.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,106 @@
/**
* 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.Map;
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.ManagedTransformationProvider.PersistedTransformation;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link ManagedTransformationProvider} implements a {@link TransformationProvider} for
* managed transformations stored in a JSON database
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@Component(service = { TransformationProvider.class, ManagedTransformationProvider.class }, immediate = true)
public class ManagedTransformationProvider extends
AbstractManagedProvider<Transformation, String, PersistedTransformation> implements TransformationProvider {
@Activate
public ManagedTransformationProvider(final @Reference StorageService storageService) {
super(storageService);
}
@Override
protected String getStorageName() {
return Transformation.class.getName();
}
@Override
protected String keyToString(String key) {
return key;
}
@Override
protected @Nullable Transformation toElement(String key, PersistedTransformation persistableElement) {
return new Transformation(persistableElement.uid, persistableElement.label, persistableElement.type,
persistableElement.configuration);
}
@Override
protected PersistedTransformation toPersistableElement(Transformation element) {
return new PersistedTransformation(element);
}
@Override
public void add(Transformation element) {
checkConfiguration(element);
super.add(element);
}
@Override
public @Nullable Transformation update(Transformation element) {
checkConfiguration(element);
return super.update(element);
}
private static void checkConfiguration(Transformation element) {
Matcher matcher = TransformationRegistry.CONFIG_UID_PATTERN.matcher(element.getUID());
if (!matcher.matches()) {
throw new IllegalArgumentException(
"The transformation configuration UID '" + element.getUID() + "' is invalid.");
}
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 PersistedTransformation {
public @NonNullByDefault({}) String uid;
public @NonNullByDefault({}) String label;
public @NonNullByDefault({}) String type;
public @NonNullByDefault({}) Map<String, String> configuration;
protected PersistedTransformation() {
// default constructor for deserialization
}
public PersistedTransformation(Transformation configuration) {
this.uid = configuration.getUID();
this.label = configuration.getLabel();
this.type = configuration.getType();
this.configuration = configuration.getConfiguration();
}
}
}

View File

@ -12,6 +12,7 @@
*/
package org.openhab.core.transform;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -19,32 +20,30 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.Identifiable;
/**
* The {@link TransformationConfiguration} encapsulates a transformation configuration
* The {@link Transformation} encapsulates a transformation configuration
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class TransformationConfiguration implements Identifiable<String> {
public class Transformation implements Identifiable<String> {
public static final String FUNCTION = "function";
private final String uid;
private final String label;
private final String type;
private final @Nullable String language;
private final String content;
private final Map<String, String> configuration;
/**
* @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
* @param configuration the configuration (containing e.g. the transformation function)
*/
public TransformationConfiguration(String uid, String label, String type, @Nullable String language,
String content) {
public Transformation(String uid, String label, String type, Map<String, String> configuration) {
this.uid = uid;
this.label = label;
this.type = type;
this.content = content;
this.language = language;
this.configuration = configuration;
}
@Override
@ -60,12 +59,8 @@ public class TransformationConfiguration implements Identifiable<String> {
return type;
}
public @Nullable String getLanguage() {
return language;
}
public String getContent() {
return content;
public Map<String, String> getConfiguration() {
return configuration;
}
@Override
@ -76,19 +71,19 @@ public class TransformationConfiguration implements Identifiable<String> {
if (o == null || getClass() != o.getClass()) {
return false;
}
TransformationConfiguration that = (TransformationConfiguration) o;
Transformation that = (Transformation) o;
return uid.equals(that.uid) && label.equals(that.label) && type.equals(that.type)
&& Objects.equals(language, that.language) && content.equals(that.content);
&& configuration.equals(that.configuration);
}
@Override
public int hashCode() {
return Objects.hash(uid, label, type, language, content);
return Objects.hash(uid, label, type, configuration);
}
@Override
public String toString() {
return "TransformationConfiguration{uid='" + uid + "', label='" + label + "', type='" + type + "', language='"
+ language + "', content='" + content + "'}";
return "TransformationConfiguration{uid='" + uid + "', label='" + label + "', type='" + type
+ "', configuration='" + configuration + "'}";
}
}

View File

@ -16,11 +16,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.common.registry.Provider;
/**
* The {@link TransformationConfigurationProvider} is implemented by providers for transformation configurations
* The {@link TransformationProvider} is implemented by providers for transformations
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public interface TransformationConfigurationProvider extends Provider<TransformationConfiguration> {
public interface TransformationProvider extends Provider<Transformation> {
}

View File

@ -21,29 +21,29 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.Registry;
/**
* The {@link TransformationConfigurationRegistry} is the interface for the transformation configuration registry
* The {@link TransformationRegistry} is the interface for the transformation registry
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public interface TransformationConfigurationRegistry extends Registry<TransformationConfiguration, String> {
public interface TransformationRegistry extends Registry<Transformation, 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
* Get a localized version of the transformation 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).
* @return the requested {@link Transformation} (or <code>null</code> if not found).
*/
@Nullable
TransformationConfiguration get(String uid, @Nullable Locale locale);
Transformation get(String uid, @Nullable Locale locale);
/**
* Get all configurations which match the given types
* Get all transformations which match the given types
*
* @param types a {@link Collection} of configuration types
* @return a {@link Collection} of {@link TransformationConfiguration}s
* @return a {@link Collection} of {@link Transformation}s
*/
Collection<TransformationConfiguration> getConfigurations(Collection<String> types);
Collection<Transformation> getTransformations(Collection<String> types);
}

View File

@ -24,10 +24,10 @@ 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.openhab.core.transform.ManagedTransformationProvider;
import org.openhab.core.transform.Transformation;
import org.openhab.core.transform.TransformationProvider;
import org.openhab.core.transform.TransformationRegistry;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@ -35,30 +35,29 @@ import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
/**
* The {@link TransformationConfigurationRegistryImpl} implements the {@link TransformationConfigurationRegistry}
* The {@link TransformationRegistryImpl} implements the {@link TransformationRegistry}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@Component(immediate = true)
public class TransformationConfigurationRegistryImpl
extends AbstractRegistry<TransformationConfiguration, String, TransformationConfigurationProvider>
implements TransformationConfigurationRegistry {
public class TransformationRegistryImpl extends AbstractRegistry<Transformation, String, TransformationProvider>
implements TransformationRegistry {
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);
public TransformationRegistryImpl(@Reference LocaleProvider localeProvider) {
super(TransformationProvider.class);
this.localeProvider = localeProvider;
}
@Override
public @Nullable TransformationConfiguration get(String uid, @Nullable Locale locale) {
TransformationConfiguration configuration = null;
public @Nullable Transformation get(String uid, @Nullable Locale locale) {
Transformation configuration = null;
String language = Objects.requireNonNullElse(locale, localeProvider.getLocale()).getLanguage();
Matcher uidMatcher = CONFIG_UID_PATTERN.matcher(uid);
@ -82,21 +81,21 @@ public class TransformationConfigurationRegistryImpl
}
@Override
public Collection<TransformationConfiguration> getConfigurations(Collection<String> types) {
public Collection<Transformation> getTransformations(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) {
protected void setManagedProvider(ManagedTransformationProvider provider) {
super.setManagedProvider(provider);
}
protected void unsetManagedProvider(ManagedTransformationConfigurationProvider provider) {
protected void unsetManagedProvider(ManagedTransformationProvider provider) {
super.unsetManagedProvider(provider);
}
@Override
protected void addProvider(Provider<TransformationConfiguration> provider) {
protected void addProvider(Provider<Transformation> provider) {
// overridden to make method available for testing
super.addProvider(provider);
}

View File

@ -18,6 +18,7 @@ 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 static org.openhab.core.transform.Transformation.FUNCTION;
import java.io.File;
import java.io.IOException;
@ -26,6 +27,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.util.Map;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNull;
@ -42,27 +44,27 @@ import org.mockito.quality.Strictness;
import org.openhab.core.common.registry.ProviderChangeListener;
/**
* The {@link FileTransformationConfigurationProviderTest} includes tests for the
* {@link FileTransformationConfigurationProvider}
* The {@link FileTransformationProviderTest} includes tests for the
* {@link FileTransformationProvider}
*
* @author Jan N. Klug - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class FileTransformationConfigurationProviderTest {
public class FileTransformationProviderTest {
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 Transformation INITIAL_CONFIGURATION = new Transformation(INITIAL_FILENAME, INITIAL_FILENAME,
FOO_TYPE, Map.of(FUNCTION, 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 @Mock @NonNullByDefault({}) ProviderChangeListener<@NonNull Transformation> listenerMock;
private @NonNullByDefault({}) FileTransformationConfigurationProvider provider;
private @NonNullByDefault({}) FileTransformationProvider provider;
private @NonNullByDefault({}) Path targetPath;
@BeforeEach
@ -72,7 +74,7 @@ public class FileTransformationConfigurationProviderTest {
// set initial content
Files.write(targetPath.resolve(INITIAL_FILENAME), INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
provider = new FileTransformationConfigurationProvider(targetPath);
provider = new FileTransformationProvider(targetPath);
provider.addProviderChangeListener(listenerMock);
}
@ -95,8 +97,8 @@ public class FileTransformationConfigurationProviderTest {
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);
Transformation addedConfiguration = new Transformation(ADDED_FILENAME, ADDED_FILENAME, FOO_TYPE,
Map.of(FUNCTION, ADDED_CONTENT));
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_CREATE, path);
@ -109,8 +111,8 @@ public class FileTransformationConfigurationProviderTest {
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");
Transformation updatedConfiguration = new Transformation(INITIAL_FILENAME, INITIAL_FILENAME, FOO_TYPE,
Map.of(FUNCTION, "updated"));
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_MODIFY, path);
@ -136,8 +138,7 @@ public class FileTransformationConfigurationProviderTest {
Files.write(path, INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
TransformationConfiguration expected = new TransformationConfiguration(fileName, fileName, FOO_TYPE, "de",
INITIAL_CONTENT);
Transformation expected = new Transformation(fileName, fileName, FOO_TYPE, Map.of(FUNCTION, INITIAL_CONTENT));
provider.processWatchEvent(watchEventMock, StandardWatchEventKinds.ENTRY_CREATE, path);
assertThat(provider.getAll(), hasItem(expected));

View File

@ -15,6 +15,9 @@ 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 static org.openhab.core.transform.Transformation.FUNCTION;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -30,35 +33,35 @@ 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}
* The {@link ManagedTransformationProviderTest} includes tests for the
* {@link ManagedTransformationProvider}
*
* @author Jan N. Klug - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class ManagedTransformationConfigurationProviderTest {
public class ManagedTransformationProviderTest {
private @Mock @NonNullByDefault({}) ProviderChangeListener<@NonNull TransformationConfiguration> listenerMock;
private @Mock @NonNullByDefault({}) ProviderChangeListener<@NonNull Transformation> listenerMock;
private @NonNullByDefault({}) ManagedTransformationConfigurationProvider provider;
private @NonNullByDefault({}) ManagedTransformationProvider provider;
@BeforeEach
public void setup() {
VolatileStorageService storageService = new VolatileStorageService();
provider = new ManagedTransformationConfigurationProvider(storageService);
provider = new ManagedTransformationProvider(storageService);
provider.addProviderChangeListener(listenerMock);
}
@Test
public void testValidConfigurationsAreAdded() {
TransformationConfiguration withoutLanguage = new TransformationConfiguration("config:foo:identifier", "",
"foo", null, "content");
Transformation withoutLanguage = new Transformation("config:foo:identifier", "", "foo",
Map.of(FUNCTION, "content"));
provider.add(withoutLanguage);
TransformationConfiguration withLanguage = new TransformationConfiguration("config:foo:identifier:de", "",
"foo", "de", "content");
Transformation withLanguage = new Transformation("config:foo:identifier:de", "", "foo",
Map.of(FUNCTION, "content"));
provider.add(withLanguage);
Mockito.verify(listenerMock).added(provider, withoutLanguage);
@ -67,10 +70,10 @@ public class ManagedTransformationConfigurationProviderTest {
@Test
public void testValidConfigurationsIsUpdated() {
TransformationConfiguration configuration = new TransformationConfiguration("config:foo:identifier", "", "foo",
null, "content");
TransformationConfiguration updatedConfiguration = new TransformationConfiguration("config:foo:identifier", "",
"foo", null, "updated");
Transformation configuration = new Transformation("config:foo:identifier", "", "foo",
Map.of(FUNCTION, "content"));
Transformation updatedConfiguration = new Transformation("config:foo:identifier", "", "foo",
Map.of(FUNCTION, "updated"));
provider.add(configuration);
provider.update(updatedConfiguration);
@ -81,45 +84,27 @@ public class ManagedTransformationConfigurationProviderTest {
@Test
public void testUidFormatValidation() {
TransformationConfiguration inValidUid = new TransformationConfiguration("invalid:foo:identifier", "", "foo",
null, "content");
Transformation inValidUid = new Transformation("invalid:foo:identifier", "", "foo",
Map.of(FUNCTION, "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");
Transformation typeNotMatching = new Transformation("config:foo:identifier", "", "bar",
Map.of(FUNCTION, "content"));
assertThrows(IllegalArgumentException.class, () -> provider.add(typeNotMatching));
}
@Test
public void testSerializationDeserializationResultsInSameConfiguration() {
TransformationConfiguration configuration = new TransformationConfiguration("config:foo:identifier", "", "foo",
null, "content");
Transformation configuration = new Transformation("config:foo:identifier", "", "foo",
Map.of(FUNCTION, "content"));
provider.add(configuration);
TransformationConfiguration configuration1 = provider.get("config:foo:identifier");
Transformation configuration1 = provider.get("config:foo:identifier");
assertThat(configuration, is(configuration1));
}

View File

@ -14,8 +14,10 @@ package org.openhab.core.transform.internal;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.openhab.core.transform.Transformation.FUNCTION;
import java.util.Locale;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
@ -27,54 +29,54 @@ 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;
import org.openhab.core.transform.ManagedTransformationProvider;
import org.openhab.core.transform.Transformation;
/**
* The {@link TransformationConfigurationRegistryImplTest} includes tests for the
* {@link TransformationConfigurationRegistryImpl}
* The {@link TransformationRegistryImplTest} includes tests for the
* {@link TransformationRegistryImpl}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class TransformationConfigurationRegistryImplTest {
public class TransformationRegistryImplTest {
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 Transformation MANAGED_WITHOUT_LANGUAGE = new Transformation(MANAGED_WITHOUT_LANGUAGE_UID, "",
SERVICE, Map.of(FUNCTION, MANAGED_WITHOUT_LANGUAGE_UID));
private static final Transformation MANAGED_WITH_EN_LANGUAGE = new Transformation(MANAGED_WITH_EN_LANGUAGE_UID, "",
SERVICE, Map.of(FUNCTION, MANAGED_WITH_EN_LANGUAGE_UID));
private static final Transformation MANAGED_WITH_DE_LANGUAGE = new Transformation(MANAGED_WITH_DE_LANGUAGE_UID, "",
SERVICE, Map.of(FUNCTION, 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 static final Transformation FILE_WITHOUT_LANGUAGE = new Transformation(FILE_WITHOUT_LANGUAGE_UID, "",
SERVICE, Map.of(FUNCTION, FILE_WITHOUT_LANGUAGE_UID));
private static final Transformation FILE_WITH_EN_LANGUAGE = new Transformation(FILE_WITH_EN_LANGUAGE_UID, "",
SERVICE, Map.of(FUNCTION, FILE_WITH_EN_LANGUAGE_UID));
private static final Transformation FILE_WITH_DE_LANGUAGE = new Transformation(FILE_WITH_DE_LANGUAGE_UID, "",
SERVICE, Map.of(FUNCTION, FILE_WITH_DE_LANGUAGE_UID));
private @Mock @NonNullByDefault({}) LocaleProvider localeProviderMock;
private @Mock @NonNullByDefault({}) ManagedTransformationConfigurationProvider providerMock;
private @Mock @NonNullByDefault({}) ManagedTransformationProvider providerMock;
private @NonNullByDefault({}) TransformationConfigurationRegistryImpl registry;
private @NonNullByDefault({}) TransformationRegistryImpl registry;
@BeforeEach
public void setup() {
Mockito.when(localeProviderMock.getLocale()).thenReturn(Locale.US);
registry = new TransformationConfigurationRegistryImpl(localeProviderMock);
registry = new TransformationRegistryImpl(localeProviderMock);
registry.addProvider(providerMock);
registry.added(providerMock, MANAGED_WITHOUT_LANGUAGE);
registry.added(providerMock, MANAGED_WITH_EN_LANGUAGE);