Skip to content

Commit 46923b3

Browse files
Add ChangeUnitConstructor annotation to allow mongock to pick constructors for dependency injection (#549)
* add ChangeUnitConstructor * enhance exception when no valid constructor exist * add findChangeUnitConstructor in ChangeLogRuntimeImpl * add warning when all valid constructors are not annotated with ChangeUnitConstructor * add tests to validate valid constructor behaviour
1 parent 8d4acb1 commit 46923b3

File tree

9 files changed

+212
-2
lines changed

9 files changed

+212
-2
lines changed

mongock-core/mongock-api/src/main/java/io/mongock/api/annotations/ChangeUnit.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* The concept is basically the same, a class that wraps the logic of the migration
1919
* <p>
2020
* Classes annotated with @ChangeUnit must have the following:
21+
* - One(and only one) one valid constructor annotated with @ChangeUnitConstructor(mandatory if more than one constructor exist after version 6)
2122
* - One(and only one) method annotated with @Execution(mandatory)
2223
* - One(and only one) method annotated with @RollbackExecution(mandatory)
2324
* - At most, one method annotated with @BeforeExecution(optional)
@@ -28,6 +29,7 @@
2829
* <p>
2930
* - For new changeLogs/changeSets created from version 5: Annotated you class migration class with the annotation @ChangeUnit
3031
*
32+
* @see ChangeUnitConstructor
3133
* @see Execution
3234
* @see BeforeExecution
3335
* @see RollbackExecution
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.mongock.api.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.CONSTRUCTOR)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface ChangeUnitConstructor {
11+
}

mongock-core/mongock-runner/mongock-runner-core/src/main/java/io/mongock/runner/core/executor/ChangeLogRuntimeImpl.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.mongock.runner.core.executor;
22

33
import io.changock.migration.api.annotations.NonLockGuarded;
4-
54
import io.mongock.api.annotations.ChangeUnit;
5+
import io.mongock.api.annotations.ChangeUnitConstructor;
66
import io.mongock.api.exception.MongockException;
77
import io.mongock.driver.api.common.DependencyInjectionException;
88
import io.mongock.driver.api.driver.ChangeSetDependency;
@@ -20,10 +20,14 @@
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Parameter;
2222
import java.util.ArrayList;
23+
import java.util.Arrays;
2324
import java.util.HashSet;
2425
import java.util.List;
26+
import java.util.Optional;
2527
import java.util.Set;
2628
import java.util.function.Function;
29+
import java.util.function.Supplier;
30+
import java.util.stream.Stream;
2731

2832
public class ChangeLogRuntimeImpl implements ChangeLogRuntime {
2933
private static final Logger logger = LoggerFactory.getLogger(ChangeLogRuntimeImpl.class);
@@ -120,6 +124,30 @@ private String getParameterName(Parameter parameter) {
120124

121125

122126
private Constructor<?> getConstructor(Class<?> type) {
123-
return type.getConstructors()[0];
127+
return findChangeUnitConstructor(type)
128+
.orElseGet(() -> findDefaultConstructor(type));
129+
}
130+
131+
private Constructor<?> findDefaultConstructor(Class<?> type) {
132+
Constructor<?>[] constructors = type.getConstructors();
133+
if (constructors.length == 0) {
134+
throw new MongockException("Mongock cannot find a valid constructor for changeUnit[%s]", type.getName());
135+
}
136+
if (constructors.length > 1) {
137+
logger.warn("Mongock found multiple constructors for changeUnit[{}]. " +
138+
"It's recommended to annotate the one you want Mongock to use with @ChangeUnitConstructor. " +
139+
"FROM VERSION 6 THIS WILL CAUSE AN ERROR ", type.getName());
140+
}
141+
return constructors[0];
142+
}
143+
144+
private Optional<Constructor<?>> findChangeUnitConstructor(Class<?> type) {
145+
Supplier<Stream<Constructor<?>>> changeUnitConstructorsSupplier = () -> Arrays.stream(type.getConstructors())
146+
.filter(constructor -> constructor.isAnnotationPresent(ChangeUnitConstructor.class));
147+
if (changeUnitConstructorsSupplier.get().count() > 1) {
148+
throw new MongockException("Found multiple constructors for changeUnit[%s] without annotation @ChangeUnitConstructor." +
149+
" Annotate the one you want Mongock to use to instantiate your changeUnit", type.getName());
150+
}
151+
return changeUnitConstructorsSupplier.get().findFirst();
124152
}
125153
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.mongock.runner.core.changelogs.withConstructor;
2+
3+
import io.mongock.api.annotations.ChangeUnit;
4+
import io.mongock.api.annotations.Execution;
5+
import io.mongock.api.annotations.RollbackExecution;
6+
7+
@ChangeUnit(id = "ChangeUnitWithDefaultConstructor", order = "1")
8+
public class ChangeUnitWithDefaultConstructor {
9+
10+
@Execution
11+
public void execution() {
12+
}
13+
14+
@RollbackExecution
15+
public void rollbackExecution() {
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.mongock.runner.core.changelogs.withConstructor;
2+
3+
import io.mongock.api.annotations.ChangeUnit;
4+
import io.mongock.api.annotations.ChangeUnitConstructor;
5+
import io.mongock.api.annotations.Execution;
6+
import io.mongock.api.annotations.RollbackExecution;
7+
8+
@ChangeUnit(id = "ChangeUnitWithMoreThanOneChangeUnitConstructor", order = "1")
9+
public class ChangeUnitWithMoreThanOneChangeUnitConstructor {
10+
11+
@ChangeUnitConstructor
12+
public ChangeUnitWithMoreThanOneChangeUnitConstructor() {
13+
}
14+
15+
@ChangeUnitConstructor
16+
public ChangeUnitWithMoreThanOneChangeUnitConstructor(String dummy) {
17+
}
18+
19+
@Execution
20+
public void execution() {
21+
}
22+
23+
@RollbackExecution
24+
public void rollbackExecution() {
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.mongock.runner.core.changelogs.withConstructor;
2+
3+
import io.mongock.api.annotations.ChangeUnit;
4+
import io.mongock.api.annotations.Execution;
5+
import io.mongock.api.annotations.RollbackExecution;
6+
7+
@ChangeUnit(id = "ChangeUnitWithValidConstructor", order = "1")
8+
public class ChangeUnitWithValidConstructor {
9+
10+
public ChangeUnitWithValidConstructor() {
11+
}
12+
13+
@Execution
14+
public void execution() {
15+
}
16+
17+
@RollbackExecution
18+
public void rollbackExecution() {
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.mongock.runner.core.changelogs.withConstructor;
2+
3+
import io.mongock.api.annotations.ChangeUnit;
4+
import io.mongock.api.annotations.ChangeUnitConstructor;
5+
import io.mongock.api.annotations.Execution;
6+
import io.mongock.api.annotations.RollbackExecution;
7+
8+
@ChangeUnit(id = "ChangeUnitWithValidConstructorsHavingChangeUnitConstructor", order = "1")
9+
public class ChangeUnitWithValidConstructorsHavingChangeUnitConstructor {
10+
11+
public static final String DUMMY_VALUE = "dummyValue";
12+
private final String dummy;
13+
14+
public ChangeUnitWithValidConstructorsHavingChangeUnitConstructor(String dummy) {
15+
this.dummy = dummy;
16+
}
17+
18+
19+
@ChangeUnitConstructor
20+
public ChangeUnitWithValidConstructorsHavingChangeUnitConstructor() {
21+
this.dummy = DUMMY_VALUE;
22+
}
23+
24+
@Execution
25+
public void execution() {
26+
}
27+
28+
@RollbackExecution
29+
public void rollbackExecution() {
30+
}
31+
32+
public String getDummy() {
33+
return dummy;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.mongock.runner.core.changelogs.withConstructor;
2+
3+
import io.mongock.api.annotations.ChangeUnit;
4+
import io.mongock.api.annotations.Execution;
5+
import io.mongock.api.annotations.RollbackExecution;
6+
7+
@ChangeUnit(id = "ChangeUnitWithoutValidConstructor", order = "1")
8+
public class ChangeUnitWithoutValidConstructor {
9+
10+
private ChangeUnitWithoutValidConstructor() {
11+
}
12+
13+
@Execution
14+
public void execution() {
15+
}
16+
17+
@RollbackExecution
18+
public void rollbackExecution() {
19+
}
20+
}

mongock-core/mongock-runner/mongock-runner-core/src/test/java/io/mongock/runner/core/executor/ChangeUnitExecutorImplTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
import io.mongock.runner.core.changelogs.skipmigration.withnochangeset.ChangeLogWithNoChangeSet;
2727
import io.mongock.runner.core.changelogs.system.NewChangeUnit;
2828
import io.mongock.runner.core.changelogs.system.SystemChangeUnit;
29+
import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithDefaultConstructor;
30+
import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithMoreThanOneChangeUnitConstructor;
31+
import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithValidConstructor;
32+
import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithValidConstructorsHavingChangeUnitConstructor;
33+
import io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithoutValidConstructor;
2934
import io.mongock.runner.core.changelogs.withRollback.AdvanceChangeLogWithBefore;
3035
import io.mongock.runner.core.changelogs.withRollback.AdvanceChangeLogWithBeforeAndChangeSetFailing;
3136
import io.mongock.runner.core.changelogs.withRollback.BasicChangeLogWithExceptionInChangeSetAndRollback;
@@ -63,6 +68,8 @@
6368

6469
import static org.junit.Assert.assertEquals;
6570
import static org.junit.Assert.assertFalse;
71+
import static org.junit.Assert.assertNotNull;
72+
import static org.junit.Assert.assertThrows;
6673
import static org.junit.Assert.assertTrue;
6774
import static org.mockito.Mockito.doThrow;
6875
import static org.mockito.Mockito.mock;
@@ -964,6 +971,50 @@ public void shouldNotRollbackManuallyAnyChangeSetsAndStoreChangeEntries_whenSeco
964971

965972
}
966973

974+
@Test
975+
public void shouldNotCreateInstanceWhenNoValidConstructorExist() {
976+
ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager());
977+
MongockException mongockException = assertThrows(MongockException.class,
978+
() -> changeLogRuntime.getInstance(ChangeUnitWithoutValidConstructor.class));
979+
assertEquals("Mongock cannot find a valid constructor for " +
980+
"changeUnit[io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithoutValidConstructor]",
981+
mongockException.getMessage());
982+
}
983+
984+
@Test
985+
public void shouldNotCreateInstanceWhenMoreThanOneConstructorIsAnnotatedWithChangeUnitConstructor() {
986+
ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager());
987+
MongockException mongockException = assertThrows(MongockException.class,
988+
() -> changeLogRuntime.getInstance(ChangeUnitWithMoreThanOneChangeUnitConstructor.class));
989+
assertEquals("Found multiple constructors for" +
990+
" changeUnit[io.mongock.runner.core.changelogs.withConstructor.ChangeUnitWithMoreThanOneChangeUnitConstructor] " +
991+
"without annotation @ChangeUnitConstructor. " +
992+
"Annotate the one you want Mongock to use to instantiate your changeUnit",
993+
mongockException.getMessage());
994+
}
995+
996+
@Test
997+
public void shouldCreateInstanceWhenOnlyOneValidConstructorExistWithoutChangeUnitConstructor() {
998+
ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager());
999+
assertNotNull(changeLogRuntime.getInstance(ChangeUnitWithValidConstructor.class));
1000+
}
1001+
1002+
@Test
1003+
public void shouldCreateInstanceWithDefaultConstructor() {
1004+
ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager());
1005+
assertNotNull(changeLogRuntime.getInstance(ChangeUnitWithDefaultConstructor.class));
1006+
}
1007+
1008+
@Test
1009+
public void shouldCreateInstanceWhenOneValidConstructorExistWithChangeUnitConstructor() {
1010+
ChangeLogRuntimeImpl changeLogRuntime = getChangeLogRuntime(new DependencyManager());
1011+
ChangeUnitWithValidConstructorsHavingChangeUnitConstructor instance =
1012+
(ChangeUnitWithValidConstructorsHavingChangeUnitConstructor) changeLogRuntime
1013+
.getInstance(ChangeUnitWithValidConstructorsHavingChangeUnitConstructor.class);
1014+
assertNotNull(instance);
1015+
assertEquals(ChangeUnitWithValidConstructorsHavingChangeUnitConstructor.DUMMY_VALUE, instance.getDummy());
1016+
}
1017+
9671018
private SortedSet<ChangeLogItem> createInitialChangeLogsByPackage(Class<?>... executorChangeLogClass) {
9681019
List<String> packages = Stream.of(executorChangeLogClass)
9691020
.map(clazz -> clazz.getPackage().getName())

0 commit comments

Comments
 (0)