Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2025 Flamingock (https://www.flamingock.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.flamingock.internal.common.core.preview;

import io.flamingock.internal.common.core.error.FlamingockException;

import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class ChangeOrderExtractor {


// For template files: must start with _order__ (e.g., _002__whatever.yaml, _V1_2_3__whatever.yaml)
// Recommended format: _YYYYMMDD_NN__description (e.g., _20250101_01__create_users.yaml)
// Captures ORDER before double underscore separator
private static final Pattern SIMPLE_FILE_ORDER_REGEX_PATTERN = Pattern.compile("^_(.+?)__(.+)$");

// For class names: must have _order__ at the beginning of the class name after package
// (e.g., com.mycompany.mypackage._002__MyChange or com.mycompany.OuterClass$_V1_2_3__InnerChange)
// Captures ORDER before double underscore separator
private static final Pattern FILE_WITH_PACKAGE_ORDER_REGEX_PATTERN = Pattern.compile("[.$]_(.+?)__(.+)$");

private ChangeOrderExtractor() {
}

/**
* For TemplateLoadedChange - validates order from template file name
*/
public static String extractOrderFromFile(String changeId, String fileName) {
return getOrderFromFileName(fileName, false)
.orElseThrow(() -> getFlamingockException(changeId, "fileName", "yaml"));
}

/**
* For CodeLoadedChange - validates order from class name
*/
public static String extractOrderFromClassName(String changeId, String classPath) {
return getOrderFromFileName(classPath, true)
.orElseThrow(() -> getFlamingockException(changeId, "className", "java"));
}


private static Optional<String> getOrderFromFileName(String fileName, boolean withPackage) {
if (fileName == null) {
return Optional.empty();
}
Pattern pattern = withPackage ? FILE_WITH_PACKAGE_ORDER_REGEX_PATTERN : SIMPLE_FILE_ORDER_REGEX_PATTERN;

Matcher matcher = pattern.matcher(fileName);

if (matcher.find()) {
return Optional.ofNullable(matcher.group(1));
}

return Optional.empty();
}


private static FlamingockException getFlamingockException(String changeId, String fileType, String fileExt) {
return new FlamingockException(String.format("Change[%s] : order must be present in the %s(e.g. _0001__%s.%s)",
changeId, fileType, changeId, fileExt));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.flamingock.api.annotations.Recovery;
import io.flamingock.api.annotations.Rollback;
import io.flamingock.api.annotations.TargetSystem;
import io.flamingock.internal.common.core.preview.ChangeOrderExtractor;
import io.flamingock.internal.common.core.preview.CodePreviewChange;
import io.flamingock.internal.common.core.preview.PreviewMethod;
import io.flamingock.internal.common.core.task.RecoveryDescriptor;
Expand Down Expand Up @@ -135,11 +136,15 @@ CodePreviewTaskBuilder setTypeElement(TypeElement typeElement) {
Change changeAnnotation = typeElement.getAnnotation(Change.class);
TargetSystem targetSystemAnnotation = typeElement.getAnnotation(TargetSystem.class);
Recovery recoveryAnnotation = typeElement.getAnnotation(Recovery.class);

if(changeAnnotation != null) {
setId(changeAnnotation.id());
setOrder(null);//TODO replace with order from class
String changeId = changeAnnotation.id();
String classPath = typeElement.getQualifiedName().toString();
String order = ChangeOrderExtractor.extractOrderFromClassName(changeId, classPath);
setId(changeId);
setOrder(order);
setAuthor(changeAnnotation.author());
setSourceClassPath(typeElement.getQualifiedName().toString());
setSourceClassPath(classPath);
setExecutionMethod(getAnnotatedMethodInfo(typeElement, Apply.class).orElse(null));
setRollbackMethod(getAnnotatedMethodInfo(typeElement, Rollback.class).orElse(null));
setBeforeExecutionMethod(getAnnotatedMethodInfo(typeElement, BeforeExecution.class).orElse(null));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.flamingock.internal.common.core.preview.builder;

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

private String fileName;
private String id;
private String order;
private String author;
private String templateClassPath;
private String profilesString;
Expand Down Expand Up @@ -65,11 +65,6 @@ public TemplatePreviewTaskBuilder setId(String id) {
return this;
}

public TemplatePreviewTaskBuilder setOrder(String order) {
this.order = order;
return this;
}

public TemplatePreviewTaskBuilder setAuthor(String author) {
this.author = author;
return this;
Expand Down Expand Up @@ -117,6 +112,8 @@ public void setRecovery(RecoveryDescriptor recovery) {
public TemplatePreviewChange build() {

List<String> profiles = getProfiles();
String order = ChangeOrderExtractor.extractOrderFromFile(id, fileName);

return new TemplatePreviewChange(
fileName,
id,
Expand Down Expand Up @@ -147,8 +144,9 @@ private List<String> getProfiles() {


TemplatePreviewTaskBuilder setFromDefinition(ChangeTemplateFileContent templateTaskDescriptor) {
//fileName is set in "setFileName" method
//order is extract from the fileName in the "build" method
setId(templateTaskDescriptor.getId());
setOrder(templateTaskDescriptor.getOrder());
setAuthor(templateTaskDescriptor.getAuthor());
setTemplate(templateTaskDescriptor.getTemplate());
setProfilesString(templateTaskDescriptor.getProfiles());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

public class ChangeTemplateFileContent {
private String id;
private String order;
private String author;
private String template;
private String profiles; //colon-separated list of profiles
Expand All @@ -35,7 +34,6 @@ public ChangeTemplateFileContent() {
}

public ChangeTemplateFileContent(String id,
String order,
String author,
String template,
String profiles,
Expand All @@ -46,7 +44,6 @@ public ChangeTemplateFileContent(String id,
TargetSystemDescriptor targetSystem,
RecoveryDescriptor recovery) {
this.id = id;
this.order = order;
this.author = author;
this.template = template;
this.profiles = profiles;
Expand All @@ -67,14 +64,6 @@ public void setId(String id) {
this.id = id;
}

public String getOrder() {
return order;
}

public void setOrder(String order) {
this.order = order;
}

public String getAuthor() {
return author;
}
Expand All @@ -99,7 +88,6 @@ public void setProfiles(String profiles) {
this.profiles = profiles;
}


public Boolean getTransactional() {
return transactional;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.flamingock.internal.core.task.loaded;

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

private CodeLoadedTaskBuilder setPreview(CodePreviewChange preview) {
setId(preview.getId());
setOrderInContent(preview.getOrder().orElse(null));
setOrder(preview.getOrder().orElse(null));
setAuthor(preview.getAuthor());
setChangeClass(preview.getSource());
setRunAlways(preview.isRunAlways());
Expand Down Expand Up @@ -105,7 +106,7 @@ public CodeLoadedTaskBuilder setRecovery(RecoveryDescriptor recovery) {
return this;
}

public CodeLoadedTaskBuilder setOrderInContent(String orderInContent) {
public CodeLoadedTaskBuilder setOrder(String orderInContent) {
this.orderInContent = orderInContent;
return this;
}
Expand Down Expand Up @@ -163,8 +164,9 @@ public CodeLoadedChange build() {
}

private void setFromFlamingockChangeAnnotation(Class<?> sourceClass, Change annotation) {
setId(annotation.id());
setOrderInContent(null);//TODO replace with order from class
String changeId = annotation.id();
setId(changeId);
setOrder(ChangeOrderExtractor.extractOrderFromClassName(changeId, sourceClass.getName()));
setAuthor(annotation.author());
setChangeClass(sourceClass.getName());
setTransactional(annotation.transactional());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ static CodeLoadedTaskBuilder getCodeBuilderInstance(Class<?> sourceClass) {

LoadedTaskBuilder<LOADED_TASK> setRecovery(RecoveryDescriptor recovery);

LoadedTaskBuilder<LOADED_TASK> setOrderInContent(String order);

LoadedTaskBuilder<LOADED_TASK> setRunAlways(boolean runAlways);

LoadedTaskBuilder<LOADED_TASK> setTransactional(boolean transactional);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class TemplateLoadedTaskBuilder implements LoadedTaskBuilder<TemplateLoad

private String fileName;
private String id;
private String orderInContent;
private String order;
private String author;
private String templateName;
private List<String> profiles;
Expand Down Expand Up @@ -76,8 +76,8 @@ public TemplateLoadedTaskBuilder setRecovery(RecoveryDescriptor recovery) {
return this;
}

public TemplateLoadedTaskBuilder setOrderInContent(String order) {
this.orderInContent = order;
public TemplateLoadedTaskBuilder setOrder(String order) {
this.order = order;
return this;
}

Expand Down Expand Up @@ -136,8 +136,7 @@ public TemplateLoadedChange build() {
Class<? extends ChangeTemplate<?, ?, ?>> templateClass = ChangeTemplateManager.getTemplate(templateName)
.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)));

String order = ChangeOrderUtil.getMatchedOrderFromFile(id, orderInContent, fileName);


return new TemplateLoadedChange(
fileName,
id,
Expand All @@ -157,9 +156,10 @@ public TemplateLoadedChange build() {
}

private TemplateLoadedTaskBuilder setPreview(TemplatePreviewChange preview) {

setFileName(preview.getFileName());
setId(preview.getId());
setOrderInContent(preview.getOrder().orElse(null));
setOrder(preview.getOrder().orElse(null));
setAuthor(preview.getAuthor());
setTemplateName(preview.getTemplateName());
setProfiles(preview.getProfiles());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ void setUp() {
void shouldBuildWithOrderInContentWhenOrderInContentPresentAndNoOrderInSource() {
// Given
builder.setId("test-id")
.setOrderInContent("001")
.setOrder("001")
.setChangeClass("java.lang.String") // Using existing class for simplicity
.setRunAlways(false)
.setTransactional(true)
Expand All @@ -52,47 +52,12 @@ void shouldBuildWithOrderInContentWhenOrderInContentPresentAndNoOrderInSource()
assertEquals(String.class, result.getImplementationClass());
}

@Test
@DisplayName("Should build with order from source when orderInContent is null and order in source is present")
void shouldBuildWithOrderFromSourceWhenOrderInContentIsNullAndOrderInSourceIsPresent() {
// Given
builder.setId("test-id")
.setOrderInContent(null)
.setChangeClass("com.mypackage._002__MyClass")
.setRunAlways(false)
.setTransactional(true)
.setSystem(false);

// When & Then
// This will throw ClassNotFoundException since the class doesn't exist
// But it will call the order validation before that, so we can test the order logic
RuntimeException exception = assertThrows(RuntimeException.class, () -> builder.build());
assertInstanceOf(ClassNotFoundException.class, exception.getCause());
}

@Test
@DisplayName("Should build with orderInContent when orderInContent matches order in source")
void shouldBuildWithOrderInContentWhenOrderInContentMatchesOrderInSource() {
// Given
builder.setId("test-id")
.setOrderInContent("002")
.setChangeClass("java.lang._002__Test") // This will extract "002" from class name
.setRunAlways(false)
.setTransactional(true)
.setSystem(false);

// When & Then
// This will throw ClassNotFoundException, but order validation happens first
RuntimeException exception = assertThrows(RuntimeException.class, () -> builder.build());
assertInstanceOf(ClassNotFoundException.class, exception.getCause());
}

@Test
@DisplayName("Should throw exception when orderInContent does not match order in source")
void shouldThrowExceptionWhenOrderInContentDoesNotMatchOrderInSource() {
// Given
builder.setId("test-id")
.setOrderInContent("001")
.setOrder("001")
.setChangeClass("com.mypackage._002__MyClass")
.setRunAlways(false)
.setTransactional(true)
Expand All @@ -110,7 +75,7 @@ void shouldThrowExceptionWhenOrderInContentDoesNotMatchOrderInSource() {
void shouldThrowExceptionWhenBothOrderInContentAndOrderInSourceAreMissing() {
// Given
builder.setId("test-id")
.setOrderInContent(null)
.setOrder(null)
.setChangeClass("java.lang.String")
.setRunAlways(false)
.setTransactional(true)
Expand All @@ -128,7 +93,7 @@ void shouldThrowExceptionWhenBothOrderInContentAndOrderInSourceAreMissing() {
void shouldBuildWithOrderFromSourceWhenOrderInContentIsEmptyString() {
// Given
builder.setId("test-id")
.setOrderInContent("")
.setOrder("")
.setChangeClass("com.mypackage._004__MyClass")
.setRunAlways(false)
.setTransactional(true)
Expand All @@ -147,7 +112,7 @@ void shouldBuildWithOrderFromSourceWhenOrderInContentIsEmptyString() {
void shouldBuildWithOrderFromSourceWhenOrderInContentIsBlankString() {
// Given
builder.setId("test-id")
.setOrderInContent(" ")
.setOrder(" ")
.setChangeClass("com.mypackage._005__MyClass")
.setRunAlways(false)
.setTransactional(true)
Expand All @@ -166,7 +131,7 @@ void shouldBuildWithOrderFromSourceWhenOrderInContentIsBlankString() {
void shouldWorkWithRealClassWhenOrderValidationPasses() {
// Given - using a real class that exists
builder.setId("test-id")
.setOrderInContent("001")
.setOrder("001")
.setChangeClass("java.lang.String")
.setRunAlways(false)
.setTransactional(true)
Expand All @@ -189,7 +154,7 @@ void shouldWorkWithRealClassWhenOrderValidationPasses() {
void shouldHandleBeforeExecutionFlagCorrectly() {
// Given
builder.setId("test-id")
.setOrderInContent("001")
.setOrder("001")
.setChangeClass("java.lang.String")
.setBeforeExecution(true)
.setRunAlways(false)
Expand Down
Loading
Loading