RepositoryWorkspaceService.java

package com.taxonomy.workspace.service;

import com.taxonomy.dsl.storage.DslGitRepository;
import com.taxonomy.dsl.storage.DslGitRepositoryFactory;
import com.taxonomy.workspace.model.RepositoryLifecycleState;
import com.taxonomy.workspace.model.SystemRepository;
import com.taxonomy.workspace.model.UserWorkspace;
import com.taxonomy.workspace.model.WorkspaceProvisioningStatus;
import com.taxonomy.workspace.model.WorkspaceRelationshipType;
import com.taxonomy.workspace.repository.UserWorkspaceRepository;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.time.Instant;
import java.util.UUID;

/** Creates isolated working copies from an explicitly selected central repository. */
@Service
public class RepositoryWorkspaceService {

    private static final String WORKSPACE_BRANCH = "draft";
    private static final String TRACKING_BRANCH = "sync-base";

    private final UserWorkspaceRepository workspaceRepository;
    private final SystemRepositoryService systemRepositoryService;
    private final DslGitRepositoryFactory repositoryFactory;

    public RepositoryWorkspaceService(
            UserWorkspaceRepository workspaceRepository,
            SystemRepositoryService systemRepositoryService,
            DslGitRepositoryFactory repositoryFactory) {
        this.workspaceRepository = workspaceRepository;
        this.systemRepositoryService = systemRepositoryService;
        this.repositoryFactory = repositoryFactory;
    }

    /**
     * Create and provision a personal working copy from the requested repository and branch.
     *
     * <p>This method deliberately has no surrounding Spring transaction. The initial
     * {@link WorkspaceProvisioningStatus#PROVISIONING} metadata must commit before JGit
     * allocation starts, and a later {@link WorkspaceProvisioningStatus#FAILED} update must
     * remain durable even though the caller receives an exception. Each Spring Data
     * {@code save} operation supplies its own transaction boundary.</p>
     *
     * <p>The factory performs the one and only initial DSL seed on the historic
     * {@code draft} workspace branch. The service records that commit and creates the
     * semantic {@code sync-base} ref at exactly the same commit; it must not manufacture a
     * second, unrelated initial commit on another branch.</p>
     */
    public UserWorkspace createWorkingCopy(
            String username,
            String sourceRepositoryId,
            String requestedSourceBranch,
            String displayName,
            String description) {
        String user = requireText(username, "username");
        SystemRepository source = systemRepositoryService.getRepository(sourceRepositoryId);
        if (source.getLifecycleState() != RepositoryLifecycleState.ACTIVE) {
            throw new IllegalStateException(
                    "Source repository is not active: " + sourceRepositoryId);
        }

        String sourceBranch = requestedSourceBranch == null || requestedSourceBranch.isBlank()
                ? source.getDefaultBranch()
                : requestedSourceBranch.strip();
        String name = requireText(displayName, "displayName");
        Instant now = Instant.now();

        UserWorkspace workspace = new UserWorkspace();
        workspace.setWorkspaceId(UUID.randomUUID().toString());
        workspace.setUsername(user);
        workspace.setDisplayName(name);
        workspace.setDescription(description);
        workspace.setCurrentBranch(WORKSPACE_BRANCH);
        workspace.setBaseBranch(sourceBranch);
        workspace.setSourceBranch(sourceBranch);
        workspace.setSourceRepositoryId(source.getRepositoryId());
        workspace.setRelationshipType(WorkspaceRelationshipType.WORKING_COPY);
        workspace.setShared(false);
        workspace.setDefault(false);
        workspace.setArchived(false);
        workspace.setTopologyMode(source.getTopologyMode());
        workspace.setProvisioningStatus(WorkspaceProvisioningStatus.PROVISIONING);
        workspace.setSyncTargetBranch(sourceBranch);
        workspace.setCreatedAt(now);
        workspace.setLastAccessedAt(now);
        workspaceRepository.save(workspace);

        boolean workspaceStorageAttempted = false;
        try {
            DslGitRepository sourceGit =
                    repositoryFactory.getCentralRepository(source.getRepositoryId());
            String sourceDsl = sourceGit.getDslAtHead(sourceBranch);
            String sourceCommit = sourceGit.getHeadCommit(sourceBranch);
            if (sourceDsl == null || sourceDsl.isBlank() || sourceCommit == null) {
                throw new IllegalStateException(
                        "Source branch has no architecture content: " + sourceBranch);
            }

            workspaceStorageAttempted = true;
            DslGitRepository workspaceGit = repositoryFactory.createWorkspaceRepository(
                    workspace.getWorkspaceId(), source.getRepositoryId(), sourceBranch);
            String workspaceCommit = workspaceGit.getHeadCommit(WORKSPACE_BRANCH);
            if (workspaceCommit == null) {
                throw new IllegalStateException(
                        "Workspace seed did not create branch " + WORKSPACE_BRANCH);
            }
            String trackingCommit = workspaceGit.createBranch(
                    TRACKING_BRANCH, WORKSPACE_BRANCH);
            if (trackingCommit == null) {
                throw new IllegalStateException(
                        "Workspace seed did not create tracking branch " + TRACKING_BRANCH);
            }

            workspace.setBaseCommit(sourceCommit);
            workspace.setCurrentCommit(workspaceCommit);
            workspace.setLastFetchedCommit(sourceCommit);
            workspace.setLastIntegratedCommit(sourceCommit);
            workspace.setProvisioningStatus(WorkspaceProvisioningStatus.READY);
            workspace.setProvisionedAt(Instant.now());
            workspace.setProvisioningError(null);
            return workspaceRepository.save(workspace);
        } catch (IOException | RuntimeException exception) {
            if (workspaceStorageAttempted) {
                try {
                    repositoryFactory.deleteWorkspaceRepository(workspace.getWorkspaceId());
                } catch (RuntimeException cleanupException) {
                    exception.addSuppressed(cleanupException);
                }
            }
            workspace.setProvisioningStatus(WorkspaceProvisioningStatus.FAILED);
            workspace.setProvisioningError(safeMessage(exception));
            workspace.setLastAccessedAt(Instant.now());
            workspaceRepository.save(workspace);
            throw new IllegalStateException(
                    "Could not provision working copy from repository " + sourceRepositoryId,
                    exception);
        }
    }

    private static String requireText(String value, String field) {
        if (value == null || value.isBlank()) {
            throw new IllegalArgumentException(field + " must not be blank");
        }
        return value.strip();
    }

    private static String safeMessage(Throwable throwable) {
        return throwable.getMessage() == null
                ? throwable.getClass().getSimpleName()
                : throwable.getMessage();
    }
}