Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephan Merkli2021-03-30 07:25:43 +0000
committerStephan Merkli2021-04-29 11:48:16 +0000
commit262a6aadbc5f229f91d5cbef4ef25531d9f55292 (patch)
tree6a815dd489426c83a409c37788cc5e61ef4b6b35
parentec2e69d848b5dc0f93d0236ab57f9a19f19ec363 (diff)
downloadorg.eclipse.scout.rt-262a6aadbc5f229f91d5cbef4ef25531d9f55292.tar.gz
org.eclipse.scout.rt-262a6aadbc5f229f91d5cbef4ef25531d9f55292.tar.xz
org.eclipse.scout.rt-262a6aadbc5f229f91d5cbef4ef25531d9f55292.zip
Data object migration support: add initial contribution
A persisted data object (in database or via an export feature) may undergo structural changes during its lifetime. To allow processing an older version of such a data object it must be first migrated to the newest structure. Change-Id: I258cce3dccf192a770919b3e1b6dd18149dc8f8f Reviewed-on: https://git.eclipse.org/r/c/scout/org.eclipse.scout.rt/+/179103 Reviewed-by: Stephan Merkli <stephan.merkli@bsi-software.com> Tested-by: Scout Bot <scout-bot@eclipse.org>
-rw-r--r--org.eclipse.scout.rt.dataobject.test/pom.xml6
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureMigrationHandlerTest.java115
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/ConstantUuidProvider.java35
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/TestDoStructureMigrationInventory.java73
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/AbstractDoStructureMigrationHandlerCompletenessTest.java104
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/TestingResourceHelper.java87
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/signature/AbstractDataObjectSignatureTest.java72
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/DataObjectInventoryTest.java23
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectFixtureTypeVersions.java36
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectProjectFixtureTypeVersions.java3
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContextTest.java97
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationHelperTest.java226
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationInventoryTest.java290
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationTestHelper.java65
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigratorTest.java380
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureDo.java77
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureMigrationHandler_3.java57
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureTargetContextData.java37
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureDo.java66
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureMigrationHandler_3.java57
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureTargetContextData.java37
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureDo.java107
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureDoStructureMigrationHandler_2.java35
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureRawOnlyDoStructureMigrationTargetContextData.java32
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureStructureMigrationTargetContextData.java53
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureTypedOnlyDoStructureMigrationTargetContextData.java37
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureDo.java78
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureDoStructureMigrationHandler_2.java68
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureTargetContextData.java42
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3.java68
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureCaseSensitiveNameMigrationHandler_2.java57
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureDo.java66
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureFamilyFriendlyMigrationHandlerInvalidTypeVersionToUpdate_3.java66
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PostalAddressFixtureDo.java78
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2.java43
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDo.java85
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_2.java40
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_3.java43
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_4.java55
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_5.java60
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/ZipCodeFixtureGlobalContextData.java31
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/AlfaFixtureNamespace.java29
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/AlfaFixtureTypeVersions.java62
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/BravoFixtureNamespace.java29
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/BravoFixtureTypeVersions.java66
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/CharlieFixtureNamespace.java29
-rw-r--r--org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/CharlieFixtureTypeVersions.java84
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/DataObjectInventory.java19
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/enumeration/EnumInventory.java3
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureMigrationHandler.java65
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureRenameMigrationHandler.java91
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContext.java159
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContextDataTarget.java47
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationHelper.java268
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationInventory.java530
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationPassThroughLogger.java49
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationStatsContextData.java61
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrator.java165
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationGlobalContextData.java30
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationHandler.java67
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationLocalContextData.java32
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationLogger.java32
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationTargetContextData.java31
-rw-r--r--org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/MigrationDataObjectVisitor.java102
-rw-r--r--org.eclipse.scout.rt.jackson.test/src/test/java/org/eclipse/scout/rt/jackson/dataobject/fixture/JacksonFixtureTypeVersions.java3
-rw-r--r--org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModelTest.java11
-rw-r--r--org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespace.java2
-rw-r--r--org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespaceVersioned.java3
-rw-r--r--org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModel.java23
-rw-r--r--org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/Namespaces.java7
70 files changed, 5117 insertions, 69 deletions
diff --git a/org.eclipse.scout.rt.dataobject.test/pom.xml b/org.eclipse.scout.rt.dataobject.test/pom.xml
index 9ca86842c5..bff0296af6 100644
--- a/org.eclipse.scout.rt.dataobject.test/pom.xml
+++ b/org.eclipse.scout.rt.dataobject.test/pom.xml
@@ -46,6 +46,12 @@
<artifactId>mockito-core</artifactId>
</dependency>
+ <!-- Jackson used to execute tests using JSON-serialization -->
+ <dependency>
+ <groupId>org.eclipse.scout.rt</groupId>
+ <artifactId>org.eclipse.scout.rt.jackson</artifactId> <!-- use .jackson and not .jackson.test due to cyclic dependencies -->
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
diff --git a/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureMigrationHandlerTest.java b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureMigrationHandlerTest.java
new file mode 100644
index 0000000000..d8636ae11a
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureMigrationHandlerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.eclipse.scout.rt.testing.platform.util.ScoutAssert.assertEqualsWithComparisonFailure;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.scout.rt.dataobject.DataObjectVisitors;
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.IDataObjectMapper;
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.IPrettyPrintDataObjectMapper;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.testing.TestingResourceHelper;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.BeanMetaData;
+import org.eclipse.scout.rt.platform.IBean;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+import org.eclipse.scout.rt.platform.util.ObjectUtility;
+import org.eclipse.scout.rt.platform.util.uuid.IUuidProvider;
+import org.eclipse.scout.rt.testing.platform.BeanTestingHelper;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract test implementation for a single {@link IDoStructureMigrationHandler}.
+ */
+public abstract class AbstractDoStructureMigrationHandlerTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractDoStructureMigrationHandlerTest.class);
+
+ private static IBean<?> s_uuidProvider;
+
+ @BeforeClass
+ public static void beforeClass() {
+ // Use a constant uuid provider in order to receive the same tests results on each run
+ s_uuidProvider = BEANS.get(BeanTestingHelper.class).registerBean(new BeanMetaData(IUuidProvider.class, new ConstantUuidProvider()));
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ BEANS.get(BeanTestingHelper.class).unregisterBean(s_uuidProvider);
+ }
+
+ protected Class<?> getResourceBaseClass() {
+ return this.getClass();
+ }
+
+ /**
+ * @param filenamePrefix
+ * Filename prefix of file in resource folder
+ * @param fromVersionClass
+ * From version is only required to determine to file to load that contains the previous version (source)
+ * @param toVersionClass
+ * To version is required to determine the file to load that contains the expected data object (target)
+ */
+ public void testMigration(String filenamePrefix, Class<? extends ITypeVersion> fromVersionClass, Class<? extends ITypeVersion> toVersionClass) throws IOException {
+ testMigration(filenamePrefix, BEANS.get(fromVersionClass).getVersion().unwrap(), toVersionClass);
+ }
+
+ public void testMigration(String filenamePrefix, String fromVersionText, Class<? extends ITypeVersion> toVersionClass) throws IOException {
+ NamespaceVersion toVersion = BEANS.get(toVersionClass).getVersion();
+ IPrettyPrintDataObjectMapper dataObjectMapper = BEANS.get(IPrettyPrintDataObjectMapper.class);
+
+ IDoEntity actual; // 'actual' because loaded from fromVersion and then migrated -> represents actual data object
+ try (InputStream in = getClass().getResourceAsStream(getFilename(filenamePrefix, fromVersionText))) {
+ assertNotNull("File " + getFilename(filenamePrefix, fromVersionText) + " is missing", in);
+ actual = (IDoEntity) dataObjectMapper.readValueRaw(in);
+ }
+
+ DoStructureMigrationContext ctx = BEANS.get(DoStructureMigrationContext.class);
+ boolean changed = BEANS.get(DoStructureMigrator.class).migrateDataObject(ctx, actual, toVersion);
+
+ assertTrue("Data object was not changed by migration", changed);
+
+ IDoEntity expected = null; // from toVersion -> expected data object (according to .json file)
+ String filename = getFilename(filenamePrefix, toVersion.unwrap());
+ File referenceFile = new File(BEANS.get(TestingResourceHelper.class).getTestResourceDirectory(getResourceBaseClass()), filename);
+ if (referenceFile.exists()) {
+ try (FileInputStream fis = new FileInputStream(referenceFile)) {
+ expected = (IDoEntity) dataObjectMapper.readValueRaw(fis);
+ }
+ }
+
+ if (!ObjectUtility.equals(actual, expected)) {
+ DataObjectVisitors.forEach(actual, IDoEntity.class, e -> assertEquals("Always use raw DoEntity class in migration - use readRaw, cloneRaw, etc.", DoEntity.class, e.getClass()));
+
+ BEANS.get(TestingResourceHelper.class).writeTestResource(getResourceBaseClass(), filename, actual);
+
+ // Do not use pretty print output for logging. Single line log entry is useful for failing jenkins tests.
+ LOG.warn("Expected content for {}: {}", referenceFile.getName(), BEANS.get(IDataObjectMapper.class).writeValue(actual));
+ assertEqualsWithComparisonFailure("Expected DO entity structure differs from actual content. Verify the changes in the result JSON file and commit the changed file.", expected, actual);
+ fail("Expected DO entity structure differs from actual content. Verify the changes in the result JSON file and commit the changed file.");
+ }
+ }
+
+ protected String getFilename(String filenamePrefix, String typeVersion) {
+ return filenamePrefix + "-" + typeVersion + ".json";
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/ConstantUuidProvider.java b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/ConstantUuidProvider.java
new file mode 100644
index 0000000000..18ea72d297
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/ConstantUuidProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import java.util.Random;
+import java.util.UUID;
+
+import org.eclipse.scout.rt.platform.IBeanManager;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.uuid.IUuidProvider;
+
+/**
+ * A UUID provider for testing that always returns the same order of UUIDs. This provider needs to be registered
+ * <strong>manually</strong> with the {@link IBeanManager}.
+ */
+@IgnoreBean
+public class ConstantUuidProvider implements IUuidProvider {
+
+ private final Random m_random = new Random(42); // initialize with a constant seed so that output is predictable over different runs
+
+ @Override
+ public UUID createUuid() {
+ byte[] bytes = new byte[16];
+ m_random.nextBytes(bytes);
+ return UUID.nameUUIDFromBytes(bytes);
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/TestDoStructureMigrationInventory.java b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/TestDoStructureMigrationInventory.java
new file mode 100644
index 0000000000..3545cc5452
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/migration/TestDoStructureMigrationInventory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.eclipse.scout.rt.platform.util.Assertions.assertFalse;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.namespace.INamespace;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+@IgnoreBean
+public class TestDoStructureMigrationInventory extends DoStructureMigrationInventory {
+
+ protected final List<INamespace> m_internalNamespaces = new ArrayList<>();
+ protected final Collection<ITypeVersion> m_internalTypeVersions = new ArrayList<>();
+ protected final Collection<Class<? extends IDoStructureMigrationTargetContextData>> m_internalContextDataClasses = new ArrayList<>();
+ protected final List<IDoStructureMigrationHandler> m_internalMigrationHandlers = new ArrayList<>();
+
+ public TestDoStructureMigrationInventory(
+ List<INamespace> namespaces,
+ Collection<ITypeVersion> typeVersions,
+ Collection<Class<? extends IDoStructureMigrationTargetContextData>> contextDataClasses,
+ IDoStructureMigrationHandler... migrationHandlers) {
+ assertFalse(CollectionUtility.isEmpty(namespaces), "namespaces must be set");
+ assertFalse(CollectionUtility.isEmpty(typeVersions), "typeVersions must be set");
+
+ m_internalNamespaces.addAll(namespaces);
+ m_internalTypeVersions.addAll(typeVersions);
+ if (contextDataClasses != null) {
+ m_internalContextDataClasses.addAll(contextDataClasses);
+ }
+
+ if (migrationHandlers != null) {
+ Collections.addAll(m_internalMigrationHandlers, migrationHandlers);
+ }
+
+ init();
+ }
+
+ @Override
+ protected List<INamespace> getAllNamespaces() {
+ return m_internalNamespaces;
+ }
+
+ @Override
+ protected Collection<ITypeVersion> getAllTypeVersions() {
+ return m_internalTypeVersions;
+ }
+
+ @Override
+ protected Collection<Class<? extends IDoStructureMigrationTargetContextData>> getAllContextDataClasses() {
+ return m_internalContextDataClasses;
+ }
+
+ @Override
+ protected List<IDoStructureMigrationHandler> getAllMigrationHandlers() {
+ return m_internalMigrationHandlers;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/AbstractDoStructureMigrationHandlerCompletenessTest.java b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/AbstractDoStructureMigrationHandlerCompletenessTest.java
new file mode 100644
index 0000000000..8216986cc1
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/AbstractDoStructureMigrationHandlerCompletenessTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.testing;
+
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandlerTest;
+import org.eclipse.scout.rt.dataobject.migration.IDoStructureMigrationHandler;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.inventory.ClassInventory;
+import org.eclipse.scout.rt.platform.inventory.IClassInfo;
+import org.eclipse.scout.rt.platform.util.StringUtility;
+import org.junit.Test;
+
+/**
+ * Completeness test to check that for all {@link IDoStructureMigrationHandler} a corresponding
+ * {@link AbstractDoStructureMigrationHandlerTest} exists.
+ */
+public abstract class AbstractDoStructureMigrationHandlerCompletenessTest {
+
+ /**
+ * @return the package name prefix within this completeness tests looks for {@link IDoStructureMigrationHandler}
+ */
+ protected abstract String getPackageNamePrefix();
+
+ /**
+ * @return a list of classes/test classes that will be excluded from completeness checking. This may be used if there
+ * is a very special case where a successful standard test cannot be implemented.
+ */
+ protected Set<Class<?>> getExclusionList() {
+ return Collections.emptySet();
+ }
+
+ @Test
+ public void testCompleteness() {
+ // Convention: migration handler naming contains version number: LoremMigrationHandler_1_2_0
+ // Naming of test class: LoremMigrationHandler_1_2_0_Test
+ Set<Class<?>> excludedMigrationHandlers = getExclusionList();
+ Set<String> expectedTestClassNames = findClasses(IDoStructureMigrationHandler.class)
+ .filter(clazz -> !excludedMigrationHandlers.contains(clazz))
+ .map(Class::getName)
+ .map(className -> className + "_Test")
+ .collect(Collectors.toSet());
+
+ Set<String> actualTestClassNames = findClasses(AbstractDoStructureMigrationHandlerTest.class)
+ .map(Class::getName)
+ .collect(Collectors.toSet());
+
+ Set<String> missingTestClassNames = new HashSet<>(expectedTestClassNames);
+ missingTestClassNames.removeAll(actualTestClassNames);
+
+ Set<String> unknownTestClassNames = new HashSet<>(actualTestClassNames);
+ unknownTestClassNames.removeAll(expectedTestClassNames);
+
+ if (!missingTestClassNames.isEmpty()) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("There are missing test classes:\n");
+ builder.append(StringUtility.join("\n", missingTestClassNames));
+ if (!unknownTestClassNames.isEmpty()) {
+ builder.append("\n");
+ builder.append("Maybe the name of one of these is wrong :\n");
+ builder.append(StringUtility.join("\n", unknownTestClassNames));
+ }
+
+ fail(builder.toString());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected <T> Stream<Class<T>> findClasses(Class<T> clazz) {
+ return ClassInventory.get().getAllKnownSubClasses(clazz).stream()
+ .filter(this::acceptClass)
+ .map(classInfo -> (Class<T>) classInfo.resolveClass())
+ .filter(resolvedClass -> !Modifier.isStatic(resolvedClass.getModifiers()));
+ }
+
+ protected boolean acceptClass(IClassInfo ci) {
+ if (!ci.isInstanciable()) {
+ return false;
+ }
+
+ if (ci.hasAnnotation(IgnoreBean.class)) {
+ return false; // e.g. test migration handlers
+ }
+
+ String className = ci.name();
+ return className.startsWith(getPackageNamePrefix());
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/TestingResourceHelper.java b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/TestingResourceHelper.java
new file mode 100644
index 0000000000..29338ddcae
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/TestingResourceHelper.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.testing;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+
+import org.eclipse.scout.rt.dataobject.IDataObject;
+import org.eclipse.scout.rt.dataobject.IPrettyPrintDataObjectMapper;
+import org.eclipse.scout.rt.platform.ApplicationScoped;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.exception.PlatformException;
+
+@ApplicationScoped
+public class TestingResourceHelper {
+
+ /**
+ * @return Directory in src/test/resource that corresponds to the package of <code>resourceBaseClass</code>.
+ */
+ public File getTestResourceDirectory(Class<?> resourceBaseClass) {
+ File moduleDirectory = getModuleDirectory(resourceBaseClass);
+
+ String modulePath = resourceBaseClass.getPackage().getName().replaceAll("\\.", Matcher.quoteReplacement(File.separator));
+ return new File(moduleDirectory, "src/test/resources/" + modulePath + "/");
+ }
+
+ protected File getModuleDirectory(Class<?> resourceBaseClass) {
+ // Replace the content of the out file when failing
+ URL location = resourceBaseClass.getProtectionDomain().getCodeSource().getLocation();
+ if (!"file".equalsIgnoreCase(location.getProtocol())) {
+ fail(String.format("not a file location (%s)", location));
+ }
+
+ try {
+ File moduleDirectory = new File(location.toURI()).getParentFile().getParentFile();
+ assertTrue("Module directory doesn't exist: " + moduleDirectory.getAbsolutePath(), moduleDirectory.exists());
+ return moduleDirectory;
+ }
+ catch (URISyntaxException e) {
+ throw new PlatformException("Failed to create URI from location {}", location, e);
+ }
+ }
+
+ /**
+ * Writes the data object to the test resources.
+ *
+ * @param resourceBaseClass
+ * Resource base class is used to determine to directory, see {@link #getTestResourceDirectory(Class)}.
+ * @param filename
+ * Filename
+ * @param dataObject
+ * Data object
+ */
+ public void writeTestResource(Class<?> resourceBaseClass, String filename, IDataObject dataObject) {
+ File outputFile = new File(getTestResourceDirectory(resourceBaseClass), filename);
+ if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
+ fail("Unable to create target directory for file: " + outputFile.getParentFile()); // in case directory doesn't exist yet and couldn't be created (e.g. initial test execution)
+ return;
+ }
+
+ String dataObjectJson = BEANS.get(IPrettyPrintDataObjectMapper.class).writeValue(dataObject);
+ dataObjectJson = dataObjectJson.replaceAll("\\r\\n", "\\\n").trim(); // only use \n
+
+ try (OutputStream out = new FileOutputStream(outputFile)) {
+ out.write(dataObjectJson.getBytes(StandardCharsets.UTF_8));
+ }
+ catch (IOException e) {
+ throw new PlatformException("Failed to write new data object file", e);
+ }
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/signature/AbstractDataObjectSignatureTest.java b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/signature/AbstractDataObjectSignatureTest.java
index 5648d4a7b3..b79d341f1f 100644
--- a/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/signature/AbstractDataObjectSignatureTest.java
+++ b/org.eclipse.scout.rt.dataobject.test/src/main/java/org/eclipse/scout/rt/dataobject/testing/signature/AbstractDataObjectSignatureTest.java
@@ -10,25 +10,19 @@
*/
package org.eclipse.scout.rt.dataobject.testing.signature;
-import static org.junit.Assert.*;
+import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.Set;
-import java.util.regex.Matcher;
import org.eclipse.scout.rt.dataobject.IDataObjectMapper;
import org.eclipse.scout.rt.dataobject.IDoEntity;
import org.eclipse.scout.rt.dataobject.IPrettyPrintDataObjectMapper;
import org.eclipse.scout.rt.dataobject.TypeVersion;
+import org.eclipse.scout.rt.dataobject.testing.TestingResourceHelper;
import org.eclipse.scout.rt.platform.BEANS;
-import org.eclipse.scout.rt.platform.exception.PlatformException;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
import org.eclipse.scout.rt.platform.util.FileUtility;
import org.eclipse.scout.rt.platform.util.ObjectUtility;
import org.eclipse.scout.rt.platform.util.StringUtility;
@@ -62,7 +56,7 @@ public abstract class AbstractDataObjectSignatureTest {
/**
* Only data object with matching package name prefixes are added to signature.
*/
- protected abstract Set<String> getPackageNamePrefixes();
+ protected abstract String getPackageNamePrefix();
/**
* @return Full filename of signature file.
@@ -110,16 +104,16 @@ public abstract class AbstractDataObjectSignatureTest {
@Test
public void testStructure() {
- DataObjectSignatureDo signature = BEANS.get(DataObjectSignatureGenerator.class).createSignature(getPackageNamePrefixes(), this::acceptDataObject, this::acceptAttribut);
+ DataObjectSignatureDo signature = BEANS.get(DataObjectSignatureGenerator.class).createSignature(CollectionUtility.hashSet(getPackageNamePrefix()), this::acceptDataObject, this::acceptAttribut);
compareSignatures(signature);
}
protected void compareSignatures(DataObjectSignatureDo currentSignature) {
IDataObjectMapper dataObjectMapper = BEANS.get(IPrettyPrintDataObjectMapper.class);
- File referenceFile = new File(getApiSignatureJsonDirectory(), getFilename());
+ File referenceFile = new File(BEANS.get(TestingResourceHelper.class).getTestResourceDirectory(getResourceBaseClass()), getFilename());
if (!referenceFile.exists()) {
- writeCurrentSignature(referenceFile, currentSignature);
+ writeCurrentSignature(currentSignature);
fail("No previous signature file available. Initial signature file was created");
}
@@ -129,7 +123,7 @@ public abstract class AbstractDataObjectSignatureTest {
previousSignature = dataObjectMapper.readValue(fis, DataObjectSignatureDo.class);
}
catch (IOException | RuntimeException e) { // runtime exception possible due to data object mapper
- writeCurrentSignature(referenceFile, currentSignature);
+ writeCurrentSignature(currentSignature);
LOG.warn("Failed to read previous signature file", e);
fail("Failed to read previous signature file.");
}
@@ -141,60 +135,22 @@ public abstract class AbstractDataObjectSignatureTest {
// This is more a sanity check. If the data objects are not equal but there are no differences detected by the comparator, the comparator doesn't detect all differences yet.
boolean differentDataObjects = !ObjectUtility.equals(previousSignature, currentSignature);
if (differentDataObjects || !comparator.getDifferences().isEmpty()) {
- writeCurrentSignature(referenceFile, currentSignature);
+ writeCurrentSignature(currentSignature);
String details = comparator.getDifferences().isEmpty() ? "Comparator was unable to detect the differences, please review file changes manually." : StringUtility.join("\n", comparator.getDifferences());
fail("Review all signature differences and create corresponding migrations if necessary before committing any changes in file " + getFilename() + ":\n" + details);
}
}
- protected File getApiSignatureJsonDirectory() {
- File moduleDirectory = getModuleDirectory();
-
- String modulePath = getResourceBaseClass().getPackage().getName().replaceAll("\\.", Matcher.quoteReplacement(File.separator));
- return new File(moduleDirectory, "src/test/resources/" + modulePath + "/");
- }
-
- protected File getModuleDirectory() {
- // Replace the content of the out file when failing
- URL location = getClass().getProtectionDomain().getCodeSource().getLocation();
- if (!"file".equalsIgnoreCase(location.getProtocol())) {
- fail(String.format("not a file location (%s)", location));
- }
-
- try {
- File moduleDirectory = new File(location.toURI()).getParentFile().getParentFile();
- assertTrue("Module directory doesn't exist: " + moduleDirectory.getAbsolutePath(), moduleDirectory.exists());
- return moduleDirectory;
- }
- catch (URISyntaxException e) {
- throw new PlatformException("Failed to create URI from location {}", location, e);
- }
- }
-
- protected void writeCurrentSignature(File referenceFile, DataObjectSignatureDo signature) {
+ protected void writeCurrentSignature(DataObjectSignatureDo signature) {
// Test by default creates a new file to prevent accidental self-healing of test.
// This behavior can be changed by setting the appropriate system property (not recommended).
- File outputFile;
+ String filename;
if (System.getProperty("dataObjectSignatureTest.overwriteReferenceFile") != null) {
- outputFile = referenceFile;
+ filename = getFilename();
}
else {
- outputFile = new File(getApiSignatureJsonDirectory(), FileUtility.getFilenameParts(getFilename())[0] + "-to-be-reviewed.json");
- }
-
- if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
- fail("Unable to create target directory for reference file: " + outputFile.getParentFile()); // in case directory doesn't exist yet and couldn't be created (e.g. initial test execution)
- return;
- }
-
- String currentSignatureJson = BEANS.get(IPrettyPrintDataObjectMapper.class).writeValue(signature);
- currentSignatureJson = currentSignatureJson.replaceAll("\\r\\n", "\\\n").trim(); // only use \n
-
- try (OutputStream out = new FileOutputStream(outputFile)) {
- out.write(currentSignatureJson.getBytes(StandardCharsets.UTF_8));
- }
- catch (IOException e) {
- throw new PlatformException("Failed to write new signature file", e);
+ filename = FileUtility.getFilenameParts(getFilename())[0] + "-to-be-reviewed.json";
}
+ BEANS.get(TestingResourceHelper.class).writeTestResource(getResourceBaseClass(), filename, signature);
}
}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/DataObjectInventoryTest.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/DataObjectInventoryTest.java
index 68fc7d2195..dea986ec07 100644
--- a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/DataObjectInventoryTest.java
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/DataObjectInventoryTest.java
@@ -18,7 +18,9 @@ import java.util.Optional;
import org.eclipse.scout.rt.dataobject.fixture.DataObjectFixtureTypeVersions.DataObjectFixture_1_0_0;
import org.eclipse.scout.rt.dataobject.fixture.DataObjectFixtureTypeVersions.DataObjectFixture_1_0_0_034;
+import org.eclipse.scout.rt.dataobject.fixture.DataObjectFixtureTypeVersions.DataObjectFixture_1_0_0_Duplicate;
import org.eclipse.scout.rt.dataobject.fixture.DataObjectFixtureTypeVersions.DataObjectFixture_No_Version;
+import org.eclipse.scout.rt.dataobject.fixture.DataObjectFixtureTypeVersions.NonRegisteredNamespaceFixture_1_0_0;
import org.eclipse.scout.rt.dataobject.fixture.DataObjectProjectFixtureTypeVersions.DataObjectProjectFixture_1_2_3_004;
import org.eclipse.scout.rt.dataobject.fixture.DateFixtureDo;
import org.eclipse.scout.rt.dataobject.fixture.EntityFixtureDo;
@@ -81,12 +83,29 @@ public class DataObjectInventoryTest {
public void testValidateTypeVersionImplementors() {
m_inventory.validateTypeVersionImplementors(); // no exception
- IBean<Object> fixtureNoVersionBean = BEANS.get(BeanTestingHelper.class).registerBean(new BeanMetaData(DataObjectFixture_No_Version.class));
+ BeanTestingHelper beanTestingHelper = BEANS.get(BeanTestingHelper.class);
+ IBean<Object> fixtureNoVersionBean = beanTestingHelper.registerBean(new BeanMetaData(DataObjectFixture_No_Version.class));
try {
assertThrows(AssertionException.class, () -> m_inventory.validateTypeVersionImplementors());
}
finally {
- BEANS.get(BeanTestingHelper.class).unregisterBean(fixtureNoVersionBean);
+ beanTestingHelper.unregisterBean(fixtureNoVersionBean);
+ }
+
+ IBean<Object> fixtureNoRegisteredNamespaceBean = beanTestingHelper.registerBean(new BeanMetaData(NonRegisteredNamespaceFixture_1_0_0.class));
+ try {
+ assertThrows(AssertionException.class, () -> m_inventory.validateTypeVersionImplementors());
+ }
+ finally {
+ beanTestingHelper.unregisterBean(fixtureNoRegisteredNamespaceBean);
+ }
+
+ IBean<Object> fixtureDuplicateVersionBean = beanTestingHelper.registerBean(new BeanMetaData(DataObjectFixture_1_0_0_Duplicate.class));
+ try {
+ assertThrows(AssertionException.class, () -> m_inventory.validateTypeVersionImplementors());
+ }
+ finally {
+ beanTestingHelper.unregisterBean(fixtureDuplicateVersionBean);
}
}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectFixtureTypeVersions.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectFixtureTypeVersions.java
index 970a6168d8..dad6534641 100644
--- a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectFixtureTypeVersions.java
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectFixtureTypeVersions.java
@@ -55,4 +55,40 @@ public final class DataObjectFixtureTypeVersions {
return Collections.emptyList();
}
}
+
+ /**
+ * Manually registered in {@link DataObjectInventoryTest}.
+ */
+ @IgnoreBean
+ public static final class NonRegisteredNamespaceFixture_1_0_0 implements ITypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of("noRegisteredNamespace", "1.0.0");
+
+ @Override
+ public NamespaceVersion getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ public Collection<NamespaceVersion> getDependencies() {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Manually registered in {@link DataObjectInventoryTest}.
+ */
+ @IgnoreBean
+ public static final class DataObjectFixture_1_0_0_Duplicate implements ITypeVersion {
+
+ @Override
+ public NamespaceVersion getVersion() {
+ return DataObjectFixture_1_0_0.VERSION;
+ }
+
+ @Override
+ public Collection<NamespaceVersion> getDependencies() {
+ return Collections.emptyList();
+ }
+ }
}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectProjectFixtureTypeVersions.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectProjectFixtureTypeVersions.java
index 0fdc73484a..978aacda93 100644
--- a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectProjectFixtureTypeVersions.java
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/fixture/DataObjectProjectFixtureTypeVersions.java
@@ -15,6 +15,9 @@ import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
public final class DataObjectProjectFixtureTypeVersions {
+ private DataObjectProjectFixtureTypeVersions() {
+ }
+
public static final class DataObjectProjectFixture_1_2_3_004 extends AbstractTypeVersion {
public static final NamespaceVersion VERSION = NamespaceVersion.of(DataObjectProjectFixtureNamespace.ID, "1.2.3.004");
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContextTest.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContextTest.java
new file mode 100644
index 0000000000..e8609d1adb
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContextTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.junit.Assert.*;
+
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.ZipCodeFixtureGlobalContextData;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.util.Assertions.AssertionException;
+import org.junit.Test;
+
+public class DoStructureMigrationContextTest {
+
+ @Test
+ public void testDefaults() {
+ DoStructureMigrationContext ctx = BEANS.get(DoStructureMigrationContext.class);
+ assertTrue(ctx.getGlobal(IDoStructureMigrationLogger.class) instanceof DoStructureMigrationPassThroughLogger);
+ assertTrue(ctx.getLogger() instanceof DoStructureMigrationPassThroughLogger);
+ assertEquals(ctx.getLogger(), ctx.getGlobal(IDoStructureMigrationLogger.class));
+ }
+
+ @Test
+ public void testGlobalBean() {
+ DoStructureMigrationContext ctx = BEANS.get(DoStructureMigrationContext.class);
+ assertTrue(ctx.getGlobal(DoStructureMigrationStatsContextData.class) instanceof DoStructureMigrationStatsContextData);
+ assertTrue(ctx.getStats() instanceof DoStructureMigrationStatsContextData);
+ assertEquals(ctx.getStats(), ctx.getGlobal(DoStructureMigrationStatsContextData.class));
+ }
+
+ @Test
+ public void testGlobalManually() {
+ DoStructureMigrationContext ctx = BEANS.get(DoStructureMigrationContext.class);
+ assertNull(ctx.getGlobal(ZipCodeFixtureGlobalContextData.class));
+ ZipCodeFixtureGlobalContextData zipCodeContextData = new ZipCodeFixtureGlobalContextData();
+ ctx.putGlobal(zipCodeContextData);
+ assertEquals(zipCodeContextData, ctx.getGlobal(ZipCodeFixtureGlobalContextData.class));
+ }
+
+ @Test
+ public void testLocal() {
+ DoStructureMigrationContext ctx = BEANS.get(DoStructureMigrationContext.class);
+ assertNull(ctx.get(HouseFixtureStructureMigrationTargetContextData.class));
+
+ HouseFixtureStructureMigrationTargetContextData houseFixtureContextData = BEANS.get(HouseFixtureStructureMigrationTargetContextData.class);
+ ctx.push(houseFixtureContextData); // not initialized, for this test okay
+ assertEquals(houseFixtureContextData, ctx.get(HouseFixtureStructureMigrationTargetContextData.class));
+
+ HouseFixtureStructureMigrationTargetContextData houseFixtureContextData2 = BEANS.get(HouseFixtureStructureMigrationTargetContextData.class);
+ ctx.push(houseFixtureContextData2); // not initialized, for this test okay
+
+ assertEquals(houseFixtureContextData2, ctx.get(HouseFixtureStructureMigrationTargetContextData.class));
+
+ assertThrows(AssertionException.class, () -> ctx.remove(houseFixtureContextData)); // not on top of the stack
+
+ // remove instance on top of the stack
+ ctx.remove(houseFixtureContextData2);
+
+ // first instance is returned
+ assertEquals(houseFixtureContextData, ctx.get(HouseFixtureStructureMigrationTargetContextData.class));
+
+ // remove first instance
+ ctx.remove(houseFixtureContextData);
+
+ // no instance anymore
+ assertNull(ctx.get(HouseFixtureStructureMigrationTargetContextData.class));
+ }
+
+ @Test
+ public void testClone() {
+ DoStructureMigrationContext ctx = BEANS.get(DoStructureMigrationContext.class);
+
+ ZipCodeFixtureGlobalContextData zipCodeContextData = new ZipCodeFixtureGlobalContextData();
+ ctx.putGlobal(zipCodeContextData);
+
+ HouseFixtureStructureMigrationTargetContextData houseFixtureContextData = BEANS.get(HouseFixtureStructureMigrationTargetContextData.class);
+ ctx.push(houseFixtureContextData); // not initialized, for this test okay
+
+ DoStructureMigrationContext ctxCopy = ctx.copy();
+
+ // Global (same reference)
+ assertSame(ctx.getGlobal(ZipCodeFixtureGlobalContextData.class), ctxCopy.getGlobal(ZipCodeFixtureGlobalContextData.class));
+ assertSame(ctx.getStats(), ctxCopy.getStats());
+ assertSame(ctx.getLogger(), ctxCopy.getLogger());
+
+ // Local (not copied)
+ assertNull(ctxCopy.get(HouseFixtureStructureMigrationTargetContextData.class));
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationHelperTest.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationHelperTest.java
new file mode 100644
index 0000000000..c46fca122d
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationHelperTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.eclipse.scout.rt.testing.platform.util.ScoutAssert.assertEqualsWithComparisonFailure;
+import static org.junit.Assert.*;
+
+import org.eclipse.scout.rt.dataobject.DoEntityBuilder;
+import org.eclipse.scout.rt.dataobject.IDataObject;
+import org.eclipse.scout.rt.dataobject.IDataObjectMapper;
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CharlieCustomerFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CustomerFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PostalAddressFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_5;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+import org.eclipse.scout.rt.platform.util.ImmutablePair;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class DoStructureMigrationHelperTest {
+
+ private static DoStructureMigrationHelper s_helper;
+
+ @BeforeClass
+ public static void beforeClass() {
+ s_helper = BEANS.get(DoStructureMigrationHelper.class);
+ }
+
+ /**
+ * These constants must never change.
+ */
+ @Test
+ public void testConstants() {
+ assertEquals("_type", DoStructureMigrationHelper.TYPE_ATTRIBUTE_NAME);
+ assertEquals("_typeVersion", DoStructureMigrationHelper.TYPE_VERSION_ATTRIBUTE_NAME);
+ }
+
+ @Test
+ public void testGetType() {
+ assertNull(s_helper.getType(BEANS.get(DoEntityBuilder.class).build()));
+ assertEquals("alfaFixture.Example", s_helper.getType(BEANS.get(DoEntityBuilder.class).put("_type", "alfaFixture.Example").build()));
+ }
+
+ @Test
+ public void testSetType() {
+ IDoEntity doEntity = BEANS.get(DoEntityBuilder.class).build();
+ s_helper.setType(doEntity, "alfaFixture.Example");
+ assertEquals("alfaFixture.Example", s_helper.getType(doEntity));
+ assertEquals("alfaFixture.Example", doEntity.getString("_type"));
+ }
+
+ @Test
+ public void testGetTypeVersion() {
+ assertNull(s_helper.getTypeVersion(BEANS.get(DoEntityBuilder.class).build()));
+ assertEquals(AlfaFixture_1.VERSION, s_helper.getTypeVersion(BEANS.get(DoEntityBuilder.class).put("_typeVersion", "alfaFixture-1").build()));
+ }
+
+ @Test
+ public void testSetTypeVersion() {
+ IDoEntity doEntity = BEANS.get(DoEntityBuilder.class).build();
+ s_helper.setTypeVersion(doEntity, AlfaFixture_1.VERSION);
+ assertEquals(AlfaFixture_1.VERSION, s_helper.getTypeVersion(doEntity));
+ assertEquals(AlfaFixture_1.VERSION.unwrap(), doEntity.getString("_typeVersion"));
+ }
+
+ @Test
+ public void testUpdateTypeVersion() {
+ IDoEntity doEntity = BEANS.get(DoEntityBuilder.class).build();
+ s_helper.setTypeVersion(doEntity, AlfaFixture_1.VERSION);
+ assertFalse(s_helper.updateTypeVersion(doEntity, AlfaFixture_1.VERSION)); // already up-to-date
+ assertTrue(s_helper.updateTypeVersion(doEntity, AlfaFixture_2.VERSION)); // changed
+ assertEquals(AlfaFixture_2.VERSION.unwrap(), doEntity.getString("_typeVersion")); // verify
+ assertFalse(s_helper.updateTypeVersion(doEntity, AlfaFixture_2.VERSION)); // already up-to-date
+ }
+
+ @Test
+ public void testCollectRawDataObjectTypeVersions() {
+ DoStructureMigrationHelper helper = BEANS.get(DoStructureMigrationHelper.class);
+
+ // Single data object
+ Assert.assertEquals(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION)),
+ helper.collectRawDataObjectTypeVersions(rawDataObject(BEANS.get(HouseFixtureDo.class))));
+
+ // Data object containing another data objects (house owner attribute not set here)
+ Assert.assertEquals(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_5.VERSION),
+ new ImmutablePair<>("charlieFixture.PostalAddressFixture", CharlieFixture_2.VERSION)),
+ helper.collectRawDataObjectTypeVersions(rawDataObject(
+ BEANS.get(HouseFixtureDo.class)
+ .withRooms(BEANS.get(RoomFixtureDo.class))
+ .withPostalAddress(BEANS.get(PostalAddressFixtureDo.class)))));
+
+ // Data object with a subclass but using origin class (thus no BEANS.get) [not a real case]
+ Assert.assertEquals(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("alfaFixture.CustomerFixture", AlfaFixture_3.VERSION)),
+ helper.collectRawDataObjectTypeVersions(rawDataObject(
+ BEANS.get(HouseFixtureDo.class)
+ .withOwner(new CustomerFixtureDo()))));
+
+ // Data object with a subclass using replaced class
+ Assert.assertEquals(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("alfaFixture.CustomerFixture", CharlieFixture_3.VERSION)),
+ helper.collectRawDataObjectTypeVersions(rawDataObject(
+ BEANS.get(HouseFixtureDo.class)
+ .withOwner(BEANS.get(CharlieCustomerFixtureDo.class)))));
+
+ // Must not find any type versions because a typed data object is used. When requesting type versions of raw data object,
+ // typed data object must not be returned (only raw objects are relevant for migration).
+ assertTrue(helper.collectRawDataObjectTypeVersions(BEANS.get(HouseFixtureDo.class)).isEmpty());
+ }
+
+ @Test
+ public void testIsMigrationApplicable() {
+ assertFalse(s_helper.isMigrationApplicable(BEANS.get(RoomFixtureDo.class), CharlieFixture_2.VERSION)); // typed data object (not raw)
+ assertTrue(s_helper.isMigrationApplicable(BEANS.get(DoEntityBuilder.class).put("_type", "charlieFixture.RoomFixture").build(), CharlieFixture_2.VERSION)); // no type version (yet)
+ assertFalse(s_helper.isMigrationApplicable(BEANS.get(DoEntityBuilder.class).put("_type", "charlieFixture.RoomFixture").put("_typeVersion", AlfaFixture_1.VERSION.unwrap()).build(), CharlieFixture_2.VERSION)); // different namespace
+ assertFalse(s_helper.isMigrationApplicable(BEANS.get(DoEntityBuilder.class).put("_type", "charlieFixture.RoomFixture").put("_typeVersion", CharlieFixture_2.VERSION.unwrap()).build(), CharlieFixture_2.VERSION)); // same version
+ assertTrue(s_helper.isMigrationApplicable(BEANS.get(DoEntityBuilder.class).put("_type", "charlieFixture.RoomFixture").put("_typeVersion", CharlieFixture_1.VERSION.unwrap()).build(), CharlieFixture_2.VERSION)); // lower version
+ assertFalse(s_helper.isMigrationApplicable(BEANS.get(DoEntityBuilder.class).put("_type", "charlieFixture.RoomFixture").put("_typeVersion", CharlieFixture_3.VERSION.unwrap()).build(), CharlieFixture_2.VERSION)); // higher version (invalid)
+ }
+
+ /**
+ * References {@link HouseFixtureDo} with type version {@link CharlieFixture_2} and {@link RoomFixtureDo} with type
+ * version {@link CharlieFixture_5}.
+ */
+ @Test
+ public void testIsAnyMigrationRequired() {
+ assertFalse(s_helper.isAnyMigrationRequired(CollectionUtility.emptyHashMap())); // no versions at all -> not outdated
+
+ assertTrue(s_helper.isAnyMigrationRequired(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_1.VERSION.unwrap()),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_1.VERSION.unwrap()))));
+
+ // invalid constellation, all data objects within a persisted data object have a consistent type version
+ assertTrue(s_helper.isAnyMigrationRequired(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION.unwrap()),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_1.VERSION.unwrap()))));
+
+ // invalid constellation, all data objects within a persisted data object have a consistent type version
+ assertTrue(s_helper.isAnyMigrationRequired(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_1.VERSION.unwrap()),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_5.VERSION.unwrap()))));
+
+ assertFalse(s_helper.isAnyMigrationRequired(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION.unwrap()),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_5.VERSION.unwrap()))));
+ }
+
+ /**
+ * References {@link HouseFixtureDo} that has type version {@link CharlieFixture_2}.
+ */
+ @Test
+ public void testIsMigrationRequired() {
+ assertTrue(s_helper.isMigrationRequired("charlieFixture.HouseFixture", null)); // no type version
+ assertFalse(s_helper.isMigrationRequired("charlieFixture.HouseFixture", NamespaceVersion.of("unknown", "1.0.0"))); // unknown type version
+ assertTrue(s_helper.isMigrationRequired("charlieFixture.BuildingFixture", CharlieFixture_1.VERSION)); // unknown type name (e.g. renamed)
+ assertTrue(s_helper.isMigrationRequired("charlieFixture.HouseFixture", CharlieFixture_1.VERSION)); // lower type version
+ assertFalse(s_helper.isMigrationRequired("charlieFixture.HouseFixture", CharlieFixture_2.VERSION)); // same type version
+ assertFalse(s_helper.isMigrationRequired("charlieFixture.HouseFixture", CharlieFixture_3.VERSION)); // newer type version (invalid)
+ }
+
+ @Test
+ public void testRenameTypeName() {
+ // Regular rename
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class).put("_type", "lorem").build();
+ assertTrue(s_helper.renameTypeName(actual, "ipsum"));
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class).put("_type", "ipsum").build();
+ assertEqualsWithComparisonFailure(expected, actual);
+
+ // Already renamed
+ actual = BEANS.get(DoEntityBuilder.class).put("_type", "ipsum").build();
+ assertFalse(s_helper.renameTypeName(actual, "ipsum"));
+ expected = BEANS.get(DoEntityBuilder.class).put("_type", "ipsum").build();
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ @Test
+ public void testRenameAttribute() {
+ // non-existing attribute
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class).put("lorem", "ipsum").build();
+ assertFalse(s_helper.renameAttribute(actual, "dolor", "sid"));
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class).put("lorem", "ipsum").build();
+ assertEqualsWithComparisonFailure(expected, actual);
+
+ // existing attribute
+ actual = BEANS.get(DoEntityBuilder.class).put("lorem", "ipsum").build();
+ assertTrue(s_helper.renameAttribute(actual, "lorem", "sid"));
+ expected = BEANS.get(DoEntityBuilder.class).put("sid", "ipsum").build();
+ assertEqualsWithComparisonFailure(expected, actual);
+
+ // existing attribute (node only, no value)
+ actual = BEANS.get(DoEntityBuilder.class).put("lorem", null).build();
+ assertTrue(s_helper.renameAttribute(actual, "lorem", "sid"));
+ expected = BEANS.get(DoEntityBuilder.class).put("sid", null).build();
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ protected IDataObject rawDataObject(IDataObject dataObject) {
+ IDataObjectMapper mapper = BEANS.get(IDataObjectMapper.class);
+ return mapper.readValueRaw(mapper.writeValue(dataObject));
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationInventoryTest.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationInventoryTest.java
new file mode 100644
index 0000000000..6bb3df5c54
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationInventoryTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.DoEntityBuilder;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CharlieCustomerFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CharlieCustomerFixtureTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CustomerFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CustomerFixtureTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureDoStructureMigrationHandler_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureRawOnlyDoStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureTypedOnlyDoStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PetFixtureFamilyFriendlyMigrationHandlerInvalidTypeVersionToUpdate_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDoStructureMigrationHandler_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDoStructureMigrationHandler_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDoStructureMigrationHandler_4;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDoStructureMigrationHandler_5;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureNamespace;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_6;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_7;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureNamespace;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureNamespace;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_4;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_5;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.exception.PlatformException;
+import org.eclipse.scout.rt.platform.util.Assertions.AssertionException;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+import org.eclipse.scout.rt.platform.util.ImmutablePair;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests for {@link DoStructureMigrationInventory}.
+ */
+public class DoStructureMigrationInventoryTest {
+
+ private static DoStructureMigrationInventory s_inventory;
+
+ @BeforeClass
+ public static void beforeClass() {
+ s_inventory = new TestDoStructureMigrationInventory(
+ Arrays.asList(new AlfaFixtureNamespace(), new BravoFixtureNamespace(), new CharlieFixtureNamespace()),
+ Arrays.asList(
+ new AlfaFixture_1(), new AlfaFixture_2(), new AlfaFixture_3(), new AlfaFixture_6(), // AlfaFixture_7 is explicitly not registered
+ new BravoFixture_1(), new BravoFixture_2(), new BravoFixture_3(),
+ new CharlieFixture_1(), new CharlieFixture_2(), new CharlieFixture_3(), new CharlieFixture_4(), new CharlieFixture_5()),
+ Arrays.asList(
+ HouseFixtureStructureMigrationTargetContextData.class,
+ HouseFixtureRawOnlyDoStructureMigrationTargetContextData.class,
+ HouseFixtureTypedOnlyDoStructureMigrationTargetContextData.class,
+ CustomerFixtureTargetContextData.class,
+ CharlieCustomerFixtureTargetContextData.class),
+ new HouseFixtureDoStructureMigrationHandler_2(),
+ new RoomFixtureDoStructureMigrationHandler_2(),
+ new RoomFixtureDoStructureMigrationHandler_3(),
+ new RoomFixtureDoStructureMigrationHandler_4(),
+ new RoomFixtureDoStructureMigrationHandler_5(),
+ new PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3());
+ }
+
+ /**
+ * Tests for {@link DoStructureMigrationInventory#validateMigrationHandlerUniqueness(Map)} ()}.
+ */
+ @Test
+ public void testValidateMigrationHandlerUniqueness() {
+ TestDoStructureMigrationInventory inventory = new TestDoStructureMigrationInventory(
+ Arrays.asList(new AlfaFixtureNamespace(), new BravoFixtureNamespace(), new CharlieFixtureNamespace()),
+ Arrays.asList(
+ new AlfaFixture_1(), new AlfaFixture_2(), new AlfaFixture_3(), new AlfaFixture_6(),
+ new BravoFixture_1(), new BravoFixture_2(), new BravoFixture_3(),
+ new CharlieFixture_1(), new CharlieFixture_2(), new CharlieFixture_3(), new CharlieFixture_4(), new CharlieFixture_5()),
+ Collections.emptyList(),
+ new PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3());
+
+ assertNotNull(inventory); // no validation error on creation of inventory
+
+ assertThrows(PlatformException.class, () -> new TestDoStructureMigrationInventory(
+ Arrays.asList(new AlfaFixtureNamespace(), new BravoFixtureNamespace(), new CharlieFixtureNamespace()),
+ Arrays.asList(
+ new AlfaFixture_1(), new AlfaFixture_2(), new AlfaFixture_3(), new AlfaFixture_6(),
+ new BravoFixture_1(), new BravoFixture_2(), new BravoFixture_3(),
+ new CharlieFixture_1(), new CharlieFixture_2(), new CharlieFixture_3(), new CharlieFixture_4(), new CharlieFixture_5()),
+ Collections.emptyList(),
+ new PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3(),
+ new PetFixtureFamilyFriendlyMigrationHandlerInvalidTypeVersionToUpdate_3())); // added this migration handler here -> validation error because multiple migration handlers per type version/type name
+ }
+
+ /**
+ * Validates internal structure of inventory ({@link DoStructureMigrationInventory#m_orderedVersions}.
+ */
+ @Test
+ public void testOrdered() {
+ assertEquals(
+ Arrays.asList(
+ AlfaFixture_1.VERSION, BravoFixture_1.VERSION, CharlieFixture_1.VERSION,
+ AlfaFixture_2.VERSION, BravoFixture_2.VERSION, CharlieFixture_2.VERSION,
+ AlfaFixture_3.VERSION, BravoFixture_3.VERSION, CharlieFixture_3.VERSION,
+ CharlieFixture_4.VERSION,
+ AlfaFixture_6.VERSION,
+ CharlieFixture_5.VERSION // after charlieFixture-4, must not necessarily be after alfaFixture-6
+ ),
+ new ArrayList<>(s_inventory.m_orderedVersions));
+ }
+
+ /**
+ * Validates internal structure of inventory ({@link DoStructureMigrationInventory#m_typeNameVersions}.
+ */
+ @Test
+ public void testTypeNameVersions() {
+ assertEquals(
+ CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.BuildingFixture", Arrays.asList(CharlieFixture_2.VERSION)),
+ new ImmutablePair<>("charlieFixture.RoomFixture", Arrays.asList(CharlieFixture_2.VERSION, CharlieFixture_3.VERSION, CharlieFixture_4.VERSION, CharlieFixture_5.VERSION)),
+ new ImmutablePair<>("bravoFixture.PetFixture", Arrays.asList(BravoFixture_3.VERSION))),
+ s_inventory.m_typeNameVersions);
+ }
+
+ @Test
+ public void testTypeNameToCurrentTypeVersion() {
+ assertEquals(CharlieFixture_2.VERSION, s_inventory.m_typeNameToCurrentTypeVersion.get("charlieFixture.HouseFixture"));
+ assertEquals(CharlieFixture_5.VERSION, s_inventory.m_typeNameToCurrentTypeVersion.get("charlieFixture.RoomFixture"));
+ assertEquals(CharlieFixture_2.VERSION, s_inventory.m_typeNameToCurrentTypeVersion.get("charlieFixture.PostalAddressFixture"));
+ }
+
+ @Test
+ public void testGetDoMigrationContextValues() {
+ Assert.assertThrows(AssertionException.class, () -> s_inventory.getDoMigrationContextValues(null));
+
+ assertEquals(0, s_inventory.getDoMigrationContextValues(BEANS.get(DoEntityBuilder.class).put("_type", "unknown").build()).size());
+
+ Set<Class<? extends IDoStructureMigrationTargetContextData>> contextDataSet;
+
+ assertEquals(CollectionUtility.hashSet(HouseFixtureStructureMigrationTargetContextData.class, HouseFixtureRawOnlyDoStructureMigrationTargetContextData.class),
+ s_inventory.getDoMigrationContextValues(BEANS.get(DoEntityBuilder.class).put("_type", "charlieFixture.HouseFixture").build()));
+
+ assertEquals(CollectionUtility.hashSet(HouseFixtureStructureMigrationTargetContextData.class, HouseFixtureTypedOnlyDoStructureMigrationTargetContextData.class),
+ s_inventory.getDoMigrationContextValues(BEANS.get(HouseFixtureDo.class)));
+
+ // Subclasses data object, using origin instance (thus new instead of BEANS.get) [not a real case]
+ assertEquals(CollectionUtility.hashSet(CustomerFixtureTargetContextData.class),
+ s_inventory.getDoMigrationContextValues(new CustomerFixtureDo()));
+
+ assertEquals(CollectionUtility.hashSet(CustomerFixtureTargetContextData.class, CharlieCustomerFixtureTargetContextData.class),
+ s_inventory.getDoMigrationContextValues(BEANS.get(CharlieCustomerFixtureDo.class)));
+ }
+
+ @Test
+ public void testFindNextMigrationHandlerVersion() {
+ // Missing migration handler from charlieFixture-1 to -2 [lorem.Migrationless].
+ assertNull(s_inventory.findNextMigrationHandlerVersion("charlieFixture.PostalAddressFixture", CharlieFixture_1.VERSION));
+
+ // Regular case [lorem.Example]
+ assertEquals(CharlieFixture_2.VERSION, s_inventory.findNextMigrationHandlerVersion("charlieFixture.RoomFixture", null));
+ assertEquals(CharlieFixture_2.VERSION, s_inventory.findNextMigrationHandlerVersion("charlieFixture.RoomFixture", CharlieFixture_1.VERSION));
+ assertEquals(CharlieFixture_3.VERSION, s_inventory.findNextMigrationHandlerVersion("charlieFixture.RoomFixture", CharlieFixture_2.VERSION));
+ assertEquals(CharlieFixture_4.VERSION, s_inventory.findNextMigrationHandlerVersion("charlieFixture.RoomFixture", CharlieFixture_3.VERSION));
+ assertEquals(CharlieFixture_5.VERSION, s_inventory.findNextMigrationHandlerVersion("charlieFixture.RoomFixture", CharlieFixture_4.VERSION));
+ assertNull(s_inventory.findNextMigrationHandlerVersion("charlieFixture.RoomFixture", CharlieFixture_5.VERSION)); // current type version
+ assertNull(s_inventory.findNextMigrationHandlerVersion("charlieFixture.RoomFixture", AlfaFixture_7.VERSION)); // invalid, unknown type version
+
+ // [lorem.One/lorem.Two]
+ assertEquals(CharlieFixture_2.VERSION, s_inventory.findNextMigrationHandlerVersion("charlieFixture.BuildingFixture", CharlieFixture_1.VERSION));
+ // invalid, BuildingFixture not available for this type version (next version from full list is returned due to possible renamings)
+ assertEquals(CharlieFixture_4.VERSION, s_inventory.findNextMigrationHandlerVersion("charlieFixture.BuildingFixture", CharlieFixture_3.VERSION));
+ assertNull(s_inventory.findNextMigrationHandlerVersion("charlieFixture.HouseFixture", CharlieFixture_2.VERSION)); // current type version
+ }
+
+ @Test
+ public void testGetVersions() {
+ assertThrows(AssertionException.class, () -> s_inventory.getVersions(CollectionUtility.emptyHashMap(), AlfaFixture_7.VERSION)); // alfaFixture-7 is unknown
+
+ assertEquals(CollectionUtility.emptyArrayList(), s_inventory.getVersions(CollectionUtility.emptyHashMap(), null));
+ assertEquals(CollectionUtility.emptyArrayList(), s_inventory.getVersions(CollectionUtility.emptyHashMap(), CharlieFixture_2.VERSION));
+
+ // Only versions with handlers are returned
+
+ assertEquals(Arrays.asList(CharlieFixture_2.VERSION, BravoFixture_3.VERSION, CharlieFixture_3.VERSION, CharlieFixture_4.VERSION, CharlieFixture_5.VERSION),
+ s_inventory.getVersions(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.BuildingFixture", CharlieFixture_1.VERSION),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_1.VERSION)),
+ null));
+
+ assertEquals(Arrays.asList(CharlieFixture_3.VERSION, CharlieFixture_4.VERSION, CharlieFixture_5.VERSION),
+ s_inventory.getVersions(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_2.VERSION)),
+ null));
+
+ assertEquals(Arrays.asList(CharlieFixture_4.VERSION, CharlieFixture_5.VERSION),
+ s_inventory.getVersions(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_3.VERSION)),
+ null));
+
+ assertEquals(Arrays.asList(CharlieFixture_5.VERSION),
+ s_inventory.getVersions(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_4.VERSION)),
+ null));
+
+ assertEquals(CollectionUtility.emptyArrayList(),
+ s_inventory.getVersions(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_5.VERSION)),
+ null));
+
+ // With limit of toVersion
+ assertEquals(Arrays.asList(CharlieFixture_3.VERSION),
+ s_inventory.getVersions(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_2.VERSION)),
+ CharlieFixture_3.VERSION));
+
+ // With limit of toVersion (lower than first returned version CharlieFixture_3)
+ assertEquals(Collections.emptyList(),
+ s_inventory.getVersions(CollectionUtility.hashMap(
+ new ImmutablePair<>("charlieFixture.HouseFixture", CharlieFixture_2.VERSION),
+ new ImmutablePair<>("charlieFixture.RoomFixture", CharlieFixture_2.VERSION)),
+ CharlieFixture_2.VERSION));
+ }
+
+ @Test
+ public void testGetMigrationHandlers() {
+ assertThrows(AssertionException.class, () -> s_inventory.getMigrationHandlers(null));
+ assertThrows(AssertionException.class, () -> s_inventory.getMigrationHandlers(AlfaFixture_7.VERSION)); // no registered
+
+ // alfaFixture-1
+ assertTrue(s_inventory.getMigrationHandlers(AlfaFixture_1.VERSION).isEmpty()); // no handlers
+
+ Map<String, IDoStructureMigrationHandler> migrationHandlers;
+
+ // charlieFixture-2
+ migrationHandlers = s_inventory.getMigrationHandlers(CharlieFixture_2.VERSION);
+ assertEquals(2, migrationHandlers.size());
+
+ assertTrue(migrationHandlers.get("charlieFixture.BuildingFixture") instanceof HouseFixtureDoStructureMigrationHandler_2);
+ assertTrue(migrationHandlers.get("charlieFixture.RoomFixture") instanceof RoomFixtureDoStructureMigrationHandler_2);
+
+ // bravoFixture-3
+ migrationHandlers = s_inventory.getMigrationHandlers(BravoFixture_3.VERSION);
+ assertEquals(1, migrationHandlers.size());
+
+ assertTrue(migrationHandlers.get("bravoFixture.PetFixture") instanceof PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3);
+
+ // charlieFixture-3
+ migrationHandlers = s_inventory.getMigrationHandlers(CharlieFixture_3.VERSION);
+ assertEquals(1, migrationHandlers.size());
+ assertTrue(migrationHandlers.get("charlieFixture.RoomFixture") instanceof RoomFixtureDoStructureMigrationHandler_3);
+
+ // charlieFixture-4
+ migrationHandlers = s_inventory.getMigrationHandlers(CharlieFixture_4.VERSION);
+ assertEquals(1, migrationHandlers.size());
+ assertTrue(migrationHandlers.get("charlieFixture.RoomFixture") instanceof RoomFixtureDoStructureMigrationHandler_4);
+
+ // charlieFixture-5
+ migrationHandlers = s_inventory.getMigrationHandlers(CharlieFixture_5.VERSION);
+ assertEquals(1, migrationHandlers.size());
+ assertTrue(migrationHandlers.get("charlieFixture.RoomFixture") instanceof RoomFixtureDoStructureMigrationHandler_5);
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationTestHelper.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationTestHelper.java
new file mode 100644
index 0000000000..867b0af4e2
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationTestHelper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureRawOnlyDoStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureTypedOnlyDoStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PersonFixtureTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureNamespace;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_6;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_7;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureNamespace;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureNamespace;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_4;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_5;
+import org.eclipse.scout.rt.platform.ApplicationScoped;
+import org.eclipse.scout.rt.platform.namespace.INamespace;
+
+/**
+ * Helper methods used within tests to access common fixtures (namespaces, type versions, context data classes).
+ */
+@ApplicationScoped
+public class DoStructureMigrationTestHelper {
+
+ public List<INamespace> getFixtureNamespaces() {
+ return Arrays.asList(new AlfaFixtureNamespace(), new BravoFixtureNamespace(), new CharlieFixtureNamespace());
+ }
+
+ public Collection<ITypeVersion> getFixtureTypeVersions() {
+ return Arrays.asList(
+ new AlfaFixture_1(), new AlfaFixture_2(), new AlfaFixture_3(), new AlfaFixture_6(), new AlfaFixture_7(),
+ new BravoFixture_1(), new BravoFixture_2(), new BravoFixture_3(),
+ new CharlieFixture_1(), new CharlieFixture_2(), new CharlieFixture_3(), new CharlieFixture_4(), new CharlieFixture_5());
+ }
+
+ public Collection<Class<? extends IDoStructureMigrationTargetContextData>> getFixtureContextDataClasses() {
+ return Arrays.asList(
+ HouseFixtureStructureMigrationTargetContextData.class,
+ HouseFixtureRawOnlyDoStructureMigrationTargetContextData.class,
+ HouseFixtureTypedOnlyDoStructureMigrationTargetContextData.class,
+ PersonFixtureTargetContextData.class);
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigratorTest.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigratorTest.java
new file mode 100644
index 0000000000..2bd9e78631
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigratorTest.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.eclipse.scout.rt.platform.util.Assertions.assertTrue;
+import static org.eclipse.scout.rt.testing.platform.util.ScoutAssert.assertEqualsWithComparisonFailure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.scout.rt.dataobject.DoEntityBuilder;
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CharlieCustomerFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CharlieCustomerFixtureMigrationHandler_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CustomerFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.CustomerFixtureMigrationHandler_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.HouseFixtureDoStructureMigrationHandler_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PersonFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PersonFixtureDoStructureMigrationHandler_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PersonFixtureTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PetFixtureCaseSensitiveNameMigrationHandler_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PetFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PostalAddressFixtureDo;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDoStructureMigrationHandler_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDoStructureMigrationHandler_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDoStructureMigrationHandler_4;
+import org.eclipse.scout.rt.dataobject.migration.fixture.house.RoomFixtureDoStructureMigrationHandler_5;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_5;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.BeanMetaData;
+import org.eclipse.scout.rt.platform.IBean;
+import org.eclipse.scout.rt.testing.platform.BeanTestingHelper;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests for {@link DoStructureMigrator}.
+ */
+public class DoStructureMigratorTest {
+
+ private static final List<IBean<?>> TEST_BEANS = new ArrayList<>();
+
+ private static DoStructureMigrationContext s_migrationContext;
+ private static DoStructureMigrator s_migrator;
+
+ @BeforeClass
+ public static void beforeClass() {
+ DoStructureMigrationTestHelper testHelper = BEANS.get(DoStructureMigrationTestHelper.class);
+ TestDoStructureMigrationInventory inventory = new TestDoStructureMigrationInventory(
+ testHelper.getFixtureNamespaces(),
+ testHelper.getFixtureTypeVersions(),
+ testHelper.getFixtureContextDataClasses(),
+ new PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2(),
+ new PetFixtureCaseSensitiveNameMigrationHandler_2(),
+ new PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3(),
+ new CustomerFixtureMigrationHandler_3(),
+ new CharlieCustomerFixtureMigrationHandler_3(),
+ new HouseFixtureDoStructureMigrationHandler_2(),
+ new RoomFixtureDoStructureMigrationHandler_2(),
+ new RoomFixtureDoStructureMigrationHandler_3(),
+ new RoomFixtureDoStructureMigrationHandler_4(),
+ new RoomFixtureDoStructureMigrationHandler_5(),
+ new PersonFixtureDoStructureMigrationHandler_2());
+
+ TEST_BEANS.add(BEANS.get(BeanTestingHelper.class).registerBean(new BeanMetaData(TestDoStructureMigrationInventory.class, inventory).withReplace(true)));
+
+ s_migrationContext = BEANS.get(DoStructureMigrationContext.class);
+ s_migrator = BEANS.get(DoStructureMigrator.class);
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ BEANS.get(BeanTestingHelper.class).unregisterBeans(TEST_BEANS);
+ }
+
+ /**
+ * Tests migration of a data object without prior type version with an implemented migration handler.
+ * <p>
+ * Uses data object {@link PetFixtureDo} and migration handlers {@link PetFixtureCaseSensitiveNameMigrationHandler_2}.
+ */
+ @Test
+ public void testMigrationWithoutPreviousTypeVersion() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "bravoFixture.PetFixture")
+ .put("name", "CHARLIE")
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual, BravoFixture_2.VERSION)); // stop at bravoFixture-2, otherwise namespace will change too
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "bravoFixture.PetFixture")
+ .put("_typeVersion", BravoFixture_2.VERSION.unwrap())
+ .put("name", "Charlie")
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ /**
+ * Tests migration of a data object without prior type version with an empty migration handler.
+ * <p>
+ * Uses data object {@link PostalAddressFixtureDo} and migration handlers
+ * {@link PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2}.
+ */
+ @Test
+ public void testMigrationUpdateVersionOnly() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PostalAddressFixture")
+ .put("street", "Main street 12")
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PostalAddressFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap())
+ .put("street", "Main street 12")
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ /**
+ * Tests migration of a data object with prior type version and with an empty migration handler.
+ * <p>
+ * Uses data object {@link PostalAddressFixtureDo} and migration handlers
+ * {@link PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2}.
+ */
+ @Test
+ public void testUpdateVersionWithPreviousTypeVersion() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PostalAddressFixture")
+ .put("_typeVersion", CharlieFixture_1.VERSION.unwrap())
+ .put("street", "Main street 12")
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PostalAddressFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap())
+ .put("street", "Main street 12")
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ /**
+ * Tests namespace change.
+ * <p>
+ * Uses data object {@link PetFixtureDo} and migration handlers {@link PetFixtureCaseSensitiveNameMigrationHandler_2}
+ * and {@link PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3}.
+ */
+ @Test
+ public void testNamespaceChange() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "bravoFixture.PetFixture")
+ .put("_typeVersion", BravoFixture_1.VERSION.unwrap())
+ .put("name", "JOHN")
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "alfaFixture.PetFixture")
+ .put("_typeVersion", AlfaFixture_3.VERSION.unwrap())
+ .put("name", "John")
+ .put("familyFriendly", true)
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ /**
+ * Uses data object {@link CustomerFixtureDo} and migration handlers {@link CustomerFixtureMigrationHandler_3}.
+ */
+ @Test
+ public void testMigrationHandlerForOriginalDataObject() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "alfaFixture.CustomerFixture")
+ .put("_typeVersion", AlfaFixture_1.VERSION.unwrap())
+ .put("firstName", "John")
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "alfaFixture.CustomerFixture")
+ .put("_typeVersion", AlfaFixture_3.VERSION.unwrap())
+ .put("firstName", "JOHN") // uppercase due to CustomerFixtureMigrationHandler_3
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ /**
+ * Migration handler for original data objects are not executed anymore when a data object is replaced an and own type
+ * version is used.
+ * <p>
+ * Uses data object {@link CharlieCustomerFixtureDo} and migration handlers
+ * {@link CharlieCustomerFixtureMigrationHandler_3}.
+ */
+ @Test
+ public void testMigrationHandlerForSubclassedDataObject() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "alfaFixture.CustomerFixture")
+ .put("_typeVersion", CharlieFixture_1.VERSION.unwrap())
+ .put("firstName", "John")
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "alfaFixture.CustomerFixture")
+ .put("_typeVersion", CharlieFixture_3.VERSION.unwrap())
+ .put("firstName", "john") // lowercase due to CharlieCustomerFixtureMigrationHandler_3
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ @Test
+ public void testHouseFixtureMigration_1_to_5() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.BuildingFixture")
+ .put("_typeVersion", CharlieFixture_1.VERSION.unwrap())
+ .put("name", "Family Doe")
+ .putList("rooms", BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.RoomFixture")
+ .put("_typeVersion", CharlieFixture_1.VERSION.unwrap())
+ .put("roomName", "Kitchen")
+ .build())
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.HouseFixture") // BuildingFixture -> HouseFixture
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap()) // updated version
+ .put("name", "Family Doe")
+ .putList("rooms", BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.RoomFixture")
+ .put("_typeVersion", CharlieFixture_5.VERSION.unwrap()) // updated version
+ .put("name", "Kitchen") // roomName -> name
+ .put("displayText", "Family Doe: Kitchen") // display text was calculated based on name of house and room
+ .build())
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ @Test
+ public void testHouseFixtureMigration_3_to_5() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.HouseFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap())
+ .put("name", "Family Doe")
+ .putList("rooms", BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.RoomFixture")
+ .put("_typeVersion", CharlieFixture_3.VERSION.unwrap())
+ .put("name", "Kitchen")
+ .put("areaInSquareFoot", 10764)
+ .build())
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.HouseFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap())
+ .put("name", "Family Doe")
+ .putList("rooms", BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.RoomFixture")
+ .put("_typeVersion", CharlieFixture_5.VERSION.unwrap()) // updated version
+ .put("name", "Kitchen")
+ .put("displayText", "Family Doe: Kitchen (1000m2)") // display text was calculated based on areaInSquareMeter and name of house and room
+ .put("areaInSquareMeter", 1000) // areaInSquareFoot -> areaInSquareMeter
+ .build())
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ /**
+ * Uses data object {@link PersonFixtureDo} and migration handlers {@link PersonFixtureDoStructureMigrationHandler_2}
+ * with {@link PersonFixtureTargetContextData}.
+ * <p>
+ * {@link PersonFixtureTargetContextData} contains additional assertions used to verify correct order of migration vs.
+ * context data initialization.
+ */
+ @Test
+ public void testStackedLocalContextData() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_1.VERSION.unwrap())
+ .put("name", "John Doe")
+ .putList("children", BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_1.VERSION.unwrap())
+ .put("name", "John Doe Junior")
+ .putList("children", BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_1.VERSION.unwrap())
+ .put("name", "John Doe Junior 2. Gen")
+ .build())
+ .build())
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap()) // updated version
+ .put("name", "John Doe")
+ .put("relation", "(none)") // add relation
+ .putList("children", BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap())// updated version
+ .put("name", "John Doe Junior")
+ .put("relation", "Child of John Doe") // added relation
+ .putList("children", BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap())// updated version
+ .put("name", "John Doe Junior 2. Gen")
+ .put("relation", "Child of John Doe Junior") // added relation
+ .build())
+ .build())
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+
+ /**
+ * Uses data object {@link PersonFixtureDo} and migration handlers {@link PersonFixtureDoStructureMigrationHandler_2}
+ * with {@link PersonFixtureTargetContextData}.
+ */
+ @Test
+ public void testListManipulationViaMigrationHandler() {
+ IDoEntity actual = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_1.VERSION.unwrap())
+ .put("name", "example")
+ .build();
+
+ assertTrue(s_migrator.migrateDataObject(s_migrationContext, actual));
+
+ IDoEntity expected = BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap()) // updated version
+ .put("name", "example")
+ .put("relation", "(none)") // added relation
+ .putList("children", BEANS.get(DoEntityBuilder.class) // added child
+ .put("_type", "charlieFixture.PersonFixture")
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap())
+ .put("name", "Jane Doe")
+ .put("relation", "(undefined)")
+ .build())
+ .build();
+
+ assertEqualsWithComparisonFailure(expected, actual);
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureDo.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureDo.java
new file mode 100644
index 0000000000..1996b32d6c
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureDo.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import javax.annotation.Generated;
+
+import org.eclipse.scout.rt.dataobject.DoValue;
+import org.eclipse.scout.rt.dataobject.TypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureNamespace;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureNamespace;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_3;
+import org.eclipse.scout.rt.platform.Replace;
+
+/**
+ * {@link CustomerFixtureDo} from {@link AlfaFixtureNamespace} is replaced in {@link CharlieFixtureNamespace}.
+ * <p>
+ * If a data object having a {@link TypeVersion} annotation is replaced, an own {@link TypeVersion} annotation should be
+ * applied due to different data object structure. Migration handlers from origin namespace will not trigger anymore on
+ * this data object, thus replacing data objects having a type version is not recommended (use contributions instead).
+ * <p>
+ * Type name renames unchanged.
+ * <p>
+ * Migration handler to switch namespace is not present (assuming that data object were only persisted when subclassed
+ * bean was already used).
+ * <p>
+ * Changes in charlie namespace:
+ * <ul>
+ * <li>charlieFixture-3: using lowercase first name</li>
+ * </ul>
+ *
+ * @since charlieFixture-2
+ */
+@TypeVersion(CharlieFixture_3.class)
+@Replace
+public class CharlieCustomerFixtureDo extends CustomerFixtureDo {
+
+ public DoValue<String> emailAddress() {
+ return doValue("emailAddress");
+ }
+
+ /* **************************************************************************
+ * GENERATED CONVENIENCE METHODS
+ * *************************************************************************/
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public CharlieCustomerFixtureDo withEmailAddress(String emailAddress) {
+ emailAddress().set(emailAddress);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getEmailAddress() {
+ return emailAddress().get();
+ }
+
+ @Override
+ @Generated("DoConvenienceMethodsGenerator")
+ public CharlieCustomerFixtureDo withFirstName(String firstName) {
+ firstName().set(firstName);
+ return this;
+ }
+
+ @Override
+ @Generated("DoConvenienceMethodsGenerator")
+ public CharlieCustomerFixtureDo withLastName(String lastName) {
+ lastName().set(lastName);
+ return this;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureMigrationHandler_3.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureMigrationHandler_3.java
new file mode 100644
index 0000000000..b712acd54a
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureMigrationHandler_3.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_3;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+import org.eclipse.scout.rt.platform.util.ObjectUtility;
+import org.eclipse.scout.rt.platform.util.StringUtility;
+
+@IgnoreBean
+public class CharlieCustomerFixtureMigrationHandler_3 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return CharlieFixture_3.class;
+ }
+
+ /**
+ * References {@link CustomerFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("alfaFixture.CustomerFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (!doEntity.has("firstName")) {
+ return false; // nothing to migrate
+ }
+
+ String oldName = doEntity.getString("firstName");
+ String newName = StringUtility.lowercase(oldName); // CustomerFixtureMigrationHandler_3 uses uppercase
+
+ if (ObjectUtility.notEquals(oldName, newName)) {
+ doEntity.put("firstName", newName);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureTargetContextData.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureTargetContextData.java
new file mode 100644
index 0000000000..af8b27eba4
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CharlieCustomerFixtureTargetContextData.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContextDataTarget;
+import org.eclipse.scout.rt.dataobject.migration.IDoStructureMigrationTargetContextData;
+
+@DoStructureMigrationContextDataTarget(doEntityClasses = {CharlieCustomerFixtureDo.class})
+public class CharlieCustomerFixtureTargetContextData implements IDoStructureMigrationTargetContextData {
+
+ private String m_emailAddress;
+
+ public String getEmailAddress() {
+ return m_emailAddress;
+ }
+
+ @Override
+ public boolean initialize(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (doEntity instanceof CharlieCustomerFixtureDo) {
+ CharlieCustomerFixtureDo house = (CharlieCustomerFixtureDo) doEntity;
+ m_emailAddress = house.getEmailAddress();
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureDo.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureDo.java
new file mode 100644
index 0000000000..a7ce8b4fa2
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureDo.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import javax.annotation.Generated;
+
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.DoValue;
+import org.eclipse.scout.rt.dataobject.TypeName;
+import org.eclipse.scout.rt.dataobject.TypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+
+/**
+ * Changes:
+ * <ul>
+ * <li>alfaFixture-3: using uppercase first name</li>
+ * </ul>
+ *
+ * @since alfaFixture-1
+ */
+@TypeName("alfaFixture.CustomerFixture")
+@TypeVersion(AlfaFixture_3.class)
+public class CustomerFixtureDo extends DoEntity {
+
+ public DoValue<String> firstName() {
+ return doValue("firstName");
+ }
+
+ public DoValue<String> lastName() {
+ return doValue("lastName");
+ }
+
+ /* **************************************************************************
+ * GENERATED CONVENIENCE METHODS
+ * *************************************************************************/
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public CustomerFixtureDo withFirstName(String firstName) {
+ firstName().set(firstName);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getFirstName() {
+ return firstName().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public CustomerFixtureDo withLastName(String lastName) {
+ lastName().set(lastName);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getLastName() {
+ return lastName().get();
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureMigrationHandler_3.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureMigrationHandler_3.java
new file mode 100644
index 0000000000..a04df30336
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureMigrationHandler_3.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+import org.eclipse.scout.rt.platform.util.ObjectUtility;
+import org.eclipse.scout.rt.platform.util.StringUtility;
+
+@IgnoreBean
+public class CustomerFixtureMigrationHandler_3 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return AlfaFixture_3.class;
+ }
+
+ /**
+ * References {@link CustomerFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("alfaFixture.CustomerFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (!doEntity.has("firstName")) {
+ return false; // nothing to migrate
+ }
+
+ String oldName = doEntity.getString("firstName");
+ String newName = StringUtility.uppercase(oldName); // CharlieCustomerFixtureMigrationHandler_3 uses lowercase
+
+ if (ObjectUtility.notEquals(oldName, newName)) {
+ doEntity.put("firstName", newName);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureTargetContextData.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureTargetContextData.java
new file mode 100644
index 0000000000..830afbd884
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/CustomerFixtureTargetContextData.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContextDataTarget;
+import org.eclipse.scout.rt.dataobject.migration.IDoStructureMigrationTargetContextData;
+
+@DoStructureMigrationContextDataTarget(doEntityClasses = {CustomerFixtureDo.class})
+public class CustomerFixtureTargetContextData implements IDoStructureMigrationTargetContextData {
+
+ private String m_firstName;
+
+ public String getFirstName() {
+ return m_firstName;
+ }
+
+ @Override
+ public boolean initialize(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (doEntity instanceof CustomerFixtureDo) {
+ CustomerFixtureDo house = (CustomerFixtureDo) doEntity;
+ m_firstName = house.getFirstName();
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureDo.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureDo.java
new file mode 100644
index 0000000000..e031e9d7e9
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureDo.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Generated;
+
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.DoList;
+import org.eclipse.scout.rt.dataobject.DoValue;
+import org.eclipse.scout.rt.dataobject.TypeName;
+import org.eclipse.scout.rt.dataobject.TypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+
+/**
+ * Change history:
+ * <ul>
+ * <li>charlieFixture-2: charlieFixture.BuildingFixture -> charlieFixture.HouseFixture, postalAddress and owner
+ * attributes were added</li>
+ * </ul>
+ *
+ * @since charlieFixture-1
+ */
+@TypeName("charlieFixture.HouseFixture")
+@TypeVersion(CharlieFixture_2.class)
+public class HouseFixtureDo extends DoEntity {
+
+ public DoValue<String> name() {
+ return doValue("name");
+ }
+
+ public DoValue<PostalAddressFixtureDo> postalAddress() {
+ return doValue("postalAddress");
+ }
+
+ public DoValue<CustomerFixtureDo> owner() {
+ return doValue("owner");
+ }
+
+ public DoList<RoomFixtureDo> rooms() {
+ return doList("rooms");
+ }
+
+ /* **************************************************************************
+ * GENERATED CONVENIENCE METHODS
+ * *************************************************************************/
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public HouseFixtureDo withName(String name) {
+ name().set(name);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getName() {
+ return name().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public HouseFixtureDo withPostalAddress(PostalAddressFixtureDo postalAddress) {
+ postalAddress().set(postalAddress);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PostalAddressFixtureDo getPostalAddress() {
+ return postalAddress().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public HouseFixtureDo withOwner(CustomerFixtureDo owner) {
+ owner().set(owner);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public CustomerFixtureDo getOwner() {
+ return owner().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public HouseFixtureDo withRooms(Collection<? extends RoomFixtureDo> rooms) {
+ rooms().updateAll(rooms);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public HouseFixtureDo withRooms(RoomFixtureDo... rooms) {
+ rooms().updateAll(rooms);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public List<RoomFixtureDo> getRooms() {
+ return rooms().get();
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureDoStructureMigrationHandler_2.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureDoStructureMigrationHandler_2.java
new file mode 100644
index 0000000000..5889cb2119
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureDoStructureMigrationHandler_2.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Map;
+
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureRenameMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+
+/**
+ * Rename `charlieFixture.BuildingFixture` to `charlieFixture.HouseFixture`.
+ */
+@IgnoreBean
+public class HouseFixtureDoStructureMigrationHandler_2 extends AbstractDoStructureRenameMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return CharlieFixture_2.class;
+ }
+
+ @Override
+ protected void initTypeNameTranslations(Map<String, String> typeNameTranslations) {
+ typeNameTranslations.put("charlieFixture.BuildingFixture", "charlieFixture.HouseFixture");
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureRawOnlyDoStructureMigrationTargetContextData.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureRawOnlyDoStructureMigrationTargetContextData.java
new file mode 100644
index 0000000000..8df83ff6ba
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureRawOnlyDoStructureMigrationTargetContextData.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContextDataTarget;
+import org.eclipse.scout.rt.dataobject.migration.IDoStructureMigrationTargetContextData;
+
+@DoStructureMigrationContextDataTarget(typeNames = {"charlieFixture.HouseFixture"})
+public class HouseFixtureRawOnlyDoStructureMigrationTargetContextData implements IDoStructureMigrationTargetContextData {
+
+ private String m_name;
+
+ public String getName() {
+ return m_name;
+ }
+
+ @Override
+ public boolean initialize(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ m_name = doEntity.getString("name");
+ return true;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureStructureMigrationTargetContextData.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureStructureMigrationTargetContextData.java
new file mode 100644
index 0000000000..084a8d37de
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureStructureMigrationTargetContextData.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContextDataTarget;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationHelper;
+import org.eclipse.scout.rt.dataobject.migration.IDoStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.platform.BEANS;
+
+@DoStructureMigrationContextDataTarget(doEntityClasses = {HouseFixtureDo.class}, typeNames = {"charlieFixture.HouseFixture"})
+public class HouseFixtureStructureMigrationTargetContextData implements IDoStructureMigrationTargetContextData {
+
+ private String m_name;
+ private int m_numberOfRooms;
+
+ public String getName() {
+ return m_name;
+ }
+
+ public int getNumberOfRooms() {
+ return m_numberOfRooms;
+ }
+
+ @Override
+ public boolean initialize(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (doEntity instanceof HouseFixtureDo) {
+ HouseFixtureDo house = (HouseFixtureDo) doEntity;
+ m_name = house.getName();
+ m_numberOfRooms = house.rooms().exists() ? house.getRooms().size() : 0;
+ return true;
+ }
+
+ DoStructureMigrationHelper helper = BEANS.get(DoStructureMigrationHelper.class);
+ String typeName = helper.getType(doEntity);
+ if ("charlieFixture.HouseFixture".equals(typeName)) {
+ m_name = doEntity.getString("name");
+ m_numberOfRooms = doEntity.has("rooms") ? doEntity.getList("rooms").size() : 0;
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureTypedOnlyDoStructureMigrationTargetContextData.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureTypedOnlyDoStructureMigrationTargetContextData.java
new file mode 100644
index 0000000000..922b109443
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/HouseFixtureTypedOnlyDoStructureMigrationTargetContextData.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContextDataTarget;
+import org.eclipse.scout.rt.dataobject.migration.IDoStructureMigrationTargetContextData;
+
+@DoStructureMigrationContextDataTarget(doEntityClasses = {HouseFixtureDo.class})
+public class HouseFixtureTypedOnlyDoStructureMigrationTargetContextData implements IDoStructureMigrationTargetContextData {
+
+ private String m_name;
+
+ public String getName() {
+ return m_name;
+ }
+
+ @Override
+ public boolean initialize(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (doEntity instanceof HouseFixtureDo) {
+ HouseFixtureDo house = (HouseFixtureDo) doEntity;
+ m_name = house.getName();
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureDo.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureDo.java
new file mode 100644
index 0000000000..3a268160da
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureDo.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Generated;
+
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.DoList;
+import org.eclipse.scout.rt.dataobject.DoValue;
+import org.eclipse.scout.rt.dataobject.TypeName;
+import org.eclipse.scout.rt.dataobject.TypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+
+/**
+ * Changes:
+ * <ul>
+ * <li>charlieFixture-2: added relation attribute
+ * </ul>
+ */
+@TypeName("charlieFixture.PersonFixture")
+@TypeVersion(CharlieFixture_2.class)
+public class PersonFixtureDo extends DoEntity {
+
+ public DoValue<String> name() {
+ return doValue("name");
+ }
+
+ public DoValue<String> relation() {
+ return doValue("relation");
+ }
+
+ public DoList<PersonFixtureDo> children() {
+ return doList("children");
+ }
+
+ /* **************************************************************************
+ * GENERATED CONVENIENCE METHODS
+ * *************************************************************************/
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PersonFixtureDo withName(String name) {
+ name().set(name);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getName() {
+ return name().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PersonFixtureDo withChildren(Collection<? extends PersonFixtureDo> children) {
+ children().updateAll(children);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PersonFixtureDo withChildren(PersonFixtureDo... children) {
+ children().updateAll(children);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public List<PersonFixtureDo> getChildren() {
+ return children().get();
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureDoStructureMigrationHandler_2.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureDoStructureMigrationHandler_2.java
new file mode 100644
index 0000000000..a4cad0444f
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureDoStructureMigrationHandler_2.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.DoEntityBuilder;
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+/**
+ * Add relation and if name is "example", create an example child entry.
+ */
+@IgnoreBean
+public class PersonFixtureDoStructureMigrationHandler_2 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return CharlieFixture_2.class;
+ }
+
+ /**
+ * References {@link PersonFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("charlieFixture.PersonFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (doEntity.has("relation")) {
+ return false; // already migrated
+ }
+
+ PersonFixtureTargetContextData personContextData = ctx.get(PersonFixtureTargetContextData.class);
+ String relation = personContextData == null ? "(none)" : "Child of " + personContextData.getName();
+ doEntity.put("relation", relation);
+
+ if ("example".equals(doEntity.getString("name"))) {
+ List<IDoEntity> children = doEntity.getList("children", IDoEntity.class);
+ children.add(BEANS.get(DoEntityBuilder.class)
+ .put("_type", "charlieFixture.PersonFixture")
+ // when no type version is added or charlieFixture-1 is used, this migration handler would be called for the added child too
+ .put("_typeVersion", CharlieFixture_2.VERSION.unwrap())
+ .put("name", "Jane Doe")
+ .put("relation", "(undefined)")
+ .build());
+ }
+
+ return true;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureTargetContextData.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureTargetContextData.java
new file mode 100644
index 0000000000..dca3f08cc8
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PersonFixtureTargetContextData.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import static org.eclipse.scout.rt.platform.util.Assertions.*;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContextDataTarget;
+import org.eclipse.scout.rt.dataobject.migration.IDoStructureMigrationTargetContextData;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+
+@DoStructureMigrationContextDataTarget(typeNames = {"charlieFixture.PersonFixture"})
+public class PersonFixtureTargetContextData implements IDoStructureMigrationTargetContextData {
+
+ private String m_name;
+
+ public String getName() {
+ return m_name;
+ }
+
+ @Override
+ public boolean initialize(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ // There are no migrations for charlieFixture-1, thus this context may only be initialized for >= charlieFixture-2.
+ // A context data must only be initialized with an already migrated data object for a certain version.
+ // The data object itself must be migrated as well as the correct type version must be set (in case type version switches are used within context)
+ assertNotNull(doEntity.getString("relation")); // created by PersonFixtureDoStructureMigrationHandler_2
+ assertEquals(CharlieFixture_2.VERSION, NamespaceVersion.of(doEntity.getString("_typeVersion"))); // version must be updated before context data is initialized
+
+ m_name = doEntity.getString("name");
+ return true;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3.java
new file mode 100644
index 0000000000..70dee78875
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationHelper;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_3;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+/**
+ * For a type version and type name only one migration handler can exists, thus renaming namespace of type name and
+ * adding new attribut in same migration handler.
+ */
+@IgnoreBean
+public class PetFixtureAlfaNamespaceFamilyFriendlyMigrationHandler_3 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return BravoFixture_3.class;
+ }
+
+ protected NamespaceVersion typeVersionToUpdate() {
+ return AlfaFixture_3.VERSION;
+ }
+
+ /**
+ * References {@link PetFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("bravoFixture.PetFixture"); // references the old type name (is renamed to alfaFixture.PetFixture in PetFixtureAlfaNamespaceMigrationHandler_3)
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ boolean changed = false;
+ changed |= BEANS.get(DoStructureMigrationHelper.class).renameTypeName(doEntity, "alfaFixture.PetFixture");
+
+ if (doEntity.has("familyFriendly")) {
+ return changed; // already migrated
+ }
+
+ doEntity.put("familyFriendly", true);
+ return true;
+ }
+
+ @Override
+ protected boolean updateTypeVersion(IDoEntity doEntity) {
+ return BEANS.get(DoStructureMigrationHelper.class).updateTypeVersion(doEntity, typeVersionToUpdate());
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureCaseSensitiveNameMigrationHandler_2.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureCaseSensitiveNameMigrationHandler_2.java
new file mode 100644
index 0000000000..6ff5ef5d6a
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureCaseSensitiveNameMigrationHandler_2.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_2;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+import org.eclipse.scout.rt.platform.util.ObjectUtility;
+import org.eclipse.scout.rt.platform.util.StringUtility;
+
+@IgnoreBean
+public class PetFixtureCaseSensitiveNameMigrationHandler_2 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return BravoFixture_2.class;
+ }
+
+ /**
+ * References {@link PetFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("bravoFixture.PetFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (!doEntity.has("name")) {
+ return false; // nothing to migrate
+ }
+
+ String oldName = doEntity.getString("name");
+ String newName = StringUtility.uppercaseFirst(StringUtility.lowercase(oldName));
+
+ if (ObjectUtility.notEquals(oldName, newName)) {
+ doEntity.put("name", newName);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureDo.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureDo.java
new file mode 100644
index 0000000000..12656c3aa7
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureDo.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import javax.annotation.Generated;
+
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.DoValue;
+import org.eclipse.scout.rt.dataobject.TypeName;
+import org.eclipse.scout.rt.dataobject.TypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+
+/**
+ * Introduced in namespace bravo with type version bravo-1, attribute name. Changes:
+ * <ul>
+ * <li>bravo-2: transform name to first letter uppercase</li>
+ * <li>alfa-3: changed to alfa namespace (type name and type version) and added new attribute familyFriendly default
+ * true</li>
+ * </ul>
+ */
+@TypeName("alfaFixture.PetFixture")
+@TypeVersion(AlfaFixture_3.class)
+public class PetFixtureDo extends DoEntity {
+
+ public DoValue<String> name() {
+ return doValue("name");
+ }
+
+ public DoValue<Boolean> familyFriendly() {
+ return doValue("familyFriendly");
+ }
+
+ /* **************************************************************************
+ * GENERATED CONVENIENCE METHODS
+ * *************************************************************************/
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PetFixtureDo withName(String name) {
+ name().set(name);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getName() {
+ return name().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PetFixtureDo withFamilyFriendly(Boolean familyFriendly) {
+ familyFriendly().set(familyFriendly);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public Boolean isFamilyFriendly() {
+ return familyFriendly().get();
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureFamilyFriendlyMigrationHandlerInvalidTypeVersionToUpdate_3.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureFamilyFriendlyMigrationHandlerInvalidTypeVersionToUpdate_3.java
new file mode 100644
index 0000000000..d296d7b3d9
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PetFixtureFamilyFriendlyMigrationHandlerInvalidTypeVersionToUpdate_3.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationHelper;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationInventoryTest;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_3;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+/**
+ * A second migration handler for type version {@link BravoFixture_3} for the type name 'bravoFixture.PetFixture', only
+ * used within {@link DoStructureMigrationInventoryTest#testValidateMigrationHandlerUniqueness()}.
+ */
+@IgnoreBean
+public class PetFixtureFamilyFriendlyMigrationHandlerInvalidTypeVersionToUpdate_3 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return BravoFixture_3.class;
+ }
+
+ protected NamespaceVersion typeVersionToUpdate() {
+ return AlfaFixture_3.VERSION;
+ }
+
+ /**
+ * References {@link PetFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("bravoFixture.PetFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (doEntity.has("lorem")) {
+ return false; // already migrated
+ }
+
+ doEntity.put("lorem", "ipsum");
+ return true;
+ }
+
+ @Override
+ protected boolean updateTypeVersion(IDoEntity doEntity) {
+ return BEANS.get(DoStructureMigrationHelper.class).updateTypeVersion(doEntity, typeVersionToUpdate());
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PostalAddressFixtureDo.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PostalAddressFixtureDo.java
new file mode 100644
index 0000000000..48a9d204ea
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PostalAddressFixtureDo.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import javax.annotation.Generated;
+
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.DoValue;
+import org.eclipse.scout.rt.dataobject.TypeName;
+import org.eclipse.scout.rt.dataobject.TypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+
+/**
+ * No changes.
+ *
+ * @since charlieFixture-2 but introduced and persisted without a type version first.
+ */
+@TypeName("charlieFixture.PostalAddressFixture")
+@TypeVersion(CharlieFixture_2.class)
+public class PostalAddressFixtureDo extends DoEntity {
+
+ public DoValue<String> street() {
+ return doValue("street");
+ }
+
+ public DoValue<String> zipCode() {
+ return doValue("zipCode");
+ }
+
+ public DoValue<String> city() {
+ return doValue("city");
+ }
+
+ /* **************************************************************************
+ * GENERATED CONVENIENCE METHODS
+ * *************************************************************************/
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PostalAddressFixtureDo withStreet(String street) {
+ street().set(street);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getStreet() {
+ return street().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PostalAddressFixtureDo withZipCode(String zipCode) {
+ zipCode().set(zipCode);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getZipCode() {
+ return zipCode().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public PostalAddressFixtureDo withCity(String city) {
+ city().set(city);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getCity() {
+ return city().get();
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2.java
new file mode 100644
index 0000000000..8ba60bc634
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+@IgnoreBean
+public class PostalAddressFixtureUpdateVersionOnlyMigrationHandler_2 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return CharlieFixture_2.class;
+ }
+
+ /**
+ * References {@link PetFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("charlieFixture.PostalAddressFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ return false; // only update version, no real migration
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDo.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDo.java
new file mode 100644
index 0000000000..efb3fb63ff
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDo.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import javax.annotation.Generated;
+
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.DoValue;
+import org.eclipse.scout.rt.dataobject.TypeName;
+import org.eclipse.scout.rt.dataobject.TypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_5;
+
+/**
+ * Change history:
+ * <ul>
+ * <li>charlieFixture-2: roomName -> name</li>
+ * <li>charlieFixture-3: added areaInSquareFoot</li> (would not need an update of type version if no real migration is
+ * required)
+ * <li>charlieFixture-4: changed areaInSquareFoot to areaInSquareMeter</li>
+ * <li>charlieFixture-5: added displayText</li>
+ * </ul>
+ *
+ * @since charlieFixture-1
+ */
+@TypeName("charlieFixture.RoomFixture")
+@TypeVersion(CharlieFixture_5.class)
+public class RoomFixtureDo extends DoEntity {
+
+ public DoValue<String> name() {
+ return doValue("name");
+ }
+
+ public DoValue<String> displayText() {
+ return doValue("displayText");
+ }
+
+ public DoValue<Integer> areaInSquareMeter() {
+ return doValue("areaInSquareMeter");
+ }
+
+ /* **************************************************************************
+ * GENERATED CONVENIENCE METHODS
+ * *************************************************************************/
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public RoomFixtureDo withName(String name) {
+ name().set(name);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getName() {
+ return name().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public RoomFixtureDo withDisplayText(String displayText) {
+ displayText().set(displayText);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public String getDisplayText() {
+ return displayText().get();
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public RoomFixtureDo withAreaInSquareMeter(Integer areaInSquareMeter) {
+ areaInSquareMeter().set(areaInSquareMeter);
+ return this;
+ }
+
+ @Generated("DoConvenienceMethodsGenerator")
+ public Integer getAreaInSquareMeter() {
+ return areaInSquareMeter().get();
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_2.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_2.java
new file mode 100644
index 0000000000..539d6cb71a
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_2.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Map;
+
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureRenameMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_2;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+import org.eclipse.scout.rt.platform.util.ImmutablePair;
+
+/**
+ * Changes `roomName` to `name`.
+ */
+@IgnoreBean
+public class RoomFixtureDoStructureMigrationHandler_2 extends AbstractDoStructureRenameMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return CharlieFixture_2.class;
+ }
+
+ /**
+ * References {@link RoomFixtureDo}.
+ */
+ @Override
+ protected void initAttributeNameTranslations(Map<String, Map<String, String>> attributNameTranslations) {
+ attributNameTranslations.put("charlieFixture.RoomFixture", CollectionUtility.hashMap(new ImmutablePair<>("roomName", "name")));
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_3.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_3.java
new file mode 100644
index 0000000000..6020288d71
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_3.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_3;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+@IgnoreBean
+public class RoomFixtureDoStructureMigrationHandler_3 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return CharlieFixture_3.class;
+ }
+
+ /**
+ * References {@link RoomFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("charlieFixture.RoomFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ return false; // type version update only, no real migration
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_4.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_4.java
new file mode 100644
index 0000000000..6182dd2bfa
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_4.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_4;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+@IgnoreBean
+public class RoomFixtureDoStructureMigrationHandler_4 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return CharlieFixture_4.class;
+ }
+
+ /**
+ * References {@link RoomFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("charlieFixture.RoomFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (!doEntity.has("areaInSquareFoot")) {
+ // already migrated or nothing to migrate
+ return false;
+ }
+
+ Integer areaInSquareFoot = doEntity.get("areaInSquareFoot", Integer.class);
+ if (areaInSquareFoot != null) {
+ int areaInSquareMeter = Math.round(areaInSquareFoot.intValue() / 10.764f);
+ doEntity.put("areaInSquareMeter", areaInSquareMeter);
+ }
+ doEntity.remove("areaInSquareFoot");
+
+ return true;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_5.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_5.java
new file mode 100644
index 0000000000..53b8304d72
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/RoomFixtureDoStructureMigrationHandler_5.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.AbstractDoStructureMigrationHandler;
+import org.eclipse.scout.rt.dataobject.migration.DoStructureMigrationContext;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.CharlieFixtureTypeVersions.CharlieFixture_5;
+import org.eclipse.scout.rt.platform.IgnoreBean;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+import org.eclipse.scout.rt.platform.util.StringUtility;
+
+@IgnoreBean
+public class RoomFixtureDoStructureMigrationHandler_5 extends AbstractDoStructureMigrationHandler {
+
+ @Override
+ public Class<? extends ITypeVersion> toTypeVersionClass() {
+ return CharlieFixture_5.class;
+ }
+
+ /**
+ * References {@link RoomFixtureDo}.
+ */
+ @Override
+ public Set<String> getTypeNames() {
+ return CollectionUtility.hashSet("charlieFixture.RoomFixture");
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ if (doEntity.has("displayText")) {
+ // already migrated
+ return false;
+ }
+
+ // Default display text format: [house name]: [room name] ([area in square meter]m2)
+ String roomName = doEntity.getString("name");
+ Integer areaInSquareMeter = doEntity.get("areaInSquareMeter", Integer.class);
+ if (areaInSquareMeter != null) {
+ roomName += " (" + areaInSquareMeter + "m2)";
+ }
+ String houseName = ctx.get(HouseFixtureStructureMigrationTargetContextData.class).getName();
+ String displayText = StringUtility.join(": ", houseName, roomName);
+
+ doEntity.put("displayText", displayText);
+
+ return true;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/ZipCodeFixtureGlobalContextData.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/ZipCodeFixtureGlobalContextData.java
new file mode 100644
index 0000000000..91af00dba3
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/house/ZipCodeFixtureGlobalContextData.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.house;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.scout.rt.dataobject.migration.IDoStructureMigrationGlobalContextData;
+
+// not a Bean, manually created
+public class ZipCodeFixtureGlobalContextData implements IDoStructureMigrationGlobalContextData {
+
+ private final ConcurrentHashMap<String, String> m_zipCodeToCityMap = new ConcurrentHashMap<>();
+
+ public ZipCodeFixtureGlobalContextData() {
+ m_zipCodeToCityMap.put("20001", "Washington");
+ m_zipCodeToCityMap.put("37201", "Nashville");
+ m_zipCodeToCityMap.put("02101", "Boston");
+ }
+
+ public String getCity(String zipCode) {
+ return m_zipCodeToCityMap.get(zipCode);
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/AlfaFixtureNamespace.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/AlfaFixtureNamespace.java
new file mode 100644
index 0000000000..7ab7089cb5
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/AlfaFixtureNamespace.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.version;
+
+import org.eclipse.scout.rt.platform.namespace.INamespace;
+
+public final class AlfaFixtureNamespace implements INamespace {
+
+ public static final String ID = "alfaFixture";
+ public static final double ORDER = 9000;
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public double getOrder() {
+ return ORDER;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/AlfaFixtureTypeVersions.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/AlfaFixtureTypeVersions.java
new file mode 100644
index 0000000000..af221a8100
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/AlfaFixtureTypeVersions.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.version;
+
+import org.eclipse.scout.rt.dataobject.AbstractTypeVersion;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+
+public final class AlfaFixtureTypeVersions {
+
+ public static final class AlfaFixture_1 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(AlfaFixtureNamespace.ID, "1");
+
+ public AlfaFixture_1() {
+ super(VERSION);
+ }
+ }
+
+ public static final class AlfaFixture_2 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(AlfaFixtureNamespace.ID, "2");
+
+ public AlfaFixture_2() {
+ super(VERSION);
+ }
+ }
+
+ public static final class AlfaFixture_3 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(AlfaFixtureNamespace.ID, "3");
+
+ public AlfaFixture_3() {
+ super(VERSION);
+ }
+ }
+
+ public static final class AlfaFixture_6 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(AlfaFixtureNamespace.ID, "6");
+
+ public AlfaFixture_6() {
+ super(VERSION);
+ }
+ }
+
+ public static final class AlfaFixture_7 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(AlfaFixtureNamespace.ID, "7");
+
+ public AlfaFixture_7() {
+ super(VERSION);
+ }
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/BravoFixtureNamespace.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/BravoFixtureNamespace.java
new file mode 100644
index 0000000000..305dd9f5a0
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/BravoFixtureNamespace.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.version;
+
+import org.eclipse.scout.rt.platform.namespace.INamespace;
+
+public final class BravoFixtureNamespace implements INamespace {
+
+ public static final String ID = "bravoFixture";
+ public static final double ORDER = 9100;
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public double getOrder() {
+ return ORDER;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/BravoFixtureTypeVersions.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/BravoFixtureTypeVersions.java
new file mode 100644
index 0000000000..b8ff4e7377
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/BravoFixtureTypeVersions.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.version;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.eclipse.scout.rt.dataobject.AbstractTypeVersion;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.AlfaFixtureTypeVersions.AlfaFixture_3;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+
+public final class BravoFixtureTypeVersions {
+
+ public static final class BravoFixture_1 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(BravoFixtureNamespace.ID, "1");
+
+ public BravoFixture_1() {
+ super(VERSION);
+ }
+
+ @Override
+ protected Collection<Class<? extends ITypeVersion>> getDependencyClasses() {
+ return Arrays.asList(AlfaFixture_1.class);
+ }
+ }
+
+ public static final class BravoFixture_2 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(BravoFixtureNamespace.ID, "2");
+
+ public BravoFixture_2() {
+ super(VERSION);
+ }
+
+ @Override
+ protected Collection<Class<? extends ITypeVersion>> getDependencyClasses() {
+ return Arrays.asList(AlfaFixture_2.class);
+ }
+ }
+
+ public static final class BravoFixture_3 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(BravoFixtureNamespace.ID, "3");
+
+ public BravoFixture_3() {
+ super(VERSION);
+ }
+
+ @Override
+ protected Collection<Class<? extends ITypeVersion>> getDependencyClasses() {
+ return Arrays.asList(AlfaFixture_3.class);
+ }
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/CharlieFixtureNamespace.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/CharlieFixtureNamespace.java
new file mode 100644
index 0000000000..dc61109ab0
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/CharlieFixtureNamespace.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.version;
+
+import org.eclipse.scout.rt.platform.namespace.INamespace;
+
+public final class CharlieFixtureNamespace implements INamespace {
+
+ public static final String ID = "charlieFixture";
+ public static final double ORDER = 9200;
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public double getOrder() {
+ return ORDER;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/CharlieFixtureTypeVersions.java b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/CharlieFixtureTypeVersions.java
new file mode 100644
index 0000000000..d1f2c0dce6
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject.test/src/test/java/org/eclipse/scout/rt/dataobject/migration/fixture/version/CharlieFixtureTypeVersions.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration.fixture.version;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.eclipse.scout.rt.dataobject.AbstractTypeVersion;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_1;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_2;
+import org.eclipse.scout.rt.dataobject.migration.fixture.version.BravoFixtureTypeVersions.BravoFixture_3;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+
+public final class CharlieFixtureTypeVersions {
+
+ public static final class CharlieFixture_1 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(CharlieFixtureNamespace.ID, "1");
+
+ public CharlieFixture_1() {
+ super(VERSION);
+ }
+
+ @Override
+ protected Collection<Class<? extends ITypeVersion>> getDependencyClasses() {
+ return Arrays.asList(BravoFixture_1.class);
+ }
+ }
+
+ public static final class CharlieFixture_2 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(CharlieFixtureNamespace.ID, "2");
+
+ public CharlieFixture_2() {
+ super(VERSION);
+ }
+
+ @Override
+ protected Collection<Class<? extends ITypeVersion>> getDependencyClasses() {
+ return Arrays.asList(BravoFixture_2.class);
+ }
+ }
+
+ public static final class CharlieFixture_3 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(CharlieFixtureNamespace.ID, "3");
+
+ public CharlieFixture_3() {
+ super(VERSION);
+ }
+
+ @Override
+ protected Collection<Class<? extends ITypeVersion>> getDependencyClasses() {
+ return Arrays.asList(BravoFixture_3.class);
+ }
+ }
+
+ public static final class CharlieFixture_4 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(CharlieFixtureNamespace.ID, "4");
+
+ public CharlieFixture_4() {
+ super(VERSION);
+ }
+ }
+
+ public static final class CharlieFixture_5 extends AbstractTypeVersion {
+
+ public static final NamespaceVersion VERSION = NamespaceVersion.of(CharlieFixtureNamespace.ID, "5");
+
+ public CharlieFixture_5() {
+ super(VERSION);
+ }
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/DataObjectInventory.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/DataObjectInventory.java
index ab73540694..3445f9bb9f 100644
--- a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/DataObjectInventory.java
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/DataObjectInventory.java
@@ -179,6 +179,24 @@ public class DataObjectInventory {
.collect(Collectors.joining("\n"));
assertTrue(StringUtility.isNullOrEmpty(typeVersionsWithoutNamespaceVersion), "Missing namespace version for implementors of {}:\n{}", ITypeVersion.class.getName(), typeVersionsWithoutNamespaceVersion);
+
+ String typeVersionsWithUnknownNamespace = BEANS.all(ITypeVersion.class).stream()
+ .filter(typeVersion -> Namespaces.get().byId(typeVersion.getVersion().getNamespace()) == null)
+ .map(ITypeVersion::getClass)
+ .map(Class::getName)
+ .collect(Collectors.joining("\n"));
+
+ assertTrue(StringUtility.isNullOrEmpty(typeVersionsWithUnknownNamespace), "No registered namespaces found for type versions:\n{}", typeVersionsWithUnknownNamespace);
+
+ String duplicateTypeVersions = BEANS.all(ITypeVersion.class).stream()
+ .collect(Collectors.groupingBy(ITypeVersion::getVersion))
+ .entrySet()
+ .stream()
+ .filter(entry -> entry.getValue().size() > 1)
+ .map(entry -> entry.getKey().unwrap() + ": " + entry.getValue().stream().map(typeVersion -> typeVersion.getClass().getName()).collect(Collectors.joining(", ")))
+ .collect(Collectors.joining("\n"));
+
+ assertTrue(StringUtility.isNullOrEmpty(duplicateTypeVersions), "Multiple type version classes for the same namespace version detected:\n{}", duplicateTypeVersions);
}
protected void validateTypeVersionRequired() {
@@ -231,7 +249,6 @@ public class DataObjectInventory {
NamespaceVersion version = typeVersion.getVersion(); // never null, validated in #validateTypeVersionImplementors
NamespaceVersion registeredVersion = m_classToTypeVersion.put(entityClass, version);
assertNull(registeredVersion, "{} was already registered with type version {}, register each class only once.", clazz, registeredVersion, TypeVersion.class.getSimpleName());
- assertNotNull(Namespaces.get().byId(version.getNamespace()), "No registered namespace found for type version '{}' of data object '{}'.", version.unwrap(), clazz);
LOG.debug("Registered class {} with type version '{}'", entityClass, version);
}
else {
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/enumeration/EnumInventory.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/enumeration/EnumInventory.java
index 394a10feb2..df5c064dbf 100644
--- a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/enumeration/EnumInventory.java
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/enumeration/EnumInventory.java
@@ -16,7 +16,6 @@ import java.util.Map;
import javax.annotation.PostConstruct;
-import org.eclipse.scout.rt.dataobject.DataObjectInventory;
import org.eclipse.scout.rt.platform.ApplicationScoped;
import org.eclipse.scout.rt.platform.inventory.ClassInventory;
import org.eclipse.scout.rt.platform.inventory.IClassInfo;
@@ -31,7 +30,7 @@ import org.slf4j.LoggerFactory;
@ApplicationScoped
public class EnumInventory {
- private static final Logger LOG = LoggerFactory.getLogger(DataObjectInventory.class);
+ private static final Logger LOG = LoggerFactory.getLogger(EnumInventory.class);
/**
* <b>NOTE:</b> These from/to class maps contains the static, compile-time "class" to "enum name" mapping as defined
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureMigrationHandler.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureMigrationHandler.java
new file mode 100644
index 0000000000..99892bc26a
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureMigrationHandler.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+
+/**
+ * Abstract implementation of a {@link IDoStructureMigrationHandler} supporting {@link Class} of {@link ITypeVersion}
+ * instead of {@link NamespaceVersion} and auto-update of type version to {@link #toTypeVersion()}.
+ */
+public abstract class AbstractDoStructureMigrationHandler implements IDoStructureMigrationHandler {
+
+ private final NamespaceVersion m_toTypeVersion;
+
+ protected AbstractDoStructureMigrationHandler() {
+ m_toTypeVersion = BEANS.get(toTypeVersionClass()).getVersion();
+ }
+
+ @Override
+ public NamespaceVersion toTypeVersion() {
+ return m_toTypeVersion;
+ }
+
+ public abstract Class<? extends ITypeVersion> toTypeVersionClass();
+
+ @Override
+ public boolean applyMigration(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ boolean changed = false;
+ changed |= migrate(ctx, doEntity);
+ changed |= updateTypeVersion(doEntity); // new type version needs to be applied because migration is only completed when data object has an updated type version
+ return changed;
+ }
+
+ /**
+ * For convience. Same as {@link #applyMigration(DoStructureMigrationContext, IDoEntity)} except that the type version
+ * of the data object itself must not be updated, the caller of this method takes care of handling type version
+ * updates.
+ * <p>
+ * If special type version update handling is required, override {@link #updateTypeVersion(IDoEntity)}
+ *
+ * @return <code>true</code> if data object was changed in any way, <code>false</code> otherwise.
+ */
+ protected abstract boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity);
+
+ /**
+ * Updates the type version of the data object to {@link #toTypeVersion()}.
+ *
+ * @return <code>true</code> if the new type version was set, <code>false</code> if the type version was already
+ * up-to-date (equal).
+ */
+ protected boolean updateTypeVersion(IDoEntity doEntity) {
+ return BEANS.get(DoStructureMigrationHelper.class).updateTypeVersion(doEntity, toTypeVersion());
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureRenameMigrationHandler.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureRenameMigrationHandler.java
new file mode 100644
index 0000000000..891222f77f
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/AbstractDoStructureRenameMigrationHandler.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.platform.BEANS;
+
+/**
+ * Abstract implementation of a {@link IDoStructureMigrationHandler} supporting simple definition of type name and
+ * attribute name renamings.
+ */
+public abstract class AbstractDoStructureRenameMigrationHandler extends AbstractDoStructureMigrationHandler {
+
+ protected final Map<String, String> m_typeNameTranslations = new HashMap<>();
+ protected final Map<String, Map<String, String>> m_attributNameTranslations = new HashMap<>();
+
+ protected AbstractDoStructureRenameMigrationHandler() {
+ initTypeNameTranslations(m_typeNameTranslations);
+ initAttributeNameTranslations(m_attributNameTranslations);
+ }
+
+ /**
+ * Add type name translations.
+ * <p>
+ * Example renames the data object with type name "example.Lorem" to "example.Ipsum".
+ *
+ * <pre>
+ * typeNameTranslations.put("example.Lorem", "example.Ipsum");
+ * </pre>
+ */
+ protected void initTypeNameTranslations(Map<String, String> typeNameTranslations) {
+ }
+
+ /**
+ * Add attribute name translations.
+ * <p>
+ * Example renames the attribute "ipsum" to "dolor" in the data object with type name "example.Lorem" (new type name
+ * if renamed in {@link #initTypeNameTranslations(Map)}).
+ *
+ * <pre>
+ * attributNameTranslations.put("example.Lorem", CollectionUtility.hashMap(new ImmutablePair<>("ipsum", "dolor")));
+ * </pre>
+ */
+ protected void initAttributeNameTranslations(Map<String, Map<String, String>> attributNameTranslations) {
+ }
+
+ @Override
+ public Set<String> getTypeNames() {
+ Set<String> typeNames = new HashSet<>();
+ typeNames.addAll(m_typeNameTranslations.keySet());
+ typeNames.addAll(m_attributNameTranslations.keySet());
+ return typeNames;
+ }
+
+ @Override
+ protected boolean migrate(DoStructureMigrationContext ctx, IDoEntity doEntity) {
+ DoStructureMigrationHelper helper = BEANS.get(DoStructureMigrationHelper.class);
+ boolean changed = false;
+ String typeName = helper.getType(doEntity);
+ if (m_typeNameTranslations.containsKey(typeName)) {
+ typeName = m_typeNameTranslations.get(typeName);
+ helper.setType(doEntity, typeName);
+ changed = true;
+ }
+
+ if (m_attributNameTranslations.containsKey(typeName)) {
+ Map<String, String> attributeTranslations = m_attributNameTranslations.get(typeName);
+ for (Entry<String, String> entry : attributeTranslations.entrySet()) {
+ String name = entry.getKey();
+ String newName = entry.getValue();
+ changed |= helper.renameAttribute(doEntity, name, newName);
+ }
+ }
+
+ return changed;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContext.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContext.java
new file mode 100644
index 0000000000..6600337126
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContext.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.eclipse.scout.rt.platform.util.Assertions.*;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.Bean;
+
+/**
+ * This class represents the context used during migration of data objects.
+ * <p>
+ * Methods related with global context data are thread-safe ({@link #putGlobal(IDoStructureMigrationGlobalContextData)}
+ * and {@link #getGlobal(Class)}). Methods related with local context data aren't. To use this context in multiple
+ * threads, use {@link #copy()} in a new thread.
+ */
+@Bean
+public class DoStructureMigrationContext {
+
+ // maybe accessed by different threads
+ protected final ConcurrentHashMap<Class<? extends IDoStructureMigrationGlobalContextData>, IDoStructureMigrationGlobalContextData> m_globalContextDataMap;
+
+ // always access by single thread only
+ protected final Map<Class<? extends IDoStructureMigrationLocalContextData>, Deque<IDoStructureMigrationLocalContextData>> m_localContextDataMap;
+
+ public DoStructureMigrationContext() {
+ m_globalContextDataMap = new ConcurrentHashMap<>();
+ m_localContextDataMap = new HashMap<>();
+ initDefaults();
+ }
+
+ protected DoStructureMigrationContext(DoStructureMigrationContext other) {
+ m_globalContextDataMap = other.m_globalContextDataMap; // use the same one
+ m_localContextDataMap = new HashMap<>(); // use new one (single thread usage only).
+ }
+
+ /**
+ * Initializes default context data.
+ */
+ protected void initDefaults() {
+ putGlobal(BEANS.get(DoStructureMigrationPassThroughLogger.class));
+ }
+
+ /**
+ * Clones the context and keeps the same reference for global context data map but creates a new context map for local
+ * context data.
+ */
+ protected DoStructureMigrationContext copy() {
+ return new DoStructureMigrationContext(this);
+ }
+
+ /**
+ * If the provided context data class has a {@link Bean} annotation, it is auto-created.
+ *
+ * @return Value for given global context data class.
+ */
+ public <T extends IDoStructureMigrationGlobalContextData> T getGlobal(Class<T> contextDataClass) {
+ assertNotNull(contextDataClass, "contextDataClass is required");
+ IDoStructureMigrationGlobalContextData contextData = m_globalContextDataMap.computeIfAbsent(contextDataClass, k -> {
+ // auto-create global context data with @Bean annotation if not present yet
+ if (contextDataClass.getAnnotation(Bean.class) != null) {
+ return BEANS.get(contextDataClass);
+ }
+ return null;
+ });
+ return contextDataClass.cast(contextData);
+ }
+
+ /**
+ * Put given global context data.
+ */
+ public DoStructureMigrationContext putGlobal(IDoStructureMigrationGlobalContextData contextData) {
+ assertNotNull(contextData, "contextData is required");
+ m_globalContextDataMap.put(contextData.getIdentifierClass(), contextData);
+ return this;
+ }
+
+ /**
+ * @return Value for given locale context data class.
+ */
+ public <T extends IDoStructureMigrationLocalContextData> T get(Class<T> contextDataClass) {
+ assertNotNull(contextDataClass, "contextDataClass is required");
+
+ Deque<IDoStructureMigrationLocalContextData> deque = m_localContextDataMap.get(contextDataClass);
+ if (deque == null || deque.isEmpty()) {
+ return null;
+ }
+ return contextDataClass.cast(deque.peek());
+ }
+
+ /**
+ * Internal usage only.
+ * <p>
+ * Assign given context data. Usage:
+ *
+ * <pre>
+ * ctx.push(contextData);
+ * try {
+ * // migrate data object
+ * }
+ * finally {
+ * ctx.remove(contextData);
+ * }
+ * </pre>
+ */
+ protected DoStructureMigrationContext push(IDoStructureMigrationLocalContextData contextData) {
+ assertNotNull(contextData, "contextData is required");
+ Deque<IDoStructureMigrationLocalContextData> deque = m_localContextDataMap.computeIfAbsent(contextData.getIdentifierClass(), k -> new ArrayDeque<>());
+ deque.push(contextData);
+ return this;
+ }
+
+ /**
+ * Internal usage only.
+ * <p>
+ * Remove context data from context (instance must be the same as used for
+ * {@link #push(IDoStructureMigrationLocalContextData)}.
+ */
+ protected void remove(IDoStructureMigrationLocalContextData contextData) {
+ assertNotNull(contextData, "contextData is required");
+ Deque<IDoStructureMigrationLocalContextData> deque = m_localContextDataMap.get(contextData.getIdentifierClass());
+ assertNotNull(deque, "no context data found for {}", contextData.getIdentifierClass());
+ IDoStructureMigrationLocalContextData dequeElement = deque.peek(); // implementation detail: first peek only and check if same instance, then remove
+ assertTrue(contextData == dequeElement, "last element in deque is not element to remove: remove '{}', deque: '{}'", contextData, dequeElement);
+ deque.pop();
+ if (deque.isEmpty()) {
+ // remove deque if empty
+ m_localContextDataMap.remove(contextData.getIdentifierClass());
+ }
+ }
+
+ /**
+ * Convenience method to access global logger.
+ */
+ public IDoStructureMigrationLogger getLogger() {
+ return getGlobal(IDoStructureMigrationLogger.class);
+ }
+
+ /**
+ * Convenience method to access global stats.
+ */
+ public DoStructureMigrationStatsContextData getStats() {
+ return getGlobal(DoStructureMigrationStatsContextData.class);
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContextDataTarget.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContextDataTarget.java
new file mode 100644
index 0000000000..d58f088362
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationContextDataTarget.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+
+/**
+ * To be used with {@link IDoStructureMigrationTargetContextData} to specify for which raw ({@link #typeNames()}) or
+ * typed ({@link #doEntityClasses()} data object such a context data should be created.
+ * <p>
+ * A data object is migrated before the corresponding context data is created, thus use new type name if a rename was
+ * applied. A context data is created if the type name (for raw data objects) or data object class (for typed data
+ * objects) matches the currently processed (and migrated) data object.
+ */
+@Documented
+@Retention(RUNTIME)
+@Target(TYPE)
+public @interface DoStructureMigrationContextDataTarget {
+
+ /**
+ * @return Type names for which the annotated context data is created.
+ */
+ String[] typeNames() default {};
+
+ /**
+ * {@link IDoEntity} and {@link DoEntity} are not allowed values.
+ *
+ * @return Data object classes for which the annotated context data is created.
+ */
+ Class<? extends IDoEntity>[] doEntityClasses() default {};
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationHelper.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationHelper.java
new file mode 100644
index 0000000000..49d2990137
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationHelper.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.eclipse.scout.rt.platform.util.Assertions.assertNotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.scout.rt.dataobject.DataObjectInventory;
+import org.eclipse.scout.rt.dataobject.DataObjectVisitors;
+import org.eclipse.scout.rt.dataobject.DoNode;
+import org.eclipse.scout.rt.dataobject.IDataObject;
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.platform.ApplicationScoped;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+import org.eclipse.scout.rt.platform.namespace.Namespaces;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+import org.eclipse.scout.rt.platform.util.ObjectUtility;
+
+/**
+ * Helper for data object structure migration.
+ */
+@ApplicationScoped
+public class DoStructureMigrationHelper {
+
+ /**
+ * Name of type attribute used for serialization.
+ */
+ public static final String TYPE_ATTRIBUTE_NAME = "_type";
+
+ /**
+ * Name of type version attribute used for serialization.
+ */
+ public static final String TYPE_VERSION_ATTRIBUTE_NAME = "_typeVersion";
+
+ /**
+ * @param doEntity
+ * Raw data object (typed data objects don't have a type attribute).
+ */
+ public String getType(IDoEntity doEntity) {
+ assertNotNull(doEntity, "doEntity is required");
+ return doEntity.getString(TYPE_ATTRIBUTE_NAME);
+ }
+
+ /**
+ * Set the {@link #TYPE_ATTRIBUTE_NAME} to the provided data object with the provided type name
+ *
+ * @param doEntity
+ * Add type attribute to this data object.
+ * @param typeName
+ * The typeName of the type attribute.
+ */
+ public void setType(IDoEntity doEntity, String typeName) {
+ assertNotNull(doEntity, "doEntity is required");
+ doEntity.put(TYPE_ATTRIBUTE_NAME, typeName);
+ }
+
+ /**
+ * @param doEntity
+ * Raw data object (typed data objects don't have a type version attribute).
+ */
+ public NamespaceVersion getTypeVersion(IDoEntity doEntity) {
+ assertNotNull(doEntity, "doEntity is required");
+ return NamespaceVersion.of(doEntity.getString(TYPE_VERSION_ATTRIBUTE_NAME));
+ }
+
+ /**
+ * Set the {@link #TYPE_VERSION_ATTRIBUTE_NAME} to the provided data object with the provided value.
+ *
+ * @param doEntity
+ * Add type version attribute to this raw data object.
+ * @param version
+ * The value of the type version attribute.
+ */
+ public void setTypeVersion(IDoEntity doEntity, NamespaceVersion version) {
+ assertNotNull(doEntity, "doEntity is required");
+ doEntity.put(TYPE_VERSION_ATTRIBUTE_NAME, version == null ? null : version.unwrap());
+ }
+
+ /**
+ * Updates the type version of the data object.
+ *
+ * @return <code>true</code> if the new version was set, <code>false</code> if the type version was already up-to-date
+ * (equal).
+ */
+ public boolean updateTypeVersion(IDoEntity doEntity, NamespaceVersion newVersion) {
+ assertNotNull(doEntity, "doEntity is required");
+ NamespaceVersion currentVersion = getTypeVersion(doEntity);
+ if (ObjectUtility.equals(currentVersion, newVersion)) {
+ return false; // type version already up-to-date
+ }
+
+ setTypeVersion(doEntity, newVersion);
+ return true;
+ }
+
+ /**
+ * Each {@link IDoEntity} within the given data object is present in the returned map, the map value (version) might
+ * be <code>null</code> though.
+ * <p>
+ * If the map is empty this means that no raw {@link IDoEntity} (i.e. one with a type name) was found.
+ *
+ * @param rawDataObject
+ * Raw data object. If a typed data object is provided, no versions will be returned.
+ * @return Map of type name to type version
+ */
+ public Map<String, NamespaceVersion> collectRawDataObjectTypeVersions(IDataObject rawDataObject) {
+ Map<String, NamespaceVersion> typeVersionByTypeName = new HashMap<>();
+
+ DataObjectVisitors.forEachRec(rawDataObject, IDoEntity.class, doEntity -> {
+ NamespaceVersion typeVersion = getTypeVersion(doEntity);
+ String typeName = getType(doEntity);
+ if (typeName != null) {
+ typeVersionByTypeName.put(typeName, typeVersion); // version might be null if a data object was persisted without a version yet
+ }
+ });
+
+ return typeVersionByTypeName;
+ }
+
+ /**
+ * A migration of a given version is applicable for the given data object if
+ * <ul>
+ * <li>it's a raw data object (has a type name in `_type')
+ * <li>and the type version</li>
+ * <ul>
+ * <li>is missing</li>
+ * <li>or is available and the namespaces are equal (given vs. type version of data object) and the data object's type
+ * version is smaller than the given one
+ * </ul>
+ * </ul>
+ */
+ public boolean isMigrationApplicable(IDoEntity doEntity, NamespaceVersion version) {
+ if (getType(doEntity) == null) {
+ return false; // no raw data object
+ }
+
+ NamespaceVersion doEntityTypeVersionValue = getTypeVersion(doEntity);
+ if (doEntityTypeVersionValue == null) {
+ return true; // the initial persisted data object didn't have a type version so migrations should be applied (data object should never be persisted without a type version).
+ }
+
+ if (!doEntityTypeVersionValue.namespaceEquals(version)) {
+ // The prefix of the type names is not relevant for this comparison and must not be used because when a data object is replaced, the type names will not
+ // be changed, only the type version will be (resulting in a difference between type name prefix and type version name).
+ // Example: a migration handler for version charlie-1.1.0 might migrate a data object with the type name bravo.Example.
+ // A migration handler for version bravo-1.2.0 and a type name bravo.Example will not migrate a replaced data object anymore.
+ // Any migrations must be manually adapter after changing a type version of a replaced data object.
+ return false; // different namespace in type version, no migration
+ }
+
+ int compare = NamespaceVersion.compareVersion(doEntityTypeVersionValue, version);
+ if (compare < 0) {
+ return true; // type version is smaller then current version
+ }
+
+ return false; // type version of data object might be equal or even higher already than given version
+ }
+
+ /**
+ * Quick check to verify if any migration might be required for the given type name/version pairs.
+ * <p>
+ * There is no guarantee that even if this method returns <code>true</code>, there will be applicable migration
+ * handlers. But if the method returns <code>false</code>, there is no need to apply migrations (e.g. all type
+ * versions are already up-to-date or unknown namespaces are present).
+ *
+ * @return <code>true</code> if for at least one type name {@link #isMigrationRequired(String, NamespaceVersion)}
+ * returns <code>true</code>.
+ */
+ public boolean isAnyMigrationRequired(Map<String, String> typeVersionsByTypeName) {
+ if (CollectionUtility.isEmpty(typeVersionsByTypeName)) {
+ return false; // no types present
+ }
+
+ for (Entry<String, String> entry : typeVersionsByTypeName.entrySet()) {
+ String typeName = entry.getKey();
+ String typeVersion = entry.getValue();
+ NamespaceVersion typeVersionValue = NamespaceVersion.of(typeVersion);
+ if (isMigrationRequired(typeName, typeVersionValue)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether it's possible that a migration might be required for this type name with the given type version.
+ * <p>
+ * Behaves similar as {@link #isMigrationApplicable(IDoEntity, NamespaceVersion)}.
+ */
+ protected boolean isMigrationRequired(String typeName, NamespaceVersion typeVersion) {
+ if (typeVersion == null) {
+ // Invalid because any persisted data object must have a type version.
+ // Assume that migration is required and that a migration handler will add a type version.
+ return true;
+ }
+
+ if (Namespaces.get().byId(typeVersion.getNamespace()) == null) {
+ // If the type version has an unknown namespace, ignore it (shouldn't happen, e.g. foreign data objects).
+ // A migration might still be required, but because the namespace is unknown, there won't be any migration handler for this one.
+ return false;
+ }
+
+ DataObjectInventory inventory = BEANS.get(DataObjectInventory.class);
+ Class<? extends IDoEntity> doEntityClass = inventory.fromTypeName(typeName);
+ if (doEntityClass == null) {
+ return true; // type name is unknown, probably renamed, assume outdated
+ }
+
+ NamespaceVersion currentTypeVersion = inventory.getTypeVersion(doEntityClass);
+ if (!typeVersion.namespaceEquals(currentTypeVersion)) {
+ return true; // namespace is different, thus might have been changed in a newer version, assume outdated
+ }
+
+ int compare = NamespaceVersion.compareVersion(typeVersion, currentTypeVersion);
+ if (compare < 0) {
+ return true; // previous < current -> outdated
+ }
+
+ // Type version is equal or higher than the one from the Java class (inventory).
+ // There shouldn't exist persisted data object with type versions higher than the one from the corresponding Java class.
+ // May be a result of an imported configuration from a newer system.
+ return false;
+ }
+
+ /**
+ * Renames the type name of the data object.
+ *
+ * @return <code>true</code> if type name was not already the new type name, <code>false</code> otherwise.
+ */
+ public boolean renameTypeName(IDoEntity doEntity, String newTypeName) {
+ if (ObjectUtility.equals(getType(doEntity), newTypeName)) {
+ return false;
+ }
+
+ setType(doEntity, newTypeName);
+ return true;
+ }
+
+ /**
+ * Renames an attribute on the data object.
+ *
+ * @return <code>true</code> if the attribute node with 'attributeName' exists and was renamed, <code>false</code>
+ * otherwise.
+ */
+ public boolean renameAttribute(IDoEntity doEntity, String attributeName, String newAttributeName) {
+ if (!doEntity.has(attributeName)) {
+ return false;
+ }
+
+ DoNode<?> node = doEntity.getNode(attributeName);
+ doEntity.remove(attributeName);
+ doEntity.putNode(newAttributeName, node);
+ return true;
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationInventory.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationInventory.java
new file mode 100644
index 0000000000..555d3d8818
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationInventory.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.eclipse.scout.rt.platform.util.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+
+import org.eclipse.scout.rt.dataobject.DataObjectInventory;
+import org.eclipse.scout.rt.dataobject.DoEntity;
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.dataobject.ITypeVersion;
+import org.eclipse.scout.rt.platform.ApplicationScoped;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.CreateImmediately;
+import org.eclipse.scout.rt.platform.exception.PlatformException;
+import org.eclipse.scout.rt.platform.inventory.ClassInventory;
+import org.eclipse.scout.rt.platform.inventory.IClassInfo;
+import org.eclipse.scout.rt.platform.inventory.IClassInventory;
+import org.eclipse.scout.rt.platform.namespace.INamespace;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersionedModel;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersionedModel.VersionedItems;
+import org.eclipse.scout.rt.platform.namespace.Namespaces;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+/**
+ * Inventory of data object structure migration related classes (namespaces, versions, migration handlers, context data
+ * classes).
+ */
+@ApplicationScoped
+@CreateImmediately // apply validations at startup
+public class DoStructureMigrationInventory {
+
+ protected final LinkedHashSet<String> m_namespaces = new LinkedHashSet<>();
+ protected final LinkedHashSet<NamespaceVersion> m_orderedVersions = new LinkedHashSet<>(); // ordered versions according to VersionedItemInventory
+ protected ByNamespaceVersionComparator m_comparator = null;
+ // only one migration handler per type version and type name can exist
+ protected final Map<NamespaceVersion, Map<String /* type name */, IDoStructureMigrationHandler>> m_migrationHandlers = new HashMap<>();
+
+ protected final Map<String /* type name */, Set<Class<? extends IDoStructureMigrationTargetContextData>>> m_doContextDataClassByTypeName = new HashMap<>();
+ // Contains subclasses (replaced data objects) of classes within DoStructureMigrationContextDataTarget#doEntityClasses too.
+ // This is a performance optimization so that lookup via getDoMigrationContextValues doesn't need to resolve parent classes.
+ protected final Map<Class<? extends IDoEntity>, Set<Class<? extends IDoStructureMigrationTargetContextData>>> m_doContextDataClassByDoEntityClass = new HashMap<>();
+
+ /**
+ * For each type name the versions with available migration handlers.
+ */
+ protected final Map<String, List<NamespaceVersion>> m_typeNameVersions = new HashMap<>();
+
+ /**
+ * Based on current data from {@link DataObjectInventory}.
+ */
+ protected final Map<String, NamespaceVersion> m_typeNameToCurrentTypeVersion = new HashMap<>();
+
+ /**
+ * @return All namespaces (sorted).
+ */
+ protected List<INamespace> getAllNamespaces() {
+ return Namespaces.get().all();
+ }
+
+ /**
+ * @return All type versions.
+ */
+ protected Collection<ITypeVersion> getAllTypeVersions() {
+ return BEANS.all(ITypeVersion.class);
+ }
+
+ /**
+ * @return All context data classes ({@link IDoStructureMigrationTargetContextData} annotated with
+ * {@link DoStructureMigrationContextDataTarget}).
+ */
+ protected Collection<Class<? extends IDoStructureMigrationTargetContextData>> getAllContextDataClasses() {
+ //noinspection unchecked
+ return ClassInventory.get().getKnownAnnotatedTypes(DoStructureMigrationContextDataTarget.class)
+ .stream()
+ .filter(IClassInfo::isInstanciable)
+ .map(IClassInfo::resolveClass)
+ .filter(IDoStructureMigrationTargetContextData.class::isAssignableFrom)
+ .map(clazz -> (Class<? extends IDoStructureMigrationTargetContextData>) clazz)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * @return All migration handlers (sorted).
+ */
+ protected List<IDoStructureMigrationHandler> getAllMigrationHandlers() {
+ return BEANS.all(IDoStructureMigrationHandler.class);
+ }
+
+ @PostConstruct
+ protected void init() {
+ // Collect namespaces
+ getAllNamespaces().stream().map(INamespace::getId).forEach(m_namespaces::add);
+
+ // Collect and build version model
+ NamespaceVersionedModel<ITypeVersion> model = createDefaultModel();
+ VersionedItems<ITypeVersion> items = model.getItems();
+ assertTrue(items.isValid(), "Type version model is not valid");
+
+ items.getItems().stream().map(ITypeVersion::getVersion).forEach(m_orderedVersions::add);
+
+ m_comparator = new ByNamespaceVersionComparator(new ArrayList<>(m_orderedVersions));
+
+ // Collect current type name/type versions
+ DataObjectInventory dataObjectInventory = BEANS.get(DataObjectInventory.class);
+ for (Entry<String, Class<? extends IDoEntity>> entry : dataObjectInventory.getTypeNameToClassMap().entrySet()) {
+ String typeName = entry.getKey();
+ NamespaceVersion typeVersion = dataObjectInventory.getTypeVersion(entry.getValue());
+ if (typeVersion != null) {
+ m_typeNameToCurrentTypeVersion.put(typeName, typeVersion);
+ }
+ }
+
+ // Collect IDataObjectDoMigrationContextValue
+ IClassInventory classInventory = ClassInventory.get();
+ for (Class<? extends IDoStructureMigrationTargetContextData> contextValueClass : getAllContextDataClasses()) {
+ DoStructureMigrationContextDataTarget annotation = contextValueClass.getAnnotation(DoStructureMigrationContextDataTarget.class);
+ assertNotNull(annotation, "Annotation @{} is missing on {}", DoStructureMigrationContextDataTarget.class.getSimpleName(), contextValueClass);
+ if (annotation.typeNames() != null) {
+ for (String typeName : annotation.typeNames()) {
+ Set<Class<? extends IDoStructureMigrationTargetContextData>> contextDataClasses = m_doContextDataClassByTypeName.computeIfAbsent(typeName, k -> new HashSet<>());
+ contextDataClasses.add(contextValueClass);
+ }
+ }
+ if (annotation.doEntityClasses() != null) {
+ for (Class<? extends IDoEntity> doEntityClass : annotation.doEntityClasses()) {
+ assertFalse(doEntityClass == IDoEntity.class || doEntityClass == DoEntity.class, "{}: {} and {} are invalid do entity classes for the annotation {}",
+ contextValueClass, IDoEntity.class.getSimpleName(), DoEntity.class.getSimpleName(), DoStructureMigrationContextDataTarget.class.getSimpleName());
+
+ // class itself
+ m_doContextDataClassByDoEntityClass.computeIfAbsent(doEntityClass, k -> new HashSet<>()).add(contextValueClass);
+ // all known subclasses
+ //noinspection unchecked
+ classInventory.getAllKnownSubClasses(doEntityClass).stream()
+ .map(IClassInfo::resolveClass) // all subclasses of doEntityClass implement IDoEntity too, thus cast is okay
+ .map(clazz -> (Class<? extends IDoEntity>) clazz)
+ .map(clazz -> m_doContextDataClassByDoEntityClass.computeIfAbsent(clazz, k -> new HashSet<>()))
+ .forEach(contextDataClasses -> contextDataClasses.add(contextValueClass));
+ }
+ }
+ }
+
+ // Collect migration handlers
+ Map<NamespaceVersion, Map<String /* type name */, List<IDoStructureMigrationHandler>>> migrationHandlersPerVersionAndTypeName = new HashMap<>();
+ Map<String, Set<NamespaceVersion>> unorderedTypeNameVersions = new HashMap<>();
+ for (IDoStructureMigrationHandler migrationHandler : getAllMigrationHandlers()) {
+ validateMigrationHandler(migrationHandler);
+
+ // Collect migration handlers
+ Map<String, List<IDoStructureMigrationHandler>> migrationHandlersPerTypeName = migrationHandlersPerVersionAndTypeName.computeIfAbsent(migrationHandler.toTypeVersion(), k -> new HashMap<>());
+ for (String typeName : migrationHandler.getTypeNames()) {
+ List<IDoStructureMigrationHandler> migrationHandlers = migrationHandlersPerTypeName.computeIfAbsent(typeName, k -> new ArrayList<>());
+ migrationHandlers.add(migrationHandler);
+
+ // there might be several migration handlers for the same type name and type version, thus use set to eliminate duplicates (sorting occurs afterwards)
+ Set<NamespaceVersion> versions = unorderedTypeNameVersions.computeIfAbsent(typeName, k -> new HashSet<>());
+ versions.add(migrationHandler.toTypeVersion());
+ }
+ }
+
+ validateMigrationHandlerUniqueness(migrationHandlersPerVersionAndTypeName);
+
+ // validation takes care that there is only one migration handler per type version and type name
+ migrationHandlersPerVersionAndTypeName.forEach((version, migrationHandlersPerTypeName) -> {
+ Map<String, IDoStructureMigrationHandler> migrationHandlerPerTypeName = new HashMap<>();
+ migrationHandlersPerTypeName.forEach((typeName, migrationHandlers) -> migrationHandlerPerTypeName.put(typeName, CollectionUtility.firstElement(migrationHandlers)));
+ m_migrationHandlers.put(version, migrationHandlerPerTypeName);
+ });
+
+ // Sort and add versions to m_typeNameVersions.
+ for (Entry<String, Set<NamespaceVersion>> entry : unorderedTypeNameVersions.entrySet()) {
+ List<NamespaceVersion> versions = new ArrayList<>(entry.getValue());
+ versions.sort(m_comparator);
+ m_typeNameVersions.put(entry.getKey(), versions);
+ }
+ }
+
+ protected void validateMigrationHandlerUniqueness(Map<NamespaceVersion, Map<String, List<IDoStructureMigrationHandler>>> migrationHandlersPerVersionAndTypeName) {
+ // Verify that there there is not more than one migration handler per type version and type name
+ StringBuilder duplicateBuilder = new StringBuilder();
+ for (Entry<NamespaceVersion, Map<String, List<IDoStructureMigrationHandler>>> versionEntry : migrationHandlersPerVersionAndTypeName.entrySet()) {
+ NamespaceVersion version = versionEntry.getKey();
+ for (Entry<String, List<IDoStructureMigrationHandler>> migrationHandlerEntry : versionEntry.getValue().entrySet()) {
+ if (migrationHandlerEntry.getValue().size() > 1) {
+ if (duplicateBuilder.length() > 0) {
+ duplicateBuilder.append("\n");
+ }
+ duplicateBuilder.append(migrationHandlerEntry.getKey());
+ duplicateBuilder.append("@");
+ duplicateBuilder.append(version.unwrap());
+ duplicateBuilder.append(": ");
+ duplicateBuilder.append(migrationHandlerEntry.getValue().stream().map(Object::getClass).map(Class::getSimpleName).collect(Collectors.joining(", ")));
+ }
+ }
+ }
+
+ if (duplicateBuilder.length() > 0) {
+ throw new PlatformException("Found multiple migration handlers for the same type version/type name:\n{}", duplicateBuilder.toString());
+ }
+ }
+
+ protected NamespaceVersionedModel<ITypeVersion> createDefaultModel() {
+ return NamespaceVersionedModel.<ITypeVersion> newBuilder()
+ .withNames(getAllNamespaces().stream().map(INamespace::getId).collect(Collectors.toList()))
+ .withItems(getAllTypeVersions())
+ .build();
+ }
+
+ /**
+ * Validations:
+ * <ul>
+ * <li>There are type names
+ * <li>Verifies that the target version ({@link IDoStructureMigrationHandler#toTypeVersion()}) of the migration
+ * handler are known versions
+ * <li>For all type names there is a type version as least as high as the target type version of the migration handler
+ * </ul>
+ */
+ protected <T extends IDoStructureMigrationHandler> T validateMigrationHandler(T migrationHandler) {
+ // Check for missing type names
+ if (CollectionUtility.isEmpty(migrationHandler.getTypeNames())) {
+ throw new PlatformException("Migration handler {} doesn't have any type names", migrationHandler);
+ }
+
+ // Check for invalid type names
+ if (CollectionUtility.containsAny(migrationHandler.getTypeNames(), null, "")) {
+ throw new PlatformException("Migration handler {} has invalid type names (empty, null)", migrationHandler);
+ }
+
+ // Check for unknown toTypeVersion
+ NamespaceVersion toTypeVersion = migrationHandler.toTypeVersion();
+ if (!m_orderedVersions.contains(toTypeVersion)) {
+ throw new PlatformException("Unknown toTypeVersion value {}. Make sure that the type version value is registered within a {}", toTypeVersion, ITypeVersion.class.getSimpleName());
+ }
+
+ // The namespace of the previous type version of a data object must be equal to the namespace of the version of the migration handler, otherwise no migration handler
+ // will be called because DoStructureMigrationHelper#isMigrationApplicable will return false on different type version namespaces.
+ // This cannot be validated here because we don't have access to the previous type version.
+
+ validateDataObjectTypeVersion(migrationHandler);
+
+ return migrationHandler;
+ }
+
+ /**
+ * Validates that if there is a migration handler for a certain type version and type names, each data object must
+ * have at least this type version, otherwise the migration handler would set a higher version than the data object
+ * has (assuming that {@link IDoStructureMigrationHandler#toTypeVersion()} is the target type version).
+ * <p>
+ * This validation prevents that a migration handler is written but the type version on the data object class was not
+ * updated accordingly.
+ * <p>
+ * It's not an exact validation (best effort only).
+ */
+ protected void validateDataObjectTypeVersion(IDoStructureMigrationHandler migrationHandler) {
+ NamespaceVersion typeVersionToUpdate = migrationHandler.toTypeVersion();
+ DataObjectInventory inventory = BEANS.get(DataObjectInventory.class);
+ for (String typeName : migrationHandler.getTypeNames()) {
+ Class<? extends IDoEntity> doEntityClass = inventory.fromTypeName(typeName);
+ if (doEntityClass == null) {
+ // Maybe an old migration handler where the type name was changed in the meantime. Skip it.
+ // When an old migration handler is involved the consistency of the type version should have been check then anyway, so it seems
+ // to be fine just skipping it.
+ continue;
+ }
+
+ // There might be false positives when in case of and old migration handler the type name was changed and a new data object got the same name later on.
+ // We ignore this scenario currently, maybe need to handle it in a future release (e.g. by only checking the migration handlers for the newest version?).
+ NamespaceVersion doEntityVersion = inventory.getTypeVersion(doEntityClass);
+ if (doEntityVersion == null) {
+ throw new PlatformException("Missing a type version (at least {}) for {} specified as type name in {}", typeVersionToUpdate, typeName, migrationHandler);
+ }
+
+ if (doEntityVersion.namespaceEquals(typeVersionToUpdate) && NamespaceVersion.compareVersion(doEntityVersion, typeVersionToUpdate) < 0) {
+ // Only compare if namespace is equal. If the DO is replaced the namespace is not equal anymore
+ throw new PlatformException("Entity do '{}' has specified a lower version than the migration handler '{}'. [entityDoVersion={}, migrationHandlerVersion={}]",
+ typeName, migrationHandler.getClass().getSimpleName(), doEntityVersion, typeVersionToUpdate);
+ }
+ }
+ }
+
+ /**
+ * For each type name {@link #findNextMigrationHandlerVersion(String, NamespaceVersion)} is called. The lowest version
+ * of all type names defines the starting point.
+ *
+ * @param typeNames
+ * Map from type name to version.
+ * @param toVersion
+ * <code>null</code> to return versions up to the newest version or a known type version to only return
+ * versions up to the provided version.
+ * @return List of versions for which migrations must be applied, might be an empty list (never <code>null</code>).
+ */
+ public List<NamespaceVersion> getVersions(Map<String, NamespaceVersion> typeNames, NamespaceVersion toVersion) {
+ assertTrue(toVersion == null || m_orderedVersions.contains(toVersion), "toVersion '{}' is unknown", toVersion);
+
+ NamespaceVersion lowestNextVersion = null;
+ for (Entry<String, NamespaceVersion> entry : typeNames.entrySet()) {
+ NamespaceVersion nextVersion = findNextMigrationHandlerVersion(entry.getKey(), entry.getValue());
+ if (nextVersion == null) {
+ continue; // type name must not be considered (e.g. already current version)
+ }
+
+ if (lowestNextVersion == null || m_comparator.compare(lowestNextVersion, nextVersion) > 0) {
+ lowestNextVersion = nextVersion; // found a new lowest version
+ }
+ }
+
+ if (lowestNextVersion == null) {
+ // lowestNextVersion is null if there are no migration handlers available for the given type name/versions.
+ return Collections.emptyList();
+ }
+
+ List<NamespaceVersion> versions = new ArrayList<>(m_orderedVersions);
+ int lowerIndex = versions.indexOf(lowestNextVersion);
+ versions = versions.subList(lowerIndex, versions.size());
+
+ if (toVersion != null) {
+ // limit upper version if required
+ int upperIndex = versions.indexOf(toVersion);
+ if (upperIndex < 0) {
+ // toVersion is not part of version if and only if lower bound was already higher than toVersion -> no versions
+ return Collections.emptyList();
+ }
+
+ versions = versions.subList(0, upperIndex + 1); // inclusive
+ }
+
+ // remove all versions that don't have migration handlers
+ versions.removeIf(version -> !m_migrationHandlers.containsKey(version));
+
+ return new ArrayList<>(versions);
+ }
+
+ /**
+ * Determines the lowest possible version for which a migration handler could be triggered for the provided type
+ * name/version.
+ * <p>
+ * If the type name doesn't have the current version yet (based on {@link DataObjectInventory}, the returned version
+ * is the first version > provided version this type name is affected by a migration handler.
+ *
+ * @param version
+ * Type version of given type name (might be <code>null</code> if persisted without a type version yet)
+ * @return Lowest possible version or <code>null</code> if there will be no migration handler (already newest version
+ * or due to other reasons).
+ */
+ protected NamespaceVersion findNextMigrationHandlerVersion(String typeName, NamespaceVersion version) {
+ // Example used within comments:
+ //
+ // - Type name "lorem.Migrationless" with current type version "lorem-2" (@TypeVersion)
+ // - Missing migration handler to "lorem-2"
+ //
+ // - Type name "lorem.MissingMigration" with current type version "lorem-3" (@TypeVersion)
+ // - Migration handler for type version "lorem-2"
+ // - Missing migration handler to "lorem-3"
+ //
+ // - Type name "lorem.Example" with current type version "lorem-3" (@TypeVersion)
+ // - Migration handler for type version "lorem-2"
+ // - Migration handler for type version "lorem-3"
+ //
+ // - Type name "lorem.Beta" (previously "lorem.Alpha" since "lorem-1") with current type version "lorem-4" (@TypeVersion)
+ // - Migration handler for type version "lorem-2" that acts on "lorem.Alpha" and renames it to "lorem.Beta"
+ // - Migration handler for type version "lorem-4" that acts on "lorem.Beta"
+ //
+ // - Type name "lorem.Two" (previously "lorem.One" since "lorem-1") with current type version "lorem-2" (@TypeVersion)
+ // - Migration handler for type version "lorem-2" that acts on "lorem.One" and renames it to "lorem.Two"
+ //
+ // - Type name "lorem.One" with current type version "lorem-6" (@TypeVersion) (introduced newly with "lorem-4", different data object, using same name as an old one until "lorem-2")
+ // - Migration handler for type version "lorem-5" that acts on "lorem.One"
+ // - Migration handler for type version "lorem-6" that acts on "lorem.One"
+ //
+ // - Type name "lorem.Switch" with current type version "ipsum-3" (@TypeVersion) (introduced with "lorem-5")
+ // - Migration handler for type version "lorem-6" that acts on "lorem.Switch" and uses "ipsum-2" as typeVersionToUpdate
+ // - Migration handler for type version "ipsum-3" that acts on "lorem.Switch"
+ //
+ // - Type name "ipsum.SwitchAndRename" with current type version "ipsum-3" (@TypeVersion) (introduced with "lorem.SwitchAndRename" in type version "lorem-5")
+ // - Migration handler for type version "lorem-6" that acts on "lorem.SwitchAndRename" and renames it to "ipsum.SwitchAndRename" and uses "ipsum-2" as typeVersionToUpdate
+ // - Migration handler for type version "ipsum-3" that acts on "ipsum.SwitchAndRename"
+ //
+ // - Known type versions "lorem-1", "lorem-2", "lorem-3", "lorem-4", "lorem-5", "lorem-6", "ipsum-2", "ipsum-3"
+
+ NamespaceVersion typeVersion = m_typeNameToCurrentTypeVersion.get(typeName);
+ if (typeVersion != null && typeVersion.equals(version)) {
+ // Already the current type version, thus no need to check for migration handlers.
+ return null; // Example: "lorem-3" (current type version)
+ }
+
+ // No type version maybe found for previously known type names due to renaming (example "lorem.Alpha").
+
+ List<NamespaceVersion> versions = m_typeNameVersions.get(typeName);
+ if (versions == null) {
+ // Despite not having the current type version, no migration handlers are available that would handle this type name (rare case, unknown/foreign type name, no migration to trigger).
+ return null; // Example: type name "other.Example" or "lorem.Migrationless"
+ }
+
+ if (version == null) {
+ // Persisted data object didn't have a type version yet -> start with first migration
+ // Example: type name "lorem.Example" without a type version
+ return CollectionUtility.firstElement(versions);
+ }
+
+ if (!m_orderedVersions.contains(version)) {
+ // The type version for this type name has a version that is unknown.
+ // If nothing went wrong with the corresponding data object when it was persisted, the type version could only be a newer one not yet known to this system (inserted into this system via e.g. import).
+ // This check is required because m_comparator must only be used with known type versions.
+ return null; // Example: "lorem.Example" with type version "lorem-7" (e.g. exported from a newer system)
+ }
+
+ // Search for the provided type version within the version having a migration handler for this type name.
+ int retVal = Collections.binarySearch(versions, version, m_comparator);
+ if (retVal >= 0) {
+ // Exact match found: look for the next migration handler for this type name
+ if (retVal + 1 == versions.size()) { // no more migration handlers
+ // Example: "lorem.MissingMigration" with type version "lorem-2" (invalid, no migration handler present)
+ // Example: "lorem.Alpha" with type version "lorem-2" (invalid, because should have been renamed to "lorem.Beta" when having "lorem-2")
+ return null;
+ }
+
+ // The version after that one is the next version that should run a migration handler for this type name.
+ return versions.get(retVal + 1); // Example: "lorem.Example" with type version "lorem-2" -> "lorem-3" | "lorem.One" with type version "lorem-5" -> "lorem-6"
+ }
+
+ // No exact match found, happens for example if there was no migration handler executed yet for this type name.
+
+ // binarySearch retVal = (-(insertion point) - 1)
+ int insertionPoint = -retVal - 1;
+ if (insertionPoint == versions.size()) { // all elements in the list are less than the specified key
+ // There is no newer version with migration handlers available for this type name but data object still has a different @TypeVersion annotation
+ // Two scenarios:
+ // - 1: a renamed type name
+ // - 2: an outdated @TypeVersion annotation and missing migration handler (example "lorem.Example" type version "lorem-4" but @TypeVersion is "lorem-3")
+
+ // Returning null would be invalid because there are other migration handlers that are relevant for the new type name.
+ // Thus return next from full list.
+ List<NamespaceVersion> orderedVersions = new ArrayList<>(m_orderedVersions);
+ int index = orderedVersions.indexOf(version);
+ if (index + 1 < orderedVersions.size()) {
+ // Example: "lorem.Alpha" with type version "lorem-1" -> "lorem-2" based on m_orderedVersions (first relevant migration handler for "lorem.Beta" is in "lorem-4")
+ // Example: "lorem.SwitchAndRename" with type version "lorem-5" -> "lorem-6" based on m_orderedVersions (first relevant migration handler for "ipsum.SwitchAndRename" is in "ipsum-3")
+ return orderedVersions.get(index + 1);
+ }
+
+ // If the current version is the last version in m_orderedVersions then something is wrong
+ return null; // Example: scenario 2
+ }
+
+ // insertionPoint: the index of the first element greater than the key
+ // Example: "lorem.Example" with type version "lorem-1" -> "lorem-2"
+ // Example: "lorem.One" with type version "lorem-1" -> "lorem-2"
+ // Example: "lorem.One" with type version "lorem-4" -> "lorem-5"
+ // Example: "lorem.Switch" with type version "lorem-5" -> "lorem-6"
+ // Example: "lorem.Switch" with type version "ipsum-2" -> "lorem-3"
+ // Example: "ipsum.SwitchAndRename" with type version "ipsum-2" -> "lorem-3"
+ return versions.get(insertionPoint);
+ }
+
+ public Map<String, IDoStructureMigrationHandler> getMigrationHandlers(NamespaceVersion version) {
+ assertNotNull(version, "version is required");
+ assertTrue(m_orderedVersions.contains(version), "version is unknown");
+ return m_migrationHandlers.computeIfAbsent(version, k -> Collections.emptyMap());
+ }
+
+ public Set<Class<? extends IDoStructureMigrationTargetContextData>> getDoMigrationContextValues(IDoEntity doEntity) {
+ assertNotNull(doEntity, "doEntity is required");
+
+ String typeName = BEANS.get(DoStructureMigrationHelper.class).getType(doEntity);
+ if (typeName != null) {
+ return m_doContextDataClassByTypeName.getOrDefault(typeName, Collections.emptySet());
+ }
+ else { // data object
+ return m_doContextDataClassByDoEntityClass.getOrDefault(doEntity.getClass(), Collections.emptySet());
+ }
+ }
+
+ /**
+ * Comparator for {@link NamespaceVersion} based on the total order of all versions according to the dependency model.
+ * <p>
+ * This comparator can only be used if the versions to compare are known, i.e. part of the ones provided in the
+ * constructor.
+ */
+ protected static class ByNamespaceVersionComparator implements Comparator<NamespaceVersion> {
+
+ private final Map<NamespaceVersion, Integer> m_ordering;
+
+ /**
+ * @param versions
+ * Versions (<code>null</code> versions are not allowed).
+ */
+ public ByNamespaceVersionComparator(List<NamespaceVersion> versions) {
+ m_ordering = createOrdering(versions);
+ }
+
+ protected static Map<NamespaceVersion, Integer> createOrdering(List<NamespaceVersion> versions) {
+ Map<NamespaceVersion, Integer> nameOrder = new HashMap<>();
+ for (int i = 0; i < versions.size(); i++) {
+ nameOrder.put(assertNotNull(versions.get(i)), i); // version must never be null
+ }
+ return nameOrder;
+ }
+
+ @Override
+ public int compare(NamespaceVersion o1, NamespaceVersion o2) {
+ // comparison only allowed for known versions, thus no versions is missing in ordering - no <null> handling
+ Integer s1 = assertNotNull(m_ordering.get(o1), "order for o1 ({}) is missing", o1.unwrap());
+ Integer s2 = assertNotNull(m_ordering.get(o2), "order for o2 ({}) is missing", o2.unwrap());
+ return s1.compareTo(s2);
+ }
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationPassThroughLogger.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationPassThroughLogger.java
new file mode 100644
index 0000000000..95c3c838f6
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationPassThroughLogger.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import org.eclipse.scout.rt.platform.Bean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Thread-safe logger that directly outputs to {@link Logger}.
+ */
+@Bean
+public class DoStructureMigrationPassThroughLogger implements IDoStructureMigrationLogger {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DoStructureMigrationPassThroughLogger.class);
+
+ @Override
+ public void trace(String message, Object... args) {
+ LOG.trace(message, args);
+ }
+
+ @Override
+ public void debug(String message, Object... args) {
+ LOG.debug(message, args);
+ }
+
+ @Override
+ public void info(String message, Object... args) {
+ LOG.info(message, args);
+ }
+
+ @Override
+ public void warn(String message, Object... args) {
+ LOG.warn(message, args);
+ }
+
+ @Override
+ public void error(String message, Object... args) {
+ LOG.error(message, args);
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationStatsContextData.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationStatsContextData.java
new file mode 100644
index 0000000000..99787535b7
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrationStatsContextData.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.scout.rt.dataobject.IDataObject;
+import org.eclipse.scout.rt.platform.Bean;
+import org.eclipse.scout.rt.platform.util.StringUtility;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Thread-safe implementation of stats for data object structure migration.
+ */
+@Bean
+public class DoStructureMigrationStatsContextData implements IDoStructureMigrationGlobalContextData {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DoStructureMigrationStatsContextData.class);
+
+ protected final AtomicInteger m_dataObjectsProcessed = new AtomicInteger();
+ protected final AtomicInteger m_dataObjectsChanged = new AtomicInteger();
+ protected final AtomicLong m_accumulatedMigrationDurationNano = new AtomicLong(); // nanoseconds
+
+ protected void incrementDataObjectsProcessed() {
+ m_dataObjectsProcessed.incrementAndGet();
+ }
+
+ protected void incrementDataObjectsChanged() {
+ m_dataObjectsChanged.incrementAndGet();
+ }
+
+ /**
+ * @param startNano
+ * {@link System#nanoTime()} when migration was started
+ */
+ protected void addMigrationDuration(long startNano) {
+ m_accumulatedMigrationDurationNano.addAndGet(System.nanoTime() - startNano);
+ }
+
+ /**
+ * @param name
+ * Name to print for entities
+ * @param entityCount
+ * Number of entities processed (optional), can be different then the number of calls made to
+ * {@link DoStructureMigrator#migrateDataObject(DoStructureMigrationContext, IDataObject)}.
+ */
+ public void printStats(String name, Integer entityCount) {
+ LOG.info("Migration of {}{} entities finished in {} milliseconds accumulated migration time. Changed {} of {} processed data objects.",
+ entityCount == null ? "" : entityCount + " ", name, StringUtility.formatNanos(m_accumulatedMigrationDurationNano.get()), m_dataObjectsChanged.get(), m_dataObjectsProcessed.get());
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrator.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrator.java
new file mode 100644
index 0000000000..2177e65db0
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/DoStructureMigrator.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import static org.eclipse.scout.rt.platform.util.Assertions.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.scout.rt.dataobject.IDataObject;
+import org.eclipse.scout.rt.dataobject.IDataObjectMapper;
+import org.eclipse.scout.rt.platform.ApplicationScoped;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
+
+/**
+ * Main class for data object structure migration.
+ * <p>
+ * Example usage:
+ *
+ * <pre>
+ * DoStructureMigrationContext ctx = BEANS.get(DoStructureMigrationContext.class);
+ * DoStructureMigratorResult<ExampleDo> result = BEANS.get(DoStructureMigrator.class).migrateDataObject(ctx, rawContent, ExampleDo.class);
+ * ctx.getStats().printStats("example", 1);
+ * </pre>
+ */
+@ApplicationScoped
+public class DoStructureMigrator {
+
+ /**
+ * Migrates the data object provided by string (UTF-8 encoded) and casts it to the given data object class.
+ *
+ * @return Result with typed data object and a flag if a migration was applied.
+ */
+ public <T extends IDataObject> DoStructureMigratorResult<T> migrateDataObject(DoStructureMigrationContext ctx, String json, Class<T> valueType) {
+ assertNotNull(json, "json is required");
+ return migrateDataObject(ctx, new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)), valueType);
+ }
+
+ /**
+ * Migrates the data object provided by input stream and casts it to the given data object class.
+ *
+ * @return Result with typed data object and a flag if a migration was applied.
+ */
+ public <T extends IDataObject> DoStructureMigratorResult<T> migrateDataObject(DoStructureMigrationContext ctx, InputStream inputStream, Class<T> valueType) {
+ assertNotNull(inputStream, "inputStream is required");
+ IDataObjectMapper dataObjectMapper = BEANS.get(IDataObjectMapper.class);
+ IDataObject dataObject = dataObjectMapper.readValueRaw(inputStream);
+ return migrateDataObject(ctx, dataObject, valueType);
+ }
+
+ /**
+ * Migrates the data object provided as raw data object and casts it to the given data object class.
+ *
+ * @return Result with typed data object and a flag if a migration was applied.
+ */
+ public <T extends IDataObject> DoStructureMigratorResult<T> migrateDataObject(DoStructureMigrationContext ctx, IDataObject dataObject, Class<T> valueType) {
+ assertNotNull(valueType, "valueType is required");
+ IDataObjectMapper dataObjectMapper = BEANS.get(IDataObjectMapper.class);
+ boolean changed = migrateDataObject(ctx, dataObject);
+ String json = dataObjectMapper.writeValue(dataObject);
+ T typedDataObject = dataObjectMapper.readValue(json, valueType);
+ return new DoStructureMigratorResult<>(typedDataObject, changed);
+ }
+
+ /**
+ * Migrates the raw data object.
+ * <p>
+ * Uses latest version to migrate too.
+ */
+ public boolean migrateDataObject(DoStructureMigrationContext ctx, IDataObject dataObject) {
+ return migrateDataObject(ctx, dataObject, (NamespaceVersion) null /* latest version */);
+ }
+
+ /**
+ * <b>ATTENTION:</b> use {@link #migrateDataObject(DoStructureMigrationContext, IDataObject)} instead. Only use this
+ * for tests and very special cases.
+ * <p>
+ * Migrates the raw data object.
+ *
+ * @param dataObject
+ * Raw data object, might be a partial non-raw data object (i.e. _type info on certain entities). Only raw
+ * data object parts are migrated.
+ * @param toVersion
+ * Versions to migrate to, <code>null</code> if migrating to latest version.
+ */
+ public boolean migrateDataObject(DoStructureMigrationContext ctx, IDataObject dataObject, NamespaceVersion toVersion) {
+ assertNotNull(ctx, "ctx is required");
+ assertNotNull(dataObject, "dataObject is required");
+
+ DoStructureMigrationContext ctxCopy = ctx.copy(); // copy context to work on own stack for local context data.
+ DoStructureMigrationStatsContextData stats = ctxCopy.getStats();
+ IDoStructureMigrationLogger logger = ctxCopy.getLogger();
+
+ long start = System.nanoTime();
+
+ stats.incrementDataObjectsProcessed();
+ logger.trace("Data object before migration: {}", dataObject);
+
+ Map<String, NamespaceVersion> typeVersions = BEANS.get(DoStructureMigrationHelper.class).collectRawDataObjectTypeVersions(dataObject);
+ if (typeVersions.isEmpty()) {
+ logger.debug("No data object entities with a type name found within {}", dataObject);
+ stats.addMigrationDuration(start);
+ return false;
+ }
+
+ List<NamespaceVersion> versions = BEANS.get(DoStructureMigrationInventory.class).getVersions(typeVersions, toVersion);
+ if (versions.isEmpty()) {
+ stats.addMigrationDuration(start);
+ return false;
+ }
+
+ boolean changed = false;
+ for (NamespaceVersion version : versions) {
+ changed |= migrateDataObject(ctxCopy, version, dataObject);
+ }
+
+ if (changed) {
+ stats.incrementDataObjectsChanged();
+ logger.trace("Data object after migration: {}", dataObject);
+ }
+
+ logger.debug("Applied migrations [{} -> {}] on {}", CollectionUtility.firstElement(versions), CollectionUtility.lastElement(versions), dataObject);
+
+ stats.addMigrationDuration(start);
+ return changed;
+ }
+
+ protected boolean migrateDataObject(DoStructureMigrationContext ctx, NamespaceVersion version, IDataObject dataObject) {
+ MigrationDataObjectVisitor visitor = new MigrationDataObjectVisitor(ctx, version);
+ visitor.migrate(dataObject);
+ return visitor.isChanged();
+ }
+
+ public static class DoStructureMigratorResult<T extends IDataObject> {
+
+ private T m_dataObject;
+ private boolean m_changed;
+
+ public DoStructureMigratorResult(T dataObject, boolean changed) {
+ m_dataObject = dataObject;
+ m_changed = changed;
+ }
+
+ public T getDataObject() {
+ return m_dataObject;
+ }
+
+ public boolean isChanged() {
+ return m_changed;
+ }
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationGlobalContextData.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationGlobalContextData.java
new file mode 100644
index 0000000000..bbb53af021
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationGlobalContextData.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+/**
+ * A global structure migration context data.
+ * <p>
+ * If a concrete subclass contains an @Bean annotation, it is auto-created when first accessed via
+ * {@link DoStructureMigrationContext#getGlobal(Class)} and not existing yet.
+ * <p>
+ * Because a global context data may be accessed concurrently by different threads each implementation must be
+ * thread-safe.
+ */
+public interface IDoStructureMigrationGlobalContextData {
+
+ /**
+ * @return Class used as identifier for context data (map key).
+ */
+ default Class<? extends IDoStructureMigrationGlobalContextData> getIdentifierClass() {
+ return this.getClass();
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationHandler.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationHandler.java
new file mode 100644
index 0000000000..0951a7da4b
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.platform.ApplicationScoped;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+
+/**
+ * Interface for a data object structure migration handler.
+ */
+@ApplicationScoped
+public interface IDoStructureMigrationHandler {
+
+ /**
+ * Type version triggering the migration.
+ * <p>
+ * A migration handler is only called when the type name matches (see {@link #getTypeNames()}), the type version
+ * namespaces are equal and the type version of the data object is lower than the version of the migration handler.
+ * <p>
+ * This type version is usually the one the migrated data object must have after
+ * {@link #applyMigration(DoStructureMigrationContext, IDoEntity)} was called. Two exceptions:
+ * <ul>
+ * <li>A data object changes its namespace (e.g. triggered with bravo-2 but updated to alfa-3)
+ * <li>A data object is replaced by an existing one: no type version update (e.g. LoremDo is integrated into IpsumDo)
+ * </ul>
+ *
+ * @return non-null namespace version.
+ */
+ NamespaceVersion toTypeVersion();
+
+ /**
+ * {@link #applyMigration(DoStructureMigrationContext, IDoEntity)} is called for these type names if type version
+ * requirements are satisfied (see {@link #toTypeVersion()}).
+ * <p>
+ * One migration handler may be triggered by several different type names (e.g. one migration handler for a bunch of
+ * renamings or data objects with a common structure).
+ *
+ * @return non-empty set.
+ */
+ Set<String> getTypeNames();
+
+ /**
+ * Migrates the given data object.
+ * <p>
+ * The type version update must be part of the migration, i.e. the implementation must guarantee that the type version
+ * of the corresponding data object is updated.
+ *
+ * @param ctx
+ * Context
+ * @param doEntity
+ * Do entity according to {@link #getTypeNames()}.
+ * @return <code>true</code> if data object was changed in any way (including type version update only),
+ * <code>false</code> otherwise.
+ */
+ boolean applyMigration(DoStructureMigrationContext ctx, IDoEntity doEntity);
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationLocalContextData.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationLocalContextData.java
new file mode 100644
index 0000000000..b6f80f3372
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationLocalContextData.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+/**
+ * A structure migration context data that is local to a single thread.
+ * <p>
+ * Only implement this interface directly instead of {@link IDoStructureMigrationTargetContextData} if
+ * {@link #getIdentifierClass()} is used (because local context data cannot be pushed manually to
+ * {@link DoStructureMigrationContext}, only classes implementing {@link IDoStructureMigrationTargetContextData} are
+ * auto-created when a data object is traversed).
+ * <p>
+ * A local context data is stacked, i.e. if multiple context data for the same {@link #getIdentifierClass()} are added,
+ * the last one is retrieved.
+ */
+public interface IDoStructureMigrationLocalContextData {
+
+ /**
+ * @return Class used as identifier for context data (map key).
+ */
+ default Class<? extends IDoStructureMigrationLocalContextData> getIdentifierClass() {
+ return this.getClass();
+ }
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationLogger.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationLogger.java
new file mode 100644
index 0000000000..f00c9ef384
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationLogger.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+/**
+ * Interface of a global context data used as logger.
+ */
+public interface IDoStructureMigrationLogger extends IDoStructureMigrationGlobalContextData {
+
+ @Override
+ default Class<? extends IDoStructureMigrationGlobalContextData> getIdentifierClass() {
+ return IDoStructureMigrationLogger.class;
+ }
+
+ void trace(String message, Object... args);
+
+ void debug(String message, Object... args);
+
+ void info(String message, Object... args);
+
+ void warn(String message, Object... args);
+
+ void error(String message, Object... args);
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationTargetContextData.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationTargetContextData.java
new file mode 100644
index 0000000000..364a7791ff
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/IDoStructureMigrationTargetContextData.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.platform.Bean;
+
+/**
+ * Interface for migration context data classes that are created and pushed as local context data to
+ * {@link DoStructureMigrationContext} when a corresponding data object is traversed (see
+ * {@link DoStructureMigrationContextDataTarget} annotation for data object matching).
+ */
+@Bean
+public interface IDoStructureMigrationTargetContextData extends IDoStructureMigrationLocalContextData {
+
+ /**
+ * A context for a data object is initialized after the corresponding data object was migrated to the specific
+ * version.
+ *
+ * @return <code>true</true> if it's a valid context and should be used, <code>false</false> to discard context.
+ **/
+ boolean initialize(DoStructureMigrationContext ctx, IDoEntity doEntity);
+}
diff --git a/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/MigrationDataObjectVisitor.java b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/MigrationDataObjectVisitor.java
new file mode 100644
index 0000000000..2eb9564f86
--- /dev/null
+++ b/org.eclipse.scout.rt.dataobject/src/main/java/org/eclipse/scout/rt/dataobject/migration/MigrationDataObjectVisitor.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * BSI Business Systems Integration AG - initial API and implementation
+ */
+package org.eclipse.scout.rt.dataobject.migration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.scout.rt.dataobject.AbstractDataObjectVisitor;
+import org.eclipse.scout.rt.dataobject.IDataObject;
+import org.eclipse.scout.rt.dataobject.IDoEntity;
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.namespace.NamespaceVersion;
+
+/**
+ * Data object visitor to migrate data objects.
+ */
+public class MigrationDataObjectVisitor extends AbstractDataObjectVisitor {
+
+ protected final DoStructureMigrationHelper m_helper;
+ protected final DoStructureMigrationInventory m_inventory;
+
+ protected final DoStructureMigrationContext m_ctx;
+ protected final NamespaceVersion m_version;
+ protected final Map<String, IDoStructureMigrationHandler> m_migrationHandlerPerTypeName;
+
+ protected boolean m_changed = false;
+
+ public MigrationDataObjectVisitor(DoStructureMigrationContext ctx, NamespaceVersion version) {
+ m_helper = BEANS.get(DoStructureMigrationHelper.class);
+ m_inventory = BEANS.get(DoStructureMigrationInventory.class);
+
+ m_ctx = ctx;
+ m_version = version;
+
+ m_migrationHandlerPerTypeName = m_inventory.getMigrationHandlers(version);
+ }
+
+ public boolean isChanged() {
+ return m_changed;
+ }
+
+ public void migrate(IDataObject dataObject) {
+ visit(dataObject);
+ }
+
+ @Override
+ protected void caseDoEntity(IDoEntity entity) {
+ // Migrate data object before creating context data, otherwise, in case of recursive data objects,
+ // the context data available in IDoStructureMigrationHandler#migrate would be the one from the current data object thus preventing access to parent context.
+ migrateInternal(entity);
+
+ // Create context data based on migrated data object
+ List<IDoStructureMigrationTargetContextData> localContextDataList = pushLocalContextData(entity);
+ try {
+ super.caseDoEntity(entity);
+ }
+ finally {
+ localContextDataList.forEach(m_ctx::remove);
+ }
+ }
+
+ protected List<IDoStructureMigrationTargetContextData> pushLocalContextData(IDoEntity doEntity) {
+ List<IDoStructureMigrationTargetContextData> localContextDataList = new ArrayList<>();
+ Set<Class<? extends IDoStructureMigrationTargetContextData>> contextDataClasses = m_inventory.getDoMigrationContextValues(doEntity);
+ for (Class<? extends IDoStructureMigrationTargetContextData> contextDataClass : contextDataClasses) {
+ IDoStructureMigrationTargetContextData contextValue = BEANS.get(contextDataClass);
+ if (contextValue.initialize(m_ctx, doEntity)) {
+ localContextDataList.add(contextValue);
+ }
+ }
+
+ localContextDataList.forEach(m_ctx::push);
+ return localContextDataList;
+ }
+
+ /**
+ * Migrates the data object and updates all type versions accordingly.
+ */
+ protected void migrateInternal(IDoEntity doEntity) {
+ if (!m_helper.isMigrationApplicable(doEntity, m_version)) {
+ return;
+ }
+
+ String typeName = m_helper.getType(doEntity);
+ IDoStructureMigrationHandler migrationHandler = m_migrationHandlerPerTypeName.get(typeName);
+ if (migrationHandler == null) {
+ return;
+ }
+
+ m_changed |= migrationHandler.applyMigration(m_ctx, doEntity);
+ }
+}
diff --git a/org.eclipse.scout.rt.jackson.test/src/test/java/org/eclipse/scout/rt/jackson/dataobject/fixture/JacksonFixtureTypeVersions.java b/org.eclipse.scout.rt.jackson.test/src/test/java/org/eclipse/scout/rt/jackson/dataobject/fixture/JacksonFixtureTypeVersions.java
index 3e7d46c152..12c9ec6d78 100644
--- a/org.eclipse.scout.rt.jackson.test/src/test/java/org/eclipse/scout/rt/jackson/dataobject/fixture/JacksonFixtureTypeVersions.java
+++ b/org.eclipse.scout.rt.jackson.test/src/test/java/org/eclipse/scout/rt/jackson/dataobject/fixture/JacksonFixtureTypeVersions.java
@@ -14,6 +14,9 @@ import org.eclipse.scout.rt.dataobject.AbstractTypeVersion;
public final class JacksonFixtureTypeVersions {
+ private JacksonFixtureTypeVersions() {
+ }
+
public static final class JacksonFixture_1_0_0 extends AbstractTypeVersion {
}
}
diff --git a/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModelTest.java b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModelTest.java
index 417894a7ad..e59ae97d55 100644
--- a/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModelTest.java
+++ b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModelTest.java
@@ -42,6 +42,9 @@ public class NamespaceVersionedModelTest {
assertItems(
Arrays.asList(item("alfa-5")),
inventory.getItems(versions(version("alfa-3")), versions(version("alfa-5"), version("bravo-0"))));
+ assertItems(
+ Arrays.asList(item("alfa-5")),
+ inventory.getItems());
}
@Test
@@ -56,6 +59,10 @@ public class NamespaceVersionedModelTest {
assertItems(
Arrays.asList(item("alfa-4"), item("charlie-8"), item("charlie-9"), item("alfa-5"), item("charlie-10")),
inventory.getItems(versions(version("alfa-3"), version("charlie-7")), versions(version("alfa-5"), version("charlie-10"))));
+
+ assertItems(
+ Arrays.asList(item("alfa-4"), item("charlie-8"), item("charlie-9"), item("alfa-5"), item("charlie-10")),
+ inventory.getItems());
}
@Test
@@ -72,6 +79,10 @@ public class NamespaceVersionedModelTest {
assertItems(
Arrays.asList(item("alfa-4"), item("bravo-15"), item("charlie-8"), item("charlie-9"), item("alfa-5"), item("bravo-18"), item("charlie-10")),
inventory.getItems(versions(version("alfa-3"), version("bravo-14"), version("charlie-7")), versions(version("alfa-5"), version("bravo-18"), version("charlie-10"))));
+
+ assertItems(
+ Arrays.asList(item("alfa-4"), item("bravo-15"), item("charlie-8"), item("charlie-9"), item("alfa-5"), item("bravo-18"), item("charlie-10")),
+ inventory.getItems());
}
@Test
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespace.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespace.java
index 753a44cbb9..031e4ca13d 100644
--- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespace.java
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespace.java
@@ -24,7 +24,7 @@ public interface INamespace {
/**
* Namespace IDs should be lowercase.
*
- * @return Unique ID for the namespace.
+ * @return non-empty unique ID for the namespace.
*/
String getId();
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespaceVersioned.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespaceVersioned.java
index 4fd538ab6b..840af74524 100644
--- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespaceVersioned.java
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/INamespaceVersioned.java
@@ -17,6 +17,9 @@ import java.util.Collection;
*/
public interface INamespaceVersioned {
+ /**
+ * @return non-null namespace version.
+ */
NamespaceVersion getVersion();
/**
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModel.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModel.java
index 3822fee2d5..44a7faf798 100644
--- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModel.java
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/NamespaceVersionedModel.java
@@ -300,6 +300,16 @@ public class NamespaceVersionedModel<T extends INamespaceVersioned> {
}
/**
+ * This method retrieves all items. The items are ordered according to their dependencies.
+ *
+ * @return non null {@link VersionedItems}
+ */
+ public VersionedItems<T> getItems() {
+ Set<T> allItemsUnordered = m_items.values().stream().flatMap(List::stream).collect(Collectors.toSet());
+ return orderByDependencies(sortByName(allItemsUnordered), null);
+ }
+
+ /**
* This method retrieves all items which are in the given version range. The items are ordered according to their
* dependencies. Even if the from- and to-versions are not equal, no item at all might be returned.
* <p>
@@ -374,11 +384,14 @@ public class NamespaceVersionedModel<T extends INamespaceVersioned> {
if (items.contains(dep)) {
return true;
}
- NamespaceVersion depVersion = dep.getVersion();
- Optional<NamespaceVersion> fromVersion = fromVersions.stream().filter(depVersion::namespaceEquals).findFirst();
- // if from version has no version for given name dependency is satisfied implicit; see also getItemsUnordered
- if (fromVersion.isPresent() && compareVersion(depVersion, fromVersion.get()) > 0) {
- unsatisfiedDependencies.add(dep);
+
+ if (fromVersions != null) {
+ NamespaceVersion depVersion = dep.getVersion();
+ Optional<NamespaceVersion> fromVersion = fromVersions.stream().filter(depVersion::namespaceEquals).findFirst();
+ // if from version has no version for given name dependency is satisfied implicit; see also getItemsUnordered
+ if (fromVersion.isPresent() && compareVersion(depVersion, fromVersion.get()) > 0) {
+ unsatisfiedDependencies.add(dep);
+ }
}
return false;
}).collect(Collectors.toSet()));
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/Namespaces.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/Namespaces.java
index 3222fb3023..9b9302fb3b 100644
--- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/Namespaces.java
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/namespace/Namespaces.java
@@ -23,6 +23,7 @@ import org.eclipse.scout.rt.platform.BEANS;
import org.eclipse.scout.rt.platform.internal.BeanInstanceUtil;
import org.eclipse.scout.rt.platform.inventory.ClassInventory;
import org.eclipse.scout.rt.platform.inventory.IClassInfo;
+import org.eclipse.scout.rt.platform.util.StringUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,8 +42,14 @@ public class Namespaces {
.sorted(Comparator.comparing(INamespace::getOrder).thenComparing(namespace -> namespace.getClass().getName())) // FQN fallback in case of identical orders
.collect(Collectors.toList());
+ // Validate presence of ID
+ namespaces.stream()
+ .filter(namespace -> StringUtility.isNullOrEmpty(namespace.getId()))
+ .forEach(namespace -> LOG.error("Namespace without an ID detected: {}", namespace.getClass()));
+
// Validate ID uniqueness
namespaces.stream()
+ .filter(namespace -> !StringUtility.isNullOrEmpty(namespace.getId())) // ignore those logged before
.collect(Collectors.groupingBy(INamespace::getId))
.entrySet()
.stream()

Back to the top