SolutionDefinition.java

package com.taxonomy.portfolio.model;

import com.taxonomy.portfolio.model.PortfolioTypes.LifecycleStatus;
import com.taxonomy.portfolio.model.PortfolioTypes.OperatingModel;
import com.taxonomy.portfolio.model.PortfolioTypes.SolutionType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.persistence.Version;

import java.math.BigDecimal;
import java.time.Instant;

/** Reusable solution definition isolated by repository, workspace scope and branch. */
@Entity
@Table(name = "solution_definition", indexes = {
        @Index(name = "idx_sol_scope", columnList = "scope_key"),
        @Index(name = "idx_sol_lifecycle", columnList = "scope_key,lifecycle_status"),
        @Index(name = "idx_sol_tenant", columnList = "repository_id,workspace_scope,branch_name")
}, uniqueConstraints = {
        @UniqueConstraint(name = "uq_sol_scope_key", columnNames = {"scope_key", "solution_key"})
})
public class SolutionDefinition {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "scope_key", nullable = false, length = PortfolioTenantIdentity.MAX_SCOPE_KEY_LENGTH)
    private String scopeKey;

    @Column(name = "repository_id", nullable = false, length = 255)
    private String repositoryId;

    @Column(name = "workspace_scope", nullable = false, length = 320)
    private String workspaceScope;

    @Column(name = "branch_name", nullable = false, length = 255)
    private String branchName;

    @Column(name = "workspace_id", length = 120)
    private String workspaceId;

    @Column(name = "solution_key", nullable = false, length = 64)
    private String solutionKey;

    @Column(nullable = false, length = 240)
    private String title;

    @Column(length = 4000)
    private String description;

    @Enumerated(EnumType.STRING)
    @Column(name = "solution_type", nullable = false, length = 32)
    private SolutionType solutionType = SolutionType.OTHER;

    @Enumerated(EnumType.STRING)
    @Column(name = "operating_model", nullable = false, length = 32)
    private OperatingModel operatingModel = OperatingModel.UNSPECIFIED;

    @Enumerated(EnumType.STRING)
    @Column(name = "lifecycle_status", nullable = false, length = 32)
    private LifecycleStatus lifecycleStatus = LifecycleStatus.PLANNED;

    @Column(name = "maturity_level", nullable = false)
    private int maturityLevel;

    @Column(name = "owner_username", nullable = false, length = 160)
    private String ownerUsername;

    @Column(name = "responsible_organization", length = 240)
    private String responsibleOrganization;

    @Column(name = "cost_amount", precision = 19, scale = 2)
    private BigDecimal costAmount;

    @Column(name = "cost_currency", length = 3)
    private String costCurrency;

    @Column(name = "risk_notes", length = 2000)
    private String riskNotes;

    @Column(name = "lead_time_days")
    private Integer leadTimeDays;

    @Column(name = "extension_attributes", length = 4000)
    private String extensionAttributesJson;

    @Column(name = "created_at", nullable = false)
    private Instant createdAt;

    @Column(name = "updated_at", nullable = false)
    private Instant updatedAt;

    @Version
    @Column(name = "row_version", nullable = false)
    private long rowVersion;

    protected SolutionDefinition() {
    }

    public SolutionDefinition(String scopeKey,
                              String workspaceId,
                              String solutionKey,
                              String title,
                              String description,
                              SolutionType solutionType,
                              OperatingModel operatingModel,
                              LifecycleStatus lifecycleStatus,
                              int maturityLevel,
                              String ownerUsername,
                              String responsibleOrganization,
                              Instant now) {
        this.scopeKey = scopeKey;
        this.workspaceId = workspaceId;
        this.solutionKey = solutionKey;
        this.title = title;
        this.description = description;
        this.solutionType = solutionType != null ? solutionType : SolutionType.OTHER;
        this.operatingModel = operatingModel != null ? operatingModel : OperatingModel.UNSPECIFIED;
        this.lifecycleStatus = lifecycleStatus != null ? lifecycleStatus : LifecycleStatus.PLANNED;
        this.maturityLevel = maturityLevel;
        this.ownerUsername = ownerUsername;
        this.responsibleOrganization = responsibleOrganization;
        this.createdAt = now;
        this.updatedAt = now;
        synchronizeTenantIdentityIfEncoded();
    }

    public void update(String title,
                       String description,
                       SolutionType solutionType,
                       OperatingModel operatingModel,
                       LifecycleStatus lifecycleStatus,
                       Integer maturityLevel,
                       String ownerUsername,
                       String responsibleOrganization,
                       BigDecimal costAmount,
                       String costCurrency,
                       String riskNotes,
                       Integer leadTimeDays,
                       String extensionAttributesJson,
                       Instant now) {
        if (title != null) this.title = title;
        if (description != null) this.description = description;
        if (solutionType != null) this.solutionType = solutionType;
        if (operatingModel != null) this.operatingModel = operatingModel;
        if (lifecycleStatus != null) this.lifecycleStatus = lifecycleStatus;
        if (maturityLevel != null) this.maturityLevel = maturityLevel;
        if (ownerUsername != null) this.ownerUsername = ownerUsername;
        if (responsibleOrganization != null) this.responsibleOrganization = responsibleOrganization;
        if (costAmount != null) this.costAmount = costAmount;
        if (costCurrency != null) this.costCurrency = costCurrency;
        if (riskNotes != null) this.riskNotes = riskNotes;
        if (leadTimeDays != null) this.leadTimeDays = leadTimeDays;
        if (extensionAttributesJson != null) this.extensionAttributesJson = extensionAttributesJson;
        this.updatedAt = now;
    }

    @PrePersist
    @PreUpdate
    private void synchronizeTenantIdentity() {
        PortfolioTenantIdentity identity = PortfolioTenantIdentity.parse(scopeKey);
        String expectedWorkspaceScope = workspaceId == null || workspaceId.isBlank()
                ? PortfolioTenantIdentity.CENTRAL_SCOPE
                : PortfolioTenantIdentity.WORKSPACE_SCOPE_PREFIX + workspaceId.strip();
        if (!expectedWorkspaceScope.equals(identity.workspaceScope())) {
            throw new IllegalArgumentException(
                    "Portfolio scope key does not match workspaceId");
        }
        repositoryId = identity.repositoryId();
        workspaceScope = identity.workspaceScope();
        branchName = identity.branch();
    }

    private void synchronizeTenantIdentityIfEncoded() {
        if (PortfolioTenantIdentity.isEncoded(scopeKey)) {
            synchronizeTenantIdentity();
        }
    }

    public Long getId() { return id; }
    public String getScopeKey() { return scopeKey; }
    public String getRepositoryId() { return repositoryId; }
    public String getWorkspaceScope() { return workspaceScope; }
    public String getBranchName() { return branchName; }
    public String getWorkspaceId() { return workspaceId; }
    public String getSolutionKey() { return solutionKey; }
    public String getTitle() { return title; }
    public String getDescription() { return description; }
    public SolutionType getSolutionType() { return solutionType; }
    public OperatingModel getOperatingModel() { return operatingModel; }
    public LifecycleStatus getLifecycleStatus() { return lifecycleStatus; }
    public int getMaturityLevel() { return maturityLevel; }
    public String getOwnerUsername() { return ownerUsername; }
    public String getResponsibleOrganization() { return responsibleOrganization; }
    public BigDecimal getCostAmount() { return costAmount; }
    public String getCostCurrency() { return costCurrency; }
    public String getRiskNotes() { return riskNotes; }
    public Integer getLeadTimeDays() { return leadTimeDays; }
    public String getExtensionAttributesJson() { return extensionAttributesJson; }
    public Instant getCreatedAt() { return createdAt; }
    public Instant getUpdatedAt() { return updatedAt; }
    public long getRowVersion() { return rowVersion; }
}