From d74277f79862682de968ad91027f068007795b8f Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sat, 18 Jun 2022 14:31:45 +0200 Subject: [PATCH] Use ThingStorageEntity instead of ThingImpl/BridgeImpl for storage (#2914) Signed-off-by: Jan N. Klug --- .../json/internal/JsonStorageService.java | 5 +- .../migration/BridgeImplTypeMigrator.java | 43 ++++++ .../migration/ThingImplTypeMigrator.java | 77 +++++++++++ .../ThingStorageEntityMigratorTest.java | 105 ++++++++++++++ .../test/resources/thingMigration-input.json | 130 ++++++++++++++++++ .../test/resources/thingMigration-result.json | 70 ++++++++++ .../core/thing/ManagedThingProvider.java | 19 ++- .../thing/internal/ThingStorageEntity.java | 34 +++++ .../core/thing/ManagedThingProviderTest.java | 76 ++++++++++ .../itest.bndrun | 4 +- .../json/internal/ThingMigrationOSGiTest.java | 73 ++++++++++ .../org.openhab.core.thing.Thing.json | 130 ++++++++++++++++++ .../binding/ChangeThingTypeOSGiTest.java | 17 ++- 13 files changed, 775 insertions(+), 8 deletions(-) create mode 100644 bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/migration/BridgeImplTypeMigrator.java create mode 100644 bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/migration/ThingImplTypeMigrator.java create mode 100644 bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/ThingStorageEntityMigratorTest.java create mode 100644 bundles/org.openhab.core.storage.json/src/test/resources/thingMigration-input.json create mode 100644 bundles/org.openhab.core.storage.json/src/test/resources/thingMigration-result.json create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingStorageEntity.java create mode 100644 bundles/org.openhab.core.thing/src/test/java/org/openhab/core/thing/ManagedThingProviderTest.java create mode 100644 itests/org.openhab.core.storage.json.tests/src/main/java/org/openhab/core/storage/json/internal/ThingMigrationOSGiTest.java create mode 100644 itests/org.openhab.core.storage.json.tests/src/main/resources/org.openhab.core.thing.Thing.json diff --git a/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/JsonStorageService.java b/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/JsonStorageService.java index 84719cc754..5f11748042 100644 --- a/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/JsonStorageService.java +++ b/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/JsonStorageService.java @@ -25,6 +25,8 @@ import org.openhab.core.OpenHAB; 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.ThingImplTypeMigrator; import org.openhab.core.storage.json.internal.migration.TypeMigrator; import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Activate; @@ -51,7 +53,8 @@ public class JsonStorageService implements StorageService { /** * Contains a map of needed migrations, key is the storage name */ - private static final Map> MIGRATORS = Map.of(); + private static final Map> MIGRATORS = Map.of( // + "org.openhab.core.thing.Thing", List.of(new BridgeImplTypeMigrator(), new ThingImplTypeMigrator())); private final Logger logger = LoggerFactory.getLogger(JsonStorageService.class); diff --git a/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/migration/BridgeImplTypeMigrator.java b/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/migration/BridgeImplTypeMigrator.java new file mode 100644 index 0000000000..17918441fe --- /dev/null +++ b/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/migration/BridgeImplTypeMigrator.java @@ -0,0 +1,43 @@ +/** + * 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; + +/** + * The {@link BridgeImplTypeMigrator} implements a {@link TypeMigrator} for stored bridges + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class BridgeImplTypeMigrator extends ThingImplTypeMigrator { + + @Override + public String getOldType() { + return "org.openhab.core.thing.internal.BridgeImpl"; + } + + @Override + public String getNewType() { + return "org.openhab.core.thing.internal.ThingStorageEntity"; + } + + @Override + public JsonElement migrate(JsonElement oldValue) throws TypeMigrationException { + JsonElement newValue = super.migrate(oldValue); + newValue.getAsJsonObject().addProperty("isBridge", true); + return newValue; + } +} diff --git a/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/migration/ThingImplTypeMigrator.java b/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/migration/ThingImplTypeMigrator.java new file mode 100644 index 0000000000..19d4f4bc9d --- /dev/null +++ b/bundles/org.openhab.core.storage.json/src/main/java/org/openhab/core/storage/json/internal/migration/ThingImplTypeMigrator.java @@ -0,0 +1,77 @@ +/** + * 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 java.util.Spliterator; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +/** + * The {@link ThingImplTypeMigrator} implements a {@link TypeMigrator} for stored things + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class ThingImplTypeMigrator implements TypeMigrator { + + @Override + public String getOldType() { + return "org.openhab.core.thing.internal.ThingImpl"; + } + + @Override + public String getNewType() { + return "org.openhab.core.thing.internal.ThingStorageEntity"; + } + + @Override + public JsonElement migrate(JsonElement oldValue) throws TypeMigrationException { + JsonObject newValue = oldValue.deepCopy().getAsJsonObject(); + segmentUidToStringUid(newValue, "uid", "UID"); + segmentUidToStringUid(newValue, "bridgeUID"); + segmentUidToStringUid(newValue, "thingTypeUID"); + + for (JsonElement jsonElement : newValue.get("channels").getAsJsonArray()) { + JsonObject channel = jsonElement.getAsJsonObject(); + channel.add("itemType", channel.remove("acceptedItemType")); + channel.add("configuration", channel.remove("configuration").getAsJsonObject().get("properties")); + segmentUidToStringUid(channel, "uid"); + segmentUidToStringUid(channel, "channelTypeUID"); + } + + newValue.add("configuration", newValue.remove("configuration").getAsJsonObject().get("properties")); + newValue.addProperty("isBridge", false); + + return newValue; + } + + private void segmentUidToStringUid(JsonObject object, String name) { + segmentUidToStringUid(object, name, name); + } + + private void segmentUidToStringUid(JsonObject object, String oldName, String newName) { + JsonElement element = object.remove(oldName); + if (element != null) { + Spliterator segments = element.getAsJsonObject().get("segments").getAsJsonArray() + .spliterator(); + String uid = StreamSupport.stream(segments, false).map(JsonElement::getAsString) + .collect(Collectors.joining(":")); + object.addProperty(newName, uid); + } + } +} diff --git a/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/ThingStorageEntityMigratorTest.java b/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/ThingStorageEntityMigratorTest.java new file mode 100644 index 0000000000..1891bacc69 --- /dev/null +++ b/bundles/org.openhab.core.storage.json/src/test/java/org/openhab/core/storage/json/internal/ThingStorageEntityMigratorTest.java @@ -0,0 +1,105 @@ +/** + * 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 static org.hamcrest.Matchers.nullValue; + +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.BridgeImplTypeMigrator; +import org.openhab.core.storage.json.internal.migration.ThingImplTypeMigrator; +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 ThingStorageEntityMigratorTest} contains tests for the ThingImpl and BridgeImpl migrators + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class ThingStorageEntityMigratorTest { + + 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 inputMap; + private @NonNullByDefault({}) Map resultMap; + + @BeforeEach + public void setup() throws FileNotFoundException { + inputMap = readDatabase(Path.of("src/test/resources/thingMigration-input.json")); + resultMap = readDatabase(Path.of("src/test/resources/thingMigration-result.json")); + + assertThat(inputMap.size(), is(2)); + assertThat(resultMap.size(), is(2)); + } + + private static Stream typeMigrationsSource() { + return Stream.of(Arguments.of("deconz:deconz:00313E041ED0", new BridgeImplTypeMigrator(), true), + Arguments.of("http:url:0a500ec3d8", new ThingImplTypeMigrator(), false)); + } + + @ParameterizedTest + @MethodSource("typeMigrationsSource") + public void typeMigration(String thingUid, TypeMigrator migrator, boolean isBridge) throws TypeMigrationException { + StorageEntry inputEntry = inputMap.get(thingUid); + StorageEntry resultEntry = resultMap.get(thingUid); + + assertThat(inputEntry.getEntityClassName(), is(migrator.getOldType())); + + JsonElement entityValue = (JsonElement) inputEntry.getValue(); + assertThat(entityValue.getAsJsonObject().get("isBridge"), nullValue()); + + JsonElement newEntityValue = migrator.migrate(entityValue); + assertThat(newEntityValue.getAsJsonObject().get("isBridge").getAsBoolean(), is(isBridge)); + + assertThat(newEntityValue, is(resultEntry.getValue())); + } + + @SuppressWarnings("unchecked") + private Map readDatabase(Path path) throws FileNotFoundException { + final Map map = new ConcurrentHashMap<>(); + + FileReader reader = new FileReader(path.toFile()); + Map loadedMap = internalMapper.fromJson(reader, map.getClass()); + + if (loadedMap != null && !loadedMap.isEmpty()) { + map.putAll(loadedMap); + } + + return map; + } +} diff --git a/bundles/org.openhab.core.storage.json/src/test/resources/thingMigration-input.json b/bundles/org.openhab.core.storage.json/src/test/resources/thingMigration-input.json new file mode 100644 index 0000000000..ba7ebe5e0c --- /dev/null +++ b/bundles/org.openhab.core.storage.json/src/test/resources/thingMigration-input.json @@ -0,0 +1,130 @@ +{ + "deconz:deconz:00313E041ED0": { + "class": "org.openhab.core.thing.internal.BridgeImpl", + "value": { + "label": "Phoscon (192.168.1.100:80)", + "channels": [], + "configuration": { + "properties": { + "host": "192.168.1.100", + "httpPort": 80, + "timeout": 2000, + "websocketTimeout": 120 + } + }, + "properties": { + "UDN": "56071c34-1111-4b08-bba8-d3f29e6d43d8" + }, + "uid": { + "segments": [ + "deconz", + "deconz", + "00313E041ED0" + ], + "uid": "deconz:deconz:00313E041ED0" + }, + "thingTypeUID": { + "segments": [ + "deconz", + "deconz" + ], + "uid": "" + } + } + }, + "http:url:0a500ec3d8": { + "class": "org.openhab.core.thing.internal.ThingImpl", + "value": { + "label": "HTTP URL Thing", + "channels": [ + { + "acceptedItemType": "DateTime", + "kind": "STATE", + "uid": { + "segments": [ + "http", + "url", + "0a500ec3d8", + "lastFailure" + ], + "uid": "http:url:0a500ec3d8:lastFailure" + }, + "channelTypeUID": { + "segments": [ + "http", + "requestDateTime" + ], + "uid": "http:requestDateTime" + }, + "label": "Last Failure", + "configuration": { + "properties": { + "stateTransformation": "abcd" + } + }, + "properties": {}, + "defaultTags": [], + "autoUpdatePolicy": "DEFAULT" + }, + { + "acceptedItemType": "DateTime", + "kind": "STATE", + "uid": { + "segments": [ + "http", + "url", + "0a500ec3d8", + "lastSuccess" + ], + "uid": "http:url:0a500ec3d8:lastSuccess" + }, + "channelTypeUID": { + "segments": [ + "http", + "requestDateTime" + ], + "uid": "http:requestDateTime" + }, + "label": "Last Success", + "configuration": { + "properties": {} + }, + "properties": {}, + "defaultTags": [], + "autoUpdatePolicy": "DEFAULT" + } + ], + "configuration": { + "properties": { + "authMode": "BASIC", + "baseURL": "https://localhost:8443", + "bufferSize": 2048, + "commandMethod": "GET", + "delay": 0, + "ignoreSSLErrors": true, + "refresh": 30, + "stateMethod": "GET", + "timeout": 3000 + } + }, + "properties": { + "thingTypeVersion": "1" + }, + "uid": { + "segments": [ + "http", + "url", + "0a500ec3d8" + ], + "uid": "http:url:0a500ec3d8" + }, + "thingTypeUID": { + "segments": [ + "http", + "url" + ], + "uid": "" + } + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.core.storage.json/src/test/resources/thingMigration-result.json b/bundles/org.openhab.core.storage.json/src/test/resources/thingMigration-result.json new file mode 100644 index 0000000000..d969d0f447 --- /dev/null +++ b/bundles/org.openhab.core.storage.json/src/test/resources/thingMigration-result.json @@ -0,0 +1,70 @@ +{ + "deconz:deconz:00313E041ED0": { + "class": "org.openhab.core.thing.internal.ThingStorageEntity", + "value": { + "label": "Phoscon (192.168.1.100:80)", + "channels": [], + "properties": { + "UDN": "56071c34-1111-4b08-bba8-d3f29e6d43d8" + }, + "UID": "deconz:deconz:00313E041ED0", + "thingTypeUID": "deconz:deconz", + "configuration": { + "host": "192.168.1.100", + "httpPort": 80, + "timeout": 2000, + "websocketTimeout": 120 + }, + "isBridge": true + } + }, + "http:url:0a500ec3d8": { + "class": "org.openhab.core.thing.internal.ThingStorageEntity", + "value": { + "label": "HTTP URL Thing", + "channels": [ + { + "kind": "STATE", + "label": "Last Failure", + "configuration": { + "stateTransformation": "abcd" + }, + "properties": {}, + "defaultTags": [], + "autoUpdatePolicy": "DEFAULT", + "itemType": "DateTime", + "uid": "http:url:0a500ec3d8:lastFailure", + "channelTypeUID": "http:requestDateTime" + }, + { + "kind": "STATE", + "label": "Last Success", + "configuration": {}, + "properties": {}, + "defaultTags": [], + "autoUpdatePolicy": "DEFAULT", + "itemType": "DateTime", + "uid": "http:url:0a500ec3d8:lastSuccess", + "channelTypeUID": "http:requestDateTime" + } + ], + "configuration": { + "authMode": "BASIC", + "baseURL": "https://localhost:8443", + "bufferSize": 2048, + "commandMethod": "GET", + "delay": 0, + "ignoreSSLErrors": true, + "refresh": 30, + "stateMethod": "GET", + "timeout": 3000 + }, + "properties": { + "thingTypeVersion": "1" + }, + "UID": "http:url:0a500ec3d8", + "thingTypeUID": "http:url", + "isBridge": false + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ManagedThingProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ManagedThingProvider.java index ffef4466f4..9bce0a1ff4 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ManagedThingProvider.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/ManagedThingProvider.java @@ -13,8 +13,12 @@ package org.openhab.core.thing; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.common.registry.DefaultAbstractManagedProvider; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.common.registry.AbstractManagedProvider; import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.dto.ThingDTOMapper; +import org.openhab.core.thing.internal.BridgeImpl; +import org.openhab.core.thing.internal.ThingStorageEntity; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -31,7 +35,8 @@ import org.osgi.service.component.annotations.Reference; */ @NonNullByDefault @Component(immediate = true, service = { ThingProvider.class, ManagedThingProvider.class }) -public class ManagedThingProvider extends DefaultAbstractManagedProvider implements ThingProvider { +public class ManagedThingProvider extends AbstractManagedProvider + implements ThingProvider { @Activate public ManagedThingProvider(final @Reference StorageService storageService) { @@ -47,4 +52,14 @@ public class ManagedThingProvider extends DefaultAbstractManagedProvider