Skip to content

Commit 837482a

Browse files
authored
feat: order validation at compilation (#713)
* refactor: removed order in file content * feat: validate order at compilation
1 parent 5d2095d commit 837482a

File tree

25 files changed

+126
-186
lines changed

25 files changed

+126
-186
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.internal.common.core.preview;
17+
18+
import io.flamingock.internal.common.core.error.FlamingockException;
19+
20+
import java.util.Optional;
21+
import java.util.regex.Matcher;
22+
import java.util.regex.Pattern;
23+
24+
public final class ChangeOrderExtractor {
25+
26+
27+
// For template files: must start with _order__ (e.g., _002__whatever.yaml, _V1_2_3__whatever.yaml)
28+
// Recommended format: _YYYYMMDD_NN__description (e.g., _20250101_01__create_users.yaml)
29+
// Captures ORDER before double underscore separator
30+
private static final Pattern SIMPLE_FILE_ORDER_REGEX_PATTERN = Pattern.compile("^_(.+?)__(.+)$");
31+
32+
// For class names: must have _order__ at the beginning of the class name after package
33+
// (e.g., com.mycompany.mypackage._002__MyChange or com.mycompany.OuterClass$_V1_2_3__InnerChange)
34+
// Captures ORDER before double underscore separator
35+
private static final Pattern FILE_WITH_PACKAGE_ORDER_REGEX_PATTERN = Pattern.compile("[.$]_(.+?)__(.+)$");
36+
37+
private ChangeOrderExtractor() {
38+
}
39+
40+
/**
41+
* For TemplateLoadedChange - validates order from template file name
42+
*/
43+
public static String extractOrderFromFile(String changeId, String fileName) {
44+
return getOrderFromFileName(fileName, false)
45+
.orElseThrow(() -> getFlamingockException(changeId, "fileName", "yaml"));
46+
}
47+
48+
/**
49+
* For CodeLoadedChange - validates order from class name
50+
*/
51+
public static String extractOrderFromClassName(String changeId, String classPath) {
52+
return getOrderFromFileName(classPath, true)
53+
.orElseThrow(() -> getFlamingockException(changeId, "className", "java"));
54+
}
55+
56+
57+
private static Optional<String> getOrderFromFileName(String fileName, boolean withPackage) {
58+
if (fileName == null) {
59+
return Optional.empty();
60+
}
61+
Pattern pattern = withPackage ? FILE_WITH_PACKAGE_ORDER_REGEX_PATTERN : SIMPLE_FILE_ORDER_REGEX_PATTERN;
62+
63+
Matcher matcher = pattern.matcher(fileName);
64+
65+
if (matcher.find()) {
66+
return Optional.ofNullable(matcher.group(1));
67+
}
68+
69+
return Optional.empty();
70+
}
71+
72+
73+
private static FlamingockException getFlamingockException(String changeId, String fileType, String fileExt) {
74+
return new FlamingockException(String.format("Change[%s] : order must be present in the %s(e.g. _0001__%s.%s)",
75+
changeId, fileType, changeId, fileExt));
76+
}
77+
78+
}

core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/builder/CodePreviewTaskBuilder.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.flamingock.api.annotations.Recovery;
2121
import io.flamingock.api.annotations.Rollback;
2222
import io.flamingock.api.annotations.TargetSystem;
23+
import io.flamingock.internal.common.core.preview.ChangeOrderExtractor;
2324
import io.flamingock.internal.common.core.preview.CodePreviewChange;
2425
import io.flamingock.internal.common.core.preview.PreviewMethod;
2526
import io.flamingock.internal.common.core.task.RecoveryDescriptor;
@@ -135,11 +136,15 @@ CodePreviewTaskBuilder setTypeElement(TypeElement typeElement) {
135136
Change changeAnnotation = typeElement.getAnnotation(Change.class);
136137
TargetSystem targetSystemAnnotation = typeElement.getAnnotation(TargetSystem.class);
137138
Recovery recoveryAnnotation = typeElement.getAnnotation(Recovery.class);
139+
138140
if(changeAnnotation != null) {
139-
setId(changeAnnotation.id());
140-
setOrder(null);//TODO replace with order from class
141+
String changeId = changeAnnotation.id();
142+
String classPath = typeElement.getQualifiedName().toString();
143+
String order = ChangeOrderExtractor.extractOrderFromClassName(changeId, classPath);
144+
setId(changeId);
145+
setOrder(order);
141146
setAuthor(changeAnnotation.author());
142-
setSourceClassPath(typeElement.getQualifiedName().toString());
147+
setSourceClassPath(classPath);
143148
setExecutionMethod(getAnnotatedMethodInfo(typeElement, Apply.class).orElse(null));
144149
setRollbackMethod(getAnnotatedMethodInfo(typeElement, Rollback.class).orElse(null));
145150
setBeforeExecutionMethod(getAnnotatedMethodInfo(typeElement, BeforeExecution.class).orElse(null));

core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/builder/TemplatePreviewTaskBuilder.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.flamingock.internal.common.core.preview.builder;
1717

18+
import io.flamingock.internal.common.core.preview.ChangeOrderExtractor;
1819
import io.flamingock.internal.common.core.template.ChangeTemplateFileContent;
1920
import io.flamingock.internal.common.core.preview.TemplatePreviewChange;
2021
import io.flamingock.internal.common.core.task.RecoveryDescriptor;
@@ -31,7 +32,6 @@ class TemplatePreviewTaskBuilder implements PreviewTaskBuilder<TemplatePreviewCh
3132

3233
private String fileName;
3334
private String id;
34-
private String order;
3535
private String author;
3636
private String templateClassPath;
3737
private String profilesString;
@@ -65,11 +65,6 @@ public TemplatePreviewTaskBuilder setId(String id) {
6565
return this;
6666
}
6767

68-
public TemplatePreviewTaskBuilder setOrder(String order) {
69-
this.order = order;
70-
return this;
71-
}
72-
7368
public TemplatePreviewTaskBuilder setAuthor(String author) {
7469
this.author = author;
7570
return this;
@@ -117,6 +112,8 @@ public void setRecovery(RecoveryDescriptor recovery) {
117112
public TemplatePreviewChange build() {
118113

119114
List<String> profiles = getProfiles();
115+
String order = ChangeOrderExtractor.extractOrderFromFile(id, fileName);
116+
120117
return new TemplatePreviewChange(
121118
fileName,
122119
id,
@@ -147,8 +144,9 @@ private List<String> getProfiles() {
147144

148145

149146
TemplatePreviewTaskBuilder setFromDefinition(ChangeTemplateFileContent templateTaskDescriptor) {
147+
//fileName is set in "setFileName" method
148+
//order is extract from the fileName in the "build" method
150149
setId(templateTaskDescriptor.getId());
151-
setOrder(templateTaskDescriptor.getOrder());
152150
setAuthor(templateTaskDescriptor.getAuthor());
153151
setTemplate(templateTaskDescriptor.getTemplate());
154152
setProfilesString(templateTaskDescriptor.getProfiles());

core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/template/ChangeTemplateFileContent.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
public class ChangeTemplateFileContent {
2222
private String id;
23-
private String order;
2423
private String author;
2524
private String template;
2625
private String profiles; //colon-separated list of profiles
@@ -35,7 +34,6 @@ public ChangeTemplateFileContent() {
3534
}
3635

3736
public ChangeTemplateFileContent(String id,
38-
String order,
3937
String author,
4038
String template,
4139
String profiles,
@@ -46,7 +44,6 @@ public ChangeTemplateFileContent(String id,
4644
TargetSystemDescriptor targetSystem,
4745
RecoveryDescriptor recovery) {
4846
this.id = id;
49-
this.order = order;
5047
this.author = author;
5148
this.template = template;
5249
this.profiles = profiles;
@@ -67,14 +64,6 @@ public void setId(String id) {
6764
this.id = id;
6865
}
6966

70-
public String getOrder() {
71-
return order;
72-
}
73-
74-
public void setOrder(String order) {
75-
this.order = order;
76-
}
77-
7867
public String getAuthor() {
7968
return author;
8069
}
@@ -99,7 +88,6 @@ public void setProfiles(String profiles) {
9988
this.profiles = profiles;
10089
}
10190

102-
10391
public Boolean getTransactional() {
10492
return transactional;
10593
}

core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilder.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.flamingock.internal.core.task.loaded;
1717

18+
import io.flamingock.internal.common.core.preview.ChangeOrderExtractor;
1819
import io.flamingock.internal.util.StringUtil;
1920
import io.flamingock.api.annotations.Change;
2021
import io.flamingock.api.annotations.Recovery;
@@ -63,7 +64,7 @@ public static boolean supportsSourceClass(Class<?> sourceClass) {
6364

6465
private CodeLoadedTaskBuilder setPreview(CodePreviewChange preview) {
6566
setId(preview.getId());
66-
setOrderInContent(preview.getOrder().orElse(null));
67+
setOrder(preview.getOrder().orElse(null));
6768
setAuthor(preview.getAuthor());
6869
setChangeClass(preview.getSource());
6970
setRunAlways(preview.isRunAlways());
@@ -105,7 +106,7 @@ public CodeLoadedTaskBuilder setRecovery(RecoveryDescriptor recovery) {
105106
return this;
106107
}
107108

108-
public CodeLoadedTaskBuilder setOrderInContent(String orderInContent) {
109+
public CodeLoadedTaskBuilder setOrder(String orderInContent) {
109110
this.orderInContent = orderInContent;
110111
return this;
111112
}
@@ -163,8 +164,9 @@ public CodeLoadedChange build() {
163164
}
164165

165166
private void setFromFlamingockChangeAnnotation(Class<?> sourceClass, Change annotation) {
166-
setId(annotation.id());
167-
setOrderInContent(null);//TODO replace with order from class
167+
String changeId = annotation.id();
168+
setId(changeId);
169+
setOrder(ChangeOrderExtractor.extractOrderFromClassName(changeId, sourceClass.getName()));
168170
setAuthor(annotation.author());
169171
setChangeClass(sourceClass.getName());
170172
setTransactional(annotation.transactional());

core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/LoadedTaskBuilder.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ static CodeLoadedTaskBuilder getCodeBuilderInstance(Class<?> sourceClass) {
5353

5454
LoadedTaskBuilder<LOADED_TASK> setRecovery(RecoveryDescriptor recovery);
5555

56-
LoadedTaskBuilder<LOADED_TASK> setOrderInContent(String order);
57-
5856
LoadedTaskBuilder<LOADED_TASK> setRunAlways(boolean runAlways);
5957

6058
LoadedTaskBuilder<LOADED_TASK> setTransactional(boolean transactional);

core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/TemplateLoadedTaskBuilder.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class TemplateLoadedTaskBuilder implements LoadedTaskBuilder<TemplateLoad
3131

3232
private String fileName;
3333
private String id;
34-
private String orderInContent;
34+
private String order;
3535
private String author;
3636
private String templateName;
3737
private List<String> profiles;
@@ -76,8 +76,8 @@ public TemplateLoadedTaskBuilder setRecovery(RecoveryDescriptor recovery) {
7676
return this;
7777
}
7878

79-
public TemplateLoadedTaskBuilder setOrderInContent(String order) {
80-
this.orderInContent = order;
79+
public TemplateLoadedTaskBuilder setOrder(String order) {
80+
this.order = order;
8181
return this;
8282
}
8383

@@ -136,8 +136,7 @@ public TemplateLoadedChange build() {
136136
Class<? extends ChangeTemplate<?, ?, ?>> templateClass = ChangeTemplateManager.getTemplate(templateName)
137137
.orElseThrow(()-> new FlamingockException(String.format("Template[%s] not found. This is probably because template's name is wrong or template's library not imported", templateName)));
138138

139-
String order = ChangeOrderUtil.getMatchedOrderFromFile(id, orderInContent, fileName);
140-
139+
141140
return new TemplateLoadedChange(
142141
fileName,
143142
id,
@@ -157,9 +156,10 @@ public TemplateLoadedChange build() {
157156
}
158157

159158
private TemplateLoadedTaskBuilder setPreview(TemplatePreviewChange preview) {
159+
160160
setFileName(preview.getFileName());
161161
setId(preview.getId());
162-
setOrderInContent(preview.getOrder().orElse(null));
162+
setOrder(preview.getOrder().orElse(null));
163163
setAuthor(preview.getAuthor());
164164
setTemplateName(preview.getTemplateName());
165165
setProfiles(preview.getProfiles());

core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilderTest.java

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ void setUp() {
3737
void shouldBuildWithOrderInContentWhenOrderInContentPresentAndNoOrderInSource() {
3838
// Given
3939
builder.setId("test-id")
40-
.setOrderInContent("001")
40+
.setOrder("001")
4141
.setChangeClass("java.lang.String") // Using existing class for simplicity
4242
.setRunAlways(false)
4343
.setTransactional(true)
@@ -52,47 +52,12 @@ void shouldBuildWithOrderInContentWhenOrderInContentPresentAndNoOrderInSource()
5252
assertEquals(String.class, result.getImplementationClass());
5353
}
5454

55-
@Test
56-
@DisplayName("Should build with order from source when orderInContent is null and order in source is present")
57-
void shouldBuildWithOrderFromSourceWhenOrderInContentIsNullAndOrderInSourceIsPresent() {
58-
// Given
59-
builder.setId("test-id")
60-
.setOrderInContent(null)
61-
.setChangeClass("com.mypackage._002__MyClass")
62-
.setRunAlways(false)
63-
.setTransactional(true)
64-
.setSystem(false);
65-
66-
// When & Then
67-
// This will throw ClassNotFoundException since the class doesn't exist
68-
// But it will call the order validation before that, so we can test the order logic
69-
RuntimeException exception = assertThrows(RuntimeException.class, () -> builder.build());
70-
assertInstanceOf(ClassNotFoundException.class, exception.getCause());
71-
}
72-
73-
@Test
74-
@DisplayName("Should build with orderInContent when orderInContent matches order in source")
75-
void shouldBuildWithOrderInContentWhenOrderInContentMatchesOrderInSource() {
76-
// Given
77-
builder.setId("test-id")
78-
.setOrderInContent("002")
79-
.setChangeClass("java.lang._002__Test") // This will extract "002" from class name
80-
.setRunAlways(false)
81-
.setTransactional(true)
82-
.setSystem(false);
83-
84-
// When & Then
85-
// This will throw ClassNotFoundException, but order validation happens first
86-
RuntimeException exception = assertThrows(RuntimeException.class, () -> builder.build());
87-
assertInstanceOf(ClassNotFoundException.class, exception.getCause());
88-
}
89-
9055
@Test
9156
@DisplayName("Should throw exception when orderInContent does not match order in source")
9257
void shouldThrowExceptionWhenOrderInContentDoesNotMatchOrderInSource() {
9358
// Given
9459
builder.setId("test-id")
95-
.setOrderInContent("001")
60+
.setOrder("001")
9661
.setChangeClass("com.mypackage._002__MyClass")
9762
.setRunAlways(false)
9863
.setTransactional(true)
@@ -110,7 +75,7 @@ void shouldThrowExceptionWhenOrderInContentDoesNotMatchOrderInSource() {
11075
void shouldThrowExceptionWhenBothOrderInContentAndOrderInSourceAreMissing() {
11176
// Given
11277
builder.setId("test-id")
113-
.setOrderInContent(null)
78+
.setOrder(null)
11479
.setChangeClass("java.lang.String")
11580
.setRunAlways(false)
11681
.setTransactional(true)
@@ -128,7 +93,7 @@ void shouldThrowExceptionWhenBothOrderInContentAndOrderInSourceAreMissing() {
12893
void shouldBuildWithOrderFromSourceWhenOrderInContentIsEmptyString() {
12994
// Given
13095
builder.setId("test-id")
131-
.setOrderInContent("")
96+
.setOrder("")
13297
.setChangeClass("com.mypackage._004__MyClass")
13398
.setRunAlways(false)
13499
.setTransactional(true)
@@ -147,7 +112,7 @@ void shouldBuildWithOrderFromSourceWhenOrderInContentIsEmptyString() {
147112
void shouldBuildWithOrderFromSourceWhenOrderInContentIsBlankString() {
148113
// Given
149114
builder.setId("test-id")
150-
.setOrderInContent(" ")
115+
.setOrder(" ")
151116
.setChangeClass("com.mypackage._005__MyClass")
152117
.setRunAlways(false)
153118
.setTransactional(true)
@@ -166,7 +131,7 @@ void shouldBuildWithOrderFromSourceWhenOrderInContentIsBlankString() {
166131
void shouldWorkWithRealClassWhenOrderValidationPasses() {
167132
// Given - using a real class that exists
168133
builder.setId("test-id")
169-
.setOrderInContent("001")
134+
.setOrder("001")
170135
.setChangeClass("java.lang.String")
171136
.setRunAlways(false)
172137
.setTransactional(true)
@@ -189,7 +154,7 @@ void shouldWorkWithRealClassWhenOrderValidationPasses() {
189154
void shouldHandleBeforeExecutionFlagCorrectly() {
190155
// Given
191156
builder.setId("test-id")
192-
.setOrderInContent("001")
157+
.setOrder("001")
193158
.setChangeClass("java.lang.String")
194159
.setBeforeExecution(true)
195160
.setRunAlways(false)

0 commit comments

Comments
 (0)