From f76fee63ed9cb3a30d3c0c092d860b1cb93a481b Mon Sep 17 00:00:00 2001
From: Gerard Smyth <gerard.smyth@gmail.com>
Date: Thu, 08 May 2014 13:09:30 -0400
Subject: [PATCH] Updated the SyndicationServlet to provide an additional option to return details of the tags in the repository instead of the commits. This uses a new 'ot' request parameter to indicate the object type of the content to return, which can be ither TAG or COMMIT. If this is not provided, then COMMIT is assumed to maintain backwards compatability. If tags are returned, then the paging parameters, 'l' and 'pg' are still supported, but searching options are currently ignored.

---
 src/main/java/com/gitblit/GitBlit.java | 1699 +++++++++------------------------------------------------
 1 files changed, 283 insertions(+), 1,416 deletions(-)

diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index 4e8822b..3db5f08 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 gitblit.com.
+ * 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.
@@ -15,186 +15,136 @@
  */
 package com.gitblit;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.lang.reflect.Type;
 import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
+import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.Set;
 
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-import javax.servlet.ServletContext;
-import javax.servlet.annotation.WebListener;
+import javax.inject.Singleton;
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.wicket.resource.ContextRelativeResource;
-import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.slf4j.Logger;
-
 import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.FederationRequest;
-import com.gitblit.Constants.FederationToken;
-import com.gitblit.dagger.DaggerContextListener;
-import com.gitblit.fanout.FanoutNioService;
-import com.gitblit.fanout.FanoutService;
-import com.gitblit.fanout.FanoutSocketService;
-import com.gitblit.git.GitDaemon;
-import com.gitblit.git.GitServlet;
+import com.gitblit.Constants.Transport;
+import com.gitblit.manager.GitblitManager;
+import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
-import com.gitblit.manager.IGitblitManager;
-import com.gitblit.manager.IManager;
+import com.gitblit.manager.IGitblit;
 import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.IPluginManager;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
-import com.gitblit.manager.ISessionManager;
 import com.gitblit.manager.IUserManager;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.FederationProposal;
-import com.gitblit.models.FederationSet;
-import com.gitblit.models.GitClientApplication;
-import com.gitblit.models.ProjectModel;
+import com.gitblit.manager.ServicesManager;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.RepositoryUrl;
-import com.gitblit.models.ServerSettings;
-import com.gitblit.models.SettingModel;
-import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.ContainerUtils;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.HttpUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.JsonUtils;
-import com.gitblit.utils.ModelUtils;
-import com.gitblit.utils.ObjectCache;
+import com.gitblit.tickets.BranchTicketService;
+import com.gitblit.tickets.FileTicketService;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.tickets.NullTicketService;
+import com.gitblit.tickets.RedisTicketService;
+import com.gitblit.transport.ssh.IPublicKeyManager;
 import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitblitWicketFilter;
-import com.gitblit.wicket.WicketUtils;
-import com.google.gson.Gson;
-import com.google.gson.JsonIOException;
-import com.google.gson.JsonSyntaxException;
-import com.google.gson.reflect.TypeToken;
 
+import dagger.Module;
 import dagger.ObjectGraph;
+import dagger.Provides;
 
 /**
- * GitBlit is the servlet context listener singleton that acts as the core for
- * the web ui and the servlets. This class is either directly instantiated by
- * the GitBlitServer class (Gitblit GO) or is reflectively instantiated by the
- * servlet 3 container (Gitblit WAR or Express).
- *
- * This class is the central logic processor for Gitblit. All settings, user
- * object, and repository object operations pass through this class.
+ * GitBlit is the aggregate manager for the Gitblit webapp.  It provides all
+ * management functions and also manages some long-running services.
  *
  * @author James Moger
  *
  */
-@WebListener
-public class GitBlit extends DaggerContextListener
-					 implements IProjectManager,
-								IFederationManager,
-								IGitblitManager {
+public class GitBlit extends GitblitManager {
 
-	private static GitBlit gitblit;
+	private final ObjectGraph injector;
 
-	private final IStoredSettings goSettings;
+	private final ServicesManager servicesManager;
 
-	private final File goBaseFolder;
+	private ITicketService ticketService;
 
-	private final List<IManager> managers = new ArrayList<IManager>();
+	public GitBlit(
+			IRuntimeManager runtimeManager,
+			IPluginManager pluginManager,
+			INotificationManager notificationManager,
+			IUserManager userManager,
+			IAuthenticationManager authenticationManager,
+			IPublicKeyManager publicKeyManager,
+			IRepositoryManager repositoryManager,
+			IProjectManager projectManager,
+			IFederationManager federationManager) {
 
-	private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10);
+		super(runtimeManager,
+				pluginManager,
+				notificationManager,
+				userManager,
+				authenticationManager,
+				publicKeyManager,
+				repositoryManager,
+				projectManager,
+				federationManager);
 
-	private final List<FederationModel> federationRegistrations = Collections
-			.synchronizedList(new ArrayList<FederationModel>());
+		this.injector = ObjectGraph.create(getModules());
 
-	private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
-
-	private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
-
-	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 IStoredSettings settings;
-
-	private FileBasedConfig projectConfigs;
-
-	private FanoutService fanoutService;
-
-	private GitDaemon gitDaemon;
-
-	public GitBlit() {
-		this.goSettings = null;
-		this.goBaseFolder = null;
+		this.servicesManager = new ServicesManager(this);
 	}
 
-	public GitBlit(IStoredSettings settings, File baseFolder) {
-		this.goSettings = settings;
-		this.goBaseFolder = baseFolder;
-		gitblit = this;
-	}
-
-	/**
-	 * Returns the Gitblit singleton.
-	 *
-	 * @return gitblit singleton
-	 */
-	public static GitBlit self() {
-		return gitblit;
-	}
-
-	@SuppressWarnings("unchecked")
-	public static <X> X getManager(Class<X> managerClass) {
-		if (managerClass.isAssignableFrom(GitBlit.class)) {
-			return (X) gitblit;
-		}
-
-		for (IManager manager : gitblit.managers) {
-			if (managerClass.isAssignableFrom(manager.getClass())) {
-				return (X) manager;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Returns the path of the proposals folder. This method checks to see if
-	 * Gitblit is running on a cloud service and may return an adjusted path.
-	 *
-	 * @return the proposals folder path
-	 */
 	@Override
-	public File getProposalsFolder() {
-		return getManager(IRuntimeManager.class).getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
+	public GitBlit start() {
+		super.start();
+		logger.info("Starting services manager...");
+		servicesManager.start();
+		configureTicketService();
+		return this;
+	}
+
+	@Override
+	public GitBlit stop() {
+		super.stop();
+		servicesManager.stop();
+		ticketService.stop();
+		return this;
+	}
+
+	@Override
+	public boolean isServingRepositories() {
+		return servicesManager.isServingRepositories();
+	}
+
+	protected Object [] getModules() {
+		return new Object [] { new GitBlitModule()};
+	}
+
+	protected boolean acceptPush(Transport byTransport) {
+		if (byTransport == null) {
+			logger.info("Unknown transport, push rejected!");
+			return false;
+		}
+
+		Set<Transport> transports = new HashSet<Transport>();
+		for (String value : getSettings().getStrings(Keys.git.acceptedPushTransports)) {
+			Transport transport = Transport.fromString(value);
+			if (transport == null) {
+				logger.info(String.format("Ignoring unknown registered transport %s", value));
+				continue;
+			}
+
+			transports.add(transport);
+		}
+
+		if (transports.isEmpty()) {
+			// no transports are explicitly specified, all are acceptable
+			return true;
+		}
+
+		// verify that the transport is permitted
+		return transports.contains(byTransport);
 	}
 
 	/**
@@ -213,19 +163,46 @@
 		String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
 
 		List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
+
 		// http/https url
 		if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
 			AccessPermission permission = user.getRepositoryPermission(repository).permission;
 			if (permission.exceeds(AccessPermission.NONE)) {
+				Transport transport = Transport.fromString(request.getScheme());
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
 				list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
 			}
 		}
 
-		// git daemon url
-		String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
-		if (!StringUtils.isEmpty(gitDaemonUrl)) {
-			AccessPermission permission = getGitDaemonAccessPermission(user, repository);
+		// ssh daemon url
+		String sshDaemonUrl = servicesManager.getSshDaemonUrl(request, user, repository);
+		if (!StringUtils.isEmpty(sshDaemonUrl)) {
+			AccessPermission permission = user.getRepositoryPermission(repository).permission;
 			if (permission.exceeds(AccessPermission.NONE)) {
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
+
+				list.add(new RepositoryUrl(sshDaemonUrl, permission));
+			}
+		}
+
+		// git daemon url
+		String gitDaemonUrl = servicesManager.getGitDaemonUrl(request, user, repository);
+		if (!StringUtils.isEmpty(gitDaemonUrl)) {
+			AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository);
+			if (permission.exceeds(AccessPermission.NONE)) {
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
 				list.add(new RepositoryUrl(gitDaemonUrl, permission));
 			}
 		}
@@ -236,7 +213,7 @@
 		for (String url : settings.getStrings(Keys.web.otherUrls)) {
 			if (url.contains("{1}")) {
 				// external url requires username, only add url IF we have one
-				if(!StringUtils.isEmpty(username)) {
+				if (!StringUtils.isEmpty(username)) {
 					list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
 				}
 			} else {
@@ -244,1341 +221,231 @@
 				list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
 			}
 		}
-		return list;
-	}
 
-	protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
-		StringBuilder sb = new StringBuilder();
-		sb.append(HttpUtils.getGitblitURL(request));
-		sb.append(Constants.GIT_PATH);
-		sb.append(repository.name);
+		// sort transports by highest permission and then by transport security
+		Collections.sort(list, new Comparator<RepositoryUrl>() {
 
-		// inject username into repository url if authentication is required
-		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
-				&& !StringUtils.isEmpty(username)) {
-			sb.insert(sb.indexOf("://") + 3, username + "@");
-		}
-		return sb.toString();
-	}
-
-	protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
-		if (gitDaemon != null) {
-			String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
-			if (bindInterface.equals("localhost")
-					&& (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
-				// git daemon is bound to localhost and the request is from elsewhere
-				return null;
-			}
-			if (user.canClone(repository)) {
-				String servername = request.getServerName();
-				String url = gitDaemon.formatUrl(servername, repository.name);
-				return url;
-			}
-		}
-		return null;
-	}
-
-	protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
-		if (gitDaemon != null && user.canClone(repository)) {
-			AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
-			if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
-				if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
-					// can not authenticate clone via anonymous git protocol
-					gitDaemonPermission = AccessPermission.NONE;
-				} else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
-					// can not authenticate push via anonymous git protocol
-					gitDaemonPermission = AccessPermission.CLONE;
-				} else {
-					// normal user permission
+			@Override
+			public int compare(RepositoryUrl o1, RepositoryUrl o2) {
+				if (!o1.isExternal() && o2.isExternal()) {
+					// prefer Gitblit over external
+					return -1;
+				} else if (o1.isExternal() && !o2.isExternal()) {
+					// prefer Gitblit over external
+					return 1;
+				} else if (o1.isExternal() && o2.isExternal()) {
+					// sort by Transport ordinal
+					return o1.transport.compareTo(o2.transport);
+				} else if (o1.permission.exceeds(o2.permission)) {
+					// prefer highest permission
+					return -1;
+				} else if (o2.permission.exceeds(o1.permission)) {
+					// prefer highest permission
+					return 1;
 				}
+
+				// prefer more secure transports
+				return o1.transport.compareTo(o2.transport);
 			}
-			return gitDaemonPermission;
-		}
-		return AccessPermission.NONE;
-	}
+		});
 
-	/**
-	 * Returns the list of custom client applications to be used for the
-	 * repository url panel;
-	 *
-	 * @return a collection of client applications
-	 */
-	@Override
-	public Collection<GitClientApplication> getClientApplications() {
-		// prefer user definitions, if they exist
-		File userDefs = new File(getManager(IRuntimeManager.class).getBaseFolder(), "clientapps.json");
-		if (userDefs.exists()) {
-			Date lastModified = new Date(userDefs.lastModified());
-			if (clientApplications.hasCurrent("user", lastModified)) {
-				return clientApplications.getObject("user");
-			} else {
-				// (re)load user definitions
-				try {
-					InputStream is = new FileInputStream(userDefs);
-					Collection<GitClientApplication> clients = readClientApplications(is);
-					is.close();
-					if (clients != null) {
-						clientApplications.updateObject("user", lastModified, clients);
-						return clients;
-					}
-				} catch (IOException e) {
-					logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
-				}
-			}
-		}
-
-		// no user definitions, use system definitions
-		if (!clientApplications.hasCurrent("system", new Date(0))) {
-			try {
-				InputStream is = getClass().getResourceAsStream("/clientapps.json");
-				Collection<GitClientApplication> clients = readClientApplications(is);
-				is.close();
-				if (clients != null) {
-					clientApplications.updateObject("system", new Date(0), clients);
-				}
-			} catch (IOException e) {
-				logger.error("Failed to deserialize clientapps.json resource!", e);
-			}
-		}
-
-		return clientApplications.getObject("system");
-	}
-
-	private Collection<GitClientApplication> readClientApplications(InputStream is) {
-		try {
-			Type type = new TypeToken<Collection<GitClientApplication>>() {
-			}.getType();
-			InputStreamReader reader = new InputStreamReader(is);
-			Gson gson = JsonUtils.gson();
-			Collection<GitClientApplication> links = gson.fromJson(reader, type);
-			return links;
-		} catch (JsonIOException e) {
-			logger.error("Error deserializing client applications!", e);
-		} catch (JsonSyntaxException e) {
-			logger.error("Error deserializing client applications!", e);
-		}
-		return null;
-	}
-
-	/**
-	 * Open a file resource using the Servlet container.
-	 * @param file to open
-	 * @return InputStream of the opened file
-	 * @throws ResourceStreamNotFoundException
-	 */
-	public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException {
-		ContextRelativeResource res = WicketUtils.getResource(file);
-		return res.getResourceStream().getInputStream();
-	}
-
-	@Override
-	public UserModel getFederationUser() {
-		// the federation user is an administrator
-		UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
-		federationUser.canAdmin = true;
-		return federationUser;
-	}
-
-	/**
-	 * Adds/updates a complete user object keyed by username. This method allows
-	 * for renaming a user.
-	 *
-	 * @see IUserService.updateUserModel(String, UserModel)
-	 * @param username
-	 * @param user
-	 * @param isCreate
-	 * @throws GitBlitException
-	 */
-	@Override
-	public void updateUserModel(String username, UserModel user, boolean isCreate)
-			throws GitBlitException {
-		if (!username.equalsIgnoreCase(user.username)) {
-			if (getManager(IUserManager.class).getUserModel(user.username) != null) {
-				throw new GitBlitException(MessageFormat.format(
-						"Failed to rename ''{0}'' because ''{1}'' already exists.", username,
-						user.username));
-			}
-
-			// rename repositories and owner fields for all repositories
-			for (RepositoryModel model : getManager(IRepositoryManager.class).getRepositoryModels(user)) {
-				if (model.isUsersPersonalRepository(username)) {
-					// personal repository
-					model.addOwner(user.username);
-					String oldRepositoryName = model.name;
-					model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length());
-					model.projectPath = user.getPersonalPath();
-					getManager(IRepositoryManager.class).updateRepositoryModel(oldRepositoryName, model, false);
-				} else if (model.isOwner(username)) {
-					// common/shared repo
-					model.addOwner(user.username);
-					getManager(IRepositoryManager.class).updateRepositoryModel(model.name, model, false);
-				}
-			}
-		}
-		if (!getManager(IUserManager.class).updateUserModel(username, user)) {
-			throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
-		}
-	}
-
-	/**
-	 * Updates the TeamModel object for the specified name.
-	 *
-	 * @param teamname
-	 * @param team
-	 * @param isCreate
-	 */
-	@Override
-	public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
-			throws GitBlitException {
-		if (!teamname.equalsIgnoreCase(team.name)) {
-			if (getManager(IUserManager.class).getTeamModel(team.name) != null) {
-				throw new GitBlitException(MessageFormat.format(
-						"Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
-						team.name));
-			}
-		}
-		if (!getManager(IUserManager.class).updateTeamModel(teamname, team)) {
-			throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
-		}
-	}
-
-	private void reloadProjectMarkdown(ProjectModel project) {
-		// project markdown
-		File pmkd = new File(getManager(IRepositoryManager.class).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(getManager(IRepositoryManager.class).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 : getManager(IRepositoryManager.class).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 = getManager(IUserManager.class).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 : getManager(IRepositoryManager.class).getRepositoryList()) {
-				if (repository.indexOf('/') == -1) {
-					project.addRepository(repository);
-				}
-			}
-		} else {
-			// get repositories in subfolder
-			String folder = name.toLowerCase() + "/";
-			for (String repository : getManager(IRepositoryManager.class).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.projectPath)) {
-				ProjectModel project = getProjectModel(repository.projectPath);
-				if (project == null) {
-					logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
-							repository.projectPath));
-					continue;
-				}
-				projects.put(repository.projectPath, 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.projectPath);
-				project.repositories.add(repository.name);
-				if (project.lastChange.before(repository.lastChange)) {
-					project.lastChange = repository.lastChange;
-				}
-			}
-		}
-		return new ArrayList<ProjectModel>(projects.values());
-	}
-
-	/**
-	 * Returns Gitblit's scheduled executor service for scheduling tasks.
-	 *
-	 * @return scheduledExecutor
-	 */
-	public ScheduledExecutorService executor() {
-		return scheduledExecutor;
-	}
-
-	@Override
-	public boolean canFederate() {
-		String passphrase = settings.getString(Keys.federation.passphrase, "");
-		return !StringUtils.isEmpty(passphrase);
-	}
-
-	/**
-	 * Configures this Gitblit instance to pull any registered federated gitblit
-	 * instances.
-	 */
-	private void configureFederation() {
-		boolean validPassphrase = true;
-		String passphrase = settings.getString(Keys.federation.passphrase, "");
-		if (StringUtils.isEmpty(passphrase)) {
-			logger.warn("Federation passphrase is blank! This server can not be PULLED from.");
-			validPassphrase = false;
-		}
-		if (validPassphrase) {
-			// standard tokens
-			for (FederationToken tokenType : FederationToken.values()) {
-				logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
-						getFederationToken(tokenType)));
-			}
-
-			// federation set tokens
-			for (String set : settings.getStrings(Keys.federation.sets)) {
-				logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
-						getFederationToken(set)));
-			}
-		}
-
-		// Schedule the federation executor
-		List<FederationModel> registrations = getFederationRegistrations();
-		if (registrations.size() > 0) {
-			FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
-			scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
-		}
-	}
-
-	/**
-	 * Returns the list of federated gitblit instances that this instance will
-	 * try to pull.
-	 *
-	 * @return list of registered gitblit instances
-	 */
-	@Override
-	public List<FederationModel> getFederationRegistrations() {
-		if (federationRegistrations.isEmpty()) {
-			federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
-		}
-		return federationRegistrations;
-	}
-
-	/**
-	 * Retrieve the specified federation registration.
-	 *
-	 * @param name
-	 *            the name of the registration
-	 * @return a federation registration
-	 */
-	@Override
-	public FederationModel getFederationRegistration(String url, String name) {
-		// check registrations
-		for (FederationModel r : getFederationRegistrations()) {
-			if (r.name.equals(name) && r.url.equals(url)) {
-				return r;
-			}
-		}
-
-		// check the results
-		for (FederationModel r : getFederationResultRegistrations()) {
-			if (r.name.equals(name) && r.url.equals(url)) {
-				return r;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Returns the list of federation sets.
-	 *
-	 * @return list of federation sets
-	 */
-	@Override
-	public List<FederationSet> getFederationSets(String gitblitUrl) {
-		List<FederationSet> list = new ArrayList<FederationSet>();
-		// generate standard tokens
-		for (FederationToken type : FederationToken.values()) {
-			FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
-			fset.repositories = getRepositories(gitblitUrl, fset.token);
-			list.add(fset);
-		}
-		// generate tokens for federation sets
-		for (String set : settings.getStrings(Keys.federation.sets)) {
-			FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
-					getFederationToken(set));
-			fset.repositories = getRepositories(gitblitUrl, fset.token);
-			list.add(fset);
-		}
 		return list;
 	}
 
 	/**
-	 * Returns the list of possible federation tokens for this Gitblit instance.
-	 *
-	 * @return list of federation tokens
+	 * Detect renames and reindex as appropriate.
 	 */
 	@Override
-	public List<String> getFederationTokens() {
-		List<String> tokens = new ArrayList<String>();
-		// generate standard tokens
-		for (FederationToken type : FederationToken.values()) {
-			tokens.add(getFederationToken(type));
+	public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
+			boolean isCreate) throws GitBlitException {
+		RepositoryModel oldModel = null;
+		boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name);
+		if (isRename) {
+			oldModel = repositoryManager.getRepositoryModel(repositoryName);
 		}
-		// generate tokens for federation sets
-		for (String set : settings.getStrings(Keys.federation.sets)) {
-			tokens.add(getFederationToken(set));
+
+		super.updateRepositoryModel(repositoryName, repository, isCreate);
+
+		if (isRename && ticketService != null) {
+			ticketService.rename(oldModel, repository);
 		}
-		return tokens;
 	}
 
 	/**
-	 * Returns the specified federation token for this Gitblit instance.
-	 *
-	 * @param type
-	 * @return a federation token
+	 * Delete the user and all associated public ssh keys.
 	 */
 	@Override
-	public String getFederationToken(FederationToken type) {
-		return getFederationToken(type.name());
+	public boolean deleteUser(String username) {
+		UserModel user = userManager.getUserModel(username);
+		return deleteUserModel(user);
 	}
 
-	/**
-	 * Returns the specified federation token for this Gitblit instance.
-	 *
-	 * @param value
-	 * @return a federation token
-	 */
 	@Override
-	public String getFederationToken(String value) {
-		String passphrase = settings.getString(Keys.federation.passphrase, "");
-		return StringUtils.getSHA1(passphrase + "-" + value);
-	}
-
-	/**
-	 * Compares the provided token with this Gitblit instance's tokens and
-	 * determines if the requested permission may be granted to the token.
-	 *
-	 * @param req
-	 * @param token
-	 * @return true if the request can be executed
-	 */
-	@Override
-	public boolean validateFederationRequest(FederationRequest req, String token) {
-		String all = getFederationToken(FederationToken.ALL);
-		String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
-		String jur = getFederationToken(FederationToken.REPOSITORIES);
-		switch (req) {
-		case PULL_REPOSITORIES:
-			return token.equals(all) || token.equals(unr) || token.equals(jur);
-		case PULL_USERS:
-		case PULL_TEAMS:
-			return token.equals(all) || token.equals(unr);
-		case PULL_SETTINGS:
-		case PULL_SCRIPTS:
-			return token.equals(all);
-		default:
-			break;
+	public boolean deleteUserModel(UserModel model) {
+		boolean success = userManager.deleteUserModel(model);
+		if (success) {
+			getPublicKeyManager().removeAllKeys(model.username);
 		}
-		return false;
+		return success;
 	}
 
 	/**
-	 * Acknowledge and cache the status of a remote Gitblit instance.
-	 *
-	 * @param identification
-	 *            the identification of the pulling Gitblit instance
-	 * @param registration
-	 *            the registration from the pulling Gitblit instance
-	 * @return true if acknowledged
+	 * Delete the repository and all associated tickets.
 	 */
 	@Override
-	public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
-		// reset the url to the identification of the pulling Gitblit instance
-		registration.url = identification;
-		String id = identification;
-		if (!StringUtils.isEmpty(registration.folder)) {
-			id += "-" + registration.folder;
+	public boolean deleteRepository(String repositoryName) {
+		RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName);
+		return deleteRepositoryModel(repository);
+	}
+
+	@Override
+	public boolean deleteRepositoryModel(RepositoryModel model) {
+		boolean success = repositoryManager.deleteRepositoryModel(model);
+		if (success && ticketService != null) {
+			ticketService.deleteAll(model);
 		}
-		federationPullResults.put(id, registration);
-		return true;
+		return success;
 	}
 
 	/**
-	 * Returns the list of registration results.
+	 * Returns the configured ticket service.
 	 *
-	 * @return the list of registration results
+	 * @return a ticket service
 	 */
 	@Override
-	public List<FederationModel> getFederationResultRegistrations() {
-		return new ArrayList<FederationModel>(federationPullResults.values());
+	public ITicketService getTicketService() {
+		return ticketService;
 	}
 
-	/**
-	 * Submit a federation proposal. The proposal is cached locally and the
-	 * Gitblit administrator(s) are notified via email.
-	 *
-	 * @param proposal
-	 *            the proposal
-	 * @param gitblitUrl
-	 *            the url of your gitblit instance to send an email to
-	 *            administrators
-	 * @return true if the proposal was submitted
-	 */
-	@Override
-	public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
-		// convert proposal to json
-		String json = JsonUtils.toJsonString(proposal);
-
+	protected void configureTicketService() {
+		String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName());
+		if (StringUtils.isEmpty(clazz)) {
+			clazz = NullTicketService.class.getName();
+		}
 		try {
-			// make the proposals folder
-			File proposalsFolder = getProposalsFolder();
-			proposalsFolder.mkdirs();
-
-			// cache json to a file
-			File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
-			com.gitblit.utils.FileUtils.writeContent(file, json);
+			Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz);
+			ticketService = injector.get(serviceClass).start();
+			if (ticketService instanceof NullTicketService) {
+				logger.warn("No ticket service configured.");
+			} else if (ticketService.isReady()) {
+				logger.info("{} is ready.", ticketService);
+			} else {
+				logger.warn("{} is disabled.", ticketService);
+			}
 		} catch (Exception e) {
-			logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
+			logger.error("failed to create ticket service " + clazz, e);
+			ticketService = injector.get(NullTicketService.class).start();
 		}
-
-		// send an email, if possible
-		getManager(INotificationManager.class).sendMailToAdministrators("Federation proposal from " + proposal.url,
-				"Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token);
-		return true;
 	}
 
 	/**
-	 * Returns the list of pending federation proposals
+	 * A nested Dagger graph is used for constructor dependency injection of
+	 * complex classes.
 	 *
-	 * @return list of federation proposals
+	 * @author James Moger
+	 *
 	 */
-	@Override
-	public List<FederationProposal> getPendingFederationProposals() {
-		List<FederationProposal> list = new ArrayList<FederationProposal>();
-		File folder = getProposalsFolder();
-		if (folder.exists()) {
-			File[] files = folder.listFiles(new FileFilter() {
-				@Override
-				public boolean accept(File file) {
-					return file.isFile()
-							&& file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
+	@Module(
+			library = true,
+			injects = {
+					IStoredSettings.class,
+
+					// core managers
+					IRuntimeManager.class,
+					IPluginManager.class,
+					INotificationManager.class,
+					IUserManager.class,
+					IAuthenticationManager.class,
+					IRepositoryManager.class,
+					IProjectManager.class,
+					IFederationManager.class,
+
+					// the monolithic manager
+					IGitblit.class,
+
+					// ticket services
+					NullTicketService.class,
+					FileTicketService.class,
+					BranchTicketService.class,
+					RedisTicketService.class
 				}
-			});
-			for (File file : files) {
-				String json = com.gitblit.utils.FileUtils.readContent(file, null);
-				FederationProposal proposal = JsonUtils.fromJsonString(json,
-						FederationProposal.class);
-				list.add(proposal);
-			}
-		}
-		return list;
-	}
+			)
+	class GitBlitModule {
 
-	/**
-	 * Get repositories for the specified token.
-	 *
-	 * @param gitblitUrl
-	 *            the base url of this gitblit instance
-	 * @param token
-	 *            the federation token
-	 * @return a map of <cloneurl, RepositoryModel>
-	 */
-	@Override
-	public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
-		Map<String, String> federationSets = new HashMap<String, String>();
-		for (String set : settings.getStrings(Keys.federation.sets)) {
-			federationSets.put(getFederationToken(set), set);
+		@Provides @Singleton IStoredSettings provideSettings() {
+			return settings;
 		}
 
-		// Determine the Gitblit clone url
-		StringBuilder sb = new StringBuilder();
-		sb.append(gitblitUrl);
-		sb.append(Constants.GIT_PATH);
-		sb.append("{0}");
-		String cloneUrl = sb.toString();
-
-		// Retrieve all available repositories
-		UserModel user = getFederationUser();
-		List<RepositoryModel> list = getManager(IRepositoryManager.class).getRepositoryModels(user);
-
-		// create the [cloneurl, repositoryModel] map
-		Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
-		for (RepositoryModel model : list) {
-			// by default, setup the url for THIS repository
-			String url = MessageFormat.format(cloneUrl, model.name);
-			switch (model.federationStrategy) {
-			case EXCLUDE:
-				// skip this repository
-				continue;
-			case FEDERATE_ORIGIN:
-				// federate the origin, if it is defined
-				if (!StringUtils.isEmpty(model.origin)) {
-					url = model.origin;
-				}
-				break;
-			default:
-				break;
-			}
-
-			if (federationSets.containsKey(token)) {
-				// include repositories only for federation set
-				String set = federationSets.get(token);
-				if (model.federationSets.contains(set)) {
-					repositories.put(url, model);
-				}
-			} else {
-				// standard federation token for ALL
-				repositories.put(url, model);
-			}
-		}
-		return repositories;
-	}
-
-	/**
-	 * Creates a proposal from the token.
-	 *
-	 * @param gitblitUrl
-	 *            the url of this Gitblit instance
-	 * @param token
-	 * @return a potential proposal
-	 */
-	@Override
-	public FederationProposal createFederationProposal(String gitblitUrl, String token) {
-		FederationToken tokenType = FederationToken.REPOSITORIES;
-		for (FederationToken type : FederationToken.values()) {
-			if (token.equals(getFederationToken(type))) {
-				tokenType = type;
-				break;
-			}
-		}
-		Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
-		FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
-				repositories);
-		return proposal;
-	}
-
-	/**
-	 * Returns the proposal identified by the supplied token.
-	 *
-	 * @param token
-	 * @return the specified proposal or null
-	 */
-	@Override
-	public FederationProposal getPendingFederationProposal(String token) {
-		List<FederationProposal> list = getPendingFederationProposals();
-		for (FederationProposal proposal : list) {
-			if (proposal.token.equals(token)) {
-				return proposal;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Deletes a pending federation proposal.
-	 *
-	 * @param a
-	 *            proposal
-	 * @return true if the proposal was deleted
-	 */
-	@Override
-	public boolean deletePendingFederationProposal(FederationProposal proposal) {
-		File folder = getProposalsFolder();
-		File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
-		return file.delete();
-	}
-
-	/**
-	 * Parse the properties file and aggregate all the comments by the setting
-	 * key. A setting model tracks the current value, the default value, the
-	 * description of the setting and and directives about the setting.
-	 *
-	 * @return Map<String, SettingModel>
-	 */
-	private ServerSettings loadSettingModels(ServerSettings settingsModel) {
-		// this entire "supports" concept will go away with user service refactoring
-		UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT);
-		externalUser.password = Constants.EXTERNAL_ACCOUNT;
-		IUserManager userManager = getManager(IUserManager.class);
-		settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser);
-		settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser);
-		settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser);
-		settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser);
-		try {
-			// Read bundled Gitblit properties to extract setting descriptions.
-			// This copy is pristine and only used for populating the setting
-			// models map.
-			InputStream is = getClass().getResourceAsStream("/reference.properties");
-			BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
-			StringBuilder description = new StringBuilder();
-			SettingModel setting = new SettingModel();
-			String line = null;
-			while ((line = propertiesReader.readLine()) != null) {
-				if (line.length() == 0) {
-					description.setLength(0);
-					setting = new SettingModel();
-				} else {
-					if (line.charAt(0) == '#') {
-						if (line.length() > 1) {
-							String text = line.substring(1).trim();
-							if (SettingModel.CASE_SENSITIVE.equals(text)) {
-								setting.caseSensitive = true;
-							} else if (SettingModel.RESTART_REQUIRED.equals(text)) {
-								setting.restartRequired = true;
-							} else if (SettingModel.SPACE_DELIMITED.equals(text)) {
-								setting.spaceDelimited = true;
-							} else if (text.startsWith(SettingModel.SINCE)) {
-								try {
-									setting.since = text.split(" ")[1];
-								} catch (Exception e) {
-									setting.since = text;
-								}
-							} else {
-								description.append(text);
-								description.append('\n');
-							}
-						}
-					} else {
-						String[] kvp = line.split("=", 2);
-						String key = kvp[0].trim();
-						setting.name = key;
-						setting.defaultValue = kvp[1].trim();
-						setting.currentValue = setting.defaultValue;
-						setting.description = description.toString().trim();
-						settingsModel.add(setting);
-						description.setLength(0);
-						setting = new SettingModel();
-					}
-				}
-			}
-			propertiesReader.close();
-		} catch (NullPointerException e) {
-			logger.error("Failed to find resource copy of gitblit.properties");
-		} catch (IOException e) {
-			logger.error("Failed to load resource copy of gitblit.properties");
-		}
-		return settingsModel;
-	}
-
-	protected void configureFanout() {
-		// startup Fanout PubSub service
-		if (settings.getInteger(Keys.fanout.port, 0) > 0) {
-			String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
-			int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
-			boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
-			int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
-
-			if (useNio) {
-				if (StringUtils.isEmpty(bindInterface)) {
-					fanoutService = new FanoutNioService(port);
-				} else {
-					fanoutService = new FanoutNioService(bindInterface, port);
-				}
-			} else {
-				if (StringUtils.isEmpty(bindInterface)) {
-					fanoutService = new FanoutSocketService(port);
-				} else {
-					fanoutService = new FanoutSocketService(bindInterface, port);
-				}
-			}
-
-			fanoutService.setConcurrentConnectionLimit(limit);
-			fanoutService.setAllowAllChannelAnnouncements(false);
-			fanoutService.start();
-		}
-	}
-
-	protected void configureGitDaemon() {
-		int port = settings.getInteger(Keys.git.daemonPort, 0);
-		String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
-		if (port > 0) {
-			try {
-				// HACK temporary pending manager separation and injection
-				Gitblit gitblit = new Gitblit(
-						getManager(IRuntimeManager.class),
-						getManager(INotificationManager.class),
-						getManager(IUserManager.class),
-						getManager(ISessionManager.class),
-						getManager(IRepositoryManager.class),
-						this,
-						this,
-						this);
-				gitDaemon = new GitDaemon(gitblit);
-				gitDaemon.start();
-			} catch (IOException e) {
-				gitDaemon = null;
-				logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e);
-			}
-		}
-	}
-
-	protected final Logger getLogger() {
-		return logger;
-	}
-
-	protected final ScheduledExecutorService getScheduledExecutor() {
-		return scheduledExecutor;
-	}
-
-	/**
-	 * Configure Gitblit from the web.xml, if no configuration has already been
-	 * specified.
-	 *
-	 * @see ServletContextListener.contextInitialize(ServletContextEvent)
-	 */
-	@Override
-	protected void beforeServletInjection(ServletContext context) {
-		ObjectGraph injector = getInjector(context);
-
-		// create the runtime settings object
-		IStoredSettings runtimeSettings = injector.get(IStoredSettings.class);
-		this.settings = runtimeSettings; // XXX remove me eventually
-		final File baseFolder;
-
-		if (goSettings != null) {
-			// Gitblit GO
-			logger.debug("configuring Gitblit GO");
-			baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings);
-		} else {
-			// servlet container
-			WebXmlSettings webxmlSettings = new WebXmlSettings(context);
-			String contextRealPath = context.getRealPath("/");
-			File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null;
-
-			if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) {
-				// RedHat OpenShift
-				logger.debug("configuring Gitblit Express");
-				baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings);
-			} else {
-				// standard WAR
-				logger.debug("configuring Gitblit WAR");
-				baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings);
-			}
-
-			// Test for Tomcat forward-slash/%2F issue and auto-adjust settings
-			ContainerUtils.CVE_2007_0450.test(runtimeSettings);
+		@Provides @Singleton IRuntimeManager provideRuntimeManager() {
+			return runtimeManager;
 		}
 
-		// Runtime manager is a container for settings and other parameters
-		IRuntimeManager runtime = startManager(injector, IRuntimeManager.class);
-		runtime.setBaseFolder(baseFolder);
-		runtime.getStatus().isGO = goSettings != null;
-		runtime.getStatus().servletContainer = context.getServerInfo();
-
-		startManager(injector, INotificationManager.class);
-		startManager(injector, IUserManager.class);
-		startManager(injector, ISessionManager.class);
-		startManager(injector, IRepositoryManager.class);
-
-		logger.info("Gitblit base folder     = " + baseFolder.getAbsolutePath());
-
-		loadSettingModels(runtime.getSettingsModel());
-
-		// load and cache the project metadata
-		projectConfigs = new FileBasedConfig(runtime.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
-		getProjectConfigs();
-
-		if (true/*startFederation*/) {
-			configureFederation();
-		}
-		configureFanout();
-		configureGitDaemon();
-	}
-
-	/**
-	 * Configures Gitblit GO
-	 *
-	 * @param context
-	 * @param settings
-	 * @param baseFolder
-	 * @param runtimeSettings
-	 * @return the base folder
-	 */
-	protected File configureGO(
-			ServletContext context,
-			IStoredSettings goSettings,
-			File goBaseFolder,
-			IStoredSettings runtimeSettings) {
-
-		// merge the stored settings into the runtime settings
-		//
-		// if runtimeSettings is also a FileSettings w/o a specified target file,
-		// the target file for runtimeSettings is set to "localSettings".
-		runtimeSettings.merge(goSettings);
-		File base = goBaseFolder;
-		return base;
-	}
-
-
-	/**
-	 * Configures a standard WAR instance of Gitblit.
-	 *
-	 * @param context
-	 * @param webxmlSettings
-	 * @param contextFolder
-	 * @param runtimeSettings
-	 * @return the base folder
-	 */
-	protected File configureWAR(
-			ServletContext context,
-			WebXmlSettings webxmlSettings,
-			File contextFolder,
-			IStoredSettings runtimeSettings) {
-
-		// Gitblit is running in a standard servlet container
-		logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
-
-		String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
-
-		if (path.contains(Constants.contextFolder$) && contextFolder == null) {
-			// warn about null contextFolder (issue-199)
-			logger.error("");
-			logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
-					Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
-			logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
-			logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
-			logger.error("");
+		@Provides @Singleton IPluginManager providePluginManager() {
+			return pluginManager;
 		}
 
-		try {
-			// try to lookup JNDI env-entry for the baseFolder
-			InitialContext ic = new InitialContext();
-			Context env = (Context) ic.lookup("java:comp/env");
-			String val = (String) env.lookup("baseFolder");
-			if (!StringUtils.isEmpty(val)) {
-				path = val;
-			}
-		} catch (NamingException n) {
-			logger.error("Failed to get JNDI env-entry: " + n.getExplanation());
+		@Provides @Singleton INotificationManager provideNotificationManager() {
+			return notificationManager;
 		}
 
-		File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
-		base.mkdirs();
-
-		// try to extract the data folder resource to the baseFolder
-		File localSettings = new File(base, "gitblit.properties");
-		if (!localSettings.exists()) {
-			extractResources(context, "/WEB-INF/data/", base);
+		@Provides @Singleton IUserManager provideUserManager() {
+			return userManager;
 		}
 
-		// delegate all config to baseFolder/gitblit.properties file
-		FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
-
-		// merge the stored settings into the runtime settings
-		//
-		// if runtimeSettings is also a FileSettings w/o a specified target file,
-		// the target file for runtimeSettings is set to "localSettings".
-		runtimeSettings.merge(fileSettings);
-
-		return base;
-	}
-
-	/**
-	 * Configures an OpenShift instance of Gitblit.
-	 *
-	 * @param context
-	 * @param webxmlSettings
-	 * @param contextFolder
-	 * @param runtimeSettings
-	 * @return the base folder
-	 */
-	private File configureExpress(
-			ServletContext context,
-			WebXmlSettings webxmlSettings,
-			File contextFolder,
-			IStoredSettings runtimeSettings) {
-
-		// Gitblit is running in OpenShift/JBoss
-		String openShift = System.getenv("OPENSHIFT_DATA_DIR");
-		File base = new File(openShift);
-		logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
-
-		// Copy the included scripts to the configured groovy folder
-		String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
-		File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
-		if (!localScripts.exists()) {
-			File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
-			if (!warScripts.equals(localScripts)) {
-				try {
-					com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
-				} catch (IOException e) {
-					logger.error(MessageFormat.format(
-							"Failed to copy included Groovy scripts from {0} to {1}",
-							warScripts, localScripts));
-				}
-			}
+		@Provides @Singleton IAuthenticationManager provideAuthenticationManager() {
+			return authenticationManager;
 		}
 
-		// merge the WebXmlSettings into the runtime settings (for backwards-compatibilty)
-		runtimeSettings.merge(webxmlSettings);
-
-		// settings are to be stored in openshift/gitblit.properties
-		File localSettings = new File(base, "gitblit.properties");
-		FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
-
-		// merge the stored settings into the runtime settings
-		//
-		// if runtimeSettings is also a FileSettings w/o a specified target file,
-		// the target file for runtimeSettings is set to "localSettings".
-		runtimeSettings.merge(fileSettings);
-
-		return base;
-	}
-
-	protected void extractResources(ServletContext context, String path, File toDir) {
-		for (String resource : context.getResourcePaths(path)) {
-			// extract the resource to the directory if it does not exist
-			File f = new File(toDir, resource.substring(path.length()));
-			if (!f.exists()) {
-				InputStream is = null;
-				OutputStream os = null;
-				try {
-					if (resource.charAt(resource.length() - 1) == '/') {
-						// directory
-						f.mkdirs();
-						extractResources(context, resource, f);
-					} else {
-						// file
-						f.getParentFile().mkdirs();
-						is = context.getResourceAsStream(resource);
-						os = new FileOutputStream(f);
-						byte [] buffer = new byte[4096];
-						int len = 0;
-						while ((len = is.read(buffer)) > -1) {
-							os.write(buffer, 0, len);
-						}
-					}
-				} catch (FileNotFoundException e) {
-					logger.error("Failed to find resource \"" + resource + "\"", e);
-				} catch (IOException e) {
-					logger.error("Failed to copy resource \"" + resource + "\" to " + f, e);
-				} finally {
-					if (is != null) {
-						try {
-							is.close();
-						} catch (IOException e) {
-							// ignore
-						}
-					}
-					if (os != null) {
-						try {
-							os.close();
-						} catch (IOException e) {
-							// ignore
-						}
-					}
-				}
-			}
-		}
-	}
-
-	/**
-	 * Gitblit is being shutdown either because the servlet container is
-	 * shutting down or because the servlet container is re-deploying Gitblit.
-	 */
-	@Override
-	protected void destroyContext(ServletContext context) {
-		logger.info("Gitblit context destroyed by servlet container.");
-		for (IManager manager : managers) {
-			logger.debug("stopping {}", manager.getClass().getSimpleName());
-			manager.stop();
+		@Provides @Singleton IRepositoryManager provideRepositoryManager() {
+			return repositoryManager;
 		}
 
-		scheduledExecutor.shutdownNow();
-		if (fanoutService != null) {
-			fanoutService.stop();
-		}
-		if (gitDaemon != null) {
-			gitDaemon.stop();
-		}
-	}
-
-	/**
-	 * Creates a personal fork of the specified repository. The clone is view
-	 * restricted by default and the owner of the source repository is given
-	 * access to the clone.
-	 *
-	 * @param repository
-	 * @param user
-	 * @return the repository model of the fork, if successful
-	 * @throws GitBlitException
-	 */
-	@Override
-	public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
-		String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
-		String fromUrl = MessageFormat.format("file://{0}/{1}", getManager(IRepositoryManager.class).getRepositoriesFolder().getAbsolutePath(), repository.name);
-
-		// clone the repository
-		try {
-			JGitUtils.cloneRepository(getManager(IRepositoryManager.class).getRepositoriesFolder(), cloneName, fromUrl, true, null);
-		} catch (Exception e) {
-			throw new GitBlitException(e);
+		@Provides @Singleton IProjectManager provideProjectManager() {
+			return projectManager;
 		}
 
-		// create a Gitblit repository model for the clone
-		RepositoryModel cloneModel = repository.cloneAs(cloneName);
-		// owner has REWIND/RW+ permissions
-		cloneModel.addOwner(user.username);
-		getManager(IRepositoryManager.class).updateRepositoryModel(cloneName, cloneModel, false);
-
-		// add the owner of the source repository to the clone's access list
-		if (!ArrayUtils.isEmpty(repository.owners)) {
-			for (String owner : repository.owners) {
-				UserModel originOwner = getManager(IUserManager.class).getUserModel(owner);
-				if (originOwner != null) {
-					originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-					updateUserModel(originOwner.username, originOwner, false);
-				}
-			}
+		@Provides @Singleton IFederationManager provideFederationManager() {
+			return federationManager;
 		}
 
-		// grant origin's user list clone permission to fork
-		List<String> users = getManager(IRepositoryManager.class).getRepositoryUsers(repository);
-		List<UserModel> cloneUsers = new ArrayList<UserModel>();
-		for (String name : users) {
-			if (!name.equalsIgnoreCase(user.username)) {
-				UserModel cloneUser = getManager(IUserManager.class).getUserModel(name);
-				if (cloneUser.canClone(repository)) {
-					// origin user can clone origin, grant clone access to fork
-					cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-				}
-				cloneUsers.add(cloneUser);
-			}
+		@Provides @Singleton IGitblit provideGitblit() {
+			return GitBlit.this;
 		}
-		getManager(IUserManager.class).updateUserModels(cloneUsers);
 
-		// grant origin's team list clone permission to fork
-		List<String> teams = getManager(IRepositoryManager.class).getRepositoryTeams(repository);
-		List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
-		for (String name : teams) {
-			TeamModel cloneTeam = getManager(IUserManager.class).getTeamModel(name);
-			if (cloneTeam.canClone(repository)) {
-				// origin team can clone origin, grant clone access to fork
-				cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-			}
-			cloneTeams.add(cloneTeam);
+		@Provides @Singleton NullTicketService provideNullTicketService() {
+			return new NullTicketService(
+					runtimeManager,
+					pluginManager,
+					notificationManager,
+					userManager,
+					repositoryManager);
 		}
-		getManager(IUserManager.class).updateTeamModels(cloneTeams);
 
-		// add this clone to the cached model
-		getManager(IRepositoryManager.class).addToCachedRepositoryList(cloneModel);
-		return cloneModel;
-	}
+		@Provides @Singleton FileTicketService provideFileTicketService() {
+			return new FileTicketService(
+					runtimeManager,
+					pluginManager,
+					notificationManager,
+					userManager,
+					repositoryManager);
+		}
 
-	@Override
-	protected Object [] getModules() {
-		return new Object [] { new DaggerModule(this) };
-	}
+		@Provides @Singleton BranchTicketService provideBranchTicketService() {
+			return new BranchTicketService(
+					runtimeManager,
+					pluginManager,
+					notificationManager,
+					userManager,
+					repositoryManager);
+		}
 
-	protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) {
-		logger.debug("injecting and starting {}", clazz.getSimpleName());
-		X x = injector.get(clazz);
-		x.setup();
-		managers.add(x);
-		return x;
-	}
-
-	/**
-	 * Instantiate and inject all filters and servlets into the container using
-	 * the servlet 3 specification.
-	 */
-	@Override
-	protected void injectServlets(ServletContext context) {
-		// access restricted servlets
-		serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class);
-		serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class);
-		serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class);
-		serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class);
-		serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class);
-
-		// servlets
-		serve(context, Constants.FEDERATION_PATH, FederationServlet.class);
-		serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class);
-		serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class);
-		file(context, "/robots.txt", RobotsTxtServlet.class);
-		file(context, "/logo.png", LogoServlet.class);
-
-		// optional force basic authentication
-		filter(context, "/*", EnforceAuthenticationFilter.class, null);
-
-		// Wicket
-		String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ",");
-		Map<String, String> params = new HashMap<String, String>();
-		params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*");
-		params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore);
-		filter(context, "/*", GitblitWicketFilter.class, params);
+		@Provides @Singleton RedisTicketService provideRedisTicketService() {
+			return new RedisTicketService(
+					runtimeManager,
+					pluginManager,
+					notificationManager,
+					userManager,
+					repositoryManager);
+		}
 	}
 }

--
Gitblit v1.9.1