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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@
*/
package io.flamingock.internal.common.core.recovery;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* Represents a recovery issue that requires manual intervention during pipeline execution.
* This domain object is specifically designed for recovery scenarios and avoids the
* inappropriate use of audit-specific objects for recovery purposes.
*/
public class RecoveryIssue {

private final String changeId;

public RecoveryIssue(String changeId) {

@JsonCreator
public RecoveryIssue(@JsonProperty("changeId") String changeId) {
this.changeId = changeId;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,33 @@
*/
package io.flamingock.internal.common.core.response.data;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Contains error information when an execution fails.
*
* <p>{@code changeIds} carries the change identifiers associated with the error. It may be
* empty (e.g., lock or validate-time failures with no specific change), a single id (most
* stage-level failures), or several (e.g., a stage blocked by manual intervention because
* multiple changes need recovery).
*/
public class ErrorInfo {

private String errorType;
private String message;
private String changeId;
private List<String> changeIds;
private String stageId;

public ErrorInfo() {
this.changeIds = new ArrayList<>();
}

public ErrorInfo(String errorType, String message, String changeId, String stageId) {
public ErrorInfo(String errorType, String message, List<String> changeIds, String stageId) {
this.errorType = errorType;
this.message = message;
this.changeId = changeId;
this.changeIds = changeIds != null ? changeIds : new ArrayList<>();
this.stageId = stageId;
}

Expand All @@ -51,12 +61,12 @@ public void setMessage(String message) {
this.message = message;
}

public String getChangeId() {
return changeId;
public List<String> getChangeIds() {
return changeIds;
}

public void setChangeId(String changeId) {
this.changeId = changeId;
public void setChangeIds(List<String> changeIds) {
this.changeIds = changeIds != null ? changeIds : new ArrayList<>();
}

public String getStageId() {
Expand All @@ -68,11 +78,13 @@ public void setStageId(String stageId) {
}

/**
* Creates an ErrorInfo from a Throwable.
* Creates an ErrorInfo from a Throwable. Pass {@link Collections#emptyList()} when no
* specific change is associated with the failure, or {@link Collections#singletonList(Object)}
* for single-change cases.
*/
public static ErrorInfo fromThrowable(Throwable throwable, String changeId, String stageId) {
public static ErrorInfo fromThrowable(Throwable throwable, List<String> changeIds, String stageId) {
String errorType = throwable.getClass().getSimpleName();
String message = throwable.getMessage();
return new ErrorInfo(errorType, message, changeId, stageId);
return new ErrorInfo(errorType, message, changeIds, stageId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@ public class StageResult {

private String stageId;
private String stageName;
private StageStatus status;
private StageState state;
private long durationMs;
private List<ChangeResult> changes;

public StageResult() {
this.changes = new ArrayList<>();
this.state = StageState.NOT_STARTED;
}

private StageResult(Builder builder) {
this.stageId = builder.stageId;
this.stageName = builder.stageName;
this.status = builder.status;
this.state = builder.state != null ? builder.state : StageState.NOT_STARTED;
this.durationMs = builder.durationMs;
this.changes = builder.changes != null ? builder.changes : new ArrayList<>();
}
Expand All @@ -57,12 +58,12 @@ public void setStageName(String stageName) {
this.stageName = stageName;
}

public StageStatus getStatus() {
return status;
public StageState getState() {
return state;
}

public void setStatus(StageStatus status) {
this.status = status;
public void setState(StageState state) {
this.state = state;
}

public long getDurationMs() {
Expand All @@ -82,11 +83,11 @@ public void setChanges(List<ChangeResult> changes) {
}

public boolean isFailed() {
return status == StageStatus.FAILED;
return state.isFailed();
}

public boolean isCompleted() {
return status == StageStatus.COMPLETED;
return state.isCompleted();
}

public int getAppliedCount() {
Expand All @@ -111,10 +112,23 @@ public static Builder builder() {
return new Builder();
}

/**
* Creates a builder pre-populated from an existing result (useful for transitions that
* preserve identity fields and tweak the state).
*/
public static Builder builder(StageResult source) {
return new Builder()
.stageId(source.stageId)
.stageName(source.stageName)
.state(source.state)
.durationMs(source.durationMs)
.changes(new ArrayList<>(source.changes));
}

public static class Builder {
private String stageId;
private String stageName;
private StageStatus status;
private StageState state;
private long durationMs;
private List<ChangeResult> changes = new ArrayList<>();

Expand All @@ -128,8 +142,8 @@ public Builder stageName(String stageName) {
return this;
}

public Builder status(StageStatus status) {
this.status = status;
public Builder state(StageState state) {
this.state = state;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* 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 com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.flamingock.internal.common.core.recovery.RecoveryIssue;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = StageState.NotStarted.class, name = "NOT_STARTED"),
@JsonSubTypes.Type(value = StageState.Started.class, name = "STARTED"),
@JsonSubTypes.Type(value = StageState.Completed.class, name = "COMPLETED"),
@JsonSubTypes.Type(value = StageState.Failed.class, name = "FAILED"),
@JsonSubTypes.Type(value = StageState.BlockedForMI.class, name = "BLOCKED_MANUAL_INTERVENTION")
})
public abstract class StageState {

public static final StageState NOT_STARTED = new NotStarted();
public static final StageState STARTED = new Started();
public static final StageState COMPLETED = new Completed();

public static StageState failed(ErrorInfo info) {
return new Failed(info);
}

public static StageState blockedManualIntervention(String stageName, List<RecoveryIssue> issues) {
List<String> changeIds = issues.stream()
.map(RecoveryIssue::getChangeId)
.collect(Collectors.toList());
ErrorInfo errorInfo = new ErrorInfo(
"MANUAL_INTERVENTION_REQUIRED",
"Manual intervention required",
changeIds,
stageName
);
return new BlockedForMI(errorInfo, issues);
}

StageState() {
}

public boolean isNotStarted() {
return false;
}

public boolean isStarted() {
return false;
}

public boolean isCompleted() {
return false;
}

public boolean isFailed() {
return false;
}

public boolean isBlockedForManualIntervention() {
return false;
}

public Optional<ErrorInfo> getErrorInfo() {
return Optional.empty();
}

public List<RecoveryIssue> getRecoveryIssues() {
return Collections.emptyList();
}

static final class NotStarted extends StageState {
@Override
public boolean isNotStarted() {
return true;
}
}

static final class Started extends StageState {
@Override
public boolean isStarted() {
return true;
}
}

static final class Completed extends StageState {
@Override
public boolean isCompleted() {
return true;
}
}

static class Failed extends StageState {
private final ErrorInfo errorInfo;

@JsonCreator
Failed(@JsonProperty("errorInfo") ErrorInfo errorInfo) {
this.errorInfo = errorInfo;
}

@Override
public boolean isFailed() {
return true;
}

@Override
public Optional<ErrorInfo> getErrorInfo() {
return Optional.ofNullable(errorInfo);
}

@JsonProperty("errorInfo")
ErrorInfo serialisedErrorInfo() {
return errorInfo;
}
}

static final class BlockedForMI extends Failed {
private final List<RecoveryIssue> recoveryIssues;

@JsonCreator
BlockedForMI(
@JsonProperty("errorInfo") ErrorInfo errorInfo,
@JsonProperty("recoveryIssues") List<RecoveryIssue> recoveryIssues) {
super(errorInfo);
this.recoveryIssues = Collections.unmodifiableList(new ArrayList<>(recoveryIssues));
}

@Override
public boolean isBlockedForManualIntervention() {
return true;
}

@Override
@JsonProperty("recoveryIssues")
public List<RecoveryIssue> getRecoveryIssues() {
return recoveryIssues;
}
}
}

This file was deleted.

Loading
Loading