DslGitRepositoryFactory.java
package com.taxonomy.dsl.storage;
import com.taxonomy.workspace.model.SystemRepository;
import com.taxonomy.workspace.service.RepositoryContext;
import com.taxonomy.workspace.service.SystemRepositoryService;
import com.taxonomy.workspace.service.WorkspaceContext;
import io.github.carstenartur.jgit.storage.hibernate.HibernateRepositoryFactory;
import io.github.carstenartur.jgit.storage.hibernate.RepositoryDeletionResult;
import io.github.carstenartur.jgit.storage.hibernate.RepositoryName;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Creates and caches Taxonomy's logical Git repositories.
*
* <p>The application owns repository naming, workspace routing and seeding. The
* reusable {@code jgit-storage-hibernate-core} library owns database-backed JGit
* object, ref and reflog persistence.</p>
*/
public class DslGitRepositoryFactory implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(DslGitRepositoryFactory.class);
/** The well-known storage name retained for the migrated primary repository. */
static final String SYSTEM_REPO_NAME = SystemRepositoryService.PRIMARY_STORAGE_NAME;
/** Prefix for workspace repository names. */
static final String WORKSPACE_REPO_PREFIX = "ws-";
/** Fallback prefix used by in-memory tests without a repository catalog. */
static final String CENTRAL_REPO_PREFIX = "central-";
private final HibernateRepositoryFactory storageFactory;
private final SystemRepositoryService systemRepositoryService;
private final ConcurrentMap<String, DslGitRepository> cache = new ConcurrentHashMap<>();
/** Legacy/in-memory constructor retained for tests. */
public DslGitRepositoryFactory(HibernateRepositoryFactory storageFactory) {
this(storageFactory, null);
}
/** Create a logical-repository factory with explicit catalog resolution. */
public DslGitRepositoryFactory(
HibernateRepositoryFactory storageFactory,
SystemRepositoryService systemRepositoryService) {
this.storageFactory = storageFactory;
this.systemRepositoryService = systemRepositoryService;
}
/**
* Return the migrated primary repository.
*
* @deprecated repository-sensitive code should resolve an explicit repository ID.
*/
@Deprecated(forRemoval = false)
public DslGitRepository getSystemRepository() {
return cache.computeIfAbsent(SYSTEM_REPO_NAME, this::createRepository);
}
/** Return one explicitly selected central architecture repository. */
public DslGitRepository getCentralRepository(String repositoryId) {
return cache.computeIfAbsent(storageNameFor(repositoryId), this::createRepository);
}
/** Initialize/open a central repository under an already-reserved catalog storage name. */
public DslGitRepository createCentralRepository(String repositoryId, String storageName) {
requireText(repositoryId, "repositoryId");
requireText(storageName, "storageName");
if (systemRepositoryService != null) {
SystemRepository metadata = systemRepositoryService.getRepository(repositoryId);
if (!storageName.equals(metadata.getStorageRepositoryName())) {
throw new IllegalArgumentException(
"Storage name does not match repository catalog entry: " + repositoryId);
}
}
return cache.computeIfAbsent(storageName, this::createRepository);
}
/**
* Return a repository isolated by workspace ID.
*
* <p>This compatibility path retains the historic primary-repository seed.
* New multi-repository provisioning must use
* {@link #createWorkspaceRepository(String, String, String)}.</p>
*/
public DslGitRepository getWorkspaceRepository(String workspaceId) {
String repoName = workspaceRepositoryName(workspaceId);
return cache.computeIfAbsent(repoName, name -> {
DslGitRepository repository = createRepository(name);
seedFromCentral(repository, getSystemRepository(), "draft", "system repository");
return repository;
});
}
/**
* Open an existing workspace repository without inferring or seeding any source.
* Explicit repository contexts use this path so a missing workspace can never
* be accidentally initialized from the primary repository.
*/
public DslGitRepository openWorkspaceRepository(String workspaceId) {
return cache.computeIfAbsent(
workspaceRepositoryName(workspaceId), this::createRepository);
}
/** Create/open an isolated workspace and seed it from the selected central source. */
public DslGitRepository createWorkspaceRepository(
String workspaceId,
String sourceRepositoryId,
String sourceBranch) {
String repoName = workspaceRepositoryName(workspaceId);
DslGitRepository source = getCentralRepository(sourceRepositoryId);
String branch = requireText(sourceBranch, "sourceBranch");
return cache.computeIfAbsent(repoName, name -> {
DslGitRepository repository = createRepository(name);
seedFromCentral(repository, source, branch,
"central repository " + sourceRepositoryId);
return repository;
});
}
/** Resolve the repository selected by an explicit repository context. */
public DslGitRepository resolveRepository(RepositoryContext context) {
if (context == null) {
throw new IllegalArgumentException("RepositoryContext must not be null");
}
if (context.workspaceId() != null) {
return openWorkspaceRepository(context.workspaceId());
}
return getCentralRepository(context.repositoryId());
}
/**
* Resolve the compatibility workspace context.
*
* <p>Request-bound compatibility contexts carry an exact repository ID and
* are routed with the same fail-closed semantics as {@link RepositoryContext}.
* Only direct legacy callers using {@link WorkspaceContext#LEGACY_REPOSITORY_ID}
* retain the historic primary-repository fallback.</p>
*
* @deprecated use {@link #resolveRepository(RepositoryContext)} when repository
* identity is available.
*/
@Deprecated(forRemoval = false)
public DslGitRepository resolveRepository(WorkspaceContext context) {
if (context == null) {
return getSystemRepository();
}
if (!WorkspaceContext.LEGACY_REPOSITORY_ID.equals(context.repositoryId())) {
if (context.workspaceId() != null) {
return openWorkspaceRepository(context.workspaceId());
}
return getCentralRepository(context.repositoryId());
}
if (context.workspaceId() == null) {
return getSystemRepository();
}
return getWorkspaceRepository(context.workspaceId());
}
/** Close and remove a cached workspace handle without deleting persisted data. */
public void evict(String workspaceId) {
closeQuietly(cache.remove(workspaceRepositoryName(workspaceId)));
}
/** Delete all persisted Git state belonging to one workspace repository. */
public RepositoryDeletionResult deleteWorkspaceRepository(String workspaceId) {
String repoName = workspaceRepositoryName(workspaceId);
closeQuietly(cache.remove(repoName));
if (storageFactory == null) {
return new RepositoryDeletionResult(0, 0, 0);
}
return storageFactory.deleteRepository(new RepositoryName(repoName));
}
/** Delete storage for a non-primary central repository. */
public RepositoryDeletionResult deleteCentralRepository(String repositoryId) {
String storageName = storageNameFor(repositoryId);
if (SYSTEM_REPO_NAME.equals(storageName)) {
throw new IllegalArgumentException("The primary repository cannot be deleted");
}
closeQuietly(cache.remove(storageName));
if (storageFactory == null) {
return new RepositoryDeletionResult(0, 0, 0);
}
return storageFactory.deleteRepository(new RepositoryName(storageName));
}
/** Create either a persistent library-backed repository or an owned in-memory repository. */
protected DslGitRepository createRepository(String name) {
if (storageFactory != null) {
log.info("Opening database-backed DslGitRepository '{}'", name);
return new DslGitRepository(storageFactory, name);
}
log.info("Creating in-memory DslGitRepository '{}' (test mode)", name);
return new DslGitRepository(
new InMemoryRepository(new DfsRepositoryDescription(name)), true);
}
private void seedFromCentral(
DslGitRepository workspace,
DslGitRepository source,
String sourceBranch,
String sourceDescription) {
try {
if (!workspace.getBranchNames().isEmpty()) {
log.debug("Workspace repository already contains refs; skipping seed");
return;
}
String dsl = source.getDslAtHead(sourceBranch);
if (dsl != null && !dsl.isBlank()) {
workspace.commitDsl("draft", dsl, "system",
"Initial working copy from " + sourceDescription + "/" + sourceBranch);
log.info("Seeded workspace repository from {}/{}",
sourceDescription, sourceBranch);
} else {
log.debug("Source branch {}/{} is empty; skipping workspace seed",
sourceDescription, sourceBranch);
}
} catch (IOException exception) {
throw new IllegalStateException(
"Failed to seed workspace repository from " + sourceDescription,
exception);
}
}
private String storageNameFor(String repositoryId) {
String id = requireText(repositoryId, "repositoryId");
if (systemRepositoryService == null) {
return CENTRAL_REPO_PREFIX + id;
}
SystemRepository metadata = systemRepositoryService.getRepository(id);
String storageName = metadata.getStorageRepositoryName();
if (storageName == null || storageName.isBlank()) {
if (metadata.isPrimaryRepo()) {
return SYSTEM_REPO_NAME;
}
throw new IllegalStateException(
"Repository has no storageRepositoryName: " + repositoryId);
}
return storageName;
}
private static String workspaceRepositoryName(String workspaceId) {
return WORKSPACE_REPO_PREFIX + requireText(workspaceId, "workspaceId");
}
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 void closeQuietly(DslGitRepository repository) {
if (repository == null) {
return;
}
try {
repository.close();
} catch (RuntimeException exception) {
log.warn("Failed to close cached Git repository: {}", exception.getMessage());
}
}
/** Close all cached repository handles during application shutdown. */
@Override
public void close() {
cache.values().forEach(DslGitRepositoryFactory::closeQuietly);
cache.clear();
}
}