diff options
author | Stephan Merkli | 2021-03-30 07:25:43 +0000 |
---|---|---|
committer | Stephan Merkli | 2021-04-29 11:48:16 +0000 |
commit | 262a6aadbc5f229f91d5cbef4ef25531d9f55292 (patch) | |
tree | 6a815dd489426c83a409c37788cc5e61ef4b6b35 | |
parent | ec2e69d848b5dc0f93d0236ab57f9a19f19ec363 (diff) | |
download | org.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>
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() |