Skip to content

Commit

Permalink
storage MapDB: add migration handling
Browse files Browse the repository at this point in the history
* Migrate from Eclipse SmartHome without rule DTOs to Eclipse SmartHome
  with rule DTOs
* Migrate from Eclipse SmartHome rules to openHAB Core rules

Signed-off-by: Markus Rathgeb <maggu2810@gmail.com>
  • Loading branch information
maggu2810 committed Apr 10, 2019
1 parent c9b21e9 commit be8b0e8
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.storage.DeletableStorage;
import org.eclipse.smarthome.core.storage.Storage;
import org.eclipse.smarthome.storage.mapdb.internal.migration.MigrationException;
import org.eclipse.smarthome.storage.mapdb.internal.migration.MigrationHandler;
import org.eclipse.smarthome.storage.mapdb.internal.migration.RuleFromEshToOhMigrationHandler;
import org.eclipse.smarthome.storage.mapdb.internal.migration.RuleMigrationHandler;
import org.mapdb.DB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -48,6 +53,8 @@ public class MapDbStorage<T> implements DeletableStorage<T> {

private final Logger logger = LoggerFactory.getLogger(MapDbStorage.class);

private final Map<String, MigrationHandler> migrationHandlers;

private final String name;
private final DB db;
private final @Nullable ClassLoader classLoader;
Expand All @@ -68,6 +75,20 @@ public MapDbStorage(final DB db, final String name, final @Nullable ClassLoader
this.classLoader = classLoader;
this.map = db.createTreeMap(name).makeOrGet();
this.mapper = new GsonBuilder().registerTypeAdapterFactory(new PropertiesTypeAdapterFactory()).create();

// Create the migration helpers.
final Map<String, MigrationHandler> tmpMigrationHandlers = new HashMap<>();
addMigrationHandler(tmpMigrationHandlers, new RuleMigrationHandler(mapper));
addMigrationHandler(tmpMigrationHandlers, new RuleFromEshToOhMigrationHandler());
this.migrationHandlers = Collections.unmodifiableMap(tmpMigrationHandlers);
}

private static void addMigrationHandler(final Map<String, MigrationHandler> migrationHandlers,
final MigrationHandler migrationHandler) {
if (migrationHandlers.put(migrationHandler.getTypeNameOld(), migrationHandler) != null) {
throw new IllegalStateException(
String.format("The type \"%s\" is used multiple times.", migrationHandler.getTypeNameOld()));
}
}

@Override
Expand Down Expand Up @@ -162,6 +183,24 @@ private String serialize(T value) {
String valueTypeName = concatValue[0];
String valueAsString = concatValue[1];

// Backward compatibility
final Map<String, MigrationHandler> migrationHandlers = new HashMap<>(this.migrationHandlers);
for (;;) {
logger.debug("Check for migration handler for type \"{}\" (#migrationHandlers: {})", valueTypeName,
migrationHandlers.size());
final MigrationHandler migrationHandler = migrationHandlers.remove(valueTypeName);
if (migrationHandler == null) {
logger.debug("No migration handler for type \"{}\" found.", valueTypeName);
break;
}
try {
valueAsString = migrationHandler.migrate(valueAsString);
valueTypeName = migrationHandler.getTypeNameNew();
} catch (final MigrationException ex) {
logger.warn("Migration from type '{}' to type '{}' failed, continue with unmigrated data.", ex);
}
}

@Nullable
T value = null;
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2019 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.eclipse.smarthome.storage.mapdb.internal.migration;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* Exception to identify a migration error.
*
* @author Markus Rathgeb - Initial contribution and API
*/
@NonNullByDefault
public class MigrationException extends Exception {
private static final long serialVersionUID = 1L;

/**
* Constructor.
*
* @param cause the cause
*/
public MigrationException(final Throwable cause) {
super(cause);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2019 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.eclipse.smarthome.storage.mapdb.internal.migration;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* Definition of the {@code MigrationHandler} interface.
*
* @author Markus Rathgeb - Initial contribution and API
*/
@NonNullByDefault
public interface MigrationHandler {

/**
* Gets the old type name.
*
* @return old type name
*/
String getTypeNameOld();

/**
* Gets the new type name.
*
* @return new type name
*/
String getTypeNameNew();

/**
* Migrate the old data to the new one.
*
* @param value the old data
* @return the new data
* @throws MigrationException on error
*/
String migrate(String value) throws MigrationException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2019 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.eclipse.smarthome.storage.mapdb.internal.migration;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* Migration handler for rules.
*
* @author Markus Rathgeb - Initial contribution and API
*/
@NonNullByDefault
public class RuleFromEshToOhMigrationHandler implements MigrationHandler {

@Override
public String getTypeNameOld() {
return "org.eclipse.smarthome.automation.dto.RuleDTO";
}

@Override
public String getTypeNameNew() {
return "org.openhab.core.automation.dto.RuleDTO";
}

@Override
public String migrate(final String value) throws MigrationException {
return value;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright (c) 2010-2019 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.eclipse.smarthome.storage.mapdb.internal.migration;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

/**
* Migration handler for rules.
*
* @author Markus Rathgeb - Initial contribution and API
*/
@NonNullByDefault
public class RuleMigrationHandler implements MigrationHandler {

private final Type type = new TypeToken<Map<String, Object>>() {
}.getType();

private final Gson mapper;

/**
* Constructor.
*
* @param mapper the Gson mapper
*/
public RuleMigrationHandler(final Gson mapper) {
this.mapper = mapper;
}

@Override
public String getTypeNameOld() {
return "org.eclipse.smarthome.automation.Rule";
}

@Override
public String getTypeNameNew() {
return "org.eclipse.smarthome.automation.dto.RuleDTO";
}

@Override
public String migrate(final String value) throws MigrationException {
try {
final Map<String, Object> myMap = mapper.fromJson(value, type);
if (myMap != null) {
mergePropertiesIntoConfiguration(myMap);
mergePropertiesIntoConfiguration(myMap.get("triggers"));
mergePropertiesIntoConfiguration(myMap.get("actions"));
mergePropertiesIntoConfiguration(myMap.get("conditions"));
return mapper.toJson(myMap);
} else {
return value;
}
} catch (final JsonSyntaxException ex) {
throw new MigrationException(ex);
}
}

private void mergePropertiesIntoConfiguration(final @Nullable Object parentOfConfiguration) {
if (parentOfConfiguration instanceof Map) {
mergePropertiesIntoConfigurationOfMap((Map<?, ?>) parentOfConfiguration);
} else if (parentOfConfiguration instanceof Collection) {
mergePropertiesIntoConfigurationOfCollection((Collection<?>) parentOfConfiguration);
}
}

private void mergePropertiesIntoConfigurationOfCollection(final Collection<?> parentOfConfigurations) {
for (final Object parentOfConfiguration : parentOfConfigurations) {
mergePropertiesIntoConfiguration(parentOfConfiguration);
}
}

private void mergePropertiesIntoConfigurationOfMap(final Map<?, ?> parentOfConfiguration) {
Object tmp;
tmp = parentOfConfiguration.get("configuration");
if (tmp instanceof Map) {
final Map<?, ?> cfg = (Map<?, ?>) tmp;
tmp = cfg.remove("properties");
if (tmp instanceof Map) {
final Map<?, ?> props = (Map<?, ?>) tmp;
putAll(props, cfg);
}
}
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private static void putAll(final Map<?, ?> source, final Map<?, ?> destination) {
((Map) destination).putAll(source);
}

}

0 comments on commit be8b0e8

Please sign in to comment.