James Moger
2013-11-25 3a9e76b63f09a32e0b6812e18ffff00fab8e58e6
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();
   }
}