HibernateRepositoryResolver.java

/*******************************************************************************
 * Copyright (c) 2026 Carsten Hammer.
 *
 * 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
 *
 * Contributors:
 *     Carsten Hammer
 *******************************************************************************/
package org.eclipse.jgit.server.resolver;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.hibernate.config.HibernateSessionFactoryProvider;
import org.eclipse.jgit.storage.hibernate.repository.HibernateRepository;
import org.eclipse.jgit.storage.hibernate.repository.HibernateRepositoryBuilder;
import org.eclipse.jgit.transport.resolver.RepositoryResolver;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;

import jakarta.servlet.http.HttpServletRequest;

/**
 * Resolves Git repositories from the Hibernate database backend.
 * <p>
 * Repository instances are cached by name to avoid creating duplicate
 * connections to the same database-backed repository.
 */
public class HibernateRepositoryResolver
		implements RepositoryResolver<HttpServletRequest>, AutoCloseable {

	private final HibernateSessionFactoryProvider sessionFactoryProvider;

	private final Map<String, HibernateRepository> cache = new ConcurrentHashMap<>();

	/**
	 * Create a resolver backed by the given session factory provider.
	 *
	 * @param sessionFactoryProvider
	 *            the Hibernate session factory provider
	 */
	public HibernateRepositoryResolver(
			HibernateSessionFactoryProvider sessionFactoryProvider) {
		this.sessionFactoryProvider = sessionFactoryProvider;
	}

	@Override
	public Repository open(HttpServletRequest req, String name)
			throws ServiceNotEnabledException {
		String repoName = normalizeRepoName(name);
		try {
			return getOrCreateRepository(repoName);
		} catch (IOException e) {
			throw new ServiceNotEnabledException(e.getMessage());
		}
	}

	/**
	 * Get or create a repository by name.
	 *
	 * @param name
	 *            the repository name
	 * @return the repository
	 * @throws IOException
	 *             if repository creation fails
	 */
	public HibernateRepository getOrCreateRepository(String name)
			throws IOException {
		return cache.computeIfAbsent(name, n -> {
			try {
				return new HibernateRepositoryBuilder()
						.setSessionFactoryProvider(sessionFactoryProvider)
						.setRepositoryName(n)
						.setRepositoryDescription(
								new DfsRepositoryDescription(n))
						.build();
			} catch (IOException e) {
				throw new IllegalStateException(
						"Failed to create repository: " + n, e); //$NON-NLS-1$
			}
		});
	}

	/**
	 * Check if a repository with the given name exists in the cache.
	 *
	 * @param name
	 *            the repository name
	 * @return true if the repository is cached
	 */
	public boolean hasRepository(String name) {
		return cache.containsKey(normalizeRepoName(name));
	}

	/**
	 * Get the session factory provider.
	 *
	 * @return the session factory provider
	 */
	public HibernateSessionFactoryProvider getSessionFactoryProvider() {
		return sessionFactoryProvider;
	}

	@Override
	public void close() {
		for (HibernateRepository repo : cache.values()) {
			repo.close();
		}
		cache.clear();
	}

	private static String normalizeRepoName(String name) {
		if (name.startsWith("/")) { //$NON-NLS-1$
			name = name.substring(1);
		}
		if (name.endsWith(".git")) { //$NON-NLS-1$
			name = name.substring(0, name.length() - 4);
		}
		return name;
	}
}