/* * Copyright (c) 2009, Rickard Öberg. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.qi4j.migration; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import org.hamcrest.CoreMatchers; import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; import org.qi4j.api.io.Input; import org.qi4j.api.io.Output; import org.qi4j.api.io.Receiver; import org.qi4j.api.io.Sender; import org.qi4j.api.unitofwork.UnitOfWork; import org.qi4j.api.unitofwork.UnitOfWorkCompletionException; import org.qi4j.bootstrap.AssemblyException; import org.qi4j.bootstrap.ModuleAssembly; import org.qi4j.bootstrap.SingletonAssembler; import org.qi4j.entitystore.map.MapEntityStore; import org.qi4j.entitystore.map.StateStore; import org.qi4j.migration.assembly.EntityMigrationOperation; import org.qi4j.migration.assembly.MigrationBuilder; import org.qi4j.migration.assembly.MigrationOperation; import org.qi4j.spi.entitystore.BackupRestore; import org.qi4j.spi.service.importer.NewObjectImporter; import org.qi4j.core.testsupport.AbstractQi4jTest; import org.qi4j.test.EntityTestAssembler; import static org.junit.Assert.*; /** * JAVADOC */ public class MigrationTest extends AbstractQi4jTest { public void assemble(ModuleAssembly module) throws AssemblyException { new EntityTestAssembler().assemble(module); module.objects(MigrationEventLogger.class); module.importedServices(MigrationEventLogger.class).importedBy(NewObjectImporter.class); module.entities(TestEntity1_0.class, TestEntity1_1.class, TestEntity2_0.class, org.qi4j.migration.moved.TestEntity2_0.class); MigrationBuilder migration = new MigrationBuilder("1.0"); migration. toVersion("1.1"). renameEntity(TestEntity1_0.class.getName(), TestEntity1_1.class.getName()). atStartup(new CustomFixOperation("Fix for 1.1")). forEntities(TestEntity1_1.class.getName()). renameProperty("foo", "newFoo"). renameManyAssociation("fooManyAssoc", "newFooManyAssoc"). renameAssociation("fooAssoc", "newFooAssoc"). end(). toVersion("2.0"). renameEntity(TestEntity1_1.class.getName(), TestEntity2_0.class.getName()). atStartup(new CustomFixOperation("Fix for 2.0, 1")). atStartup(new CustomFixOperation("Fix for 2.0, 2")). forEntities(TestEntity2_0.class.getName()). addProperty("bar", "Some value"). removeProperty("newFoo", "Some value"). custom(new CustomBarOperation()). end(). toVersion("3.0"). renamePackage("org.qi4j.migration", "org.qi4j.migration.moved"). withEntities("TestEntity2_0"). end(); module.services(MigrationService.class).setMetaInfo(migration); module.entities(MigrationConfiguration.class); module.forMixin(MigrationConfiguration.class).declareDefaults().lastStartupVersion().set("1.0"); } @Test public void testMigration() throws UnitOfWorkCompletionException, IOException { // Set up version 1 String id; StringInputOutput data_v1 = new StringInputOutput(); { SingletonAssembler v1 = new SingletonAssembler() { public void assemble(ModuleAssembly module) throws AssemblyException { MigrationTest.this.assemble(module); module.layer().application().setVersion("1.0"); } }; UnitOfWork uow = v1.unitOfWorkFactory().newUnitOfWork(); TestEntity1_0 entity = uow.newEntity(TestEntity1_0.class); entity.foo().set("Some value"); entity.fooManyAssoc().add(entity); entity.fooAssoc().set(entity); id = entity.identity().get(); uow.complete(); BackupRestore backupRestore = (BackupRestore) v1.module() .serviceFinder() .findService(BackupRestore.class) .get(); backupRestore.backup().transferTo(data_v1); } // Set up version 1.1 StringInputOutput data_v1_1 = new StringInputOutput(); { SingletonAssembler v1_1 = new SingletonAssembler() { public void assemble(ModuleAssembly module) throws AssemblyException { MigrationTest.this.assemble(module); module.layer().application().setVersion("1.1"); } }; BackupRestore testData = (BackupRestore) v1_1.serviceFinder().findService(BackupRestore.class).get(); data_v1.transferTo(testData.restore()); UnitOfWork uow = v1_1.unitOfWorkFactory().newUnitOfWork(); TestEntity1_1 entity = uow.get(TestEntity1_1.class, id); assertThat("Property has been renamed", entity.newFoo().get(), CoreMatchers.equalTo("Some value")); assertThat("ManyAssociation has been renamed", entity.newFooManyAssoc().count(), CoreMatchers.equalTo(1)); assertThat("Association has been renamed", entity.newFooAssoc().get(), CoreMatchers.equalTo(entity)); uow.complete(); testData.backup().transferTo(data_v1_1); } // Set up version 2.0 { SingletonAssembler v2_0 = new SingletonAssembler() { public void assemble(ModuleAssembly module) throws AssemblyException { MigrationTest.this.assemble(module); module.layer().application().setVersion("2.0"); } }; BackupRestore testData = (BackupRestore) v2_0.serviceFinder().findService(BackupRestore.class).get(); // Test migration from 1.0 -> 2.0 { data_v1.transferTo(testData.restore()); UnitOfWork uow = v2_0.unitOfWorkFactory().newUnitOfWork(); TestEntity2_0 entity = uow.get(TestEntity2_0.class, id); assertThat("Property has been created", entity.bar().get(), CoreMatchers.equalTo("Some value")); assertThat("Custom Property has been created", entity.customBar().get(), CoreMatchers.equalTo("Hello Some value")); assertThat("ManyAssociation has been renamed", entity.newFooManyAssoc().count(), CoreMatchers.equalTo(1)); assertThat("Association has been renamed", entity.newFooAssoc().get(), CoreMatchers.equalTo(entity)); uow.complete(); } } // Set up version 3.0 { SingletonAssembler v3_0 = new SingletonAssembler() { public void assemble(ModuleAssembly module) throws AssemblyException { MigrationTest.this.assemble(module); module.layer().application().setVersion("3.0"); } }; BackupRestore testData = (BackupRestore) v3_0.serviceFinder().findService(BackupRestore.class).get(); data_v1_1.transferTo(testData.restore()); // Test migration from 1.0 -> 3.0 { data_v1.transferTo(testData.restore()); UnitOfWork uow = v3_0.unitOfWorkFactory().newUnitOfWork(); org.qi4j.migration.moved.TestEntity2_0 entity = uow.get(org.qi4j.migration.moved.TestEntity2_0.class, id); uow.complete(); } } } private static class CustomBarOperation implements EntityMigrationOperation { public boolean upgrade(JSONObject state, StateStore stateStore, Migrator migrator) throws JSONException { JSONObject properties = (JSONObject) state.get(MapEntityStore.JSONKeys.properties.name()); return migrator.addProperty(state, "customBar", "Hello " + properties.getString("bar")); } public boolean downgrade(JSONObject state, StateStore stateStore, Migrator migrator) throws JSONException { return migrator.removeProperty(state, "customBar"); } } private static class CustomFixOperation implements MigrationOperation { String msg; private CustomFixOperation(String msg) { this.msg = msg; } public void upgrade(StateStore stateStore, Migrator migrator) throws IOException { System.out.println(msg); } public void downgrade(StateStore stateStore, Migrator migrator) throws IOException { System.out.println(msg); } } private static class StringInputOutput implements Output<String, IOException>, Input<String, IOException> { final StringBuilder builder = new StringBuilder(); @Override public <SenderThrowableType extends Throwable> void receiveFrom(Sender<? extends String, SenderThrowableType> sender) throws IOException, SenderThrowableType { sender.sendTo(new Receiver<String, IOException>() { public void receive(String item) throws IOException { builder.append(item).append("\n"); } }); } @Override public <ReceiverThrowableType extends Throwable> void transferTo(Output<? super String, ReceiverThrowableType> output) throws IOException, ReceiverThrowableType { output.receiveFrom(new Sender<String, IOException>() { @Override public <ReceiverThrowableType extends Throwable> void sendTo(Receiver<? super String, ReceiverThrowableType> receiver) throws ReceiverThrowableType, IOException { BufferedReader reader = new BufferedReader(new StringReader(builder.toString())); String line; try { while ((line = reader.readLine()) != null) receiver.receive(line); } finally { reader.close(); } } }); } @Override public String toString() { return builder.toString(); } } }