From 8f74fa8f836736473ffaea410f834958de8356d9 Mon Sep 17 00:00:00 2001 From: Antonio Perez Dieppa Date: Sun, 8 Feb 2026 02:02:33 +0000 Subject: [PATCH 1/4] refactor: execution log clean and cli result --- .../cli/executor/command/ApplyCommand.java | 13 +- .../output/ExecutionResultFormatter.java | 239 +++++++++++++++++ .../core/response/data/ChangeResult.java | 172 ++++++++++++ .../core/response/data/ChangeStatus.java | 46 ++++ .../common/core/response/data/ErrorInfo.java | 78 ++++++ .../response/data/ExecuteResponseData.java | 253 +++++++++++++++++- .../core/response/data/ExecutionStatus.java} | 34 ++- .../core/response/data/StageResult.java | 158 +++++++++++ .../core/response/data/StageStatus.java} | 30 ++- .../core/operation/ExecuteOperation.java | 61 +++-- .../core/operation/ExecuteResult.java | 16 +- .../core/operation/OperationException.java | 24 +- .../core/operation/OperationSummary.java | 75 ------ .../operation/result/ChangeResultBuilder.java | 119 ++++++++ .../result/ExecutionResultBuilder.java | 165 ++++++++++++ .../operation/result/StageResultBuilder.java | 117 ++++++++ .../execution/StageExecutionException.java | 27 +- .../pipeline/execution/StageExecutor.java | 66 +++-- .../core/pipeline/execution/StageSummary.java | 64 ----- .../pipeline/execution/TaskSummarizer.java | 116 -------- .../core/pipeline/execution/TaskSummary.java | 63 ----- .../internal/core/summary/SummaryLine.java | 20 -- .../navigation/FailedChangeProcessResult.java | 11 +- .../navigator/ChangeProcessResult.java | 16 +- .../navigator/ChangeProcessStrategy.java | 9 +- .../ChangeProcessStrategyFactory.java | 22 +- .../AbstractChangeProcessStrategy.java | 55 ++-- .../strategy/NonTxChangeProcessStrategy.java | 40 +-- .../SharedTxChangeProcessStrategy.java | 36 ++- .../SimpleTxChangeProcessStrategy.java | 43 ++- .../summary/AbstractTaskStepSummaryLine.java | 174 ------------ .../navigation/summary/StepSummarizer.java | 51 ---- .../task/navigation/summary/StepSummary.java | 37 --- .../navigation/summary/StepSummaryLine.java | 24 -- 34 files changed, 1651 insertions(+), 823 deletions(-) create mode 100644 cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java create mode 100644 core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ChangeResult.java create mode 100644 core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ChangeStatus.java create mode 100644 core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ErrorInfo.java rename core/{flamingock-core/src/main/java/io/flamingock/internal/core/summary/Summary.java => flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecutionStatus.java} (50%) create mode 100644 core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/StageResult.java rename core/{flamingock-core/src/main/java/io/flamingock/internal/core/summary/Summarizer.java => flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/StageStatus.java} (52%) delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationSummary.java create mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ChangeResultBuilder.java create mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ExecutionResultBuilder.java create mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/StageResultBuilder.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageSummary.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/TaskSummarizer.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/TaskSummary.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/SummaryLine.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/AbstractTaskStepSummaryLine.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummarizer.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummary.java delete mode 100644 core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummaryLine.java diff --git a/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java b/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java index 6ecc941d0..259c4dff1 100644 --- a/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java +++ b/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java @@ -20,6 +20,7 @@ import io.flamingock.cli.executor.orchestration.CommandResult; import io.flamingock.cli.executor.orchestration.ExecutionOptions; import io.flamingock.cli.executor.output.ConsoleFormatter; +import io.flamingock.cli.executor.output.ExecutionResultFormatter; import io.flamingock.cli.executor.util.VersionProvider; import io.flamingock.internal.common.core.operation.OperationType; import io.flamingock.internal.common.core.response.data.ExecuteResponseData; @@ -122,10 +123,20 @@ public Integer call() { if (result.isSuccess()) { if (!quiet) { - ConsoleFormatter.printSuccess(result.getDurationMs()); + // Print detailed execution summary + ExecuteResponseData data = result.getData(); + if (data != null) { + ExecutionResultFormatter.print(data); + } else { + ConsoleFormatter.printSuccess(result.getDurationMs()); + } } return 0; } else { + // Print execution summary if available (even on failure, shows what was applied) + if (!quiet && result.getData() != null) { + ExecutionResultFormatter.print(result.getData()); + } ConsoleFormatter.printFailure(result.getErrorCode(), result.getErrorMessage()); return result.getExitCode(); } diff --git a/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java b/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java new file mode 100644 index 000000000..83c1e1767 --- /dev/null +++ b/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java @@ -0,0 +1,239 @@ +/* + * 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.cli.executor.output; + +import io.flamingock.internal.common.core.response.data.ChangeResult; +import io.flamingock.internal.common.core.response.data.ChangeStatus; +import io.flamingock.internal.common.core.response.data.ErrorInfo; +import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +import io.flamingock.internal.common.core.response.data.ExecutionStatus; +import io.flamingock.internal.common.core.response.data.StageResult; + +/** + * Formats execution results for CLI output. + * Provides professional, scannable output following CLI conventions. + */ +public final class ExecutionResultFormatter { + + private static final String SEPARATOR = "--------------------------------------------------------------------------------"; + private static final int CHANGE_ID_WIDTH = 30; + private static final int AUTHOR_WIDTH = 20; + + private ExecutionResultFormatter() { + } + + /** + * Formats the complete execution result for CLI display. + * + * @param result the execution result data + * @return formatted string for display + */ + public static String format(ExecuteResponseData result) { + StringBuilder sb = new StringBuilder(); + + // Print stages and changes + sb.append("\nApplying changes...\n"); + + for (StageResult stage : result.getStages()) { + sb.append(formatStage(stage)); + } + + // Print summary + sb.append("\n").append(SEPARATOR).append("\n"); + sb.append(centerText("EXECUTION SUMMARY", SEPARATOR.length())).append("\n"); + sb.append(SEPARATOR).append("\n"); + + sb.append(String.format(" Status: %s%n", formatStatus(result.getStatus()))); + sb.append(String.format(" Duration: %s%n", formatDuration(result.getTotalDurationMs()))); + sb.append(formatStagesSummary(result)); + sb.append(formatChangesSummary(result)); + + // Print error details if failed + if (result.isFailed() && result.getError() != null) { + sb.append(formatErrorDetails(result.getError())); + } + + sb.append(SEPARATOR).append("\n"); + + return sb.toString(); + } + + /** + * Formats a single stage with its changes. + */ + private static String formatStage(StageResult stage) { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("%n Stage: %s%n", stage.getStageName())); + + for (ChangeResult change : stage.getChanges()) { + sb.append(formatChange(change)); + } + + return sb.toString(); + } + + /** + * Formats a single change result line. + */ + private static String formatChange(ChangeResult change) { + String statusLabel = formatChangeStatus(change.getStatus()); + String changeId = truncateOrPad(change.getChangeId(), CHANGE_ID_WIDTH); + String author = change.getAuthor() != null + ? truncateOrPad("(author: " + change.getAuthor() + ")", AUTHOR_WIDTH) + : truncateOrPad("", AUTHOR_WIDTH); + String duration = formatChangeDuration(change); + + return String.format(" [%s] %s %s %s%n", statusLabel, changeId, author, duration); + } + + /** + * Formats the change status for display. + */ + private static String formatChangeStatus(ChangeStatus status) { + switch (status) { + case APPLIED: + return "APPLIED"; + case ALREADY_APPLIED: + return "SKIPPED"; + case FAILED: + return "FAILED "; + case ROLLED_BACK: + return "ROLLBCK"; + case NOT_REACHED: + return "SKIPPED"; + default: + return "UNKNOWN"; + } + } + + /** + * Formats the execution status for summary display. + */ + private static String formatStatus(ExecutionStatus status) { + switch (status) { + case SUCCESS: + return "SUCCESS"; + case FAILED: + return "FAILED"; + case PARTIAL: + return "PARTIAL"; + case NO_CHANGES: + return "NO CHANGES"; + default: + return "UNKNOWN"; + } + } + + /** + * Formats the stages summary line. + */ + private static String formatStagesSummary(ExecuteResponseData result) { + if (result.getFailedStages() > 0) { + return String.format(" Stages: %d completed, %d failed%n", + result.getCompletedStages(), result.getFailedStages()); + } else { + return String.format(" Stages: %d completed%n", result.getCompletedStages()); + } + } + + /** + * Formats the changes summary line. + */ + private static String formatChangesSummary(ExecuteResponseData result) { + return String.format(" Changes: %d applied, %d skipped, %d failed%n", + result.getAppliedChanges(), result.getSkippedChanges(), result.getFailedChanges()); + } + + /** + * Formats error details section. + */ + private static String formatErrorDetails(ErrorInfo error) { + StringBuilder sb = new StringBuilder(); + sb.append("\n Error:\n"); + if (error.getChangeId() != null) { + sb.append(String.format(" Change: %s%n", error.getChangeId())); + } + if (error.getStageId() != null) { + sb.append(String.format(" Stage: %s%n", error.getStageId())); + } + if (error.getErrorType() != null) { + sb.append(String.format(" Type: %s%n", error.getErrorType())); + } + if (error.getMessage() != null) { + sb.append(String.format(" Message: %s%n", error.getMessage())); + } + return sb.toString(); + } + + /** + * Formats duration for change line. + */ + private static String formatChangeDuration(ChangeResult change) { + if (change.getStatus() == ChangeStatus.ALREADY_APPLIED) { + return "Already applied"; + } else if (change.getStatus() == ChangeStatus.NOT_REACHED) { + return "Not executed"; + } else { + return formatDuration(change.getDurationMs()); + } + } + + /** + * Formats duration in human-readable format. + */ + private static String formatDuration(long millis) { + if (millis < 1000) { + return millis + "ms"; + } else if (millis < 60000) { + return String.format("%.1fs", millis / 1000.0); + } else { + return String.format("%.1fm", millis / 60000.0); + } + } + + /** + * Truncates or pads a string to the specified width. + */ + private static String truncateOrPad(String s, int width) { + if (s == null) { + s = ""; + } + if (s.length() > width) { + return s.substring(0, width - 3) + "..."; + } + return String.format("%-" + width + "s", s); + } + + /** + * Centers text within a given width. + */ + private static String centerText(String text, int width) { + if (text.length() >= width) { + return text; + } + int padding = (width - text.length()) / 2; + return String.format("%" + padding + "s%s%" + padding + "s", "", text, ""); + } + + /** + * Prints the execution result to standard output. + * + * @param result the execution result data + */ + public static void print(ExecuteResponseData result) { + System.out.print(format(result)); + } +} diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ChangeResult.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ChangeResult.java new file mode 100644 index 000000000..74977df81 --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ChangeResult.java @@ -0,0 +1,172 @@ +/* + * 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.response.data; + +/** + * Result data for an individual change execution. + */ +public class ChangeResult { + + private String changeId; + private String author; + private ChangeStatus status; + private long durationMs; + private String targetSystemId; + private String errorMessage; + private String errorType; + + public ChangeResult() { + } + + private ChangeResult(Builder builder) { + this.changeId = builder.changeId; + this.author = builder.author; + this.status = builder.status; + this.durationMs = builder.durationMs; + this.targetSystemId = builder.targetSystemId; + this.errorMessage = builder.errorMessage; + this.errorType = builder.errorType; + } + + public String getChangeId() { + return changeId; + } + + public void setChangeId(String changeId) { + this.changeId = changeId; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public ChangeStatus getStatus() { + return status; + } + + public void setStatus(ChangeStatus status) { + this.status = status; + } + + public long getDurationMs() { + return durationMs; + } + + public void setDurationMs(long durationMs) { + this.durationMs = durationMs; + } + + public String getTargetSystemId() { + return targetSystemId; + } + + public void setTargetSystemId(String targetSystemId) { + this.targetSystemId = targetSystemId; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorType() { + return errorType; + } + + public void setErrorType(String errorType) { + this.errorType = errorType; + } + + public boolean isFailed() { + return status == ChangeStatus.FAILED; + } + + public boolean isApplied() { + return status == ChangeStatus.APPLIED; + } + + public boolean isAlreadyApplied() { + return status == ChangeStatus.ALREADY_APPLIED; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String changeId; + private String author; + private ChangeStatus status; + private long durationMs; + private String targetSystemId; + private String errorMessage; + private String errorType; + + public Builder changeId(String changeId) { + this.changeId = changeId; + return this; + } + + public Builder author(String author) { + this.author = author; + return this; + } + + public Builder status(ChangeStatus status) { + this.status = status; + return this; + } + + public Builder durationMs(long durationMs) { + this.durationMs = durationMs; + return this; + } + + public Builder targetSystemId(String targetSystemId) { + this.targetSystemId = targetSystemId; + return this; + } + + public Builder errorMessage(String errorMessage) { + this.errorMessage = errorMessage; + return this; + } + + public Builder errorType(String errorType) { + this.errorType = errorType; + return this; + } + + public Builder error(Throwable throwable) { + if (throwable != null) { + this.errorType = throwable.getClass().getSimpleName(); + this.errorMessage = throwable.getMessage(); + } + return this; + } + + public ChangeResult build() { + return new ChangeResult(this); + } + } +} diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ChangeStatus.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ChangeStatus.java new file mode 100644 index 000000000..373a28d81 --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ChangeStatus.java @@ -0,0 +1,46 @@ +/* + * 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.response.data; + +/** + * Status of an individual change execution. + */ +public enum ChangeStatus { + /** + * Change was successfully applied. + */ + APPLIED, + + /** + * Change was already applied in a previous execution. + */ + ALREADY_APPLIED, + + /** + * Change failed during execution. + */ + FAILED, + + /** + * Change failed but was successfully rolled back. + */ + ROLLED_BACK, + + /** + * Change was not reached due to a prior failure. + */ + NOT_REACHED +} diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ErrorInfo.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ErrorInfo.java new file mode 100644 index 000000000..6cdc3abcc --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ErrorInfo.java @@ -0,0 +1,78 @@ +/* + * 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.response.data; + +/** + * Contains error information when an execution fails. + */ +public class ErrorInfo { + + private String errorType; + private String message; + private String changeId; + private String stageId; + + public ErrorInfo() { + } + + public ErrorInfo(String errorType, String message, String changeId, String stageId) { + this.errorType = errorType; + this.message = message; + this.changeId = changeId; + this.stageId = stageId; + } + + public String getErrorType() { + return errorType; + } + + public void setErrorType(String errorType) { + this.errorType = errorType; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getChangeId() { + return changeId; + } + + public void setChangeId(String changeId) { + this.changeId = changeId; + } + + public String getStageId() { + return stageId; + } + + public void setStageId(String stageId) { + this.stageId = stageId; + } + + /** + * Creates an ErrorInfo from a Throwable. + */ + public static ErrorInfo fromThrowable(Throwable throwable, String changeId, String stageId) { + String errorType = throwable.getClass().getSimpleName(); + String message = throwable.getMessage(); + return new ErrorInfo(errorType, message, changeId, stageId); + } +} diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecuteResponseData.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecuteResponseData.java index 689f3bc1f..48335580b 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecuteResponseData.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecuteResponseData.java @@ -17,31 +17,262 @@ import com.fasterxml.jackson.annotation.JsonTypeName; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + /** - * Response data for the EXECUTE operation. - * Currently a placeholder - detailed results will be available in a future version. + * Response data for the EXECUTE operation containing full execution results. */ @JsonTypeName("execute") public class ExecuteResponseData { - private String message; + private ExecutionStatus status; + private Instant startTime; + private Instant endTime; + private long totalDurationMs; + + // Aggregate counts + private int totalStages; + private int completedStages; + private int failedStages; + private int totalChanges; + private int appliedChanges; + private int skippedChanges; + private int failedChanges; + + // Per-stage breakdown + private List stages; + + // Error information (if failed) + private ErrorInfo error; public ExecuteResponseData() { + this.stages = new ArrayList<>(); + } + + private ExecuteResponseData(Builder builder) { + this.status = builder.status; + this.startTime = builder.startTime; + this.endTime = builder.endTime; + this.totalDurationMs = builder.totalDurationMs; + this.totalStages = builder.totalStages; + this.completedStages = builder.completedStages; + this.failedStages = builder.failedStages; + this.totalChanges = builder.totalChanges; + this.appliedChanges = builder.appliedChanges; + this.skippedChanges = builder.skippedChanges; + this.failedChanges = builder.failedChanges; + this.stages = builder.stages != null ? builder.stages : new ArrayList<>(); + this.error = builder.error; + } + + public ExecutionStatus getStatus() { + return status; + } + + public void setStatus(ExecutionStatus status) { + this.status = status; } - public ExecuteResponseData(String message) { - this.message = message; + public Instant getStartTime() { + return startTime; } - public static ExecuteResponseData placeholder() { - return new ExecuteResponseData("Execution completed. Detailed results will be available in a future version."); + public void setStartTime(Instant startTime) { + this.startTime = startTime; } - public String getMessage() { - return message; + public Instant getEndTime() { + return endTime; } - public void setMessage(String message) { - this.message = message; + public void setEndTime(Instant endTime) { + this.endTime = endTime; + } + + public long getTotalDurationMs() { + return totalDurationMs; + } + + public void setTotalDurationMs(long totalDurationMs) { + this.totalDurationMs = totalDurationMs; + } + + public int getTotalStages() { + return totalStages; + } + + public void setTotalStages(int totalStages) { + this.totalStages = totalStages; + } + + public int getCompletedStages() { + return completedStages; + } + + public void setCompletedStages(int completedStages) { + this.completedStages = completedStages; + } + + public int getFailedStages() { + return failedStages; + } + + public void setFailedStages(int failedStages) { + this.failedStages = failedStages; + } + + public int getTotalChanges() { + return totalChanges; + } + + public void setTotalChanges(int totalChanges) { + this.totalChanges = totalChanges; + } + + public int getAppliedChanges() { + return appliedChanges; + } + + public void setAppliedChanges(int appliedChanges) { + this.appliedChanges = appliedChanges; + } + + public int getSkippedChanges() { + return skippedChanges; + } + + public void setSkippedChanges(int skippedChanges) { + this.skippedChanges = skippedChanges; + } + + public int getFailedChanges() { + return failedChanges; + } + + public void setFailedChanges(int failedChanges) { + this.failedChanges = failedChanges; + } + + public List getStages() { + return stages; + } + + public void setStages(List stages) { + this.stages = stages; + } + + public ErrorInfo getError() { + return error; + } + + public void setError(ErrorInfo error) { + this.error = error; + } + + public boolean isSuccess() { + return status == ExecutionStatus.SUCCESS || status == ExecutionStatus.NO_CHANGES; + } + + public boolean isFailed() { + return status == ExecutionStatus.FAILED; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private ExecutionStatus status; + private Instant startTime; + private Instant endTime; + private long totalDurationMs; + private int totalStages; + private int completedStages; + private int failedStages; + private int totalChanges; + private int appliedChanges; + private int skippedChanges; + private int failedChanges; + private List stages = new ArrayList<>(); + private ErrorInfo error; + + public Builder status(ExecutionStatus status) { + this.status = status; + return this; + } + + public Builder startTime(Instant startTime) { + this.startTime = startTime; + return this; + } + + public Builder endTime(Instant endTime) { + this.endTime = endTime; + return this; + } + + public Builder totalDurationMs(long totalDurationMs) { + this.totalDurationMs = totalDurationMs; + return this; + } + + public Builder totalStages(int totalStages) { + this.totalStages = totalStages; + return this; + } + + public Builder completedStages(int completedStages) { + this.completedStages = completedStages; + return this; + } + + public Builder failedStages(int failedStages) { + this.failedStages = failedStages; + return this; + } + + public Builder totalChanges(int totalChanges) { + this.totalChanges = totalChanges; + return this; + } + + public Builder appliedChanges(int appliedChanges) { + this.appliedChanges = appliedChanges; + return this; + } + + public Builder skippedChanges(int skippedChanges) { + this.skippedChanges = skippedChanges; + return this; + } + + public Builder failedChanges(int failedChanges) { + this.failedChanges = failedChanges; + return this; + } + + public Builder stages(List stages) { + this.stages = stages; + return this; + } + + public Builder addStage(StageResult stage) { + if (this.stages == null) { + this.stages = new ArrayList<>(); + } + this.stages.add(stage); + return this; + } + + public Builder error(ErrorInfo error) { + this.error = error; + return this; + } + + public ExecuteResponseData build() { + return new ExecuteResponseData(this); + } } } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/Summary.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecutionStatus.java similarity index 50% rename from core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/Summary.java rename to core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecutionStatus.java index 36a5f361b..cd1fb12be 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/Summary.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecutionStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 Flamingock (https://www.flamingock.io) + * 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. @@ -13,19 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.flamingock.internal.core.summary; +package io.flamingock.internal.common.core.response.data; -import java.util.List; -import java.util.stream.Collectors; +/** + * Status of the overall execution operation. + */ +public enum ExecutionStatus { + /** + * All changes were successfully applied. + */ + SUCCESS, -public interface Summary extends SummaryLine{ + /** + * One or more changes failed during execution. + */ + FAILED, - List getLines(); + /** + * Execution was partially completed (some changes applied, some not). + */ + PARTIAL, - default String getPretty() { - return getLines() - .stream() - .map(SummaryLine::getPretty) - .collect(Collectors.joining("\n")); - } + /** + * No changes were executed (all already applied or nothing to do). + */ + NO_CHANGES } diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/StageResult.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/StageResult.java new file mode 100644 index 000000000..66cd383c9 --- /dev/null +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/StageResult.java @@ -0,0 +1,158 @@ +/* + * 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.response.data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Result data for a stage execution. + */ +public class StageResult { + + private String stageId; + private String stageName; + private StageStatus status; + private long durationMs; + private List changes; + + public StageResult() { + this.changes = new ArrayList<>(); + } + + private StageResult(Builder builder) { + this.stageId = builder.stageId; + this.stageName = builder.stageName; + this.status = builder.status; + this.durationMs = builder.durationMs; + this.changes = builder.changes != null ? builder.changes : new ArrayList<>(); + } + + public String getStageId() { + return stageId; + } + + public void setStageId(String stageId) { + this.stageId = stageId; + } + + public String getStageName() { + return stageName; + } + + public void setStageName(String stageName) { + this.stageName = stageName; + } + + public StageStatus getStatus() { + return status; + } + + public void setStatus(StageStatus status) { + this.status = status; + } + + public long getDurationMs() { + return durationMs; + } + + public void setDurationMs(long durationMs) { + this.durationMs = durationMs; + } + + public List getChanges() { + return changes; + } + + public void setChanges(List changes) { + this.changes = changes; + } + + public boolean isFailed() { + return status == StageStatus.FAILED; + } + + public boolean isCompleted() { + return status == StageStatus.COMPLETED; + } + + public int getAppliedCount() { + return (int) changes.stream() + .filter(ChangeResult::isApplied) + .count(); + } + + public int getSkippedCount() { + return (int) changes.stream() + .filter(ChangeResult::isAlreadyApplied) + .count(); + } + + public int getFailedCount() { + return (int) changes.stream() + .filter(ChangeResult::isFailed) + .count(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String stageId; + private String stageName; + private StageStatus status; + private long durationMs; + private List changes = new ArrayList<>(); + + public Builder stageId(String stageId) { + this.stageId = stageId; + return this; + } + + public Builder stageName(String stageName) { + this.stageName = stageName; + return this; + } + + public Builder status(StageStatus status) { + this.status = status; + return this; + } + + public Builder durationMs(long durationMs) { + this.durationMs = durationMs; + return this; + } + + public Builder changes(List changes) { + this.changes = changes; + return this; + } + + public Builder addChange(ChangeResult change) { + if (this.changes == null) { + this.changes = new ArrayList<>(); + } + this.changes.add(change); + return this; + } + + public StageResult build() { + return new StageResult(this); + } + } +} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/Summarizer.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/StageStatus.java similarity index 52% rename from core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/Summarizer.java rename to core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/StageStatus.java index 5c976bc0c..7367ce6df 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/Summarizer.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/StageStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 Flamingock (https://www.flamingock.io) + * 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. @@ -13,15 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.flamingock.internal.core.summary; +package io.flamingock.internal.common.core.response.data; -import io.flamingock.internal.core.task.navigation.summary.StepSummary; - -//No thread safe -public interface Summarizer { +/** + * Status of a stage execution. + */ +public enum StageStatus { + /** + * Stage completed successfully with all changes applied. + */ + COMPLETED, - Summarizer add(LINE line); + /** + * Stage failed during execution. + */ + FAILED, - StepSummary getSummary(); + /** + * Stage was skipped (all changes already applied). + */ + SKIPPED, + /** + * Stage was not started (due to prior failure). + */ + NOT_STARTED } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteOperation.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteOperation.java index 8046ea678..12c361bb7 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteOperation.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteOperation.java @@ -16,6 +16,9 @@ package io.flamingock.internal.core.operation; import io.flamingock.internal.common.core.error.FlamingockException; +import io.flamingock.internal.common.core.response.data.ErrorInfo; +import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +import io.flamingock.internal.common.core.response.data.StageResult; import io.flamingock.internal.core.event.EventPublisher; import io.flamingock.internal.core.event.model.impl.PipelineCompletedEvent; import io.flamingock.internal.core.event.model.impl.PipelineFailedEvent; @@ -23,12 +26,12 @@ import io.flamingock.internal.core.event.model.impl.StageCompletedEvent; import io.flamingock.internal.core.event.model.impl.StageFailedEvent; import io.flamingock.internal.core.event.model.impl.StageStartedEvent; +import io.flamingock.internal.core.operation.result.ExecutionResultBuilder; import io.flamingock.internal.core.pipeline.execution.ExecutableStage; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; import io.flamingock.internal.core.pipeline.execution.OrphanExecutionContext; import io.flamingock.internal.core.pipeline.execution.StageExecutionException; import io.flamingock.internal.core.pipeline.execution.StageExecutor; -import io.flamingock.internal.core.pipeline.execution.StageSummary; import io.flamingock.internal.core.pipeline.loaded.LoadedPipeline; import io.flamingock.internal.core.pipeline.loaded.stage.AbstractLoadedStage; import io.flamingock.internal.core.plan.ExecutionPlan; @@ -42,8 +45,9 @@ import java.util.ArrayList; import java.util.List; -import static io.flamingock.internal.util.ObjectUtils.requireNonNull; - +/** + * Executes the pipeline and returns structured result data. + */ public class ExecuteOperation implements Operation { private static final Logger logger = FlamingockLoggerFactory.getLogger("PipelineRunner"); @@ -79,17 +83,20 @@ public ExecuteOperation(RunnerId runnerId, } - @Override public ExecuteResult execute(ExecuteArgs args) { + ExecuteResponseData result; try { - this.execute(args.getPipeline()); + result = this.execute(args.getPipeline()); + } catch (OperationException operationException) { + result = operationException.getResult(); + throw operationException; } catch (Throwable throwable) { - throw processAndGetFlamingockException(throwable); + throw processAndGetFlamingockException(throwable, null); } finally { finalizer.run(); } - return null; + return new ExecuteResult(result); } private static List validateAndGetExecutableStages(LoadedPipeline pipeline) { @@ -102,10 +109,11 @@ private static List validateAndGetExecutableStages(LoadedPi return stages; } - private void execute(LoadedPipeline pipeline) throws FlamingockException { + private ExecuteResponseData execute(LoadedPipeline pipeline) throws FlamingockException { eventPublisher.publish(new PipelineStartedEvent()); - OperationSummary pipelineSummary = null; + ExecutionResultBuilder resultBuilder = new ExecutionResultBuilder().startTimer(); + do { List stages = validateAndGetExecutableStages(pipeline); try (ExecutionPlan execution = executionPlanner.getNextExecution(stages)) { @@ -113,14 +121,10 @@ private void execute(LoadedPipeline pipeline) throws FlamingockException { // This centralized validation ensures both community and cloud paths are validated execution.validate(); - if (pipelineSummary == null) { - pipelineSummary = new OperationSummary(execution.getPipeline()); - } - final OperationSummary pipelineSummaryTemp = pipelineSummary; if (execution.isExecutionRequired()) { execution.applyOnEach((executionId, lock, executableStage) -> { - StageSummary stageSummary = runStage(executionId, lock, executableStage); - pipelineSummaryTemp.merge(stageSummary); + StageResult stageResult = runStage(executionId, lock, executableStage); + resultBuilder.addStage(stageResult); }); } else { break; @@ -137,20 +141,25 @@ private void execute(LoadedPipeline pipeline) throws FlamingockException { } break; } catch (StageExecutionException e) { - //if it's a StageExecutionException, we can safely assume the stage started its - //execution, therefor the pipelinesSummary is initialised - requireNonNull(pipelineSummary).merge(e.getSummary()); - throw OperationException.fromExisting(e.getCause(), pipelineSummary); + resultBuilder.addStage(e.getResult()); + resultBuilder.stopTimer().failed(); + resultBuilder.error(ErrorInfo.fromThrowable(e.getCause(), e.getFailedChangeId(), e.getResult().getStageId())); + throw OperationException.fromExisting(e.getCause(), resultBuilder.build()); } } while (true); - String summary = pipelineSummary != null ? pipelineSummary.getPretty() : ""; - logger.info("Finished Flamingock process successfully\n{}", summary); + resultBuilder.stopTimer().success(); + ExecuteResponseData result = resultBuilder.build(); + + logger.info("Finished Flamingock process successfully [applied={} skipped={} duration={}ms]", + result.getAppliedChanges(), result.getSkippedChanges(), result.getTotalDurationMs()); eventPublisher.publish(new PipelineCompletedEvent()); + + return result; } - private StageSummary runStage(String executionId, Lock lock, ExecutableStage executableStage) { + private StageResult runStage(String executionId, Lock lock, ExecutableStage executableStage) { try { return startStage(executionId, lock, executableStage); } catch (StageExecutionException exception) { @@ -158,21 +167,21 @@ private StageSummary runStage(String executionId, Lock lock, ExecutableStage exe eventPublisher.publish(new PipelineFailedEvent(exception)); throw exception; } catch (Throwable generalException) { - throw processAndGetFlamingockException(generalException); + throw processAndGetFlamingockException(generalException, null); } } - private StageSummary startStage(String executionId, Lock lock, ExecutableStage executableStage) throws StageExecutionException { + private StageResult startStage(String executionId, Lock lock, ExecutableStage executableStage) throws StageExecutionException { eventPublisher.publish(new StageStartedEvent()); logger.debug("Applied state to process:\n{}", executableStage); ExecutionContext executionContext = new ExecutionContext(executionId, orphanExecutionContext.getHostname(), orphanExecutionContext.getMetadata()); StageExecutor.Output executionOutput = stageExecutor.executeStage(executableStage, executionContext, lock); eventPublisher.publish(new StageCompletedEvent(executionOutput)); - return executionOutput.getSummary(); + return executionOutput.getResult(); } - private FlamingockException processAndGetFlamingockException(Throwable exception) throws FlamingockException { + private FlamingockException processAndGetFlamingockException(Throwable exception, ExecutionResultBuilder resultBuilder) throws FlamingockException { FlamingockException flamingockException; if (exception instanceof OperationException) { OperationException pipelineException = (OperationException) exception; diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteResult.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteResult.java index cac2f9884..cf9163d80 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteResult.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteResult.java @@ -17,10 +17,24 @@ import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +/** + * Result of executing the pipeline. + * Contains structured result data for reporting and CLI output. + */ public class ExecuteResult extends AbstractOperationResult { + private final ExecuteResponseData data; + + public ExecuteResult(ExecuteResponseData data) { + this.data = data; + } + + public ExecuteResponseData getData() { + return data; + } + @Override public Object toResponseData() { - return ExecuteResponseData.placeholder(); + return data; } } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java index 50a08696e..b4a4f3e1e 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java @@ -16,27 +16,29 @@ package io.flamingock.internal.core.operation; import io.flamingock.internal.common.core.error.FlamingockException; +import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +/** + * Exception thrown when a pipeline operation fails. + * Contains the execution result data with information about what was executed. + */ public class OperationException extends FlamingockException { - public static OperationException fromExisting(Throwable exception, OperationSummary summary) { + public static OperationException fromExisting(Throwable exception, ExecuteResponseData result) { Throwable cause = exception.getCause(); return (exception instanceof FlamingockException) && cause != null - ? new OperationException(cause, summary) - : new OperationException(exception, summary); + ? new OperationException(cause, result) + : new OperationException(exception, result); } - private final OperationSummary summary; - + private final ExecuteResponseData result; - private OperationException(Throwable throwable, OperationSummary summary) { + private OperationException(Throwable throwable, ExecuteResponseData result) { super(throwable); - this.summary = summary; + this.result = result; } - public OperationSummary getSummary() { - return summary; + public ExecuteResponseData getResult() { + return result; } - - } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationSummary.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationSummary.java deleted file mode 100644 index 6ea2dc8a7..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationSummary.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2023 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.core.operation; - -import io.flamingock.internal.core.pipeline.execution.ExecutablePipeline; -import io.flamingock.internal.core.pipeline.execution.ExecutableStage; -import io.flamingock.internal.core.pipeline.execution.StageSummary; -import io.flamingock.internal.core.task.executable.ExecutableTask; -import io.flamingock.internal.core.pipeline.execution.TaskSummarizer; -import io.flamingock.internal.core.task.navigation.step.complete.CompletedAlreadyAppliedStep; -import io.flamingock.internal.core.task.navigation.summary.StepSummary; - -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; - -public class OperationSummary implements StepSummary { - - private final LinkedHashMap stageSummaries = new LinkedHashMap<>(); - - public OperationSummary(ExecutablePipeline pipeline) { - - for (ExecutableStage executableStage : pipeline.getExecutableStages()) { - StageSummary stageSummary = new StageSummary(executableStage.getName()); - - for (ExecutableTask executableTask : executableStage.getTasks()) { - if(executableTask.isAlreadyApplied()) { - stageSummary.addSummary(new TaskSummarizer(executableTask) - .add(new CompletedAlreadyAppliedStep(executableTask)) - .getSummary()); - } else { - stageSummary.addSummary(new TaskSummarizer(executableTask) - .addNotReachedTask(executableTask.getDescriptor()) - .getSummary()); - } - } - stageSummaries.put(executableStage.getName(), stageSummary); - } - } - - public void merge(StageSummary stageSummary) { - StageSummary presentStageSummary = stageSummaries.get(stageSummary.getId()); - if(presentStageSummary == null) { - stageSummaries.put(stageSummary.getId(), stageSummary); - } else { - StageSummary mergedStageSummary = presentStageSummary.merge(stageSummary); - stageSummaries.put(mergedStageSummary.getId(), mergedStageSummary); - } - - } - - public void put(StageSummary stageSummary) { - stageSummaries.put(stageSummary.getId(), stageSummary); - } - - - - @Override - public List getLines() { - return new LinkedList<>(stageSummaries.values()); - } -} 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 new file mode 100644 index 000000000..34c57f053 --- /dev/null +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ChangeResultBuilder.java @@ -0,0 +1,119 @@ +/* + * 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.core.operation.result; + +import io.flamingock.internal.common.core.response.data.ChangeResult; +import io.flamingock.internal.common.core.response.data.ChangeStatus; +import io.flamingock.internal.common.core.task.TaskDescriptor; +import io.flamingock.internal.core.task.executable.ExecutableTask; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * Builder for creating ChangeResult instances from task execution data. + */ +public class ChangeResultBuilder { + + private String changeId; + private String author; + private ChangeStatus status; + private long durationMs; + private String targetSystemId; + private Throwable error; + private LocalDateTime startTime; + + public ChangeResultBuilder() { + } + + public ChangeResultBuilder fromTask(ExecutableTask task) { + TaskDescriptor descriptor = task.getDescriptor(); + this.changeId = descriptor.getId(); + this.author = descriptor.getAuthor(); + if (descriptor.getTargetSystem() != null) { + this.targetSystemId = descriptor.getTargetSystem().getId(); + } + return this; + } + + public ChangeResultBuilder startTimer() { + this.startTime = LocalDateTime.now(); + return this; + } + + public ChangeResultBuilder stopTimer() { + if (startTime != null) { + this.durationMs = Duration.between(startTime, LocalDateTime.now()).toMillis(); + } + return this; + } + + public ChangeResultBuilder status(ChangeStatus status) { + this.status = status; + return this; + } + + public ChangeResultBuilder applied() { + this.status = ChangeStatus.APPLIED; + return this; + } + + public ChangeResultBuilder alreadyApplied() { + this.status = ChangeStatus.ALREADY_APPLIED; + return this; + } + + public ChangeResultBuilder failed(Throwable error) { + this.status = ChangeStatus.FAILED; + this.error = error; + return this; + } + + public ChangeResultBuilder rolledBack() { + this.status = ChangeStatus.ROLLED_BACK; + return this; + } + + public ChangeResultBuilder error(Throwable error) { + this.error = error; + return this; + } + + public ChangeResultBuilder notReached() { + this.status = ChangeStatus.NOT_REACHED; + return this; + } + + public ChangeResultBuilder durationMs(long durationMs) { + this.durationMs = durationMs; + return this; + } + + public ChangeResult build() { + ChangeResult.Builder builder = ChangeResult.builder() + .changeId(changeId) + .author(author) + .status(status) + .durationMs(durationMs) + .targetSystemId(targetSystemId); + + if (error != null) { + builder.error(error); + } + + return builder.build(); + } +} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ExecutionResultBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ExecutionResultBuilder.java new file mode 100644 index 000000000..4c668ec44 --- /dev/null +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/ExecutionResultBuilder.java @@ -0,0 +1,165 @@ +/* + * 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.core.operation.result; + +import io.flamingock.internal.common.core.response.data.ChangeResult; +import io.flamingock.internal.common.core.response.data.ChangeStatus; +import io.flamingock.internal.common.core.response.data.ErrorInfo; +import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +import io.flamingock.internal.common.core.response.data.ExecutionStatus; +import io.flamingock.internal.common.core.response.data.StageResult; +import io.flamingock.internal.common.core.response.data.StageStatus; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; + +/** + * Builder for creating ExecuteResponseData instances from pipeline execution data. + */ +public class ExecutionResultBuilder { + + private ExecutionStatus status; + private LocalDateTime startTime; + private LocalDateTime endTime; + private final List stages = new ArrayList<>(); + private ErrorInfo error; + + public ExecutionResultBuilder() { + } + + public ExecutionResultBuilder startTimer() { + this.startTime = LocalDateTime.now(); + return this; + } + + public ExecutionResultBuilder stopTimer() { + this.endTime = LocalDateTime.now(); + return this; + } + + public ExecutionResultBuilder addStage(StageResult stage) { + this.stages.add(stage); + return this; + } + + public ExecutionResultBuilder status(ExecutionStatus status) { + this.status = status; + return this; + } + + public ExecutionResultBuilder success() { + this.status = ExecutionStatus.SUCCESS; + return this; + } + + public ExecutionResultBuilder failed() { + this.status = ExecutionStatus.FAILED; + return this; + } + + public ExecutionResultBuilder partial() { + this.status = ExecutionStatus.PARTIAL; + return this; + } + + public ExecutionResultBuilder noChanges() { + this.status = ExecutionStatus.NO_CHANGES; + return this; + } + + public ExecutionResultBuilder error(ErrorInfo error) { + this.error = error; + return this; + } + + public ExecutionResultBuilder error(Throwable throwable, String changeId, String stageId) { + this.error = ErrorInfo.fromThrowable(throwable, changeId, stageId); + return this; + } + + public ExecuteResponseData build() { + long durationMs = 0; + if (startTime != null && endTime != null) { + durationMs = Duration.between(startTime, endTime).toMillis(); + } + + // Calculate aggregate counts from stages + int totalStages = stages.size(); + int completedStages = 0; + int failedStages = 0; + int totalChanges = 0; + int appliedChanges = 0; + int skippedChanges = 0; + int failedChanges = 0; + + for (StageResult stage : stages) { + if (stage.getStatus() == StageStatus.COMPLETED) { + completedStages++; + } else if (stage.getStatus() == StageStatus.FAILED) { + failedStages++; + } + + for (ChangeResult change : stage.getChanges()) { + totalChanges++; + if (change.getStatus() == ChangeStatus.APPLIED) { + appliedChanges++; + } else if (change.getStatus() == ChangeStatus.ALREADY_APPLIED) { + skippedChanges++; + } else if (change.getStatus() == ChangeStatus.FAILED) { + failedChanges++; + } + } + } + + // Determine status if not explicitly set + if (status == null) { + if (failedChanges > 0) { + status = ExecutionStatus.FAILED; + } else if (appliedChanges > 0) { + status = ExecutionStatus.SUCCESS; + } else { + status = ExecutionStatus.NO_CHANGES; + } + } + + Instant startInstant = startTime != null + ? startTime.atZone(ZoneId.systemDefault()).toInstant() + : null; + Instant endInstant = endTime != null + ? endTime.atZone(ZoneId.systemDefault()).toInstant() + : null; + + return ExecuteResponseData.builder() + .status(status) + .startTime(startInstant) + .endTime(endInstant) + .totalDurationMs(durationMs) + .totalStages(totalStages) + .completedStages(completedStages) + .failedStages(failedStages) + .totalChanges(totalChanges) + .appliedChanges(appliedChanges) + .skippedChanges(skippedChanges) + .failedChanges(failedChanges) + .stages(stages) + .error(error) + .build(); + } +} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/StageResultBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/StageResultBuilder.java new file mode 100644 index 000000000..197577c35 --- /dev/null +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/result/StageResultBuilder.java @@ -0,0 +1,117 @@ +/* + * 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.core.operation.result; + +import io.flamingock.internal.common.core.response.data.ChangeResult; +import io.flamingock.internal.common.core.response.data.StageResult; +import io.flamingock.internal.common.core.response.data.StageStatus; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * Builder for creating StageResult instances from stage execution data. + */ +public class StageResultBuilder { + + private String stageId; + private String stageName; + private StageStatus status; + private long durationMs; + private List changes = new ArrayList<>(); + private LocalDateTime startTime; + + public StageResultBuilder() { + } + + public StageResultBuilder stageId(String stageId) { + this.stageId = stageId; + return this; + } + + public StageResultBuilder stageName(String stageName) { + this.stageName = stageName; + return this; + } + + public StageResultBuilder startTimer() { + this.startTime = LocalDateTime.now(); + return this; + } + + public StageResultBuilder stopTimer() { + if (startTime != null) { + this.durationMs = Duration.between(startTime, LocalDateTime.now()).toMillis(); + } + return this; + } + + public StageResultBuilder status(StageStatus status) { + this.status = status; + return this; + } + + public StageResultBuilder completed() { + this.status = StageStatus.COMPLETED; + return this; + } + + public StageResultBuilder failed() { + this.status = StageStatus.FAILED; + return this; + } + + public StageResultBuilder skipped() { + this.status = StageStatus.SKIPPED; + return this; + } + + public StageResultBuilder notStarted() { + this.status = StageStatus.NOT_STARTED; + return this; + } + + public StageResultBuilder addChange(ChangeResult change) { + this.changes.add(change); + return this; + } + + public StageResultBuilder changes(List changes) { + this.changes = changes; + return this; + } + + public StageResultBuilder durationMs(long durationMs) { + this.durationMs = durationMs; + return this; + } + + public boolean hasFailedChanges() { + return changes.stream().anyMatch(ChangeResult::isFailed); + } + + public StageResult build() { + return StageResult.builder() + .stageId(stageId) + .stageName(stageName) + .status(status) + .durationMs(durationMs) + .changes(changes) + .build(); + } +} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutionException.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutionException.java index dd783102e..fffe9ff70 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutionException.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutionException.java @@ -16,26 +16,35 @@ package io.flamingock.internal.core.pipeline.execution; import io.flamingock.internal.common.core.error.FlamingockException; +import io.flamingock.internal.common.core.response.data.StageResult; +/** + * Exception thrown when a stage execution fails. + * Contains the stage result data with information about what was executed. + */ public class StageExecutionException extends FlamingockException { - public static StageExecutionException fromExisting(Throwable exception, StageSummary summary) { + public static StageExecutionException fromResult(Throwable exception, StageResult result, String failedChangeId) { Throwable cause = exception.getCause(); return (exception instanceof FlamingockException) && cause != null - ? new StageExecutionException(cause, summary) - : new StageExecutionException(exception, summary); + ? new StageExecutionException(cause, result, failedChangeId) + : new StageExecutionException(exception, result, failedChangeId); } - private final StageSummary summary; + private final StageResult result; + private final String failedChangeId; - private StageExecutionException(Throwable cause, StageSummary summary) { + private StageExecutionException(Throwable cause, StageResult result, String failedChangeId) { super(cause); - this.summary = summary; + this.result = result; + this.failedChangeId = failedChangeId; } - public StageSummary getSummary() { - return summary; + public StageResult getResult() { + return result; } - + public String getFailedChangeId() { + return failedChangeId; + } } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java index 086601e2c..0779a5fbc 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java @@ -18,10 +18,12 @@ import io.flamingock.internal.common.core.context.ContextResolver; import io.flamingock.internal.common.core.context.Dependency; import io.flamingock.internal.common.core.pipeline.StageDescriptor; +import io.flamingock.internal.common.core.response.data.StageResult; import io.flamingock.internal.core.context.PriorityContext; import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.core.external.store.lock.Lock; import io.flamingock.internal.core.external.targets.TargetSystemManager; +import io.flamingock.internal.core.operation.result.StageResultBuilder; import io.flamingock.internal.core.task.executable.ExecutableTask; import io.flamingock.internal.core.task.navigation.FailedChangeProcessResult; import io.flamingock.internal.core.task.navigation.navigator.ChangeProcessResult; @@ -36,9 +38,12 @@ import java.util.Set; import java.util.stream.Stream; +/** + * Executes stages and returns structured result data. + */ public class StageExecutor { private static final Logger logger = FlamingockLoggerFactory.getLogger("StageExecutor"); - + protected final LifecycleAuditWriter auditWriter; private final ContextResolver baseDependencyContext; @@ -64,56 +69,67 @@ public Output executeStage(ExecutableStage executableStage, LocalDateTime stageStart = LocalDateTime.now(); String stageName = executableStage.getName(); long taskCount = getTasksStream(executableStage).count(); - - logger.info("Starting stage execution [stage={} tasks={} execution_id={}]", - stageName, taskCount, executionContext.getExecutionId()); - StageSummary summary = new StageSummary(stageName); + logger.info("Starting stage execution [stage={} tasks={} execution_id={}]", + stageName, taskCount, executionContext.getExecutionId()); + + StageResultBuilder resultBuilder = new StageResultBuilder() + .stageId(stageName) + .stageName(stageName) + .startTimer(); + PriorityContext dependencyContext = new PriorityContext(baseDependencyContext); dependencyContext.addDependency(new Dependency(StageDescriptor.class, executableStage)); ChangeProcessStrategyFactory changeProcessFactory = getStepNavigatorBuilder(executionContext, lock, dependencyContext); try { logger.debug("Processing changes [stage={} context={}]", stageName, executionContext.getExecutionId()); - + getTasksStream(executableStage) .map(changeProcessFactory::setChange) .map(ChangeProcessStrategyFactory::build) .map(ChangeProcessStrategy::applyChange) .peek(result -> { - summary.addSummary(result.getSummary()); + resultBuilder.addChange(result.getResult()); if (result.isFailed()) { - logger.error("Change failed [change={} stage={}]", - result.getChangeId(), stageName); + logger.error("Change failed [change={} stage={}]", + result.getChangeId(), stageName); } else { - logger.debug("Change completed successfully [change={} stage={}]", - result.getChangeId(), stageName); + logger.debug("Change completed successfully [change={} stage={}]", + result.getChangeId(), stageName); } }) .filter(ChangeProcessResult::isFailed) .findFirst() - .map(processResult -> (FailedChangeProcessResult)processResult) + .map(processResult -> (FailedChangeProcessResult) processResult) .ifPresent(failedResult -> { + resultBuilder.stopTimer().failed(); Duration stageDuration = Duration.between(stageStart, LocalDateTime.now()); logger.debug("Stage execution failed [stage={} duration={} failed_change={}]", - stageName, formatDuration(stageDuration), failedResult.getChangeId()); - throw StageExecutionException.fromExisting(failedResult.getException(), summary); + stageName, formatDuration(stageDuration), failedResult.getChangeId()); + throw StageExecutionException.fromResult( + failedResult.getException(), + resultBuilder.build(), + failedResult.getChangeId() + ); }); + resultBuilder.stopTimer().completed(); Duration stageDuration = Duration.between(stageStart, LocalDateTime.now()); - logger.info("Stage execution completed successfully [stage={} duration={} tasks={}]", - stageName, formatDuration(stageDuration), taskCount); + logger.info("Stage execution completed successfully [stage={} duration={} tasks={}]", + stageName, formatDuration(stageDuration), taskCount); } catch (StageExecutionException stageExecutionException) { throw stageExecutionException; } catch (Throwable throwable) { + resultBuilder.stopTimer().failed(); Duration stageDuration = Duration.between(stageStart, LocalDateTime.now()); logger.debug("Stage execution failed with unexpected error [stage={} duration={} error={}]", - stageName, formatDuration(stageDuration), throwable.getMessage(), throwable); - throw StageExecutionException.fromExisting(throwable, summary); + stageName, formatDuration(stageDuration), throwable.getMessage(), throwable); + throw StageExecutionException.fromResult(throwable, resultBuilder.build(), null); } - return new Output(summary); + return new Output(resultBuilder.build()); } private ChangeProcessStrategyFactory getStepNavigatorBuilder(ExecutionContext executionContext, Lock lock, ContextResolver contextResolver) { @@ -131,17 +147,17 @@ protected Stream getTasksStream(ExecutableStage execut public static class Output { - private final StageSummary summary; + private final StageResult result; - public Output(StageSummary summary) { - this.summary = summary; + public Output(StageResult result) { + this.result = result; } - public StageSummary getSummary() { - return summary; + public StageResult getResult() { + return result; } } - + private String formatDuration(Duration duration) { long millis = duration.toMillis(); if (millis < 1000) { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageSummary.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageSummary.java deleted file mode 100644 index d25469aa4..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageSummary.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2023 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.core.pipeline.execution; - -import io.flamingock.internal.core.task.navigation.summary.StepSummary; -import io.flamingock.internal.core.task.navigation.summary.StepSummaryLine; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.stream.Collectors; - -public class StageSummary implements StepSummary, StepSummaryLine { - - private final String stageName; - - private final LinkedHashMap taskExecutionSummaries = new LinkedHashMap<>(); - - public StageSummary(String stageName) { - this.stageName = stageName; - } - - public void addSummary(TaskSummary summary) { - taskExecutionSummaries.put(summary.getId(), summary); - } - - @Override - public String getId() { - return stageName; - } - - @Override - public List getLines() { - return taskExecutionSummaries.values() - .stream() - .map(StepSummary::getLines) - .flatMap(List::stream) - .collect(Collectors.toList()); - } - - @Override - public String getPretty() { - return String.format("\nStage: %s\n%s", stageName, StepSummary.super.getPretty()); - } - - public StageSummary merge(StageSummary overriderSummary) { - for (TaskSummary overriderTask : overriderSummary.taskExecutionSummaries.values()) { - taskExecutionSummaries.put(overriderTask.getId(), overriderTask); - } - return this; - } -} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/TaskSummarizer.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/TaskSummarizer.java deleted file mode 100644 index f8652af31..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/TaskSummarizer.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2023 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.core.pipeline.execution; - -import io.flamingock.internal.core.task.navigation.step.ExecutableStep; -import io.flamingock.internal.core.task.navigation.step.afteraudit.AfterExecutionAuditStep; -import io.flamingock.internal.core.task.navigation.step.complete.CompletedAlreadyAppliedStep; -import io.flamingock.internal.core.task.navigation.step.complete.failed.CompletedFailedManualRollback; -import io.flamingock.internal.core.task.navigation.step.execution.ExecutionStep; -import io.flamingock.internal.core.task.navigation.step.rolledback.RolledBackStep; -import io.flamingock.internal.common.core.task.TaskDescriptor; -import io.flamingock.internal.core.task.navigation.summary.AbstractTaskStepSummaryLine; -import io.flamingock.internal.core.task.navigation.summary.StepSummarizer; -import io.flamingock.internal.core.task.navigation.summary.StepSummaryLine; - -import java.util.LinkedList; -import java.util.List; - -//NO thread-safe -public class TaskSummarizer implements StepSummarizer { - - private final String taskId; - - private boolean success = false; - - private List lines = new LinkedList<>(); - - public TaskSummarizer(TaskDescriptor taskDescriptor) { - this.taskId = taskDescriptor.getId(); - } - - @Override - public void clear() { - lines = new LinkedList<>(); - } - - @Override - public TaskSummarizer add(StepSummaryLine line) { - lines.add(line); - return this; - } - - @Override - public TaskSummarizer add(ExecutableStep step) { - return addStep(step.getTask().getDescriptor(), new AbstractTaskStepSummaryLine.StartedTaskSummaryLine(step)); - } - - @Override - public TaskSummarizer add(ExecutionStep step) { - return addStep(step.getTask().getDescriptor(), new AbstractTaskStepSummaryLine.AppliedTaskSummaryLine(step)); - } - - @Override - public TaskSummarizer add(AfterExecutionAuditStep step) { - return addStep(step.getTask().getDescriptor(), new AbstractTaskStepSummaryLine.AfterExecutionTaskAuditSummaryLine(step)); - } - - @Override - public TaskSummarizer add(RolledBackStep step) { - return addStep(step.getTask().getDescriptor(), new AbstractTaskStepSummaryLine.RolledBackTaskSummaryLine(step)); - } - - @Override - public TaskSummarizer add(CompletedFailedManualRollback step) { - return addStep(step.getTask().getDescriptor(), new AbstractTaskStepSummaryLine.FailedCompletedManualRollbackTaskSummaryLine(step)); - } - - @Override - public TaskSummarizer add(CompletedAlreadyAppliedStep step) { - return addStep(step.getTask().getDescriptor(), new AbstractTaskStepSummaryLine.AlreadyAppliedTaskSummaryLine(step)); - } - - @Override - public TaskSummarizer addNotReachedTask(TaskDescriptor loadedTask) { - add(new AbstractTaskStepSummaryLine.InitialTaskSummaryLine(loadedTask)); - add(new AbstractTaskStepSummaryLine.NotReachedTaskSummaryLine(loadedTask)); - return this; - } - - public TaskSummarizer setSuccessful() { - success = true; - return this; - } - - public TaskSummarizer setFailed() { - success = false; - return this; - } - - @Override - public TaskSummary getSummary() { - TaskSummary taskSummary = new TaskSummary(taskId, success); - lines.forEach(taskSummary::addLine); - return taskSummary; - } - - private TaskSummarizer addStep(TaskDescriptor loadedTask, StepSummaryLine step) { - if (lines.isEmpty()) { - add(new AbstractTaskStepSummaryLine.InitialTaskSummaryLine(loadedTask)); - } - return add(step); - } -} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/TaskSummary.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/TaskSummary.java deleted file mode 100644 index 6979c68a8..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/TaskSummary.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023 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.core.pipeline.execution; - -import io.flamingock.internal.core.task.navigation.summary.StepSummary; -import io.flamingock.internal.core.task.navigation.summary.StepSummaryLine; - -import java.util.LinkedList; -import java.util.List; - -public class TaskSummary implements StepSummary, StepSummaryLine { - - private final String taskId; - private final boolean success; - - private final List lines = new LinkedList<>(); - - public TaskSummary(String taskId, boolean success) { - this.taskId = taskId; - this.success = success; - } - - public void addLine(StepSummaryLine summary) { - lines.add(summary); - } - - @Override - public String getId() { - return taskId; - } - - @Override - public List getLines() { - return lines; - } - - @Override - public String getPretty() { - return String.format("\nTask: %s\n%s", taskId, StepSummary.super.getPretty()); - } - - - public boolean isSuccess() { - return success; - } - - public boolean isFailed() { - return !isSuccess(); - } -} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/SummaryLine.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/SummaryLine.java deleted file mode 100644 index adf36e022..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/summary/SummaryLine.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2023 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.core.summary; - -public interface SummaryLine { - String getPretty(); -} \ No newline at end of file diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/FailedChangeProcessResult.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/FailedChangeProcessResult.java index ba01cb4f0..d258e81c7 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/FailedChangeProcessResult.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/FailedChangeProcessResult.java @@ -15,14 +15,19 @@ */ package io.flamingock.internal.core.task.navigation; -import io.flamingock.internal.core.pipeline.execution.TaskSummary; +import io.flamingock.internal.common.core.response.data.ChangeResult; import io.flamingock.internal.core.task.navigation.navigator.ChangeProcessResult; +/** + * Result of a failed change process. + * Contains the change result data and the exception that caused the failure. + */ public class FailedChangeProcessResult extends ChangeProcessResult { + private final Throwable exception; - public FailedChangeProcessResult(String changeId, TaskSummary summary, Throwable exception) { - super(changeId, summary); + public FailedChangeProcessResult(String changeId, ChangeResult result, Throwable exception) { + super(changeId, result); this.exception = exception; } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessResult.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessResult.java index c0e8753c9..ef3d49be4 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessResult.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessResult.java @@ -15,24 +15,28 @@ */ package io.flamingock.internal.core.task.navigation.navigator; -import io.flamingock.internal.core.pipeline.execution.TaskSummary; +import io.flamingock.internal.common.core.response.data.ChangeResult; +/** + * Result of processing a single change. + * Contains the change result data and failure information if applicable. + */ public class ChangeProcessResult { private final String changeId; - private final TaskSummary summary; + private final ChangeResult result; - public ChangeProcessResult(String changeId, TaskSummary summary) { + public ChangeProcessResult(String changeId, ChangeResult result) { this.changeId = changeId; - this.summary = summary; + this.result = result; } public String getChangeId() { return changeId; } - public TaskSummary getSummary() { - return summary; + public ChangeResult getResult() { + return result; } public boolean isFailed() { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessStrategy.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessStrategy.java index 284f5ad0a..0597d3163 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessStrategy.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessStrategy.java @@ -15,9 +15,14 @@ */ package io.flamingock.internal.core.task.navigation.navigator; -import io.flamingock.internal.core.pipeline.execution.TaskSummary; - +/** + * Strategy interface for processing changes. + */ public interface ChangeProcessStrategy { + /** + * Applies the change and returns the result. + * @return the result of applying the change + */ ChangeProcessResult applyChange(); } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessStrategyFactory.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessStrategyFactory.java index 47c8f6cf7..0a2328a58 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessStrategyFactory.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessStrategyFactory.java @@ -20,8 +20,8 @@ import io.flamingock.internal.common.core.error.FlamingockException; import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter; import io.flamingock.internal.core.external.store.lock.Lock; +import io.flamingock.internal.core.operation.result.ChangeResultBuilder; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.core.pipeline.execution.TaskSummarizer; import io.flamingock.internal.core.runtime.proxy.LockGuardProxyFactory; import io.flamingock.internal.core.external.targets.TargetSystemManager; import io.flamingock.internal.core.external.targets.operations.TargetSystemOps; @@ -108,9 +108,9 @@ public ChangeProcessStrategyFactory setExecutionContext(ExecutionContext executi public ChangeProcessStrategy build() { changeLogger.logStartChangeProcessStrategy(change.getId()); - + TargetSystemOps targetSystemOps = getTargetSystem(); - + // Log target system resolution changeLogger.logTargetSystemResolved(change.getId(), change.getTargetSystem()); @@ -126,7 +126,7 @@ public ChangeProcessStrategy build() { auditStoreOps, baseContext, executionContext, - new TaskSummarizer(change), + new ChangeResultBuilder().fromTask(change), lockGuardProxyFactory, TimeService.getDefault() ); @@ -152,7 +152,7 @@ private TargetSystemOps getTargetSystem() { * @param auditStoreOps AuditStoreOperations interface * @param baseContext Base dependency resolution context * @param executionContext Execution context for the change - * @param summarizer Task summarizer for execution tracking + * @param resultBuilder Change result builder for execution tracking * @param proxyFactory Lock guard proxy factory * @return Configured strategy instance appropriate for the target system */ @@ -161,32 +161,32 @@ public static ChangeProcessStrategy getStrategy(ExecutableTask change, AuditStoreStepOperations auditStoreOps, ContextResolver baseContext, ExecutionContext executionContext, - TaskSummarizer summarizer, + ChangeResultBuilder resultBuilder, LockGuardProxyFactory proxyFactory, TimeService timeService) { if(!change.isTransactional()) { changeLogger.logStrategyApplication(change.getId(), targetSystemOps.getId(), "NON_TX"); - return new NonTxChangeProcessStrategy(change, executionContext, targetSystemOps, auditStoreOps, summarizer, proxyFactory, baseContext, timeService); + return new NonTxChangeProcessStrategy(change, executionContext, targetSystemOps, auditStoreOps, resultBuilder, proxyFactory, baseContext, timeService); } switch (targetSystemOps.getOperationType()) { case NON_TX: changeLogger.logStrategyApplication(change.getId(), targetSystemOps.getId(), "NON_TX"); - return new NonTxChangeProcessStrategy(change, executionContext, targetSystemOps, auditStoreOps, summarizer, proxyFactory, baseContext, timeService); + return new NonTxChangeProcessStrategy(change, executionContext, targetSystemOps, auditStoreOps, resultBuilder, proxyFactory, baseContext, timeService); case TX_NON_SYNC: case TX_AUDIT_STORE_SYNC: changeLogger.logStrategyApplication(change.getId(), targetSystemOps.getId(), "TX"); TransactionalTargetSystemOps txTargetSystemOps = (TransactionalTargetSystemOps) targetSystemOps; - return new SimpleTxChangeProcessStrategy(change, executionContext, txTargetSystemOps, auditStoreOps, summarizer, proxyFactory, baseContext, timeService); + return new SimpleTxChangeProcessStrategy(change, executionContext, txTargetSystemOps, auditStoreOps, resultBuilder, proxyFactory, baseContext, timeService); case TX_AUDIT_STORE_SHARED: default: changeLogger.logStrategyApplication(change.getId(), targetSystemOps.getId(), "TX"); TransactionalTargetSystemOps sharedTxTargetSystemOps = (TransactionalTargetSystemOps) targetSystemOps; - return new SharedTxChangeProcessStrategy(change, executionContext, sharedTxTargetSystemOps, auditStoreOps, summarizer, proxyFactory, baseContext, timeService); + return new SharedTxChangeProcessStrategy(change, executionContext, sharedTxTargetSystemOps, auditStoreOps, resultBuilder, proxyFactory, baseContext, timeService); } } @@ -204,4 +204,4 @@ private static AuditTxType getAuditTxStrategy(ExecutableTask change, TargetSyste } -} \ No newline at end of file +} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/AbstractChangeProcessStrategy.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/AbstractChangeProcessStrategy.java index 4ac3d5bdb..288d41453 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/AbstractChangeProcessStrategy.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/AbstractChangeProcessStrategy.java @@ -17,11 +17,11 @@ import io.flamingock.internal.common.core.context.Context; import io.flamingock.internal.common.core.context.ContextResolver; +import io.flamingock.internal.common.core.response.data.ChangeResult; import io.flamingock.internal.core.context.PriorityContext; import io.flamingock.internal.core.context.SimpleContext; +import io.flamingock.internal.core.operation.result.ChangeResultBuilder; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.core.pipeline.execution.TaskSummarizer; -import io.flamingock.internal.core.pipeline.execution.TaskSummary; import io.flamingock.internal.core.runtime.ExecutionRuntime; import io.flamingock.internal.core.runtime.proxy.LockGuardProxyFactory; import io.flamingock.internal.core.external.targets.operations.TargetSystemOps; @@ -43,12 +43,12 @@ /** * Abstract base class for change process strategies implementing common audit and execution patterns. - * + * *

This class provides the foundational infrastructure for executing changes across different - * target system types while maintaining consistent audit trails and execution summaries. + * target system types while maintaining consistent audit trails and execution results. * Concrete implementations define the specific transaction handling and execution flow patterns * appropriate for their target system characteristics. - * + * *

Common Execution Pattern

*
    *
  1. Check if change is already applied (skip if so)
  2. @@ -56,9 +56,9 @@ *
  3. Execute change using strategy-specific approach
  4. *
  5. Audit execution result
  6. *
  7. Handle rollbacks and cleanup as needed
  8. - *
  9. Return execution summary
  10. + *
  11. Return execution result
  12. *
- * + * *

Audit Operations

*

All strategies use consistent audit operations provided by this base class: *

    @@ -67,11 +67,11 @@ *
  • {@code auditAndLogManualRollback} - Records manual rollback operations
  • *
  • {@code auditAndLogAutoRollback} - Records automatic transaction rollbacks
  • *
- * + * *

Execution Runtime

*

The execution runtime provides dependency injection and security context for change execution, * ensuring changes have access to required dependencies while maintaining proper lock management.

- * + * * @param The type of target system operations supported by this strategy */ public abstract class AbstractChangeProcessStrategy implements ChangeProcessStrategy { @@ -83,12 +83,12 @@ public abstract class AbstractChangeProcessStrategyConcrete strategies must implement this method to define their specific * transaction handling, execution flow, and error recovery patterns. - * - * @return Task execution summary with success/failure status and step details + * + * @return Task execution result with success/failure status and details */ abstract protected ChangeProcessResult doApplyChange(); /** * Audits and logs the start of change execution. - * + * * @param startStep The initial step for the change * @param executionContext The execution context * @return The executable step ready for execution @@ -147,25 +147,22 @@ protected ExecutableStep auditAndLogStartExecution(StartStep startStep, Result auditResult = auditStoreOperations.auditStartExecution(startStep, executionContext, timeService.currentDateTime()); stepLogger.logAuditStartResult(auditResult, startStep.getLoadedTask().getId()); ExecutableStep executableStep = startStep.start(); - summarizer.add(executableStep); return executableStep; } /** * Audits and logs the execution result of a change. - * + * * @param executionStep The execution step with results * @return The after-execution audit step */ protected AfterExecutionAuditStep auditAndLogExecution(ExecutionStep executionStep) { - summarizer.add(executionStep); stepLogger.logExecutionResult(executionStep); Result auditResult = auditStoreOperations.auditExecution(executionStep, executionContext, timeService.currentDateTime()); stepLogger.logAuditExecutionResult(auditResult, executionStep.getLoadedTask()); AfterExecutionAuditStep afterExecutionAudit = executionStep.withAuditResult(auditResult); - summarizer.add(afterExecutionAudit); return afterExecutionAudit; } @@ -174,22 +171,20 @@ protected void auditAndLogManualRollback(ManualRolledBackStep rolledBackStep, Ex Result auditResult = auditStoreOperations.auditManualRollback(rolledBackStep, executionContext, timeService.currentDateTime()); stepLogger.logAuditManualRollbackResult(auditResult, rolledBackStep.getLoadedTask()); CompletedFailedManualRollback failedStep = rolledBackStep.applyAuditResult(auditResult); - summarizer.add(failedStep); } protected void auditAndLogAutoRollback(CompleteAutoRolledBackStep rolledBackStep, ExecutionContext executionContext) { Result auditResult = auditStoreOperations.auditAutoRollback(rolledBackStep, executionContext, timeService.currentDateTime()); stepLogger.logAuditAutoRollbackResult(auditResult, rolledBackStep.getLoadedTask()); - summarizer.add(rolledBackStep); } /** * Builds the execution runtime for change execution. - * + * *

The runtime provides dependency injection context and security proxies * needed for safe change execution with proper lock management. - * + * * @return Configured execution runtime */ protected ExecutionRuntime buildExecutionRuntime() { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/NonTxChangeProcessStrategy.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/NonTxChangeProcessStrategy.java index c8e167bc3..a22fa4456 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/NonTxChangeProcessStrategy.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/NonTxChangeProcessStrategy.java @@ -16,9 +16,9 @@ package io.flamingock.internal.core.task.navigation.navigator.strategy; import io.flamingock.internal.common.core.context.ContextResolver; +import io.flamingock.internal.common.core.response.data.ChangeResult; +import io.flamingock.internal.core.operation.result.ChangeResultBuilder; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.core.pipeline.execution.TaskSummarizer; -import io.flamingock.internal.core.pipeline.execution.TaskSummary; import io.flamingock.internal.core.runtime.proxy.LockGuardProxyFactory; import io.flamingock.internal.core.external.targets.operations.TargetSystemOps; import io.flamingock.internal.core.task.executable.ExecutableTask; @@ -37,11 +37,11 @@ /** * Change process strategy for non-transactional target systems. - * + * *

This strategy is used for target systems that do not support transactions, * such as message queues, REST services, S3 buckets, or other external systems * where changes cannot be atomically rolled back. - * + * *

Execution Flow

*
    *
  1. Audit change start in audit store
  2. @@ -49,13 +49,13 @@ *
  3. Audit execution result in audit store
  4. *
  5. If execution failed, attempt manual rollback of change chain
  6. *
- * + * *

Target System State Outcomes

*
    *
  • Success: Change applied to target system
  • *
  • Failure: Change not applied, rollback chain applied with best effort
  • *
- * + * *

Audit Store State Outcomes

*
    *
  • STARTED: Change execution began but process interrupted
  • @@ -63,10 +63,10 @@ *
  • STARTED → FAILED: Change execution failed
  • *
  • STARTED → FAILED → ROLLED_BACK: Change failed and rollback chain completed
  • *
- * + * *

Recovery Considerations

- *

Non-transactional target systems require careful recovery handling since changes - * cannot be automatically rolled back. The rollback chain mechanism provides best-effort + *

Non-transactional target systems require careful recovery handling since changes + * cannot be automatically rolled back. The rollback chain mechanism provides best-effort * cleanup, but manual intervention may be required for complete system consistency.

*/ public class NonTxChangeProcessStrategy extends AbstractChangeProcessStrategy { @@ -77,33 +77,42 @@ public NonTxChangeProcessStrategy(ExecutableTask change, ExecutionContext executionContext, TargetSystemOps targetSystem, AuditStoreStepOperations auditStoreOperations, - TaskSummarizer summarizer, + ChangeResultBuilder resultBuilder, LockGuardProxyFactory proxyFactory, ContextResolver baseContext, TimeService timeService) { - super(change, executionContext, targetSystem, auditStoreOperations, summarizer, proxyFactory, baseContext, timeService); + super(change, executionContext, targetSystem, auditStoreOperations, resultBuilder, proxyFactory, baseContext, timeService); } @Override protected ChangeProcessResult doApplyChange() { + resultBuilder.startTimer(); + StartStep startStep = new StartStep(change); ExecutableStep executableStep = auditAndLogStartExecution(startStep, executionContext); logger.debug("Executing non-transactional task [change={}]", change.getId()); - + ExecutionStep changeAppliedStep = targetSystemOps.applyChange(executableStep::execute, buildExecutionRuntime()); AfterExecutionAuditStep afterAudit = auditAndLogExecution(changeAppliedStep); + resultBuilder.stopTimer(); if(afterAudit instanceof FailedAfterExecutionAuditStep) { FailedAfterExecutionAuditStep failedAfterExecutionAudit = (FailedAfterExecutionAuditStep)afterAudit; rollbackActualChangeAndChain(failedAfterExecutionAudit, executionContext); - TaskSummary summary = summarizer.setFailed().getSummary(); - return new FailedChangeProcessResult(change.getId(), summary, failedAfterExecutionAudit.getMainError()); + Throwable mainError = failedAfterExecutionAudit.getMainError(); + ChangeResult result = resultBuilder + .failed(mainError) + .build(); + return new FailedChangeProcessResult(change.getId(), result, mainError); } else { - return new ChangeProcessResult(change.getId(), summarizer.setSuccessful().getSummary()); + ChangeResult result = resultBuilder + .applied() + .build(); + return new ChangeProcessResult(change.getId(), result); } } @@ -112,7 +121,6 @@ private void rollbackActualChangeAndChain(FailedAfterExecutionAuditStep rollable rollableFailedStep.getRollbackSteps().forEach(rollableStep -> { ManualRolledBackStep rolledBack = targetSystemOps.rollbackChange(rollableStep::rollback, buildExecutionRuntime()); stepLogger.logManualRollbackResult(rolledBack); - summarizer.add(rolledBack); auditAndLogManualRollback(rolledBack, executionContext); }); } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/SharedTxChangeProcessStrategy.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/SharedTxChangeProcessStrategy.java index f150432ef..0465203e5 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/SharedTxChangeProcessStrategy.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/SharedTxChangeProcessStrategy.java @@ -16,8 +16,9 @@ package io.flamingock.internal.core.task.navigation.navigator.strategy; import io.flamingock.internal.common.core.context.ContextResolver; +import io.flamingock.internal.common.core.response.data.ChangeResult; +import io.flamingock.internal.core.operation.result.ChangeResultBuilder; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.core.pipeline.execution.TaskSummarizer; import io.flamingock.internal.core.runtime.proxy.LockGuardProxyFactory; import io.flamingock.internal.core.external.targets.operations.TransactionalTargetSystemOps; import io.flamingock.internal.core.task.executable.ExecutableTask; @@ -39,10 +40,10 @@ /** * Change process strategy for transactional target systems with shared audit store. - * + * *

This strategy is used when the target system and audit store share the same database * and can participate in the same transaction. - * + * *

Execution Flow

*
    *
  1. Begin shared transaction
  2. @@ -52,20 +53,20 @@ *
  3. Commit transaction (both change and audit atomically)
  4. *
  5. On failure: attempt separate transaction to audit failure details
  6. *
- * + * *

Target System State Outcomes

*
    *
  • Success: Change applied atomically with audit
  • *
  • Failure: No changes persisted (transaction rolled back)
  • *
- * + * *

Audit Store State Outcomes

*
    *
  • STARTED → APPLIED: Successful atomic execution and audit
  • *
  • STARTED → EXECUTION_FAILED → ROLLED_BACK: Failed execution with detailed failure audit
  • *
  • No audit trail: Complete transaction failure (safe to retry)
  • *
- * + * *

Failure Handling

*

When execution fails, this strategy attempts to create a detailed failure audit trail * in a separate transaction: STARTED → EXECUTION_FAILED → ROLLED_BACK. This provides @@ -81,15 +82,17 @@ public SharedTxChangeProcessStrategy(ExecutableTask change, ExecutionContext executionContext, TransactionalTargetSystemOps targetSystemOps, AuditStoreStepOperations auditStoreOperations, - TaskSummarizer summarizer, + ChangeResultBuilder resultBuilder, LockGuardProxyFactory proxyFactory, ContextResolver baseContext, TimeService timeService) { - super(change, executionContext, targetSystemOps, auditStoreOperations, summarizer, proxyFactory, baseContext, timeService); + super(change, executionContext, targetSystemOps, auditStoreOperations, resultBuilder, proxyFactory, baseContext, timeService); } @Override protected ChangeProcessResult doApplyChange() { + resultBuilder.startTimer(); + logger.debug("Executing shared-transactional task [change={}]", change.getId()); Wrapper executionStep = new Wrapper<>(null); @@ -100,16 +103,25 @@ protected ChangeProcessResult doApplyChange() { return auditAndLogExecution(executionStep.getValue()); }, buildExecutionRuntime()); + resultBuilder.stopTimer(); + if(changeExecutionAndAudit instanceof FailedAfterExecutionAuditStep) { // Failure:this means nothing was persisted(all or nothing) auditIfExecutionFailure(executionStep); Throwable mainError = ((FailedAfterExecutionAuditStep)changeExecutionAndAudit) .getMainError(); rollbackChain((RollableFailedStep) changeExecutionAndAudit, executionContext); - return new FailedChangeProcessResult(change.getId(), summarizer.setFailed().getSummary(), mainError); + ChangeResult result = resultBuilder + .rolledBack() + .error(mainError) + .build(); + return new FailedChangeProcessResult(change.getId(), result, mainError); } else { // Success: both change and audit committed atomically - return new ChangeProcessResult(change.getId(), summarizer.setSuccessful().getSummary()); + ChangeResult result = resultBuilder + .applied() + .build(); + return new ChangeProcessResult(change.getId(), result); } } @@ -117,7 +129,7 @@ protected ChangeProcessResult doApplyChange() { /** * Creates detailed failure audit when execution fails. - * + * *

Since the main transaction was rolled back, this method attempts to create * a comprehensive failure audit trail in a separate transaction. This provides * valuable diagnostic information while maintaining system safety. @@ -125,7 +137,6 @@ protected ChangeProcessResult doApplyChange() { private void auditIfExecutionFailure(Wrapper executionStep) { ExecutionStep changeExecution = executionStep.getValue(); if(!changeExecution.isSuccessStep()) { - summarizer.clear(); targetSystemOps.applyChangeTransactional(executionRuntime -> { auditAndLogStartExecution(new StartStep(change), executionContext); auditAndLogExecution(changeExecution); @@ -142,7 +153,6 @@ private void rollbackChain(RollableFailedStep rollableFailedStep, ExecutionConte .forEach(rollableStep -> { ManualRolledBackStep rolledBack = targetSystemOps.rollbackChange(rollableStep::rollback, buildExecutionRuntime()); stepLogger.logManualRollbackResult(rolledBack); - summarizer.add(rolledBack); auditAndLogManualRollback(rolledBack, executionContext); }); } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/SimpleTxChangeProcessStrategy.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/SimpleTxChangeProcessStrategy.java index 3f70b368e..f82b07b69 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/SimpleTxChangeProcessStrategy.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/strategy/SimpleTxChangeProcessStrategy.java @@ -16,8 +16,9 @@ package io.flamingock.internal.core.task.navigation.navigator.strategy; import io.flamingock.internal.common.core.context.ContextResolver; +import io.flamingock.internal.common.core.response.data.ChangeResult; +import io.flamingock.internal.core.operation.result.ChangeResultBuilder; import io.flamingock.internal.core.pipeline.execution.ExecutionContext; -import io.flamingock.internal.core.pipeline.execution.TaskSummarizer; import io.flamingock.internal.core.runtime.proxy.LockGuardProxyFactory; import io.flamingock.internal.core.external.targets.operations.TransactionalTargetSystemOps; import io.flamingock.internal.core.task.executable.ExecutableTask; @@ -38,12 +39,12 @@ /** * Change process strategy for transactional target systems with separate audit store. - * + * *

This strategy is used when the target system supports transactions but uses a separate - * audit store (either a different database in community edition or cloud-based audit in + * audit store (either a different database in community edition or cloud-based audit in * cloud edition). Changes are applied within a transaction, but audit operations occur * in a separate transaction context. - * + * *

Execution Flow

*
    *
  1. Audit change start in separate audit store
  2. @@ -55,14 +56,14 @@ *
  3. Clear target system marker on success
  4. *
  5. On failure: audit rollback and execute rollback chain with best effort
  6. *
- * + * *

Target System State Outcomes

*
    *
  • Success: Change committed, marker cleared
  • *
  • Failure: Transaction rolled back automatically
  • *
  • Partial Success: Change applied but audit failed (marker remains)
  • *
- * + * *

Audit Store State Outcomes

*
    *
  • STARTED: Change execution began but process interrupted
  • @@ -70,7 +71,7 @@ *
  • STARTED → FAILED: Change execution failed
  • *
  • STARTED → FAILED → ROLLED_BACK: Change failed and automatic rollback audited
  • *
- * + * *

Marker Behavior

*

When the target system supports markers (not NoOpTargetSystemAuditMarker), this strategy * creates a marker indicating change application status. This marker aids in recovery scenarios @@ -84,21 +85,23 @@ public SimpleTxChangeProcessStrategy(ExecutableTask change, ExecutionContext executionContext, TransactionalTargetSystemOps targetSystemOps, AuditStoreStepOperations auditStoreOperations, - TaskSummarizer summarizer, + ChangeResultBuilder resultBuilder, LockGuardProxyFactory proxyFactory, ContextResolver baseContext, TimeService timeService) { - super(change, executionContext, targetSystemOps, auditStoreOperations, summarizer, proxyFactory, baseContext, timeService); + super(change, executionContext, targetSystemOps, auditStoreOperations, resultBuilder, proxyFactory, baseContext, timeService); } @Override protected ChangeProcessResult doApplyChange() { + resultBuilder.startTimer(); + logger.debug("Executing transactional task [change={}]", change.getId()); StartStep startStep = new StartStep(change); ExecutableStep executableStep = auditAndLogStartExecution(startStep, executionContext); - + // Apply change within target system transaction, create marker if supported ExecutionStep changeResult = targetSystemOps.applyChangeTransactional(executionRuntime -> { ExecutionStep changeAppliedResult = executableStep.execute(executionRuntime); @@ -109,16 +112,25 @@ protected ChangeProcessResult doApplyChange() { }, buildExecutionRuntime()); AfterExecutionAuditStep afterAudit = auditAndLogExecution(changeResult); + + resultBuilder.stopTimer(); + if(changeResult.isSuccessStep()) { if(afterAudit instanceof FailedAfterExecutionAuditStep) { // Change applied but audit failed - leave marker for recovery Throwable mainError = ((FailedAfterExecutionAuditStep)afterAudit) .getMainError(); - return new FailedChangeProcessResult(change.getId(), summarizer.setFailed().getSummary(), mainError); + ChangeResult result = resultBuilder + .failed(mainError) + .build(); + return new FailedChangeProcessResult(change.getId(), result, mainError); } else { // Success: change applied and audited, clear marker targetSystemOps.clearMark(change.getId()); - return new ChangeProcessResult(change.getId(), summarizer.setSuccessful().getSummary()); + ChangeResult result = resultBuilder + .applied() + .build(); + return new ChangeProcessResult(change.getId(), result); } } else { @@ -127,7 +139,11 @@ protected ChangeProcessResult doApplyChange() { .getMainError(); auditAndLogAutoRollback(); rollbackChain((RollableFailedStep) afterAudit, executionContext); - return new FailedChangeProcessResult(change.getId(), summarizer.setFailed().getSummary(), mainError); + ChangeResult result = resultBuilder + .rolledBack() + .error(mainError) + .build(); + return new FailedChangeProcessResult(change.getId(), result, mainError); } } @@ -138,7 +154,6 @@ private void rollbackChain(RollableFailedStep rollableFailedStep, ExecutionConte .forEach(rollableStep -> { ManualRolledBackStep rolledBack = targetSystemOps.rollbackChange(rollableStep::rollback, buildExecutionRuntime()); stepLogger.logManualRollbackResult(rolledBack); - summarizer.add(rolledBack); auditAndLogManualRollback(rolledBack, executionContext); }); } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/AbstractTaskStepSummaryLine.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/AbstractTaskStepSummaryLine.java deleted file mode 100644 index 7a9ac0c58..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/AbstractTaskStepSummaryLine.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2023 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.core.task.navigation.summary; - -import io.flamingock.internal.common.core.task.TaskDescriptor; -import io.flamingock.internal.core.task.navigation.step.ExecutableStep; -import io.flamingock.internal.core.task.navigation.step.afteraudit.AfterExecutionAuditStep; -import io.flamingock.internal.core.task.navigation.step.complete.CompletedAlreadyAppliedStep; -import io.flamingock.internal.core.task.navigation.step.complete.failed.CompletedFailedManualRollback; -import io.flamingock.internal.core.task.navigation.step.execution.ExecutionStep; -import io.flamingock.internal.core.task.navigation.step.rolledback.RolledBackStep; - -public abstract class AbstractTaskStepSummaryLine implements StepSummaryLine { - - private enum SummaryResult { - OK("OK", "\u2705"), - FAILED("FAILED", "\u274C"), - ALREADY_APPLIED("ALREADY APPLIED", "\u23E9"), - NOT_REACHED("NOT REACHED", "\u2754"),; - - private final String description; - private final String icon; - - SummaryResult(String description, String icon) { - this.description = description; - this.icon = icon; - } - } - - protected static SummaryResult getResultFromSuccess(boolean success) { - return success ? SummaryResult.OK : SummaryResult.FAILED; - } - - private final String id; - - protected final SummaryResult result; - - protected AbstractTaskStepSummaryLine(String id, SummaryResult result) { - this.id = id; - this.result = result; - } - - public String getId() { - return id; - } - - public String getPrettyResult() { - return String.format("%s - %s", result.icon, result.description); - } - - - public static class InitialTaskSummaryLine extends AbstractTaskStepSummaryLine { - - private final TaskDescriptor desc; - - public InitialTaskSummaryLine(TaskDescriptor loadedTask) { - super(loadedTask.getId(), null); - this.desc = loadedTask; - } - - @Override - public String getPretty() { - return desc.pretty(); - } - - } - - public static class StartedTaskSummaryLine extends AbstractTaskStepSummaryLine { - - public StartedTaskSummaryLine(ExecutableStep step) { - super(step.getTask().getId(), SummaryResult.OK); - } - - - @Override - public String getPretty() { - return String.format("\tStarted\t\t\t%s", getPrettyResult()); - } - - } - - public static class AppliedTaskSummaryLine extends AbstractTaskStepSummaryLine { - - public AppliedTaskSummaryLine(ExecutionStep step) { - super(step.getTask().getId(), getResultFromSuccess(step.isSuccessStep())); - } - - - @Override - public String getPretty() { - return String.format("\tApplied\t\t\t%s", getPrettyResult()); - } - - } - - public static class AfterExecutionTaskAuditSummaryLine extends AbstractTaskStepSummaryLine { - - - public AfterExecutionTaskAuditSummaryLine(AfterExecutionAuditStep step) { - super(step.getTask().getId(), getResultFromSuccess(step.isSuccessStep())); - } - - @Override - public String getPretty() { - return String.format("\tAudited[execution]\t%s", getPrettyResult()); - } - - } - - public static class RolledBackTaskSummaryLine extends AbstractTaskStepSummaryLine { - - public RolledBackTaskSummaryLine(RolledBackStep step) { - super(step.getTask().getId(), getResultFromSuccess(step.isSuccessStep())); - } - - @Override - public String getPretty() { - return String.format("\tRolled back\t\t\t%s", getPrettyResult()); - } - - } - - public static class FailedCompletedManualRollbackTaskSummaryLine extends AbstractTaskStepSummaryLine { - - public FailedCompletedManualRollbackTaskSummaryLine(CompletedFailedManualRollback step) { - super(step.getTask().getId(), getResultFromSuccess(step.isSuccessStep())); - } - - @Override - public String getPretty() { - return String.format("\tAudited[rollback]\t%s", getPrettyResult()); - } - - } - - public static class AlreadyAppliedTaskSummaryLine extends AbstractTaskStepSummaryLine { - - public AlreadyAppliedTaskSummaryLine(CompletedAlreadyAppliedStep step) { - super(step.getTask().getId(), SummaryResult.ALREADY_APPLIED); - } - - @Override - public String getPretty() { - return String.format("\tApplied\t\t\t%s", getPrettyResult()); - } - - } - - public static class NotReachedTaskSummaryLine extends AbstractTaskStepSummaryLine { - - public NotReachedTaskSummaryLine(TaskDescriptor loadedTask) { - super(loadedTask.getId(), SummaryResult.NOT_REACHED); - } - - @Override - public String getPretty() { - return String.format("\tApplied\t\t\t%s", getPrettyResult()); - } - - } -} \ No newline at end of file diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummarizer.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummarizer.java deleted file mode 100644 index 75960b3f9..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummarizer.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2023 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.core.task.navigation.summary; - -import io.flamingock.internal.common.core.task.TaskDescriptor; -import io.flamingock.internal.core.task.navigation.step.ExecutableStep; -import io.flamingock.internal.core.task.navigation.step.afteraudit.AfterExecutionAuditStep; -import io.flamingock.internal.core.task.navigation.step.complete.CompletedAlreadyAppliedStep; -import io.flamingock.internal.core.task.navigation.step.complete.failed.CompletedFailedManualRollback; -import io.flamingock.internal.core.task.navigation.step.execution.ExecutionStep; -import io.flamingock.internal.core.task.navigation.step.rolledback.RolledBackStep; -import io.flamingock.internal.core.summary.Summarizer; - -//No thread safe -public interface StepSummarizer> extends Summarizer { - - void clear(); - - Summarizer add(StepSummaryLine line); - - SELF add(ExecutableStep step); - - SELF add(ExecutionStep step); - - SELF add(AfterExecutionAuditStep step); - - SELF add(RolledBackStep step); - - SELF add(CompletedFailedManualRollback step); - - SELF add(CompletedAlreadyAppliedStep ignoredStep); - - SELF addNotReachedTask(TaskDescriptor loadedTask); - - - - StepSummary getSummary(); -} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummary.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummary.java deleted file mode 100644 index 1659d1249..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummary.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 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.core.task.navigation.summary; - -import io.flamingock.internal.core.summary.Summary; -import io.flamingock.internal.core.summary.SummaryLine; - -import java.util.List; -import java.util.stream.Collectors; - -public interface StepSummary extends Summary { - - List getLines(); - - @Override - default String getPretty() { - return getLines() - .stream() - .map(SummaryLine::getPretty) - .map(line -> "\t" + line) - .collect(Collectors.joining("\n")); - } - -} diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummaryLine.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummaryLine.java deleted file mode 100644 index 34d5333a4..000000000 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/summary/StepSummaryLine.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2023 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.core.task.navigation.summary; - -import io.flamingock.internal.core.summary.SummaryLine; - -public interface StepSummaryLine extends SummaryLine { - - String getId(); - -} From c8395eaefd60a70caf64ba742c9cd394fad17b86 Mon Sep 17 00:00:00 2001 From: Antonio Perez Dieppa Date: Sun, 8 Feb 2026 02:19:27 +0000 Subject: [PATCH 2/4] refactor: execution log clean and cli result --- .../output/ExecutionResultFormatter.java | 5 +---- .../core/external/store/lock/Lock.java | 19 +++++----------- .../store/lock/community/CommunityLock.java | 8 +++++-- .../core/operation/ExecuteOperation.java | 10 +++++++-- .../pipeline/execution/StageExecutor.java | 11 +++++----- .../CommunityChangeActionBuilder.java | 8 ++----- .../navigator/ChangeProcessLogger.java | 22 +++++++++---------- 7 files changed, 40 insertions(+), 43 deletions(-) diff --git a/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java b/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java index 83c1e1767..5653678dd 100644 --- a/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java +++ b/cli/flamingock-cli-executor/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java @@ -42,10 +42,7 @@ private ExecutionResultFormatter() { * @return formatted string for display */ public static String format(ExecuteResponseData result) { - StringBuilder sb = new StringBuilder(); - - // Print stages and changes - sb.append("\nApplying changes...\n"); + StringBuilder sb = new StringBuilder("\n"); for (StageResult stage : result.getStages()) { sb.append(formatStage(stage)); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/lock/Lock.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/lock/Lock.java index 6345c5897..cc72087f3 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/lock/Lock.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/lock/Lock.java @@ -18,7 +18,6 @@ import io.flamingock.internal.util.id.RunnerId; import io.flamingock.internal.util.TimeService; -import io.flamingock.internal.util.TimeUtil; import io.flamingock.internal.util.log.FlamingockLoggerFactory; import org.slf4j.Logger; @@ -144,16 +143,14 @@ public final boolean extend() throws LockException { * the lock is released(closed) and another instances acquire the lock, before process A has finished. */ public final void release() { - logger.debug("Flamingock waiting to release the lock"); + logger.debug("Releasing the lock"); synchronized (this) { try { - logger.debug("Flamingock releasing the lock"); updateLease(timeService.daysToMills(-1));//forces expiring - logger.debug("Flamingock removing the lock from database"); lockService.releaseLock(lockKey, owner); - logger.info("Flamingock released the lock"); + logger.debug("Lock released successfully"); } catch (Exception ex) { - logger.warn("Error removing the lock. Doesn't need manually intervention.", ex); + logger.warn("Error removing the lock. Doesn't need manual intervention.", ex); } } } @@ -218,18 +215,14 @@ protected void waitForLock(LocalDateTime expiresAt) { long currentLockWillExpireInMillis = expiresAt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - currentMillis; long sleepingMillis = retryFrequencyMillis; if (retryFrequencyMillis > currentLockWillExpireInMillis) { - logger.info("The configured time frequency[{} millis] is higher than the current lock's expiration", retryFrequencyMillis); + logger.debug("Configured retry frequency[{}ms] exceeds lock expiration time", retryFrequencyMillis); sleepingMillis = Math.max(currentLockWillExpireInMillis, 500L);//0.5secs the minimum waiting before retrying } - logger.info("Flamingock will try to acquire the lock in {} mills", sleepingMillis); try { - logger.info( - "Flamingock is going to sleep. Will retry in {}ms ({} minutes)", - sleepingMillis, - TimeUtil.millisToMinutes(sleepingMillis)); + logger.debug("Waiting {}ms before retrying lock acquisition", sleepingMillis); Thread.sleep(sleepingMillis); } catch (InterruptedException ex) { - logger.warn("ERROR acquiring the lock", ex); + logger.warn("Lock acquisition interrupted", ex); Thread.currentThread().interrupt(); } } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/lock/community/CommunityLock.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/lock/community/CommunityLock.java index 5d26ee488..87c6fa046 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/lock/community/CommunityLock.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/external/store/lock/community/CommunityLock.java @@ -67,9 +67,13 @@ private CommunityLockService getLockService() { private void acquire() throws LockException { Instant shouldStopTryingAt = timeService.nowPlusMillis(stopTryingAfterMillis); boolean keepLooping = true; + boolean firstAttempt = true; do { try { - logger.info("Attempting to acquire process lock [timeout={}s]", stopTryingAfterMillis / 1000); + if (firstAttempt) { + logger.debug("Attempting to acquire process lock [timeout={}s]", stopTryingAfterMillis / 1000); + firstAttempt = false; + } LockAcquisition lockAcquisition = getLockService().upsert(lockKey, owner, leaseMillis); updateLease(lockAcquisition.getAcquiredForMillis()); keepLooping = false; @@ -78,7 +82,7 @@ private void acquire() throws LockException { } } while (keepLooping); - logger.info("Process lock acquired successfully [expires_at={}]", expiresAt()); + logger.debug("Process lock acquired [expires_at={}]", expiresAt()); } } \ No newline at end of file diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteOperation.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteOperation.java index 12c361bb7..9828a69e0 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteOperation.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/ExecuteOperation.java @@ -110,6 +110,12 @@ private static List validateAndGetExecutableStages(LoadedPi } private ExecuteResponseData execute(LoadedPipeline pipeline) throws FlamingockException { + List allStages = validateAndGetExecutableStages(pipeline); + int stageCount = allStages.size(); + long changeCount = allStages.stream() + .mapToLong(stage -> stage.getTasks().size()) + .sum(); + logger.info("Flamingock execution started [stages={} changes={}]", stageCount, changeCount); eventPublisher.publish(new PipelineStartedEvent()); ExecutionResultBuilder resultBuilder = new ExecutionResultBuilder().startTimer(); @@ -151,8 +157,8 @@ private ExecuteResponseData execute(LoadedPipeline pipeline) throws FlamingockEx resultBuilder.stopTimer().success(); ExecuteResponseData result = resultBuilder.build(); - logger.info("Finished Flamingock process successfully [applied={} skipped={} duration={}ms]", - result.getAppliedChanges(), result.getSkippedChanges(), result.getTotalDurationMs()); + logger.info("Flamingock execution completed [duration={}ms applied={} skipped={}]", + result.getTotalDurationMs(), result.getAppliedChanges(), result.getSkippedChanges()); eventPublisher.publish(new PipelineCompletedEvent()); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java index 0779a5fbc..41d777631 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/pipeline/execution/StageExecutor.java @@ -70,7 +70,8 @@ public Output executeStage(ExecutableStage executableStage, String stageName = executableStage.getName(); long taskCount = getTasksStream(executableStage).count(); - logger.info("Starting stage execution [stage={} tasks={} execution_id={}]", + logger.info("Stage started [stage={}]", stageName); + logger.debug("Stage execution context [stage={} tasks={} execution_id={}]", stageName, taskCount, executionContext.getExecutionId()); StageResultBuilder resultBuilder = new StageResultBuilder() @@ -116,8 +117,10 @@ public Output executeStage(ExecutableStage executableStage, resultBuilder.stopTimer().completed(); Duration stageDuration = Duration.between(stageStart, LocalDateTime.now()); - logger.info("Stage execution completed successfully [stage={} duration={} tasks={}]", - stageName, formatDuration(stageDuration), taskCount); + StageResult stageResult = resultBuilder.build(); + logger.info("Stage completed [stage={} duration={} applied={} skipped={}]", + stageName, formatDuration(stageDuration), stageResult.getAppliedCount(), stageResult.getSkippedCount()); + return new Output(stageResult); } catch (StageExecutionException stageExecutionException) { throw stageExecutionException; @@ -128,8 +131,6 @@ public Output executeStage(ExecutableStage executableStage, stageName, formatDuration(stageDuration), throwable.getMessage(), throwable); throw StageExecutionException.fromResult(throwable, resultBuilder.build(), null); } - - return new Output(resultBuilder.build()); } private ChangeProcessStrategyFactory getStepNavigatorBuilder(ExecutionContext executionContext, Lock lock, ContextResolver contextResolver) { diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/plan/community/CommunityChangeActionBuilder.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/plan/community/CommunityChangeActionBuilder.java index c30a88d77..cdf35c516 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/plan/community/CommunityChangeActionBuilder.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/plan/community/CommunityChangeActionBuilder.java @@ -57,12 +57,8 @@ public static ChangeActionMap build(Collection changes, Map< AuditEntry auditEntry = auditSnapshot.get(change.getId()); if (auditEntry == null || auditEntry.getState() == null) { // No audit entry found - first execution - AuditTxType txStrategy = auditEntry != null ? auditEntry.getTxType() : null; - log.info("Change[{}] in state='unknown' (TxType={}) -> Action={} | Reason: {}", - change.getId(), - txStrategy != null ? txStrategy : "unknown", - ChangeAction.APPLY, - "No previous audit entry found (first execution)"); + log.debug("Change[{}] action={} | Reason: No previous audit entry (first execution)", + change.getId(), ChangeAction.APPLY); actionMap.put(change.getId(), ChangeAction.APPLY); } else { ChangeAction action = ChangeActionResolver.resolve(auditEntry); diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessLogger.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessLogger.java index 3f896c338..1d36f022e 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessLogger.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/task/navigation/navigator/ChangeProcessLogger.java @@ -45,50 +45,50 @@ public void logTargetSystemResolved(String changeId, TargetSystemDescriptor targ } public void logStrategyApplication(String changeId, String targetSystemId, String strategyType) { - logger.info("Starting change [change= {}, target= {}, strategy= {}]", changeId, targetSystemId, strategyType); + logger.debug("Executing change [change={} target={} strategy={}]", changeId, targetSystemId, strategyType); } public void logSkippedExecution(String changeId) { - logger.info("SKIPPED [change= {}, reason=already applied]", changeId); + logger.info("Change skipped [change={} reason=already_applied]", changeId); } public void logExecutionResult(ExecutionStep executionStep) { String taskId = executionStep.getTask().getId(); String duration = formatDuration(executionStep.getDuration()); - + if (executionStep instanceof SuccessApplyStep) { - logger.info("APPLIED [change= {}, duration= {}]", taskId, duration); + logger.info("Change applied [change={} duration={}]", taskId, duration); } else if (executionStep instanceof FailedExecutionStep) { FailedExecutionStep failed = (FailedExecutionStep) executionStep; - logger.error("FAILED [change= {}, duration= {}] : {}", + logger.error("Change failed [change={} duration={}]: {}", taskId, duration, failed.getMainError().getMessage()); } } public void logAutoRollback(ExecutableTask executableChange, long duration) { String formattedDuration = formatDuration(duration); - logger.info("ROLLED_BACK [change= {}, duration= {}]", executableChange.getId(), formattedDuration); + logger.info("Change rolled back [change={} duration={}]", executableChange.getId(), formattedDuration); } public void logManualRollbackResult(ManualRolledBackStep rolledBack) { String taskId = rolledBack.getTask().getId(); String duration = formatDuration(rolledBack.getDuration()); - + if (rolledBack instanceof FailedManualRolledBackStep) { FailedManualRolledBackStep failed = (FailedManualRolledBackStep) rolledBack; - logger.error("ROLLBACK_FAILED [change= {}, duration= {}] : {}", + logger.error("Rollback failed [change={} duration={}]: {}", taskId, duration, failed.getMainError().getMessage()); } else { - logger.info("ROLLED_BACK [change= {}, duration= {}]", taskId, duration); + logger.info("Change rolled back [change={} duration={}]", taskId, duration); } } public void logAuditResult(Result auditResult, String id, String description) { if (auditResult instanceof Result.Error) { - logger.error("Audit operation failed [change= {}, operation= {}] : {}", + logger.error("Audit operation failed [change={} operation={}]: {}", id, description, ((Result.Error) auditResult).getError().getMessage()); } else { - logger.debug("Audit operation completed successfully [change= {}, operation= {}]", id, description); + logger.debug("Audit operation completed [change={} operation={}]", id, description); } } From c4a04b643645630c8337f2529e5fbf756cf43fa1 Mon Sep 17 00:00:00 2001 From: Antonio Perez Dieppa Date: Mon, 9 Feb 2026 10:47:08 +0000 Subject: [PATCH 3/4] fix: unwrap exceptions --- .../core/error/FlamingockException.java | 43 +++++++++++++ .../core/operation/OperationException.java | 63 ++++++++++++++++--- .../core/runtime/ExecutionRuntime.java | 9 +-- .../internal/util/ThrowableUtil.java | 5 ++ 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/error/FlamingockException.java b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/error/FlamingockException.java index 44e345b8f..fd9087646 100644 --- a/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/error/FlamingockException.java +++ b/core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/error/FlamingockException.java @@ -16,12 +16,19 @@ package io.flamingock.internal.common.core.error; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; + /** * Exception thrown when a Flamingock operation fails. */ public class FlamingockException extends RuntimeException { + private static final int SAFE_GUARD = 32; + public FlamingockException(Throwable cause) { super(cause); } @@ -38,4 +45,40 @@ public FlamingockException(String message, Object... args) { super(String.format(message, args)); } + public static FlamingockException toFlamingockException(Throwable t) { + if (t == null) return new FlamingockException(new NullPointerException("Throwable is null")); + + // Peel common wrapper layers + Throwable cur = t; + int guard = 0; + while (guard++ < SAFE_GUARD) { // safety guard + if (cur instanceof FlamingockException) { + return (FlamingockException) cur; + } + if (cur instanceof InvocationTargetException ) { + Throwable next = ((InvocationTargetException)cur).getTargetException(); + if (next != null && next != cur) { cur = next; continue; } + break; + } + if (cur instanceof UndeclaredThrowableException ) { + Throwable next = ((UndeclaredThrowableException)cur).getUndeclaredThrowable(); + if (next != null && next != cur) { cur = next; continue; } + break; + } + if (cur instanceof ExecutionException || cur instanceof CompletionException) { + Throwable next = cur.getCause(); + if (next != null && next != cur) { cur = next; continue; } + break; + } + break; + } + + // If after peeling we hit FlamingockException, return it + if (cur instanceof FlamingockException) return (FlamingockException) cur; + + // Otherwise wrap once + return new FlamingockException(cur); + } + + } diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java index b4a4f3e1e..5b0808408 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java @@ -17,22 +17,69 @@ import io.flamingock.internal.common.core.error.FlamingockException; import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +import io.flamingock.internal.util.ThrowableUtil; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; -/** - * Exception thrown when a pipeline operation fails. - * Contains the execution result data with information about what was executed. - */ public class OperationException extends FlamingockException { + private static final int MAX_UNWRAP_DEPTH = 32; + public static OperationException fromExisting(Throwable exception, ExecuteResponseData result) { - Throwable cause = exception.getCause(); - return (exception instanceof FlamingockException) && cause != null - ? new OperationException(cause, result) - : new OperationException(exception, result); + Throwable root = unwrapKnownWrappers(exception); + + if (root instanceof FlamingockException) { + Throwable cause = root.getCause(); + if (cause != null) { + // Keep the real cause, avoid FlamingockException(...) chain + return new OperationException(cause, result); + } + // No cause available: avoid OperationException(FlamingockException) + return new OperationException(ThrowableUtil.messageOf(root), result); + } + + return new OperationException(root, result); + } + + + private static Throwable unwrapKnownWrappers(Throwable t) { + if (t == null) return new NullPointerException("Throwable is null"); + + Throwable cur = t; + int guard = 0; + + while (guard++ < MAX_UNWRAP_DEPTH) { + if (cur instanceof java.lang.reflect.InvocationTargetException) { + Throwable next = ((java.lang.reflect.InvocationTargetException) cur).getTargetException(); + if (next != null && next != cur) { cur = next; continue; } + break; + } + if (cur instanceof java.lang.reflect.UndeclaredThrowableException) { + Throwable next = ((java.lang.reflect.UndeclaredThrowableException) cur).getUndeclaredThrowable(); + if (next != null && next != cur) { cur = next; continue; } + break; + } + if (cur instanceof java.util.concurrent.ExecutionException + || cur instanceof java.util.concurrent.CompletionException) { + Throwable next = cur.getCause(); + if (next != null && next != cur) { cur = next; continue; } + break; + } + break; + } + return cur; } private final ExecuteResponseData result; + private OperationException(String message, ExecuteResponseData result) { + super(message); + this.result = result; + } + private OperationException(Throwable throwable, ExecuteResponseData result) { super(throwable); this.result = result; diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/runtime/ExecutionRuntime.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/runtime/ExecutionRuntime.java index 3dbc930c1..b9ff01d3c 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/runtime/ExecutionRuntime.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/runtime/ExecutionRuntime.java @@ -119,14 +119,7 @@ public Object executeMethodWithParameters(Object instance, Method method, Object try { return method.invoke(instance, parameters); } catch (Exception e) { - if(e instanceof FlamingockException) { - throw (FlamingockException)e; - } else { - throw e instanceof InvocationTargetException - ? new FlamingockException(((InvocationTargetException)e).getTargetException()) - : new FlamingockException(e); - - } + throw FlamingockException.toFlamingockException(e); } } diff --git a/utils/general-util/src/main/java/io/flamingock/internal/util/ThrowableUtil.java b/utils/general-util/src/main/java/io/flamingock/internal/util/ThrowableUtil.java index c750180ce..5ea3c4984 100644 --- a/utils/general-util/src/main/java/io/flamingock/internal/util/ThrowableUtil.java +++ b/utils/general-util/src/main/java/io/flamingock/internal/util/ThrowableUtil.java @@ -29,6 +29,11 @@ public final class ThrowableUtil { private ThrowableUtil() { } + public static String messageOf(Throwable t) { + String msg = t.getMessage(); + return (msg != null && !msg.isEmpty()) ? msg : t.getClass().getName(); + } + public static String serialize(Throwable e) { return serialize(e, 100); } From 7d2bb410822a9ffbbf392d2ca408a7202eeb19e1 Mon Sep 17 00:00:00 2001 From: Antonio Perez Dieppa Date: Mon, 9 Feb 2026 10:50:03 +0000 Subject: [PATCH 4/4] fix: unwrap exceptions --- .../core/operation/OperationException.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java index 5b0808408..ca0c45c9f 100644 --- a/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java +++ b/core/flamingock-core/src/main/java/io/flamingock/internal/core/operation/OperationException.java @@ -34,11 +34,11 @@ public static OperationException fromExisting(Throwable exception, ExecuteRespon if (root instanceof FlamingockException) { Throwable cause = root.getCause(); if (cause != null) { - // Keep the real cause, avoid FlamingockException(...) chain return new OperationException(cause, result); } - // No cause available: avoid OperationException(FlamingockException) - return new OperationException(ThrowableUtil.messageOf(root), result); + OperationException oe = new OperationException(ThrowableUtil.messageOf(root), result); + oe.setStackTrace(root.getStackTrace()); + return oe; } return new OperationException(root, result); @@ -52,18 +52,18 @@ private static Throwable unwrapKnownWrappers(Throwable t) { int guard = 0; while (guard++ < MAX_UNWRAP_DEPTH) { - if (cur instanceof java.lang.reflect.InvocationTargetException) { - Throwable next = ((java.lang.reflect.InvocationTargetException) cur).getTargetException(); + if (cur instanceof InvocationTargetException) { + Throwable next = ((InvocationTargetException) cur).getTargetException(); if (next != null && next != cur) { cur = next; continue; } break; } - if (cur instanceof java.lang.reflect.UndeclaredThrowableException) { - Throwable next = ((java.lang.reflect.UndeclaredThrowableException) cur).getUndeclaredThrowable(); + if (cur instanceof UndeclaredThrowableException) { + Throwable next = ((UndeclaredThrowableException) cur).getUndeclaredThrowable(); if (next != null && next != cur) { cur = next; continue; } break; } - if (cur instanceof java.util.concurrent.ExecutionException - || cur instanceof java.util.concurrent.CompletionException) { + if (cur instanceof ExecutionException + || cur instanceof CompletionException) { Throwable next = cur.getCause(); if (next != null && next != cur) { cur = next; continue; } break;