blob: 0eb0b1104426f2c474522fbe2076d347065b9895 [file] [log] [blame]
/*
* Copyright 2013 gitblit.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gitblit.manager;
import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.ModelUtils;
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
/**
* Project manager handles project-related functions.
*
* @author James Moger
*
*/
public class ProjectManager implements IProjectManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
private final IStoredSettings settings;
private final IRuntimeManager runtimeManager;
private final IUserManager userManager;
private final IRepositoryManager repositoryManager;
private FileBasedConfig projectConfigs;
public ProjectManager(
IRuntimeManager runtimeManager,
IUserManager userManager,
IRepositoryManager repositoryManager) {
this.settings = runtimeManager.getSettings();
this.runtimeManager = runtimeManager;
this.userManager = userManager;
this.repositoryManager = repositoryManager;
}
@Override
public ProjectManager start() {
// load and cache the project metadata
projectConfigs = new FileBasedConfig(runtimeManager.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
getProjectConfigs();
return this;
}
@Override
public ProjectManager stop() {
return this;
}
private void reloadProjectMarkdown(ProjectModel project) {
// project markdown
File pmkd = new File(repositoryManager.getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd");
if (pmkd.exists()) {
Date lm = new Date(pmkd.lastModified());
if (!projectMarkdownCache.hasCurrent(project.name, lm)) {
String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n");
projectMarkdownCache.updateObject(project.name, lm, mkd);
}
project.projectMarkdown = projectMarkdownCache.getObject(project.name);
}
// project repositories markdown
File rmkd = new File(repositoryManager.getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd");
if (rmkd.exists()) {
Date lm = new Date(rmkd.lastModified());
if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) {
String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n");
projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd);
}
project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name);
}
}
/**
* Returns the map of project config. This map is cached and reloaded if
* the underlying projects.conf file changes.
*
* @return project config map
*/
private Map<String, ProjectModel> getProjectConfigs() {
if (projectCache.isEmpty() || projectConfigs.isOutdated()) {
try {
projectConfigs.load();
} catch (Exception e) {
}
// project configs
String rootName = settings.getString(Keys.web.repositoryRootGroupName, "main");
ProjectModel rootProject = new ProjectModel(rootName, true);
Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>();
// cache the root project under its alias and an empty path
configs.put("", rootProject);
configs.put(rootProject.name.toLowerCase(), rootProject);
for (String name : projectConfigs.getSubsections("project")) {
ProjectModel project;
if (name.equalsIgnoreCase(rootName)) {
project = rootProject;
} else {
project = new ProjectModel(name);
}
project.title = projectConfigs.getString("project", name, "title");
project.description = projectConfigs.getString("project", name, "description");
reloadProjectMarkdown(project);
configs.put(name.toLowerCase(), project);
}
projectCache.clear();
projectCache.putAll(configs);
}
return projectCache;
}
/**
* Returns a list of project models for the user.
*
* @param user
* @param includeUsers
* @return list of projects that are accessible to the user
*/
@Override
public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
Map<String, ProjectModel> configs = getProjectConfigs();
// per-user project lists, this accounts for security and visibility
Map<String, ProjectModel> map = new TreeMap<String, ProjectModel>();
// root project
map.put("", configs.get(""));
for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) {
String rootPath = StringUtils.getRootPath(model.name).toLowerCase();
if (!map.containsKey(rootPath)) {
ProjectModel project;
if (configs.containsKey(rootPath)) {
// clone the project model because it's repository list will
// be tailored for the requesting user
project = DeepCopier.copy(configs.get(rootPath));
} else {
project = new ProjectModel(rootPath);
}
map.put(rootPath, project);
}
map.get(rootPath).addRepository(model);
}
// sort projects, root project first
List<ProjectModel> projects;
if (includeUsers) {
// all projects
projects = new ArrayList<ProjectModel>(map.values());
Collections.sort(projects);
projects.remove(map.get(""));
projects.add(0, map.get(""));
} else {
// all non-user projects
projects = new ArrayList<ProjectModel>();
ProjectModel root = map.remove("");
for (ProjectModel model : map.values()) {
if (!model.isUserProject()) {
projects.add(model);
}
}
Collections.sort(projects);
projects.add(0, root);
}
return projects;
}
/**
* Returns the project model for the specified user.
*
* @param name
* @param user
* @return a project model, or null if it does not exist
*/
@Override
public ProjectModel getProjectModel(String name, UserModel user) {
for (ProjectModel project : getProjectModels(user, true)) {
if (project.name.equalsIgnoreCase(name)) {
return project;
}
}
return null;
}
/**
* Returns a project model for the Gitblit/system user.
*
* @param name a project name
* @return a project model or null if the project does not exist
*/
@Override
public ProjectModel getProjectModel(String name) {
Map<String, ProjectModel> configs = getProjectConfigs();
ProjectModel project = configs.get(name.toLowerCase());
if (project == null) {
project = new ProjectModel(name);
if (ModelUtils.isPersonalRepository(name)) {
UserModel user = userManager.getUserModel(ModelUtils.getUserNameFromRepoPath(name));
if (user != null) {
project.title = user.getDisplayName();
project.description = "personal repositories";
}
}
} else {
// clone the object
project = DeepCopier.copy(project);
}
if (StringUtils.isEmpty(name)) {
// get root repositories
for (String repository : repositoryManager.getRepositoryList()) {
if (repository.indexOf('/') == -1) {
project.addRepository(repository);
}
}
} else {
// get repositories in subfolder
String folder = name.toLowerCase() + "/";
for (String repository : repositoryManager.getRepositoryList()) {
if (repository.toLowerCase().startsWith(folder)) {
project.addRepository(repository);
}
}
}
if (project.repositories.size() == 0) {
// no repositories == no project
return null;
}
reloadProjectMarkdown(project);
return project;
}
/**
* Returns the list of project models that are referenced by the supplied
* repository model list. This is an alternative method exists to ensure
* Gitblit does not call getRepositoryModels(UserModel) twice in a request.
*
* @param repositoryModels
* @param includeUsers
* @return a list of project models
*/
@Override
public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
for (RepositoryModel repository : repositoryModels) {
if (!includeUsers && repository.isPersonalRepository()) {
// exclude personal repositories
continue;
}
if (!projects.containsKey(repository.getProject())) {
ProjectModel project = getProjectModel(repository.getProject());
if (project == null) {
logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
repository.getProject()));
continue;
}
projects.put(repository.getProject(), project);
// clear the repo list in the project because that is the system
// list, not the user-accessible list and start building the
// user-accessible list
project.repositories.clear();
project.repositories.add(repository.name);
project.lastChange = repository.lastChange;
} else {
// update the user-accessible list
// this is used for repository count
ProjectModel project = projects.get(repository.getProject());
project.repositories.add(repository.name);
if (project.lastChange.before(repository.lastChange)) {
project.lastChange = repository.lastChange;
}
}
}
return new ArrayList<ProjectModel>(projects.values());
}
}