ExternalHibernateRepositoryService.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.jgit.server.repository;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jgit.lib.Repository;
import io.github.carstenartur.jgit.storage.hibernate.HibernateGitStorage;
import io.github.carstenartur.jgit.storage.hibernate.HibernateRepositoryFactory;
import io.github.carstenartur.jgit.storage.hibernate.RepositoryDeletionResult;
import io.github.carstenartur.jgit.storage.hibernate.RepositoryName;
/**
* Adapts the released {@code jgit-storage-hibernate-core} factory to the
* application-owned repository service contract.
*
* <p>The adapter owns every storage handle it opens. Application and transport
* code receive only public JGit {@link Repository} instances and Sandbox
* metadata. The production cut-over can therefore replace the copied backend
* without changing REST or Smart HTTP callers.</p>
*/
public final class ExternalHibernateRepositoryService implements SandboxRepositoryService {
private final HibernateRepositoryFactory repositoryFactory;
private final Object lifecycleLock= new Object();
private final Map<String, HibernateGitStorage> storages= new LinkedHashMap<>();
private boolean closed;
/** Creates an adapter for the released public repository factory. */
public ExternalHibernateRepositoryService(HibernateRepositoryFactory repositoryFactory) {
this.repositoryFactory= Objects.requireNonNull(repositoryFactory, "repositoryFactory"); //$NON-NLS-1$
}
@Override
public Repository openOrCreate(String name) throws IOException {
synchronized (lifecycleLock) {
return storageLocked(name).repository();
}
}
@Override
public SandboxRepositoryInfo info(String name) throws IOException {
synchronized (lifecycleLock) {
String normalized= normalize(name);
Repository repository= storageLocked(normalized).repository();
return new SandboxRepositoryInfo(normalized, repository.getGitwebDescription());
}
}
@Override
public SandboxRepositoryInfo setDescription(String name, String description) throws IOException {
synchronized (lifecycleLock) {
String normalized= normalize(name);
Repository repository= storageLocked(normalized).repository();
repository.setGitwebDescription(description);
return new SandboxRepositoryInfo(normalized, repository.getGitwebDescription());
}
}
@Override
public SandboxRepositoryDeletionResult delete(String name) {
synchronized (lifecycleLock) {
if (closed) {
throw new IllegalStateException("Repository service is already closed."); //$NON-NLS-1$
}
String normalized= normalize(name);
if (storages.containsKey(normalized)) {
throw new IllegalStateException(
"Repository is open in this process and cannot be deleted: " + normalized); //$NON-NLS-1$
}
RepositoryDeletionResult result= Objects.requireNonNull(
repositoryFactory.deleteRepository(new RepositoryName(normalized)),
"repository deletion result for " + normalized); //$NON-NLS-1$
return new SandboxRepositoryDeletionResult(
result.packRows(), result.reflogRows(), result.projectionRows());
}
}
@Override
public boolean isOpen(String name) {
synchronized (lifecycleLock) {
return !closed && storages.containsKey(normalize(name));
}
}
@Override
public void close() {
List<HibernateGitStorage> storagesToClose;
synchronized (lifecycleLock) {
if (closed) {
return;
}
closed= true;
storagesToClose= new ArrayList<>(storages.values());
storages.clear();
}
RuntimeException failure= null;
for (HibernateGitStorage storage : storagesToClose) {
try {
storage.close();
} catch (RuntimeException exception) {
if (failure == null) {
failure= exception;
} else {
failure.addSuppressed(exception);
}
}
}
if (failure != null) {
throw failure;
}
}
private HibernateGitStorage storageLocked(String name) {
if (closed) {
throw new IllegalStateException("Repository service is already closed."); //$NON-NLS-1$
}
String normalized= normalize(name);
return storages.computeIfAbsent(normalized,
key -> repositoryFactory.open(new RepositoryName(key)));
}
private static String normalize(String name) {
String normalized= Objects.requireNonNull(name, "name").strip(); //$NON-NLS-1$
if (normalized.startsWith("/")) { //$NON-NLS-1$
normalized= normalized.substring(1);
}
if (normalized.endsWith(".git")) { //$NON-NLS-1$
normalized= normalized.substring(0, normalized.length() - 4);
}
if (normalized.isEmpty()) {
throw new IllegalArgumentException("Repository name must not be blank."); //$NON-NLS-1$
}
return normalized;
}
}