diff --git a/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/planner/CloudExecutionPlanMapper.java b/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/planner/CloudExecutionPlanMapper.java index 88337b3f1..66a75a730 100644 --- a/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/planner/CloudExecutionPlanMapper.java +++ b/cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/planner/CloudExecutionPlanMapper.java @@ -34,6 +34,7 @@ import io.flamingock.internal.core.pipeline.execution.ExecutableStage; import io.flamingock.internal.core.pipeline.loaded.stage.AbstractLoadedStage; import io.flamingock.internal.common.core.task.TaskDescriptor; +import io.flamingock.internal.core.task.loaded.AbstractLoadedTask; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -66,7 +67,7 @@ public static ExecutionPlanRequest toRequest(List loadedSta return new ExecutionPlanRequest(lockAcquiredForMillis, requestStages); } - private static TaskRequest mapToTaskRequest(TaskDescriptor descriptor, + private static TaskRequest mapToTaskRequest(AbstractLoadedTask descriptor, Map ongoingStatusesMap) { if (ongoingStatusesMap.containsKey(descriptor.getId())) { if (ongoingStatusesMap.get(descriptor.getId()) == TargetSystemAuditMarkType.ROLLBACK) { diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/AbstractPreviewTask.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/AbstractPreviewTask.java index 92738f515..5b52e1619 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/AbstractPreviewTask.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/AbstractPreviewTask.java @@ -37,7 +37,7 @@ public AbstractPreviewTask(String id, String author, String source, boolean runAlways, - boolean transactional, + Boolean transactional, boolean system, TargetSystemDescriptor targetSystem, RecoveryDescriptor recovery, @@ -60,7 +60,7 @@ public String toString() { ", order='" + order + '\'' + ", source='" + source + '\'' + ", runAlways=" + runAlways + - ", transactional=" + transactional + + ", transactionalFlag=" + transactionalFlag + ", targetSystem='" + (getTargetSystem() != null ? getTargetSystem().getId() : null) + '\'' + ", recovery='" + (getRecovery() != null ? getRecovery().getStrategy() : null) + '\'' + '}'; diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/CodePreviewChange.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/CodePreviewChange.java index e6b26ce1f..11db39d5f 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/CodePreviewChange.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/CodePreviewChange.java @@ -40,7 +40,7 @@ public CodePreviewChange(String id, PreviewMethod applyPreviewMethod, PreviewMethod rollbackPreviewMethod, boolean runAlways, - boolean transactional, + Boolean transactional, boolean system, TargetSystemDescriptor targetSystem, RecoveryDescriptor recovery, @@ -93,7 +93,7 @@ public String toString() { ", author='" + author + '\'' + ", source='" + source + '\'' + ", runAlways=" + runAlways + - ", transactional=" + transactional + + ", transactionalFlag=" + transactionalFlag + (getTargetSystem() != null ? ", targetSystem='" + getTargetSystem().getId() + '\'' : "") + (getRecovery() != null ? ", recovery='" + getRecovery().getStrategy() + '\'' : "") + '}'; diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/TemplatePreviewChange.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/TemplatePreviewChange.java index 97756aa88..45f0cfffb 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/TemplatePreviewChange.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/TemplatePreviewChange.java @@ -39,7 +39,7 @@ public TemplatePreviewChange(String fileName, String author, String templateName, List profiles, - boolean transactional, + Boolean transactional, boolean runAlways, boolean system, Object configuration, @@ -121,7 +121,7 @@ public String toString() { ", author='" + author + '\'' + ", source='" + source + '\'' + ", runAlways=" + runAlways + - ", transactional=" + transactional + + ", transactionalFlag=" + transactionalFlag + ", system=" + system + ", targetSystem='" + (getTargetSystem() != null ? getTargetSystem().getId() : null) + '\'' + ", recovery='" + (getRecovery() != null ? getRecovery().getStrategy() : null) + '\'' + diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/builder/CodePreviewTaskBuilder.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/builder/CodePreviewTaskBuilder.java index 508e917af..fc5904001 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/builder/CodePreviewTaskBuilder.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/builder/CodePreviewTaskBuilder.java @@ -50,7 +50,7 @@ public class CodePreviewTaskBuilder implements PreviewTaskBuilder getTransactionalFlag() { + return Optional.ofNullable(transactionalFlag); } @Override @@ -135,8 +135,8 @@ public void setRunAlways(boolean runAlways) { this.runAlways = runAlways; } - public void setTransactional(boolean transactional) { - this.transactional = transactional; + public void setTransactionalFlag(Boolean transactionalFlag) { + this.transactionalFlag = transactionalFlag; } public void setSystem(boolean system) { @@ -179,7 +179,7 @@ public String toString() { .add("id='" + getId() + "'") .add("author='" + getAuthor() + "'") .add("runAlways=" + isRunAlways()) - .add("transactional=" + isTransactional()) + .add("transactionalFlag=" + transactionalFlag) .add("order=" + getOrder()) .add("sortable=" + isSortable()) .add("targetSystem=" + targetSystem) diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/task/TaskDescriptor.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/task/TaskDescriptor.java index aaf5bd3ae..601b0676f 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/task/TaskDescriptor.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/task/TaskDescriptor.java @@ -26,7 +26,7 @@ public interface TaskDescriptor extends Comparable { boolean isRunAlways(); - boolean isTransactional(); + Optional getTransactionalFlag(); boolean isSystem(); diff --git a/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/preview/builder/TemplatePreviewTaskBuilderTest.java b/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/preview/builder/TemplatePreviewTaskBuilderTest.java new file mode 100644 index 000000000..c63d713ba --- /dev/null +++ b/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/preview/builder/TemplatePreviewTaskBuilderTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2026 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.builder; + +import io.flamingock.internal.common.core.preview.TemplatePreviewChange; +import io.flamingock.internal.common.core.task.TargetSystemDescriptor; +import io.flamingock.internal.common.core.template.ChangeTemplateFileContent; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("TemplatePreviewTaskBuilder transactional nullable support") +class TemplatePreviewTaskBuilderTest { + + @Test + @DisplayName("Should preserve null transactional from YAML") + void shouldPreserveNullTransactionalFromYaml() { + ChangeTemplateFileContent content = new ChangeTemplateFileContent( + "test-id", "author", "SqlTemplate", null, + null, null, "CREATE TABLE", null, + TargetSystemDescriptor.fromId("postgresql"), null); + + TemplatePreviewChange preview = TemplatePreviewTaskBuilder.builder(content) + .setFileName("_0001__test.yaml") + .build(); + + assertEquals(Optional.empty(), preview.getTransactionalFlag()); + } + + @Test + @DisplayName("Should preserve explicit true transactional from YAML") + void shouldPreserveExplicitTrueTransactional() { + ChangeTemplateFileContent content = new ChangeTemplateFileContent( + "test-id", "author", "SqlTemplate", null, + true, null, "CREATE TABLE", null, + TargetSystemDescriptor.fromId("postgresql"), null); + + TemplatePreviewChange preview = TemplatePreviewTaskBuilder.builder(content) + .setFileName("_0001__test.yaml") + .build(); + + assertEquals(Optional.of(true), preview.getTransactionalFlag()); + } + + @Test + @DisplayName("Should preserve explicit false transactional from YAML") + void shouldPreserveExplicitFalseTransactional() { + ChangeTemplateFileContent content = new ChangeTemplateFileContent( + "test-id", "author", "SqlTemplate", null, + false, null, "CREATE TABLE", null, + TargetSystemDescriptor.fromId("postgresql"), null); + + TemplatePreviewChange preview = TemplatePreviewTaskBuilder.builder(content) + .setFileName("_0001__test.yaml") + .build(); + + assertEquals(Optional.of(false), preview.getTransactionalFlag()); + } +} diff --git a/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/task/AbstractTaskDescriptorTest.java b/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/task/AbstractTaskDescriptorTest.java new file mode 100644 index 000000000..8e236604f --- /dev/null +++ b/core/flamingock-core-commons/src/test/java/io/flamingock/internal/common/core/task/AbstractTaskDescriptorTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2026 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.task; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("AbstractTaskDescriptor transactional nullable support") +class AbstractTaskDescriptorTest { + + private static AbstractTaskDescriptor createDescriptor(Boolean transactional) { + return new AbstractTaskDescriptor( + "test-id", "001", "author", "source", + false, transactional, false, + null, null, false + ) {}; + } + + @Test + @DisplayName("getTransactionalFlag() returns empty when field is null") + void getTransactionalReturnsEmptyWhenNull() { + AbstractTaskDescriptor descriptor = createDescriptor(null); + assertEquals(Optional.empty(), descriptor.getTransactionalFlag()); + } + + @Test + @DisplayName("getTransactionalFlag() returns Optional.of(true) when field is true") + void getTransactionalReturnsPresentWhenTrue() { + AbstractTaskDescriptor descriptor = createDescriptor(true); + assertEquals(Optional.of(true), descriptor.getTransactionalFlag()); + } + + @Test + @DisplayName("getTransactionalFlag() returns Optional.of(false) when field is false") + void getTransactionalReturnsPresentWhenFalse() { + AbstractTaskDescriptor descriptor = createDescriptor(false); + assertEquals(Optional.of(false), descriptor.getTransactionalFlag()); + } + + @Test + @DisplayName("setTransactionalFlag() accepts null") + void setTransactionalFlagAcceptsNull() { + AbstractTaskDescriptor descriptor = createDescriptor(true); + descriptor.setTransactionalFlag(null); + assertEquals(Optional.empty(), descriptor.getTransactionalFlag()); + } + + @Test + @DisplayName("toString() handles null transactional without throwing") + void toStringHandlesNullTransactional() { + AbstractTaskDescriptor descriptor = createDescriptor(null); + assertDoesNotThrow(() -> descriptor.toString()); + assertTrue(descriptor.toString().contains("transactionalFlag=null")); + } +} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/AuditContextBundle.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/AuditContextBundle.java index 6de394b98..9dc713ef7 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/AuditContextBundle.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/AuditContextBundle.java @@ -20,7 +20,7 @@ import io.flamingock.internal.common.core.audit.AuditEntry; import io.flamingock.internal.common.cloud.vo.TargetSystemAuditMarkType; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.common.core.task.TaskDescriptor; +import io.flamingock.internal.core.task.loaded.AbstractLoadedTask; import static io.flamingock.internal.common.core.audit.AuditEntry.ChangeType.MONGOCK_BEFORE; import static io.flamingock.internal.common.core.audit.AuditEntry.ChangeType.MONGOCK_EXECUTION; @@ -44,14 +44,14 @@ public static AuditContextBundle.Operation fromOngoingStatusOperation(TargetSyst } private final Operation operation; - private final TaskDescriptor changeDescriptor; + private final AbstractLoadedTask changeDescriptor; private final ExecutionContext executionContext; private final RuntimeContext runtimeContext; private final AuditTxType operationType; private final String targetSystemId; public AuditContextBundle(Operation operation, - TaskDescriptor changeDescriptor, + AbstractLoadedTask changeDescriptor, ExecutionContext executionContext, RuntimeContext runtimeContext, AuditTxType auditTxType, @@ -68,7 +68,7 @@ public Operation getOperation() { return operation; } - public TaskDescriptor getChangeDescriptor() { + public AbstractLoadedTask getChangeDescriptor() { return changeDescriptor; } @@ -90,7 +90,7 @@ public String getTargetSystemId() { public AuditEntry toAuditEntry() { - TaskDescriptor loadedChange = getChangeDescriptor(); + AbstractLoadedTask loadedChange = getChangeDescriptor(); ExecutionContext stageExecutionContext = getExecutionContext(); RuntimeContext runtimeContext = getRuntimeContext(); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/ExecutionAuditContextBundle.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/ExecutionAuditContextBundle.java index d05e72908..616c69d2b 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/ExecutionAuditContextBundle.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/ExecutionAuditContextBundle.java @@ -17,11 +17,11 @@ import io.flamingock.internal.common.core.audit.AuditTxType; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.common.core.task.TaskDescriptor; +import io.flamingock.internal.core.task.loaded.AbstractLoadedTask; public class ExecutionAuditContextBundle extends AuditContextBundle { - public ExecutionAuditContextBundle(TaskDescriptor loadedTask, ExecutionContext executionContext, RuntimeContext runtimeContext, AuditTxType auditTxType, String targetSystemId) { + public ExecutionAuditContextBundle(AbstractLoadedTask loadedTask, ExecutionContext executionContext, RuntimeContext runtimeContext, AuditTxType auditTxType, String targetSystemId) { super(Operation.EXECUTION, loadedTask, executionContext, runtimeContext, auditTxType, targetSystemId); } } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/RollbackAuditContextBundle.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/RollbackAuditContextBundle.java index a9bf08dee..cf5b01512 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/RollbackAuditContextBundle.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/RollbackAuditContextBundle.java @@ -17,11 +17,11 @@ import io.flamingock.internal.common.core.audit.AuditTxType; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.common.core.task.TaskDescriptor; +import io.flamingock.internal.core.task.loaded.AbstractLoadedTask; public class RollbackAuditContextBundle extends AuditContextBundle { - public RollbackAuditContextBundle(TaskDescriptor loadedTask, ExecutionContext executionContext, RuntimeContext runtimeContext, AuditTxType auditTxType, String targetSystemId) { + public RollbackAuditContextBundle(AbstractLoadedTask loadedTask, ExecutionContext executionContext, RuntimeContext runtimeContext, AuditTxType auditTxType, String targetSystemId) { super(Operation.ROLLBACK, loadedTask, executionContext, runtimeContext, auditTxType, targetSystemId); } } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/StartExecutionAuditContextBundle.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/StartExecutionAuditContextBundle.java index 946e528f8..c6a2c7726 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/StartExecutionAuditContextBundle.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/audit/domain/StartExecutionAuditContextBundle.java @@ -17,11 +17,11 @@ import io.flamingock.internal.common.core.audit.AuditTxType; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.common.core.task.TaskDescriptor; +import io.flamingock.internal.core.task.loaded.AbstractLoadedTask; public class StartExecutionAuditContextBundle extends AuditContextBundle { - public StartExecutionAuditContextBundle(TaskDescriptor loadedTask, ExecutionContext executionContext, RuntimeContext runtimeContext, AuditTxType auditTxType, String targetSystemId) { + public StartExecutionAuditContextBundle(AbstractLoadedTask loadedTask, ExecutionContext executionContext, RuntimeContext runtimeContext, AuditTxType auditTxType, String targetSystemId) { super(Operation.START_EXECUTION, loadedTask, executionContext, runtimeContext, auditTxType, targetSystemId); } } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ChangeResultBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ChangeResultBuilder.java index 34c57f053..682b11235 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ChangeResultBuilder.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ChangeResultBuilder.java @@ -40,7 +40,7 @@ public ChangeResultBuilder() { } public ChangeResultBuilder fromTask(ExecutableTask task) { - TaskDescriptor descriptor = task.getDescriptor(); + TaskDescriptor descriptor = task.getLoadedChange(); this.changeId = descriptor.getId(); this.author = descriptor.getAuthor(); if (descriptor.getTargetSystem() != null) { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/ExecutableStage.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/ExecutableStage.java index b7a79036c..de6f18d3e 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/ExecutableStage.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/ExecutableStage.java @@ -40,7 +40,7 @@ public String getName() { @Override public Collection getLoadedTasks() { - return tasks.stream().map(ExecutableTask::getDescriptor).collect(Collectors.toList()); + return tasks.stream().map(ExecutableTask::getLoadedChange).collect(Collectors.toList()); } public List getTasks() { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/AbstractExecutableTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/AbstractExecutableTask.java index 5393d0e2c..2f4581021 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/AbstractExecutableTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/AbstractExecutableTask.java @@ -16,72 +16,77 @@ package io.flamingock.internal.core.task.executable; import io.flamingock.internal.common.core.task.RecoveryDescriptor; -import io.flamingock.internal.common.core.task.TaskDescriptor; import io.flamingock.internal.common.core.task.TargetSystemDescriptor; import io.flamingock.internal.common.core.recovery.action.ChangeAction; +import io.flamingock.internal.core.task.loaded.AbstractLoadedTask; import java.util.Objects; import java.util.Optional; -public abstract class AbstractExecutableTask implements ExecutableTask { +public abstract class AbstractExecutableTask implements ExecutableTask { private final String stageName; - protected final DESCRIPTOR descriptor; + protected final LOADED_CHANGE loadedChange; protected final ChangeAction action; public AbstractExecutableTask(String stageName, - DESCRIPTOR descriptor, + LOADED_CHANGE loadedChange, ChangeAction action) { - if (descriptor == null) { - throw new IllegalArgumentException("task descriptor cannot be null"); + if (loadedChange == null) { + throw new IllegalArgumentException("loaded change cannot be null"); } if (action == null) { throw new IllegalArgumentException("change action cannot be null"); } this.stageName = stageName; - this.descriptor = descriptor; + this.loadedChange = loadedChange; this.action = action; } @Override public String getId() { - return descriptor.getId(); + return loadedChange.getId(); } @Override public String getAuthor() { - return descriptor.getAuthor(); + return loadedChange.getAuthor(); } @Override public boolean isRunAlways() { - return descriptor.isRunAlways(); + return loadedChange.isRunAlways(); } @Override public boolean isTransactional() { - return descriptor.isTransactional(); + return loadedChange.isTransactional(); + } + + @Override + public Optional getTransactionalFlag() { + return loadedChange.getTransactionalFlag(); } @Override public boolean isSystem() { - return descriptor.isSystem(); + return loadedChange.isSystem(); } @Override public boolean isLegacy() { - return descriptor.isLegacy(); + return loadedChange.isLegacy(); } @Override public String getSource() { - return descriptor.getSource(); + return loadedChange.getSource(); } @Override public Optional getOrder() { - return descriptor.getOrder(); + return loadedChange.getOrder(); } @Override @@ -90,18 +95,18 @@ public String getStageName() { } @Override - public DESCRIPTOR getDescriptor() { - return descriptor; + public LOADED_CHANGE getLoadedChange() { + return loadedChange; } @Override public TargetSystemDescriptor getTargetSystem() { - return descriptor.getTargetSystem(); + return loadedChange.getTargetSystem(); } @Override public RecoveryDescriptor getRecovery() { - return descriptor.getRecovery(); + return loadedChange.getRecovery(); } @Override @@ -119,18 +124,18 @@ public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AbstractExecutableTask)) return false; AbstractExecutableTask that = (AbstractExecutableTask) o; - return descriptor.equals(that.descriptor); + return loadedChange.equals(that.loadedChange); } @Override public int hashCode() { - return Objects.hash(descriptor); + return Objects.hash(loadedChange); } @Override public String toString() { return "ExecutableTask{" + - "id='" + descriptor.getId() + '\'' + + "id='" + loadedChange.getId() + '\'' + ", action=" + action + ", targetSystem='" + (getTargetSystem() != null ? getTargetSystem().getId() : null) + '\'' + ", recovery='" + (getRecovery() != null ? getRecovery().getStrategy() : null) + '\'' + diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/AbstractTemplateExecutableTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/AbstractTemplateExecutableTask.java index 38a47131c..0dafff4f7 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/AbstractTemplateExecutableTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/AbstractTemplateExecutableTask.java @@ -34,29 +34,29 @@ * @param the configuration type for the template * @param the apply payload type * @param the rollback payload type - * @param the type of template loaded change + * @param the type of template loaded change */ public abstract class AbstractTemplateExecutableTask> extends ReflectionExecutableTask { - protected final Logger logger = FlamingockLoggerFactory.getLogger("TemplateTask"); + LOADED_CHANGE extends AbstractTemplateLoadedChange> extends ReflectionExecutableTask { + protected final Logger logger = FlamingockLoggerFactory.getLogger("TemplateExecutableChange"); public AbstractTemplateExecutableTask(String stageName, - T descriptor, + LOADED_CHANGE loadedChange, ChangeAction action, Method executionMethod, Method rollbackMethod) { - super(stageName, descriptor, action, executionMethod, rollbackMethod); + super(stageName, loadedChange, action, executionMethod, rollbackMethod); } protected void setConfigurationData(ChangeTemplate instance) { Class parameterClass = instance.getConfigurationClass(); - CONFIG data = descriptor.getConfigurationPayload(); + CONFIG data = loadedChange.getConfigurationPayload(); if (data != null && TemplateVoid.class != parameterClass) { instance.setConfiguration(data); } else if (TemplateVoid.class != parameterClass) { logger.warn("No 'Configuration' section provided for template-based change[{}] of type[{}]", - descriptor.getId(), descriptor.getTemplateClass().getName()); + loadedChange.getId(), loadedChange.getTemplateClass().getName()); } } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/CodeExecutableTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/CodeExecutableTask.java index 23ea02912..173100324 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/CodeExecutableTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/CodeExecutableTask.java @@ -21,7 +21,6 @@ import io.flamingock.internal.core.task.loaded.AbstractReflectionLoadedTask; import java.lang.reflect.Method; -import java.util.List; /** * This class is a reflection version of the ExecutableTask. @@ -36,16 +35,16 @@ * However, the methods are extracted in advance, so we can spot wrong configuration before starting the process and * fail fast. */ -public class CodeExecutableTask - extends ReflectionExecutableTask { +public class CodeExecutableTask + extends ReflectionExecutableTask { public CodeExecutableTask(String stageName, - REFLECTION_TASK_DESCRIPTOR descriptor, + LOADED_CHANGE loadedChange, ChangeAction action, Method executionMethod, Method rollbackMethod) { - super(stageName, descriptor, action, executionMethod, rollbackMethod); + super(stageName, loadedChange, action, executionMethod, rollbackMethod); } @@ -61,7 +60,7 @@ public void rollback(ExecutionRuntime executionRuntime) { } protected void executeInternal(ExecutionRuntime executionRuntime, Method method ) { - Object instance = executionRuntime.getInstance(descriptor.getConstructor()); + Object instance = executionRuntime.getInstance(loadedChange.getConstructor()); try { executionRuntime.executeMethodWithInjectedDependencies(instance, method); } catch (Throwable ex) { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/ExecutableTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/ExecutableTask.java index 62f415633..50ec9df26 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/ExecutableTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/ExecutableTask.java @@ -19,11 +19,11 @@ import io.flamingock.internal.common.core.task.TaskDescriptor; import io.flamingock.internal.common.core.recovery.action.ChangeAction; -import java.util.List; - public interface ExecutableTask extends TaskDescriptor { - TaskDescriptor getDescriptor(); + boolean isTransactional(); + + TaskDescriptor getLoadedChange(); String getStageName(); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/ReflectionExecutableTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/ReflectionExecutableTask.java index 1de857237..c64f793e0 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/ReflectionExecutableTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/ReflectionExecutableTask.java @@ -15,14 +15,10 @@ */ package io.flamingock.internal.core.task.executable; -import io.flamingock.internal.common.core.error.ChangeExecutionException; -import io.flamingock.internal.core.runtime.ExecutionRuntime; import io.flamingock.internal.core.task.loaded.AbstractReflectionLoadedTask; import io.flamingock.internal.common.core.recovery.action.ChangeAction; import java.lang.reflect.Method; -import java.util.LinkedList; -import java.util.List; /** * This class is a reflection version of the ExecutableTask. @@ -37,19 +33,19 @@ * However, the methods are extracted in advance, so we can spot wrong configuration before starting the process and * fail fast. */ -public abstract class ReflectionExecutableTask - extends AbstractExecutableTask implements ExecutableTask { +public abstract class ReflectionExecutableTask + extends AbstractExecutableTask implements ExecutableTask { protected final Method executionMethod; protected final Method rollbackMethod; public ReflectionExecutableTask(String stageName, - REFLECTION_TASK_DESCRIPTOR descriptor, + LOADED_CHANGE loadedChange, ChangeAction action, Method executionMethod, Method rollbackMethod) { - super(stageName, descriptor, action); + super(stageName, loadedChange, action); this.executionMethod = executionMethod; this.rollbackMethod = rollbackMethod; } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/SimpleTemplateExecutableTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/SimpleTemplateExecutableTask.java index be2e05467..16e0947b3 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/SimpleTemplateExecutableTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/SimpleTemplateExecutableTask.java @@ -38,37 +38,37 @@ public class SimpleTemplateExecutableTask> { public SimpleTemplateExecutableTask(String stageName, - SimpleTemplateLoadedChange descriptor, + SimpleTemplateLoadedChange loadedChange, ChangeAction action, Method executionMethod, Method rollbackMethod) { - super(stageName, descriptor, action, executionMethod, rollbackMethod); + super(stageName, loadedChange, action, executionMethod, rollbackMethod); } @Override public void apply(ExecutionRuntime executionRuntime) { - logger.debug("Applying change[{}] with template: {}", descriptor.getId(), descriptor.getTemplateClass()); - logger.debug("change[{}] transactional: {}", descriptor.getId(), descriptor.isTransactional()); + logger.debug("Applying change[{}] with template: {}", loadedChange.getId(), loadedChange.getTemplateClass()); + logger.debug("change[{}] transactional: {}", loadedChange.getId(), loadedChange.isTransactional()); executeInternal(executionRuntime, executionMethod); } @Override public void rollback(ExecutionRuntime executionRuntime) { - logger.debug("Rolling back change[{}] with template: {}", descriptor.getId(), descriptor.getTemplateClass()); + logger.debug("Rolling back change[{}] with template: {}", loadedChange.getId(), loadedChange.getTemplateClass()); executeInternal(executionRuntime, rollbackMethod); } protected void executeInternal(ExecutionRuntime executionRuntime, Method method) { try { AbstractChangeTemplate instance = (AbstractChangeTemplate) - executionRuntime.getInstance(descriptor.getConstructor()); + executionRuntime.getInstance(loadedChange.getConstructor()); - instance.setTransactional(descriptor.isTransactional()); - instance.setChangeId(descriptor.getId()); - logger.trace("Setting payloads for simple template change[{}]", descriptor.getId()); + instance.setTransactional(loadedChange.isTransactional()); + instance.setChangeId(loadedChange.getId()); + logger.trace("Setting payloads for simple template change[{}]", loadedChange.getId()); setConfigurationData(instance); - instance.setApplyPayload(descriptor.getApplyPayload()); - instance.setRollbackPayload(descriptor.getRollbackPayload()); + instance.setApplyPayload(loadedChange.getApplyPayload()); + instance.setRollbackPayload(loadedChange.getRollbackPayload()); executionRuntime.executeMethodWithInjectedDependencies(instance, method); } catch (Throwable ex) { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/SteppableTemplateExecutableTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/SteppableTemplateExecutableTask.java index 0c5a8fabf..5fb37181a 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/SteppableTemplateExecutableTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/executable/SteppableTemplateExecutableTask.java @@ -43,11 +43,11 @@ public class SteppableTemplateExecutableTask descriptor, + MultiStepTemplateLoadedChange loadedChange, ChangeAction action, Method executionMethod, Method rollbackMethod) { - super(stageName, descriptor, action, executionMethod, rollbackMethod); + super(stageName, loadedChange, action, executionMethod, rollbackMethod); } @Override @@ -55,7 +55,7 @@ public void apply(ExecutionRuntime executionRuntime) { AbstractChangeTemplate instance = buildInstance(executionRuntime); try { - List> steps = descriptor.getSteps(); + List> steps = loadedChange.getSteps(); while (stepIndex >= -1 && stepIndex + 1 < steps.size()) { TemplateStep currentSep = steps.get(stepIndex + 1); instance.setApplyPayload(currentSep.getApplyPayload()); @@ -72,7 +72,7 @@ public void rollback(ExecutionRuntime executionRuntime) { AbstractChangeTemplate instance = buildInstance(executionRuntime); try { - List> steps = descriptor.getSteps(); + List> steps = loadedChange.getSteps(); while (stepIndex >= 0 && stepIndex < steps.size()) { TemplateStep currentSep = steps.get(stepIndex); if(currentSep.hasRollbackPayload() && rollbackMethod != null) { @@ -93,14 +93,14 @@ public void rollback(ExecutionRuntime executionRuntime) { private AbstractChangeTemplate buildInstance(ExecutionRuntime executionRuntime) { AbstractChangeTemplate instance; try { - logger.debug("Starting execution of change[{}] with template: {}", descriptor.getId(), descriptor.getTemplateClass()); - logger.debug("change[{}] transactional: {}", descriptor.getId(), descriptor.isTransactional()); + logger.debug("Starting execution of change[{}] with template: {}", loadedChange.getId(), loadedChange.getTemplateClass()); + logger.debug("change[{}] transactional: {}", loadedChange.getId(), loadedChange.isTransactional()); instance = (AbstractChangeTemplate) - executionRuntime.getInstance(descriptor.getConstructor()); + executionRuntime.getInstance(loadedChange.getConstructor()); - instance.setTransactional(descriptor.isTransactional()); - instance.setChangeId(descriptor.getId()); + instance.setTransactional(loadedChange.isTransactional()); + instance.setChangeId(loadedChange.getId()); setConfigurationData(instance); } catch (Throwable ex) { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractLoadedChange.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractLoadedChange.java index 9f9add32c..f7ba54a9d 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractLoadedChange.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractLoadedChange.java @@ -51,12 +51,13 @@ protected AbstractLoadedChange(String fileName, Class implementationClass, Constructor constructor, boolean runAlways, + Boolean transactionalFlag, boolean transactional, boolean system, TargetSystemDescriptor targetSystem, RecoveryDescriptor recovery, boolean legacy) { - super(fileName, id, order, author, implementationClass, runAlways, transactional, system, targetSystem, recovery, legacy); + super(fileName, id, order, author, implementationClass, runAlways, transactionalFlag, transactional, system, targetSystem, recovery, legacy); this.constructor = constructor; } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractLoadedTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractLoadedTask.java index 37f697720..50ad55112 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractLoadedTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractLoadedTask.java @@ -29,17 +29,25 @@ public abstract class AbstractLoadedTask extends AbstractTaskDescriptor implements Validatable { + private final boolean transactional; + public AbstractLoadedTask(String id, String order, String author, String implementationSourceName, boolean runAlways, + Boolean transactionalFlag, boolean transactional, boolean system, TargetSystemDescriptor targetSystem, RecoveryDescriptor recovery, boolean legacy) { - super(id, order, author, implementationSourceName, runAlways, transactional, system, targetSystem, recovery, legacy); + super(id, order, author, implementationSourceName, runAlways, transactionalFlag, system, targetSystem, recovery, legacy); + this.transactional = transactional; + } + + public boolean isTransactional() { + return transactional; } public abstract Constructor getConstructor(); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractReflectionLoadedTask.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractReflectionLoadedTask.java index ee95c8499..e02885671 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractReflectionLoadedTask.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractReflectionLoadedTask.java @@ -97,12 +97,13 @@ public AbstractReflectionLoadedTask(String fileName, String author, Class implementationClass, boolean runAlways, + Boolean transactionalFlag, boolean transactional, boolean system, TargetSystemDescriptor targetSystem, RecoveryDescriptor recovery, boolean legacy) { - super(id, order, author, implementationClass.getName(), runAlways, transactional, system, targetSystem, recovery, legacy); + super(id, order, author, implementationClass.getName(), runAlways, transactionalFlag, transactional, system, targetSystem, recovery, legacy); this.fileName = fileName; this.implementationClass = implementationClass; } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractTemplateLoadedChange.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractTemplateLoadedChange.java index 8c59306f1..9f5eda43a 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractTemplateLoadedChange.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/AbstractTemplateLoadedChange.java @@ -58,6 +58,7 @@ protected AbstractTemplateLoadedChange(String changeFileName, Class> templateClass, Constructor constructor, List profiles, + Boolean transactionalFlag, boolean transactional, boolean runAlways, boolean systemTask, @@ -65,9 +66,8 @@ protected AbstractTemplateLoadedChange(String changeFileName, TargetSystemDescriptor targetSystem, RecoveryDescriptor recovery, boolean rollbackPayloadRequired) { - super(changeFileName, id, order, author, templateClass, constructor, runAlways, transactional, systemTask, targetSystem, recovery, false); + super(changeFileName, id, order, author, templateClass, constructor, runAlways, transactionalFlag, transactional, systemTask, targetSystem, recovery, false); this.profiles = profiles; - this.transactional = transactional; this.configurationPayload = configurationPayload; this.rollbackPayloadRequired = rollbackPayloadRequired; } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedChange.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedChange.java index 1c53f7175..3503b2fe5 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedChange.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedChange.java @@ -36,12 +36,13 @@ public class CodeLoadedChange extends AbstractLoadedChange { Method applyMethod, Optional rollbackMethod, boolean runAlways, + Boolean transactionalFlag, boolean transactional, boolean systemTask, TargetSystemDescriptor targetSystem, RecoveryDescriptor recovery, boolean legacy) { - super(changeClass.getSimpleName(), id, order, author, changeClass, constructor, runAlways, transactional, systemTask, targetSystem, recovery, legacy); + super(changeClass.getSimpleName(), id, order, author, changeClass, constructor, runAlways, transactionalFlag, transactional, systemTask, targetSystem, recovery, legacy); this.applyMethod = applyMethod; this.rollbackMethod = rollbackMethod; } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilder.java index 14b06a414..a5a5b5777 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilder.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilder.java @@ -43,7 +43,7 @@ public class CodeLoadedTaskBuilder implements LoadedTaskBuilder rollbackMethod; private boolean isRunAlways; - private boolean isTransactional; + private Boolean transactionalFlag; private boolean isSystem; private TargetSystemDescriptor targetSystem; private RecoveryDescriptor recovery; @@ -82,7 +82,7 @@ private CodeLoadedTaskBuilder setPreview(CodePreviewChange preview) { setApplyMethod(getApplyMethodFromPreview(preview)); setRollbackMethod(getRollbackMethodFromPreview(preview)); setRunAlways(preview.isRunAlways()); - setTransactional(preview.isTransactional()); + setTransactionalFlag(preview.getTransactionalFlag().orElse(null)); setSystem(preview.isSystem()); setTargetSystem(preview.getTargetSystem()); setRecovery(preview.getRecovery()); @@ -146,8 +146,8 @@ public CodeLoadedTaskBuilder setRunAlways(boolean runAlways) { return this; } - public CodeLoadedTaskBuilder setTransactional(boolean transactional) { - this.isTransactional = transactional; + public CodeLoadedTaskBuilder setTransactionalFlag(Boolean transactionalFlag) { + this.transactionalFlag = transactionalFlag; return this; } @@ -177,6 +177,7 @@ public CodeLoadedChange build() { Class changeClass = getClassForName(changeClassName); String order = ChangeOrderUtil.getMatchedOrderFromClassName(id, orderInContent, changeClassName); + boolean resolvedTransactional = transactionalFlag != null ? transactionalFlag : true; return new CodeLoadedChange( id, order, @@ -186,7 +187,8 @@ public CodeLoadedChange build() { applyMethod, rollbackMethod, isRunAlways, - isTransactional, + transactionalFlag, + resolvedTransactional, isSystem, targetSystem, recovery, @@ -203,7 +205,7 @@ private void setFromFlamingockChangeAnnotation(Class sourceClass, Change anno setConstructor(getConstructor(sourceClass)); setApplyMethod(getApplyMethodFromAnnotation(sourceClass)); setRollbackMethod(getRollbackMethodFromAnnotation(sourceClass)); - setTransactional(annotation.transactional()); + setTransactionalFlag(annotation.transactional()); setSystem(false); setRecoveryFromClass(sourceClass); setLegacy(false); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/LoadedTaskBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/LoadedTaskBuilder.java index 34a30b442..9f4bcaee1 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/LoadedTaskBuilder.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/LoadedTaskBuilder.java @@ -55,7 +55,7 @@ static CodeLoadedTaskBuilder getCodeBuilderInstance(Class sourceClass) { LoadedTaskBuilder setRunAlways(boolean runAlways); - LoadedTaskBuilder setTransactional(boolean transactional); + LoadedTaskBuilder setTransactionalFlag(Boolean transactionalFlag); LoadedTaskBuilder setSystem(boolean system); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/MultiStepTemplateLoadedChange.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/MultiStepTemplateLoadedChange.java index 294bc2795..48f2b0483 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/MultiStepTemplateLoadedChange.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/loaded/MultiStepTemplateLoadedChange.java @@ -52,6 +52,7 @@ public class MultiStepTemplateLoadedChange> templateClass, Constructor constructor, List profiles, + Boolean transactionalFlag, boolean transactional, boolean runAlways, boolean systemTask, @@ -60,7 +61,7 @@ public class MultiStepTemplateLoadedChange> templateClass, Constructor constructor, List profiles, + Boolean transactionalFlag, boolean transactional, boolean runAlways, boolean systemTask, @@ -62,7 +63,7 @@ public class SimpleTemplateLoadedChange> { + private static final Logger logger = FlamingockLoggerFactory.getLogger(TemplateLoadedTaskBuilder.class); + private static final TemplateValidator DEFAULT_VALIDATOR = new TemplateValidator(); private String fileName; @@ -52,7 +57,7 @@ public class TemplateLoadedTaskBuilder implements LoadedTaskBuilder profiles; private boolean runAlways; - private boolean transactional; + private Boolean transactionalFlag; private boolean system; private Object configuration; private Object applyPayload; @@ -132,8 +137,8 @@ public TemplateLoadedTaskBuilder setRunAlways(boolean runAlways) { return this; } - public TemplateLoadedTaskBuilder setTransactional(boolean transactional) { - this.transactional = transactional; + public TemplateLoadedTaskBuilder setTransactionalFlag(Boolean transactionalFlag) { + this.transactionalFlag = transactionalFlag; return this; } @@ -208,6 +213,7 @@ public TemplateLoadedTaskBuilder setSteps(Object steps) { private MultiStepTemplateLoadedChange getLoadedMultiStepTemplateChange(Class steppableTemplateClass, Constructor constructor, boolean rollbackPayloadRequired) { List> convertedSteps = convertSteps(constructor, steps);// Convert apply/rollback to typed payloads at load time TemplatePayload convertedConfig = convertConfiguration(constructor, configuration); + boolean resolvedTransactional = resolveTransactionalFromSteps(convertedSteps); return new MultiStepTemplateLoadedChange( fileName, id, @@ -216,7 +222,8 @@ private MultiStepTemplateLoadedChange getLoadedMultiStepTemplateChange(Class simpleTemplateClass, Constructor constructor, boolean rollbackPayloadRequired) { Pair convertedPayloads = convertPayloads(constructor, applyPayload, rollbackPayload);// Convert apply/rollback to typed payloads at load time TemplatePayload convertedConfig = convertConfiguration(constructor, configuration); + boolean resolvedTransactional = resolveTransactional((TemplatePayload) convertedPayloads.getFirst()); return new SimpleTemplateLoadedChange( fileName, id, @@ -239,7 +247,8 @@ private SimpleTemplateLoadedChange getLoadedSimpleTemplateChange(Class constructor, Object return (TemplatePayload) FileUtil.convertToType(configClass, configData); } + /** + * Infers transactional from apply payloads when the user didn't specify it. + * Any payload claiming supportsTransactions=false makes the change non-transactional. + */ + private static boolean inferTransactionalFromPayloads(List applyPayloads) { + for (TemplatePayload payload : applyPayloads) { + if (payload == null) continue; + Optional supports = payload.getInfo().getSupportsTransactions(); + if (supports.isPresent() && !supports.get()) { + return false; + } + } + return true; + } + + private boolean resolveTransactional(TemplatePayload applyPayload) { + if (transactionalFlag != null) return transactionalFlag; + boolean inferred = inferTransactionalFromPayloads( + applyPayload != null ? Collections.singletonList(applyPayload) : Collections.emptyList()); + logTransactionalInference(inferred); + return inferred; + } + + private boolean resolveTransactionalFromSteps(List> convertedSteps) { + if (transactionalFlag != null) return transactionalFlag; + if (convertedSteps == null || convertedSteps.isEmpty()) { + logTransactionalInference(true); + return true; + } + List applyPayloads = new ArrayList<>(); + for (TemplateStep step : convertedSteps) { + applyPayloads.add((TemplatePayload) step.getApplyPayload()); + } + boolean inferred = inferTransactionalFromPayloads(applyPayloads); + logTransactionalInference(inferred); + return inferred; + } + + private void logTransactionalInference(boolean inferred) { + if (!inferred) { + logger.info("Change '{}': transactional not specified, inferred as non-transactional from apply payload(s)", id); + } else { + logger.debug("Change '{}': transactional not specified, inferred as transactional (default)", id); + } + } + private TemplateLoadedTaskBuilder setPreview(TemplatePreviewChange preview) { this.preview = preview; setFileName(preview.getFileName()); @@ -368,7 +423,7 @@ private TemplateLoadedTaskBuilder setPreview(TemplatePreviewChange preview) { setTemplateName(preview.getTemplateName()); setProfiles(preview.getProfiles()); setRunAlways(preview.isRunAlways()); - setTransactional(preview.isTransactional()); + setTransactionalFlag(preview.getTransactionalFlag().orElse(null)); setSystem(preview.isSystem()); setConfiguration(preview.getConfiguration()); setApplyPayload(preview.getApply()); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/step/TaskStep.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/step/TaskStep.java index d9dbfe87b..943adeea1 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/step/TaskStep.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/step/TaskStep.java @@ -15,14 +15,14 @@ */ package io.flamingock.internal.core.task.navigation.step; -import io.flamingock.internal.common.core.task.TaskDescriptor; import io.flamingock.internal.core.task.executable.ExecutableTask; +import io.flamingock.internal.core.task.loaded.AbstractLoadedTask; public interface TaskStep { ExecutableTask getTask(); - default TaskDescriptor getLoadedTask() { - return getTask().getDescriptor(); + default AbstractLoadedTask getLoadedTask() { + return (AbstractLoadedTask) getTask().getLoadedChange(); } } diff --git a/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilderTest.java b/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilderTest.java index 20ef5c0df..47bc64004 100644 --- a/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilderTest.java +++ b/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/CodeLoadedTaskBuilderTest.java @@ -44,7 +44,7 @@ void shouldBuildWithOrderInContentWhenOrderInContentPresentAndNoOrderInSource() .setOrder("001") .setChangeClassName(WithoutOrderTestClass.class.getName()) .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false); // When @@ -64,7 +64,7 @@ void shouldThrowExceptionWhenOrderInContentDoesNotMatchOrderInSource() { .setOrder("001") .setChangeClassName(_002__MyClass.class.getName()) .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false); // When & Then @@ -82,7 +82,7 @@ void shouldThrowExceptionWhenBothOrderInContentAndOrderInSourceAreMissing() { .setOrder(null) .setChangeClassName(WithoutOrderTestClass.class.getName()) .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false); // When & Then @@ -100,7 +100,7 @@ void shouldBuildWithOrderFromSourceWhenOrderInContentIsEmptyString() { .setOrder("") .setChangeClassName(_002__MyClass.class.getName()) .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false); // When @@ -119,7 +119,7 @@ void shouldBuildWithOrderFromSourceWhenOrderInContentIsBlankString() { .setOrder(" ") .setChangeClassName(_002__MyClass.class.getName()) .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false); // When @@ -138,7 +138,7 @@ void shouldWorkWithRealClassWhenOrderValidationPasses() { .setOrder("001") .setChangeClassName(WithoutOrderTestClass.class.getName()) .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false); // When @@ -153,6 +153,24 @@ void shouldWorkWithRealClassWhenOrderValidationPasses() { assertFalse(result.isSystem()); } + @Test + @DisplayName("Should default to true when transactional is null") + void shouldDefaultToTrueWhenTransactionalIsNull() { + // Given + builder.setId("test-id") + .setOrder("001") + .setChangeClassName(WithoutOrderTestClass.class.getName()) + .setRunAlways(false) + .setTransactionalFlag(null) + .setSystem(false); + + // When + CodeLoadedChange result = builder.build(); + + // Then + assertTrue(result.isTransactional()); + } + // Test class with Change annotation for testing setFromFlamingockChangeAnnotation @Change(id = "annotation-test", transactional = false, author = "aperezdieppa") public static class _100__TestChangeClass { diff --git a/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/PayloadTransactionSupportValidationTest.java b/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/PayloadTransactionSupportValidationTest.java index 4d9c2c5e8..cdcd35ae4 100644 --- a/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/PayloadTransactionSupportValidationTest.java +++ b/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/PayloadTransactionSupportValidationTest.java @@ -120,7 +120,7 @@ private static SimpleTemplateLoadedChange buildSimple(boolean transactional, return new SimpleTemplateLoadedChange( "test-file.yml", "test-id", "001", "author", (Class) DummyTemplate.class, dummyConstructor, - Collections.emptyList(), transactional, + Collections.emptyList(), transactional, transactional, false, false, config, apply, rollback, null, null, false); @@ -133,7 +133,7 @@ private static MultiStepTemplateLoadedChange buildMultiStep(boolean transactiona return new MultiStepTemplateLoadedChange( "test-file.yml", "test-id", "001", "author", (Class) DummyTemplate.class, dummyConstructor, - Collections.emptyList(), transactional, + Collections.emptyList(), transactional, transactional, false, false, config, (List) steps, null, null, false); diff --git a/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/SimpleTemplateLoadedTaskBuilderTest.java b/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/SimpleTemplateLoadedTaskBuilderTest.java index 3e0685603..e21851e3f 100644 --- a/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/SimpleTemplateLoadedTaskBuilderTest.java +++ b/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/SimpleTemplateLoadedTaskBuilderTest.java @@ -22,6 +22,10 @@ import io.flamingock.api.annotations.ChangeTemplate; import io.flamingock.api.annotations.Rollback; import io.flamingock.api.template.AbstractChangeTemplate; +import io.flamingock.api.template.TemplatePayload; +import io.flamingock.api.template.TemplatePayloadInfo; +import io.flamingock.api.template.TemplatePayloadValidationError; +import io.flamingock.api.template.TemplateValidationContext; import io.flamingock.api.template.wrappers.TemplateString; import io.flamingock.api.template.wrappers.TemplateVoid; import io.flamingock.api.annotations.Apply; @@ -33,6 +37,7 @@ import org.mockito.MockedStatic; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -61,6 +66,78 @@ public void rollback() { } } + // Payload that explicitly claims supportsTransactions=false + public static class NonTxPayload implements TemplatePayload { + private String value; + + public NonTxPayload() {} + + public NonTxPayload(String value) { this.value = value; } + + public String getValue() { return value; } + + public void setValue(String value) { this.value = value; } + + @Override + public List validate(TemplateValidationContext context) { + return Collections.emptyList(); + } + + @Override + public TemplatePayloadInfo getInfo() { + TemplatePayloadInfo info = new TemplatePayloadInfo(); + info.setSupportsTransactions(false); + return info; + } + } + + // Payload that explicitly claims supportsTransactions=true + public static class TxPayload implements TemplatePayload { + private String value; + + public TxPayload() {} + + public TxPayload(String value) { this.value = value; } + + public String getValue() { return value; } + + public void setValue(String value) { this.value = value; } + + @Override + public List validate(TemplateValidationContext context) { + return Collections.emptyList(); + } + + @Override + public TemplatePayloadInfo getInfo() { + TemplatePayloadInfo info = new TemplatePayloadInfo(); + info.setSupportsTransactions(true); + return info; + } + } + + @ChangeTemplate(name = "test-nontx-template") + public static class NonTxTemplate extends AbstractChangeTemplate { + public NonTxTemplate() { super(); } + + @Apply + public void apply() {} + + @Rollback + public void rollback() {} + } + + @ChangeTemplate(name = "test-tx-template") + public static class TxTemplate extends AbstractChangeTemplate { + public TxTemplate() { super(); } + + @Apply + public void apply() {} + + @Rollback + public void rollback() {} + } + @BeforeEach void setUp() { builder = TemplateLoadedTaskBuilder.getInstance(); @@ -79,7 +156,7 @@ void shouldBuildWithOrderInContentWhenOrderInContentPresentAndNoOrderInFileName( .setFileName("test-file.yml") .setTemplateName("test-template") .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false) .setConfiguration(null) .setApplyPayload("applyPayload") @@ -115,7 +192,7 @@ void shouldBuildWithOrderFromFileNameWhenOrderInContentIsNullAndOrderInFileNameI .setFileName("_0002__test-file.yml") .setTemplateName("test-template") .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false) .setConfiguration(null) .setApplyPayload("applyPayload") @@ -147,7 +224,7 @@ void shouldBuildWithOrderInContentWhenOrderInContentMatchesOrderInFileName() { .setTemplateName("test-template") .setRunAlways(false); builder.setProfiles(Arrays.asList("test")); - builder.setTransactional(true) + builder.setTransactionalFlag(true) .setSystem(false) .setConfiguration(null) .setApplyPayload("applyPayload") @@ -184,6 +261,79 @@ void shouldThrowExceptionWhenTemplateIsNotFound() { } } + @Nested + @DisplayName("Transactional nullable support tests") + class TransactionalNullableTests { + + @Test + @DisplayName("Should default to true when transactional is null") + void shouldDefaultToTrueWhenTransactionalIsNull() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-change-template", TestChangeTemplate.class, false, true))); + + builder.setId("test-id") + .setOrder("001") + .setFileName("test-file.yml") + .setTemplateName("test-template") + .setRunAlways(false) + .setTransactionalFlag(null) + .setSystem(false) + .setApplyPayload("applyPayload") + .setRollbackPayload("rollbackPayload"); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should preserve explicit true when set") + void shouldPreserveExplicitTrueWhenSet() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-change-template", TestChangeTemplate.class, false, true))); + + builder.setId("test-id") + .setOrder("001") + .setFileName("test-file.yml") + .setTemplateName("test-template") + .setTransactionalFlag(true) + .setApplyPayload("applyPayload") + .setRollbackPayload("rollbackPayload"); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should preserve explicit false when set") + void shouldPreserveExplicitFalseWhenSet() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-change-template", TestChangeTemplate.class, false, true))); + + builder.setId("test-id") + .setOrder("001") + .setFileName("test-file.yml") + .setTemplateName("test-template") + .setTransactionalFlag(false) + .setApplyPayload("applyPayload") + .setRollbackPayload("rollbackPayload"); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertFalse(result.isTransactional()); + } + } + } + @Nested @DisplayName("Payload validation tests") class ValidationTests { @@ -202,7 +352,7 @@ void shouldReportErrorWhenApplyPayloadIsNull() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-template") - .setTransactional(true) + .setTransactionalFlag(true) .setApplyPayload(null) .setRollbackPayload("rollbackPayload"); builder.setProfiles(Arrays.asList("test")); @@ -225,7 +375,7 @@ void shouldReportErrorWhenRollbackPayloadIsNullAndRequired() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-template") - .setTransactional(true) + .setTransactionalFlag(true) .setApplyPayload("applyPayload") .setRollbackPayload(null); builder.setProfiles(Arrays.asList("test")); @@ -248,7 +398,7 @@ void shouldNotReportErrorWhenRollbackPayloadIsNullAndNotRequired() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-template") - .setTransactional(true) + .setTransactionalFlag(true) .setApplyPayload("applyPayload") .setRollbackPayload(null); builder.setProfiles(Arrays.asList("test")); @@ -261,4 +411,129 @@ void shouldNotReportErrorWhenRollbackPayloadIsNullAndNotRequired() { } } } + + @Nested + @DisplayName("Transactional inference from payload tests") + class TransactionalInferenceTests { + + @Test + @DisplayName("Should infer false when payload does not support transactions") + void shouldInferFalseWhenPayloadDoesNotSupportTx() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-nontx-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-nontx-template", NonTxTemplate.class, false, false))); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-nontx-template") + .setTransactionalFlag(null) + .setApplyPayload("applyPayload"); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertFalse(result.isTransactional()); + } + } + + @Test + @DisplayName("Should infer true when payload supports transactions") + void shouldInferTrueWhenPayloadSupportsTx() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-tx-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-tx-template", TxTemplate.class, false, false))); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-tx-template") + .setTransactionalFlag(null) + .setApplyPayload("applyPayload"); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should infer true when payload makes no claim") + void shouldInferTrueWhenPayloadMakesNoClaim() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-change-template", TestChangeTemplate.class, false, false))); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-template") + .setTransactionalFlag(null) + .setApplyPayload("applyPayload"); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should infer true when apply payload is null") + void shouldInferTrueWhenApplyPayloadIsNull() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-change-template", TestChangeTemplate.class, false, false))); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-template") + .setTransactionalFlag(null) + .setApplyPayload(null); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should respect explicit true even when payload does not support transactions") + void shouldRespectExplicitTrueEvenWhenPayloadDoesNotSupportTx() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-nontx-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-nontx-template", NonTxTemplate.class, false, false))); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-nontx-template") + .setTransactionalFlag(true) + .setApplyPayload("applyPayload"); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should respect explicit false even when payload supports transactions") + void shouldRespectExplicitFalseEvenWhenPayloadSupportsTx() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-tx-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-tx-template", TxTemplate.class, false, false))); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-tx-template") + .setTransactionalFlag(false) + .setApplyPayload("applyPayload"); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertFalse(result.isTransactional()); + } + } + } } diff --git a/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/SteppableTemplateLoadedTaskBuilderTest.java b/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/SteppableTemplateLoadedTaskBuilderTest.java index 52e569da6..9bac57a10 100644 --- a/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/SteppableTemplateLoadedTaskBuilderTest.java +++ b/core/flamingock-core/src/test/java/io/flamingock/internal/core/task/loaded/SteppableTemplateLoadedTaskBuilderTest.java @@ -19,7 +19,11 @@ import io.flamingock.api.annotations.ChangeTemplate; import io.flamingock.api.annotations.Rollback; import io.flamingock.api.template.AbstractChangeTemplate; +import io.flamingock.api.template.TemplatePayload; +import io.flamingock.api.template.TemplatePayloadInfo; +import io.flamingock.api.template.TemplatePayloadValidationError; import io.flamingock.api.template.TemplateStep; +import io.flamingock.api.template.TemplateValidationContext; import io.flamingock.api.template.wrappers.TemplateString; import io.flamingock.api.template.wrappers.TemplateVoid; import io.flamingock.internal.common.core.error.FlamingockException; @@ -85,6 +89,78 @@ public void rollback() { } } + // Payload that explicitly claims supportsTransactions=false + public static class NonTxPayload implements TemplatePayload { + private String value; + + public NonTxPayload() {} + + public NonTxPayload(String value) { this.value = value; } + + public String getValue() { return value; } + + public void setValue(String value) { this.value = value; } + + @Override + public List validate(TemplateValidationContext context) { + return Collections.emptyList(); + } + + @Override + public TemplatePayloadInfo getInfo() { + TemplatePayloadInfo info = new TemplatePayloadInfo(); + info.setSupportsTransactions(false); + return info; + } + } + + // Payload that explicitly claims supportsTransactions=true + public static class TxPayload implements TemplatePayload { + private String value; + + public TxPayload() {} + + public TxPayload(String value) { this.value = value; } + + public String getValue() { return value; } + + public void setValue(String value) { this.value = value; } + + @Override + public List validate(TemplateValidationContext context) { + return Collections.emptyList(); + } + + @Override + public TemplatePayloadInfo getInfo() { + TemplatePayloadInfo info = new TemplatePayloadInfo(); + info.setSupportsTransactions(true); + return info; + } + } + + @ChangeTemplate(name = "test-nontx-steppable-template", multiStep = true) + public static class NonTxSteppableTemplate extends AbstractChangeTemplate { + public NonTxSteppableTemplate() { super(); } + + @Apply + public void apply() {} + + @Rollback + public void rollback() {} + } + + @ChangeTemplate(name = "test-tx-steppable-template", multiStep = true) + public static class TxSteppableTemplate extends AbstractChangeTemplate { + public TxSteppableTemplate() { super(); } + + @Apply + public void apply() {} + + @Rollback + public void rollback() {} + } + @BeforeEach void setUp() { builder = TemplateLoadedTaskBuilder.getInstance(); @@ -106,7 +182,7 @@ void shouldBuildWithStepsWhenStepsProvided() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-steppable-template") - .setTransactional(true) + .setTransactionalFlag(true) .setSteps(rawSteps); builder.setProfiles(Arrays.asList("test")); @@ -144,7 +220,7 @@ void shouldBuildWithOrderInContentForSteppableTemplate() { .setFileName("test-file.yml") .setTemplateName("test-steppable-template") .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false) .setConfiguration(null) .setSteps(rawSteps); @@ -178,7 +254,7 @@ void shouldBuildWithOrderFromFileNameForSteppableTemplate() { .setFileName("_0002__test-file.yml") .setTemplateName("test-steppable-template") .setRunAlways(false) - .setTransactional(true) + .setTransactionalFlag(true) .setSystem(false) .setConfiguration(null) .setSteps(rawSteps); @@ -208,7 +284,7 @@ void shouldBuildWithEmptyStepsList() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-steppable-template") - .setTransactional(true) + .setTransactionalFlag(true) .setSteps(emptySteps); builder.setProfiles(Arrays.asList("test")); @@ -244,7 +320,7 @@ void shouldBuildWithMultipleSteps() { .setOrder("003") .setFileName("_003__multi-step.yml") .setTemplateName("test-steppable-template") - .setTransactional(true) + .setTransactionalFlag(true) .setSteps(rawSteps); builder.setProfiles(Arrays.asList("test")); @@ -308,7 +384,7 @@ void shouldBuildWithStepsHavingOnlyApply() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-steppable-template") - .setTransactional(true) + .setTransactionalFlag(true) .setSteps(rawSteps); builder.setProfiles(Arrays.asList("test")); @@ -338,7 +414,7 @@ void shouldBuildWithNullSteps() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-steppable-template") - .setTransactional(true) + .setTransactionalFlag(true) .setSteps(null); builder.setProfiles(Arrays.asList("test")); @@ -368,6 +444,59 @@ private Map createStepMapApplyOnly(Object apply) { return step; } + @Nested + @DisplayName("Transactional nullable support tests") + class TransactionalNullableTests { + + @Test + @DisplayName("Should default to true when transactional is null") + void shouldDefaultToTrueWhenTransactionalIsNull() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-steppable-template", TestSteppableTemplate.class, true, true))); + + List> rawSteps = Collections.singletonList( + createStepMap("apply1", "rollback1") + ); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-steppable-template") + .setTransactionalFlag(null) + .setSteps(rawSteps); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should preserve explicit false when set") + void shouldPreserveExplicitFalseWhenSet() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-steppable-template", TestSteppableTemplate.class, true, true))); + + List> rawSteps = Collections.singletonList( + createStepMap("apply1", "rollback1") + ); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-steppable-template") + .setTransactionalFlag(false) + .setSteps(rawSteps); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertFalse(result.isTransactional()); + } + } + } + @Nested @DisplayName("Template structure validation tests") class TemplateStructureValidationTests { @@ -462,7 +591,7 @@ void shouldReportErrorWhenStepMissingApplyPayload() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-steppable-template") - .setTransactional(true) + .setTransactionalFlag(true) .setSteps(rawSteps); builder.setProfiles(Arrays.asList("test")); @@ -489,7 +618,7 @@ void shouldReportErrorWhenStepMissingRollbackAndRequired() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-steppable-template") - .setTransactional(true) + .setTransactionalFlag(true) .setSteps(rawSteps); builder.setProfiles(Arrays.asList("test")); @@ -516,7 +645,7 @@ void shouldNotReportErrorWhenStepMissingRollbackAndNotRequired() { builder.setId("test-id") .setFileName("test-file.yml") .setTemplateName("test-steppable-template") - .setTransactional(true) + .setTransactionalFlag(true) .setSteps(rawSteps); builder.setProfiles(Arrays.asList("test")); @@ -528,4 +657,174 @@ void shouldNotReportErrorWhenStepMissingRollbackAndNotRequired() { } } } + + @Nested + @DisplayName("Transactional inference from payload tests") + class TransactionalInferenceTests { + + @Test + @DisplayName("Should infer false when any step does not support transactions") + void shouldInferFalseWhenAnyStepDoesNotSupportTx() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-nontx-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-nontx-steppable-template", NonTxSteppableTemplate.class, true, false))); + + // Use pre-built TemplateStep objects with typed payloads + TemplateStep step1 = new TemplateStep<>(); + step1.setApplyPayload(new TxPayload("apply1")); + TemplateStep step2 = new TemplateStep<>(); + step2.setApplyPayload(new NonTxPayload("apply2")); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-nontx-steppable-template") + .setTransactionalFlag(null) + .setSteps(Arrays.asList(step1, step2)); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertFalse(result.isTransactional()); + } + } + + @Test + @DisplayName("Should infer true when all steps support transactions") + void shouldInferTrueWhenAllStepsSupportTx() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-tx-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-tx-steppable-template", TxSteppableTemplate.class, true, false))); + + TemplateStep step1 = new TemplateStep<>(); + step1.setApplyPayload(new TxPayload("apply1")); + TemplateStep step2 = new TemplateStep<>(); + step2.setApplyPayload(new TxPayload("apply2")); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-tx-steppable-template") + .setTransactionalFlag(null) + .setSteps(Arrays.asList(step1, step2)); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should infer true when all steps make no claim") + void shouldInferTrueWhenAllStepsMakeNoClaim() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-steppable-template", TestSteppableTemplate.class, true, false))); + + List> rawSteps = Arrays.asList( + createStepMap("apply1", "rollback1"), + createStepMap("apply2", "rollback2") + ); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-steppable-template") + .setTransactionalFlag(null) + .setSteps(rawSteps); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should infer true when mix of support and no claim") + void shouldInferTrueWhenMixOfSupportAndNoClaim() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-tx-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-tx-steppable-template", TxSteppableTemplate.class, true, false))); + + // step1 has TxPayload (supportsTransactions=true), step2 uses raw String → TemplateString (no claim) + TemplateStep step1 = new TemplateStep<>(); + step1.setApplyPayload(new TxPayload("apply1")); + TemplateStep step2 = new TemplateStep<>(); + step2.setApplyPayload(new TxPayload("apply2")); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-tx-steppable-template") + .setTransactionalFlag(null) + .setSteps(Arrays.asList(step1, step2)); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should infer true when steps is empty") + void shouldInferTrueWhenStepsIsEmpty() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-steppable-template", TestSteppableTemplate.class, true, false))); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-steppable-template") + .setTransactionalFlag(null) + .setSteps(Collections.emptyList()); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should infer true when steps is null") + void shouldInferTrueWhenStepsIsNull() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-steppable-template", TestSteppableTemplate.class, true, false))); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-steppable-template") + .setTransactionalFlag(null) + .setSteps(null); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + + @Test + @DisplayName("Should respect explicit true when step does not support transactions") + void shouldRespectExplicitTrueWhenStepDoesNotSupportTx() { + try (MockedStatic mockedTemplateManager = mockStatic(ChangeTemplateManager.class)) { + mockedTemplateManager.when(() -> ChangeTemplateManager.getTemplate("test-nontx-steppable-template")) + .thenReturn(Optional.of(new ChangeTemplateDefinition("test-nontx-steppable-template", NonTxSteppableTemplate.class, true, false))); + + TemplateStep step1 = new TemplateStep<>(); + step1.setApplyPayload(new NonTxPayload("apply1")); + + builder.setId("test-id") + .setFileName("test-file.yml") + .setTemplateName("test-nontx-steppable-template") + .setTransactionalFlag(true) + .setSteps(Collections.singletonList(step1)); + builder.setProfiles(Arrays.asList("test")); + + AbstractTemplateLoadedChange result = builder.build(); + + assertTrue(result.isTransactional()); + } + } + } } diff --git a/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/MongockAnnotationProcessorPlugin.java b/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/MongockAnnotationProcessorPlugin.java index 43147d885..7c4f845fc 100644 --- a/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/MongockAnnotationProcessorPlugin.java +++ b/legacy/mongock-support/src/main/java/io/flamingock/support/mongock/processor/MongockAnnotationProcessorPlugin.java @@ -107,7 +107,7 @@ private CodePreviewChange getImporterChange(String targetSystemId) { builder.setOrder("00100"); builder.setAuthor("flamingock-team"); builder.setTargetSystem(new TargetSystemDescriptor(targetSystemId)); - builder.setTransactional(true); + builder.setTransactionalFlag(true); builder.setSystem(true); builder.setRecovery(RecoveryDescriptor.getDefault());