Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* The concept is basically the same, a class that wraps the logic of the migration
* <p>
* 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)
Expand All @@ -28,6 +29,7 @@
* <p>
* - 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -120,6 +124,29 @@ 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) {
Supplier<Stream<Constructor<?>>> constructorSupplier = () -> Arrays.stream(type.getConstructors());
if (constructorSupplier.get().count() > 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 constructorSupplier.get()
.findFirst()
.orElseThrow(() -> new MongockException("Mongock cannot find a valid constructor for changeUnit[%s]", type.getName()));
}

private Optional<Constructor<?>> findChangeUnitConstructor(Class<?> type) {
Supplier<Stream<Constructor<?>>> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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() {
}
}
Original file line number Diff line number Diff line change
@@ -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() {
}
}
Original file line number Diff line number Diff line change
@@ -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() {
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<ChangeLogItem> createInitialChangeLogsByPackage(Class<?>... executorChangeLogClass) {
List<String> packages = Stream.of(executorChangeLogClass)
.map(clazz -> clazz.getPackage().getName())
Expand Down