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())