From cf807edd7ad5e2297d3e85a64c55ab1ca5ee3af3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 11 Feb 2015 16:41:43 +0100 Subject: [PATCH] [New] added MigrationsHandler.runCodeMigrations() - this allows a project to add migrations to be run in a post initializer etc. --- .../model/query/StrolchTypeNavigation.java | 3 - .../li/strolch/migrations/CodeMigration.java | 6 +- .../li/strolch/migrations/DataMigration.java | 2 +- .../java/li/strolch/migrations/Migration.java | 4 + .../li/strolch/migrations/Migrations.java | 40 ++++++ .../strolch/migrations/MigrationsHandler.java | 18 +-- .../li/strolch/migrations/MigrationsTest.java | 122 ++++++++++++++++-- 7 files changed, 174 insertions(+), 21 deletions(-) diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/StrolchTypeNavigation.java b/li.strolch.model/src/main/java/li/strolch/model/query/StrolchTypeNavigation.java index a4f7c9fa2..99d216dbb 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/StrolchTypeNavigation.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/StrolchTypeNavigation.java @@ -26,9 +26,6 @@ public class StrolchTypeNavigation implements Navigation { this.type = type; } - /** - * @return the type - */ public String getType() { return this.type; } diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/CodeMigration.java b/li.strolch.service/src/main/java/li/strolch/migrations/CodeMigration.java index b7457efcf..fa298c9fb 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/CodeMigration.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/CodeMigration.java @@ -12,8 +12,12 @@ public class CodeMigration extends Migration { super(realm, version, dataFile); } + public CodeMigration(String realm, Version version) { + super(realm, version, null); + } + @Override public void migrate(ComponentContainer container, Certificate certificate) { - logger.info("[" + this.realm + "] Running migration " + this.version); + logger.info("[" + this.realm + "] Running no-op migration " + this.version); } } diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/DataMigration.java b/li.strolch.service/src/main/java/li/strolch/migrations/DataMigration.java index 88049d65d..e4b38d95c 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/DataMigration.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/DataMigration.java @@ -22,7 +22,7 @@ public class DataMigration extends Migration { public void migrate(ComponentContainer container, Certificate certificate) { XmlImportModelCommand command; - try (StrolchTransaction tx = container.getRealm(getRealm()).openTx(certificate, DataMigration.class)) { + try (StrolchTransaction tx = openTx(container, certificate)) { command = new XmlImportModelCommand(container, tx); command.setModelFile(getDataFile()); diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/Migration.java b/li.strolch.service/src/main/java/li/strolch/migrations/Migration.java index 0f78aab3c..283be50ac 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/Migration.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/Migration.java @@ -48,6 +48,10 @@ public abstract class Migration { return dataFile; } + protected StrolchTransaction openTx(ComponentContainer container, Certificate cert) { + return container.getRealm(getRealm()).openTx(cert, getClass()); + } + protected Command buildMigrationVersionChangeCommand(ComponentContainer container, StrolchTransaction tx) { Resource migrationsRes = tx.getResourceBy(MIGRATIONS_TYPE, MIGRATIONS_ID); diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java b/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java index e9c5c657d..2397a801c 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java @@ -3,6 +3,7 @@ package li.strolch.migrations; import java.io.File; import java.io.FileFilter; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedSet; @@ -103,6 +104,45 @@ public class Migrations { this.migrationsRan = migrationsRan; } + /** + * @param cert + * @param codeMigrationsByRealm + */ + public void runCodeMigrations(Certificate cert, MapOfLists codeMigrationsByRealm) { + + MapOfLists migrationsRan = new MapOfLists<>(); + + for (String realm : codeMigrationsByRealm.keySet()) { + Version currentVersion = this.currentVersions.get(realm); + + List listOfMigrations = codeMigrationsByRealm.getList(realm); + SortedSet migrations = new TreeSet<>((o1, o2) -> o1.getVersion().compareTo(o2.getVersion())); + migrations.addAll(listOfMigrations); + + Version nextVersion = currentVersion.add(0, 0, 1); + CodeMigration nextMigration = new CodeMigration(realm, nextVersion); + + SortedSet migrationsToRun = migrations.tailSet(nextMigration); + for (CodeMigration migration : migrationsToRun) { + DBC.INTERIM.assertEquals("Realms do not match!", realm, migration.getRealm()); + Version migrateVersion = migration.getVersion(); + boolean isLaterMigration = migrateVersion.compareTo(currentVersion) > 0; + DBC.INTERIM.assertTrue("Current version " + currentVersion + " is not before next " + migrateVersion, + isLaterMigration); + + migration.migrate(this.container, cert); + migrationsRan.addElement(realm, migration.getVersion()); + } + } + + if (migrationsRan.isEmpty()) { + logger.info("There were no migrations required!"); + } else { + logger.info("Migrated " + migrationsRan.size() + " realms!"); + } + this.migrationsRan = migrationsRan; + } + private static void logDetectedMigrations(Map currentVersions, Map> allDataMigrations, Map> allCodeMigrations) { diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java b/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java index 45f465fa4..cc45195ca 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java @@ -46,7 +46,6 @@ public class MigrationsHandler extends StrolchComponent { private boolean verbose; private Migrations migrations; - private MapOfLists lastMigrations; private File migrationsPath; private Timer migrationTimer; @@ -58,9 +57,9 @@ public class MigrationsHandler extends StrolchComponent { } public MapOfLists getLastMigrations() { - if (this.lastMigrations == null) + if (this.migrations == null) return new MapOfLists<>(); - return this.lastMigrations; + return this.migrations.getMigrationsRan(); } public Map getCurrentVersions(Certificate cert) { @@ -70,9 +69,8 @@ public class MigrationsHandler extends StrolchComponent { } public MapOfLists queryMigrationsToRun(Certificate cert) { - if (!this.migrationsPath.isDirectory()) { + if (!this.migrationsPath.isDirectory()) return new MapOfLists<>(); - } Map currentVersions = getCurrentVersions(cert); Migrations migrations = new Migrations(getContainer(), currentVersions, this.verbose); @@ -85,7 +83,13 @@ public class MigrationsHandler extends StrolchComponent { public void runMigrations(Certificate cert) { queryMigrationsToRun(cert); this.migrations.runMigrations(cert); - this.lastMigrations = this.migrations.getMigrationsRan(); + } + + public void runCodeMigrations(Certificate cert, MapOfLists codeMigrationsByRealm) { + Map currentVersions = getCurrentVersions(cert); + Migrations migrations = new Migrations(getContainer(), currentVersions, this.verbose); + this.migrations = migrations; + migrations.runCodeMigrations(cert, codeMigrationsByRealm); } @Override @@ -130,7 +134,6 @@ public class MigrationsHandler extends StrolchComponent { RunMigrationsAction action = new RunMigrationsAction(this.migrations); privilegeHandler.runAsSystem(RealmHandler.SYSTEM_USER_AGENT, action); - this.lastMigrations = this.migrations.getMigrationsRan(); } super.start(); @@ -180,7 +183,6 @@ public class MigrationsHandler extends StrolchComponent { } else { RunMigrationsAction runMigrationsAction = new RunMigrationsAction(MigrationsHandler.this.migrations); privilegeHandler.runAsSystem(RealmHandler.SYSTEM_USER_AGENT, runMigrationsAction); - MigrationsHandler.this.lastMigrations = MigrationsHandler.this.migrations.getMigrationsRan(); } } } diff --git a/li.strolch.service/src/test/java/li/strolch/migrations/MigrationsTest.java b/li.strolch.service/src/test/java/li/strolch/migrations/MigrationsTest.java index e03fc25e0..18ef0d1a1 100644 --- a/li.strolch.service/src/test/java/li/strolch/migrations/MigrationsTest.java +++ b/li.strolch.service/src/test/java/li/strolch/migrations/MigrationsTest.java @@ -8,8 +8,13 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import li.strolch.agent.api.ComponentContainer; +import li.strolch.command.AddOrderCommand; +import li.strolch.command.RemoveOrderCommand; +import li.strolch.model.ModelGenerator; +import li.strolch.model.Order; +import li.strolch.persistence.api.StrolchTransaction; import li.strolch.runtime.StrolchConstants; -import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.testbase.runtime.RuntimeMock; import org.junit.AfterClass; @@ -49,21 +54,122 @@ public class MigrationsTest { @Test public void shouldRunMigrations() { - PrivilegeHandler privilegeHandler = runtimeMock.getPrivilegeHandler(); - Certificate cert = privilegeHandler.authenticate("test", "test".getBytes()); - MigrationsHandler migrationsHandler = runtimeMock.getContainer().getComponent(MigrationsHandler.class); - Map currentVersions = migrationsHandler.getCurrentVersions(cert); - assertEquals("1.1.1", currentVersions.get(StrolchConstants.DEFAULT_REALM).toString()); + Map currentVersions = migrationsHandler.getCurrentVersions(certificate); + String defRealm = StrolchConstants.DEFAULT_REALM; + assertEquals("1.1.1", currentVersions.get(defRealm).toString()); assertEquals("0.0.0", currentVersions.get("other").toString()); MapOfLists lastMigrations = migrationsHandler.getLastMigrations(); List expectedMigrations = Arrays.asList(Version.valueOf("0.1.0"), Version.valueOf("0.1.1"), Version.valueOf("0.5.2"), Version.valueOf("1.0.0"), Version.valueOf("1.0.5"), Version.valueOf("1.1.1")); - assertEquals(expectedMigrations, lastMigrations.getList(StrolchConstants.DEFAULT_REALM)); + assertEquals(expectedMigrations, lastMigrations.getList(defRealm)); assertEquals(null, lastMigrations.getList("other")); - MapOfLists migrationsToRun = migrationsHandler.queryMigrationsToRun(cert); + MapOfLists migrationsToRun = migrationsHandler.queryMigrationsToRun(certificate); assertTrue("Expected to have all migrations run", migrationsToRun.isEmpty()); + + // assert new current version + currentVersions = migrationsHandler.getCurrentVersions(certificate); + assertEquals("1.1.1", currentVersions.get(defRealm).toString()); + assertEquals("0.0.0", currentVersions.get("other").toString()); + + MapOfLists codeMigrationsByRealm = new MapOfLists<>(); + // add migrations in wrong sequence - should be fixed by migration handler + codeMigrationsByRealm.addElement(defRealm, new MyMigration2(defRealm)); + codeMigrationsByRealm.addElement(defRealm, new MyMigration1(defRealm)); + codeMigrationsByRealm.addElement(defRealm, new MyMigration0(defRealm)); + migrationsHandler.runCodeMigrations(certificate, codeMigrationsByRealm); + + lastMigrations = migrationsHandler.getLastMigrations(); + assertEquals(1, lastMigrations.keySet().size()); + assertEquals(Arrays.asList(Version.valueOf("1.2.0"), Version.valueOf("1.3.0")), + lastMigrations.getList(defRealm)); + + // assert new current version + currentVersions = migrationsHandler.getCurrentVersions(certificate); + assertEquals("1.3.0", currentVersions.get(defRealm).toString()); + assertEquals("0.0.0", currentVersions.get("other").toString()); + } + + private static class MyMigration0 extends CodeMigration { + + private static final Version version = Version.valueOf("1.0.0"); + + public MyMigration0(String realm) { + super(realm, version); + } + + @Override + public void migrate(ComponentContainer container, Certificate cert) { + logger.info("[" + this.realm + "] Running migration " + this.getVersion()); + + try (StrolchTransaction tx = openTx(container, cert)) { + + Order fooOrder = ModelGenerator.createOrder("foo", "Foo", "Foo"); + + AddOrderCommand addOrderCommand = new AddOrderCommand(container, tx); + addOrderCommand.setOrder(fooOrder); + tx.addCommand(addOrderCommand); + + tx.addCommand(buildMigrationVersionChangeCommand(container, tx)); + + tx.commitOnClose(); + } + } + } + + private static class MyMigration1 extends CodeMigration { + + private static final Version version = Version.valueOf("1.2.0"); + + public MyMigration1(String realm) { + super(realm, version); + } + + @Override + public void migrate(ComponentContainer container, Certificate cert) { + logger.info("[" + this.realm + "] Running migration " + this.getVersion()); + + try (StrolchTransaction tx = openTx(container, cert)) { + + Order fooOrder = ModelGenerator.createOrder("foo", "Foo", "Foo"); + + AddOrderCommand addOrderCommand = new AddOrderCommand(container, tx); + addOrderCommand.setOrder(fooOrder); + tx.addCommand(addOrderCommand); + + tx.addCommand(buildMigrationVersionChangeCommand(container, tx)); + + tx.commitOnClose(); + } + } + } + + private static class MyMigration2 extends CodeMigration { + + private static final Version version = Version.valueOf("1.3.0"); + + public MyMigration2(String realm) { + super(realm, version); + } + + @Override + public void migrate(ComponentContainer container, Certificate cert) { + logger.info("[" + this.realm + "] Running migration " + this.getVersion()); + + try (StrolchTransaction tx = openTx(container, cert)) { + + Order fooOrder = tx.getOrderBy("Foo", "foo"); + + RemoveOrderCommand removeOrderCommand = new RemoveOrderCommand(container, tx); + removeOrderCommand.setOrder(fooOrder); + tx.addCommand(removeOrderCommand); + + tx.addCommand(buildMigrationVersionChangeCommand(container, tx)); + + tx.commitOnClose(); + } + } } }