From 3a9e76b63f09a32e0b6812e18ffff00fab8e58e6 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 29 Nov 2013 11:05:51 -0500
Subject: [PATCH] Flattened IGitblitManager, GitblitManager, and GitBlit classes

---
 src/main/java/com/gitblit/GitBlit.java |  415 +++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 371 insertions(+), 44 deletions(-)

diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index d4e89b0..d736c57 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -15,7 +15,15 @@
  */
 package com.gitblit;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
@@ -26,17 +34,22 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.FederationRequest;
 import com.gitblit.Constants.FederationToken;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
-import com.gitblit.manager.IGitblitManager;
+import com.gitblit.manager.IGitblit;
 import com.gitblit.manager.INotificationManager;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.ServicesManager;
 import com.gitblit.models.FederationModel;
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.FederationSet;
@@ -50,8 +63,19 @@
 import com.gitblit.models.SearchResult;
 import com.gitblit.models.ServerSettings;
 import com.gitblit.models.ServerStatus;
+import com.gitblit.models.SettingModel;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.JsonUtils;
+import com.gitblit.utils.ObjectCache;
+import com.gitblit.utils.StringUtils;
+import com.google.gson.Gson;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
 
 /**
  * GitBlit is an aggregate interface delegate.  It implements all the manager
@@ -62,14 +86,13 @@
  * @author James Moger
  *
  */
-public class GitBlit implements IRuntimeManager,
-								INotificationManager,
-								IUserManager,
-								IAuthenticationManager,
-								IRepositoryManager,
-								IProjectManager,
-								IGitblitManager,
-								IFederationManager {
+public class GitBlit implements IGitblit {
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
+
+	private final IStoredSettings settings;
 
 	private final IRuntimeManager runtimeManager;
 
@@ -83,9 +106,9 @@
 
 	private final IProjectManager projectManager;
 
-	private final IGitblitManager gitblitManager;
-
 	private final IFederationManager federationManager;
+
+	private final ServicesManager servicesManager;
 
 	public GitBlit(
 			IRuntimeManager runtimeManager,
@@ -94,27 +117,362 @@
 			IAuthenticationManager authenticationManager,
 			IRepositoryManager repositoryManager,
 			IProjectManager projectManager,
-			IGitblitManager gitblitManager,
 			IFederationManager federationManager) {
 
+		this.settings = runtimeManager.getSettings();
 		this.runtimeManager = runtimeManager;
 		this.notificationManager = notificationManager;
 		this.userManager = userManager;
 		this.authenticationManager = authenticationManager;
 		this.repositoryManager = repositoryManager;
 		this.projectManager = projectManager;
-		this.gitblitManager = gitblitManager;
 		this.federationManager = federationManager;
+
+		this.servicesManager = new ServicesManager(this);
 	}
 
 	@Override
 	public GitBlit start() {
+		loadSettingModels(runtimeManager.getSettingsModel());
+		logger.info("Starting services manager...");
+		servicesManager.start();
 		return this;
 	}
 
 	@Override
 	public GitBlit stop() {
+		servicesManager.stop();
 		return this;
+	}
+
+	/*
+	 * IGITBLIT
+	 */
+
+	/**
+	 * 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}", repositoryManager.getRepositoriesFolder().getAbsolutePath(), repository.name);
+
+		// clone the repository
+		try {
+			JGitUtils.cloneRepository(repositoryManager.getRepositoriesFolder(), cloneName, fromUrl, true, null);
+		} catch (Exception e) {
+			throw new GitBlitException(e);
+		}
+
+		// create a Gitblit repository model for the clone
+		RepositoryModel cloneModel = repository.cloneAs(cloneName);
+		// owner has REWIND/RW+ permissions
+		cloneModel.addOwner(user.username);
+		repositoryManager.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 = userManager.getUserModel(owner);
+				if (originOwner != null) {
+					originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+					updateUserModel(originOwner.username, originOwner, false);
+				}
+			}
+		}
+
+		// grant origin's user list clone permission to fork
+		List<String> users = repositoryManager.getRepositoryUsers(repository);
+		List<UserModel> cloneUsers = new ArrayList<UserModel>();
+		for (String name : users) {
+			if (!name.equalsIgnoreCase(user.username)) {
+				UserModel cloneUser = userManager.getUserModel(name);
+				if (cloneUser.canClone(repository)) {
+					// origin user can clone origin, grant clone access to fork
+					cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+				}
+				cloneUsers.add(cloneUser);
+			}
+		}
+		userManager.updateUserModels(cloneUsers);
+
+		// grant origin's team list clone permission to fork
+		List<String> teams = repositoryManager.getRepositoryTeams(repository);
+		List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
+		for (String name : teams) {
+			TeamModel cloneTeam = userManager.getTeamModel(name);
+			if (cloneTeam.canClone(repository)) {
+				// origin team can clone origin, grant clone access to fork
+				cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+			}
+			cloneTeams.add(cloneTeam);
+		}
+		userManager.updateTeamModels(cloneTeams);
+
+		// add this clone to the cached model
+		repositoryManager.addToCachedRepositoryList(cloneModel);
+		return cloneModel;
+	}
+
+	/**
+	 * 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 (userManager.getTeamModel(team.name) != null) {
+				throw new GitBlitException(MessageFormat.format(
+						"Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
+						team.name));
+			}
+		}
+		if (!userManager.updateTeamModel(teamname, team)) {
+			throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
+		}
+	}
+
+	/**
+	 * 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 (userManager.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 : repositoryManager.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();
+					repositoryManager.updateRepositoryModel(oldRepositoryName, model, false);
+				} else if (model.isOwner(username)) {
+					// common/shared repo
+					model.addOwner(user.username);
+					repositoryManager.updateRepositoryModel(model.name, model, false);
+				}
+			}
+		}
+		if (!userManager.updateUserModel(username, user)) {
+			throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
+		}
+	}
+
+	/**
+	 * Returns a list of repository URLs and the user access permission.
+	 *
+	 * @param request
+	 * @param user
+	 * @param repository
+	 * @return a list of repository urls
+	 */
+	@Override
+	public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+		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)) {
+				list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), 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)) {
+				list.add(new RepositoryUrl(gitDaemonUrl, permission));
+			}
+		}
+
+		// add all other urls
+		// {0} = repository
+		// {1} = username
+		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)) {
+					list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
+				}
+			} else {
+				// external url does not require username
+				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.R_PATH);
+		sb.append(repository.name);
+
+		// 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();
+	}
+
+
+	/**
+	 * 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(runtimeManager.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;
+	}
+
+	/**
+	 * 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 void loadSettingModels(ServerSettings settingsModel) {
+		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");
+		}
 	}
 
 	/*
@@ -719,36 +1077,5 @@
 	@Override
 	public boolean deletePendingFederationProposal(FederationProposal proposal) {
 		return federationManager.deletePendingFederationProposal(proposal);
-	}
-
-	/*
-	 * GITBLIT MANAGER
-	 */
-
-	@Override
-	public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
-		return gitblitManager.fork(repository, user);
-	}
-
-	@Override
-	public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
-			throws GitBlitException {
-		gitblitManager.updateTeamModel(teamname, team, isCreate);
-	}
-
-	@Override
-	public void updateUserModel(String username, UserModel user, boolean isCreate)
-			throws GitBlitException {
-		gitblitManager.updateUserModel(username, user, isCreate);
-	}
-
-	@Override
-	public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
-		return gitblitManager.getRepositoryUrls(request, user, repository);
-	}
-
-	@Override
-	public Collection<GitClientApplication> getClientApplications() {
-		return gitblitManager.getClientApplications();
 	}
 }

--
Gitblit v1.9.1