diff --git a/mongock-core/mongock-api/src/main/java/io/mongock/api/annotations/ChangeUnit.java b/mongock-core/mongock-api/src/main/java/io/mongock/api/annotations/ChangeUnit.java index e59f59dbf..3dac8746c 100644 --- a/mongock-core/mongock-api/src/main/java/io/mongock/api/annotations/ChangeUnit.java +++ b/mongock-core/mongock-api/src/main/java/io/mongock/api/annotations/ChangeUnit.java @@ -18,6 +18,7 @@ * The concept is basically the same, a class that wraps the logic of the migration *

* Classes annotated with @ChangeUnit must have the following: + * - One(and only one) one valid constructor annotated with @ChangeUnitConstructor(mandatory if more than one constructor exist after version 6) * - One(and only one) method annotated with @Execution(mandatory) * - One(and only one) method annotated with @RollbackExecution(mandatory) * - At most, one method annotated with @BeforeExecution(optional) @@ -28,6 +29,7 @@ *

* - For new changeLogs/changeSets created from version 5: Annotated you class migration class with the annotation @ChangeUnit * + * @see ChangeUnitConstructor * @see Execution * @see BeforeExecution * @see RollbackExecution diff --git a/mongock-core/mongock-api/src/main/java/io/mongock/api/annotations/ChangeUnitConstructor.java b/mongock-core/mongock-api/src/main/java/io/mongock/api/annotations/ChangeUnitConstructor.java new file mode 100644 index 000000000..ceb9c48f2 --- /dev/null +++ b/mongock-core/mongock-api/src/main/java/io/mongock/api/annotations/ChangeUnitConstructor.java @@ -0,0 +1,11 @@ +package io.mongock.api.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.CONSTRUCTOR) +@Retention(RetentionPolicy.RUNTIME) +public @interface ChangeUnitConstructor { +} diff --git a/mongock-core/mongock-runner/mongock-runner-core/src/main/java/io/mongock/runner/core/executor/ChangeLogRuntimeImpl.java b/mongock-core/mongock-runner/mongock-runner-core/src/main/java/io/mongock/runner/core/executor/ChangeLogRuntimeImpl.java index f78c70505..79b7ab2ed 100644 --- a/mongock-core/mongock-runner/mongock-runner-core/src/main/java/io/mongock/runner/core/executor/ChangeLogRuntimeImpl.java +++ b/mongock-core/mongock-runner/mongock-runner-core/src/main/java/io/mongock/runner/core/executor/ChangeLogRuntimeImpl.java @@ -1,8 +1,8 @@ package io.mongock.runner.core.executor; import io.changock.migration.api.annotations.NonLockGuarded; - import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.ChangeUnitConstructor; import io.mongock.api.exception.MongockException; import io.mongock.driver.api.common.DependencyInjectionException; import io.mongock.driver.api.driver.ChangeSetDependency; @@ -20,10 +20,14 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; public class ChangeLogRuntimeImpl implements ChangeLogRuntime { private static final Logger logger = LoggerFactory.getLogger(ChangeLogRuntimeImpl.class); @@ -120,6 +124,30 @@ private String getParameterName(Parameter parameter) { private Constructor getConstructor(Class type) { - return type.getConstructors()[0]; + return findChangeUnitConstructor(type) + .orElseGet(() -> findDefaultConstructor(type)); + } + + private Constructor findDefaultConstructor(Class type) { + Constructor[] constructors = type.getConstructors(); + if (constructors.length == 0) { + throw new MongockException("Mongock cannot find a valid constructor for changeUnit[%s]", type.getName()); + } + if (constructors.length > 1) { + logger.warn("Mongock found multiple constructors for changeUnit[{}]. " + + "It's recommended to annotate the one you want Mongock to use with @ChangeUnitConstructor. " + + "FROM VERSION 6 THIS WILL CAUSE AN ERROR ", type.getName()); + } + return constructors[0]; + } + + private Optional> findChangeUnitConstructor(Class type) { + Supplier>> changeUnitConstructorsSupplier = () -> Arrays.stream(type.getConstructors()) + .filter(constructor -> constructor.isAnnotationPresent(ChangeUnitConstructor.class)); + if (changeUnitConstructorsSupplier.get().count() > 1) { + throw new MongockException("Found multiple constructors for changeUnit[%s] without annotation @ChangeUnitConstructor." + + " Annotate the one you want Mongock to use to instantiate your changeUnit", type.getName()); + } + return changeUnitConstructorsSupplier.get().findFirst(); } } diff --git a/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithDefaultConstructor.java b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithDefaultConstructor.java new file mode 100644 index 000000000..c222d1db5 --- /dev/null +++ b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithDefaultConstructor.java @@ -0,0 +1,17 @@ +package io.mongock.runner.core.changelogs.withConstructor; + +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; + +@ChangeUnit(id = "ChangeUnitWithDefaultConstructor", order = "1") +public class ChangeUnitWithDefaultConstructor { + + @Execution + public void execution() { + } + + @RollbackExecution + public void rollbackExecution() { + } +} diff --git a/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithMoreThanOneChangeUnitConstructor.java b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithMoreThanOneChangeUnitConstructor.java new file mode 100644 index 000000000..6edb553e3 --- /dev/null +++ b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithMoreThanOneChangeUnitConstructor.java @@ -0,0 +1,26 @@ +package io.mongock.runner.core.changelogs.withConstructor; + +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.ChangeUnitConstructor; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; + +@ChangeUnit(id = "ChangeUnitWithMoreThanOneChangeUnitConstructor", order = "1") +public class ChangeUnitWithMoreThanOneChangeUnitConstructor { + + @ChangeUnitConstructor + public ChangeUnitWithMoreThanOneChangeUnitConstructor() { + } + + @ChangeUnitConstructor + public ChangeUnitWithMoreThanOneChangeUnitConstructor(String dummy) { + } + + @Execution + public void execution() { + } + + @RollbackExecution + public void rollbackExecution() { + } +} diff --git a/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithValidConstructor.java b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithValidConstructor.java new file mode 100644 index 000000000..c10e4b5b9 --- /dev/null +++ b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithValidConstructor.java @@ -0,0 +1,20 @@ +package io.mongock.runner.core.changelogs.withConstructor; + +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; + +@ChangeUnit(id = "ChangeUnitWithValidConstructor", order = "1") +public class ChangeUnitWithValidConstructor { + + public ChangeUnitWithValidConstructor() { + } + + @Execution + public void execution() { + } + + @RollbackExecution + public void rollbackExecution() { + } +} diff --git a/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithValidConstructorsHavingChangeUnitConstructor.java b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithValidConstructorsHavingChangeUnitConstructor.java new file mode 100644 index 000000000..38bcaf6db --- /dev/null +++ b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithValidConstructorsHavingChangeUnitConstructor.java @@ -0,0 +1,35 @@ +package io.mongock.runner.core.changelogs.withConstructor; + +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.ChangeUnitConstructor; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; + +@ChangeUnit(id = "ChangeUnitWithValidConstructorsHavingChangeUnitConstructor", order = "1") +public class ChangeUnitWithValidConstructorsHavingChangeUnitConstructor { + + public static final String DUMMY_VALUE = "dummyValue"; + private final String dummy; + + public ChangeUnitWithValidConstructorsHavingChangeUnitConstructor(String dummy) { + this.dummy = dummy; + } + + + @ChangeUnitConstructor + public ChangeUnitWithValidConstructorsHavingChangeUnitConstructor() { + this.dummy = DUMMY_VALUE; + } + + @Execution + public void execution() { + } + + @RollbackExecution + public void rollbackExecution() { + } + + public String getDummy() { + return dummy; + } +} diff --git a/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithoutValidConstructor.java b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithoutValidConstructor.java new file mode 100644 index 000000000..394cd454b --- /dev/null +++ b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/changelogs/withConstructor/ChangeUnitWithoutValidConstructor.java @@ -0,0 +1,20 @@ +package io.mongock.runner.core.changelogs.withConstructor; + +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; + +@ChangeUnit(id = "ChangeUnitWithoutValidConstructor", order = "1") +public class ChangeUnitWithoutValidConstructor { + + private ChangeUnitWithoutValidConstructor() { + } + + @Execution + public void execution() { + } + + @RollbackExecution + public void rollbackExecution() { + } +} diff --git a/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/executor/ChangeUnitExecutorImplTest.java b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/executor/ChangeUnitExecutorImplTest.java index 06e34cd9a..13494e49f 100644 --- a/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/executor/ChangeUnitExecutorImplTest.java +++ b/mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/executor/ChangeUnitExecutorImplTest.java @@ -26,6 +26,11 @@ import io.mongock.runner.core.changelogs.skipmigration.withnochangeset.ChangeLogWithNoChangeSet; import io.mongock.runner.core.changelogs.system.NewChangeUnit; import io.mongock.runner.core.changelogs.system.SystemChangeUnit; +import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithDefaultConstructor; +import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithMoreThanOneChangeUnitConstructor; +import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithValidConstructor; +import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithValidConstructorsHavingChangeUnitConstructor; +import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithoutValidConstructor; import io.mongock.runner.core.changelogs.withRollback.AdvanceChangeLogWithBefore; import io.mongock.runner.core.changelogs.withRollback.AdvanceChangeLogWithBeforeAndChangeSetFailing; import io.mongock.runner.core.changelogs.withRollback.BasicChangeLogWithExceptionInChangeSetAndRollback; @@ -63,6 +68,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -964,6 +971,50 @@ public void shouldNotRollbackManuallyAnyChangeSetsAndStoreChangeEntries_whenSeco } + @Test + public void shouldNotCreateInstanceWhenNoValidConstructorExist() { + ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager()); + MongockException mongockException = assertThrows(MongockException.class, + () -> changeLogRuntime.getInstance(ChangeUnitWithoutValidConstructor.class)); + assertEquals("Mongock cannot find a valid constructor for " + + "changeUnit[io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithoutValidConstructor]", + mongockException.getMessage()); + } + + @Test + public void shouldNotCreateInstanceWhenMoreThanOneConstructorIsAnnotatedWithChangeUnitConstructor() { + ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager()); + MongockException mongockException = assertThrows(MongockException.class, + () -> changeLogRuntime.getInstance(ChangeUnitWithMoreThanOneChangeUnitConstructor.class)); + assertEquals("Found multiple constructors for" + + " changeUnit[io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithMoreThanOneChangeUnitConstructor] " + + "without annotation @ChangeUnitConstructor. " + + "Annotate the one you want Mongock to use to instantiate your changeUnit", + mongockException.getMessage()); + } + + @Test + public void shouldCreateInstanceWhenOnlyOneValidConstructorExistWithoutChangeUnitConstructor() { + ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager()); + assertNotNull(changeLogRuntime.getInstance(ChangeUnitWithValidConstructor.class)); + } + + @Test + public void shouldCreateInstanceWithDefaultConstructor() { + ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager()); + assertNotNull(changeLogRuntime.getInstance(ChangeUnitWithDefaultConstructor.class)); + } + + @Test + public void shouldCreateInstanceWhenOneValidConstructorExistWithChangeUnitConstructor() { + ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager()); + ChangeUnitWithValidConstructorsHavingChangeUnitConstructor instance = + (ChangeUnitWithValidConstructorsHavingChangeUnitConstructor) changeLogRuntime + .getInstance(ChangeUnitWithValidConstructorsHavingChangeUnitConstructor.class); + assertNotNull(instance); + assertEquals(ChangeUnitWithValidConstructorsHavingChangeUnitConstructor.DUMMY_VALUE, instance.getDummy()); + } + private SortedSet createInitialChangeLogsByPackage(Class... executorChangeLogClass) { List packages = Stream.of(executorChangeLogClass) .map(clazz -> clazz.getPackage().getName())