James Moger
2011-10-21 b75734f0600c333d70a3659af82be54caf3cfd3e
src/com/gitblit/GitBlit.java
@@ -15,9 +15,12 @@
 */
package com.gitblit;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -27,6 +30,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -35,6 +39,7 @@
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.Cookie;
@@ -59,12 +64,19 @@
import com.gitblit.Constants.FederationToken;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.Metric;
import com.gitblit.models.ObjectCache;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.MetricUtils;
import com.gitblit.utils.StringUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
 * GitBlit is the servlet context listener singleton that acts as the core for
@@ -95,7 +107,13 @@
   private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
   private final ObjectCache<Long> repositorySizeCache = new ObjectCache<Long>();
   private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();
   private RepositoryResolver<Void> repositoryResolver;
   private ServletContext servletContext;
   private File repositoriesFolder;
@@ -104,6 +122,10 @@
   private IUserService userService;
   private IStoredSettings settings;
   private Map<String, SettingModel> settingModels;
   private ServerStatus serverStatus;
   private MailExecutor mailExecutor;
@@ -224,6 +246,13 @@
      return self().settings.getBoolean(Keys.web.debugMode, false);
   }
   public ServerStatus getStatus() {
      // update heap memory status
      serverStatus.heapAllocated = Runtime.getRuntime().totalMemory();
      serverStatus.heapFree = Runtime.getRuntime().freeMemory();
      return serverStatus;
   }
   /**
    * Returns the list of non-Gitblit clone urls. This allows Gitblit to
    * advertise alternative urls for Git client repository access.
@@ -248,6 +277,7 @@
   public void setUserService(IUserService userService) {
      logger.info("Setting up user service " + userService.toString());
      this.userService = userService;
      this.userService.setup(settings);
   }
   /**
@@ -416,6 +446,16 @@
   }
   /**
    * Clears all the cached data for the specified repository.
    *
    * @param repositoryName
    */
   public void clearRepositoryCache(String repositoryName) {
      repositorySizeCache.remove(repositoryName);
      repositoryMetricsCache.remove(repositoryName);
   }
   /**
    * Returns the list of all repositories available to Gitblit. This method
    * does not consider user access permissions.
    * 
@@ -466,6 +506,20 @@
         if (model != null) {
            repositories.add(model);
         }
      }
      if (getBoolean(Keys.web.showRepositorySizes, true)) {
         int repoCount = 0;
         long startTime = System.currentTimeMillis();
         ByteFormat byteFormat = new ByteFormat();
         for (RepositoryModel model : repositories) {
            if (!model.skipSizeCalculation) {
               repoCount++;
               model.size = byteFormat.format(calculateSize(model));
            }
         }
         long duration = System.currentTimeMillis() - startTime;
         logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs",
               repoCount, duration));
      }
      return repositories;
   }
@@ -520,6 +574,7 @@
         model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
         model.isFrozen = getConfig(config, "isFrozen", false);
         model.showReadme = getConfig(config, "showReadme", false);
         model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
         model.federationStrategy = FederationStrategy.fromName(getConfig(config,
               "federationStrategy", null));
         model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
@@ -532,14 +587,22 @@
   }
   /**
    * Returns the size in bytes of the repository.
    * Returns the size in bytes of the repository. Gitblit caches the
    * repository sizes to reduce the performance penalty of recursive
    * calculation. The cache is updated if the repository has been changed
    * since the last calculation.
    * 
    * @param model
    * @return size in bytes
    */
   public long calculateSize(RepositoryModel model) {
      if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
         return repositorySizeCache.getObject(model.name);
      }
      File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED);
      return com.gitblit.utils.FileUtils.folderSize(gitDir);
      long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
      repositorySizeCache.updateObject(model.name, model.lastChange, size);
      return size;
   }
   /**
@@ -578,7 +641,26 @@
   }
   /**
    * Returns the gitblit string vlaue for the specified key. If key is not
    * Returns the metrics for the default branch of the specified repository.
    * This method builds a metrics cache. The cache is updated if the
    * repository is updated. A new copy of the metrics list is returned on each
    * call so that modifications to the list are non-destructive.
    *
    * @param model
    * @param repository
    * @return a new array list of metrics
    */
   public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
      if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
         return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
      }
      List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null);
      repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
      return new ArrayList<Metric>(metrics);
   }
   /**
    * Returns the gitblit string value for the specified key. If key is not
    * set, returns defaultValue.
    * 
    * @param config
@@ -660,6 +742,9 @@
                     "Failed to rename repository permissions ''{0}'' to ''{1}''.",
                     repositoryName, repository.name));
            }
            // clear the cache
            clearRepositoryCache(repositoryName);
         }
         // load repository
@@ -700,6 +785,7 @@
      config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
      config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
      config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
      config.setBoolean("gitblit", null, "skipSizeCalculation", repository.skipSizeCalculation);
      config.setStringList("gitblit", null, "federationSets", repository.federationSets);
      config.setString("gitblit", null, "federationStrategy",
            repository.federationStrategy.name());
@@ -739,6 +825,9 @@
               return true;
            }
         }
         // clear the repository cache
         clearRepositoryCache(repositoryName);
      } catch (Throwable t) {
         logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
      }
@@ -830,8 +919,8 @@
      // Schedule the federation executor
      List<FederationModel> registrations = getFederationRegistrations();
      if (registrations.size() > 0) {
         scheduledExecutor.schedule(new FederationPullExecutor(registrations), 1,
               TimeUnit.MINUTES);
         FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
         scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
      }
   }
@@ -843,79 +932,7 @@
    */
   public List<FederationModel> getFederationRegistrations() {
      if (federationRegistrations.isEmpty()) {
         List<String> keys = settings.getAllKeys(Keys.federation._ROOT);
         keys.remove(Keys.federation.name);
         keys.remove(Keys.federation.passphrase);
         keys.remove(Keys.federation.allowProposals);
         keys.remove(Keys.federation.proposalsFolder);
         keys.remove(Keys.federation.defaultFrequency);
         keys.remove(Keys.federation.sets);
         Collections.sort(keys);
         Map<String, FederationModel> federatedModels = new HashMap<String, FederationModel>();
         for (String key : keys) {
            String value = key.substring(Keys.federation._ROOT.length() + 1);
            List<String> values = StringUtils.getStringsFromValue(value, "\\.");
            String server = values.get(0);
            if (!federatedModels.containsKey(server)) {
               federatedModels.put(server, new FederationModel(server));
            }
            String setting = values.get(1);
            if (setting.equals("url")) {
               // url of the origin Gitblit instance
               federatedModels.get(server).url = settings.getString(key, "");
            } else if (setting.equals("token")) {
               // token for the origin Gitblit instance
               federatedModels.get(server).token = settings.getString(key, "");
            } else if (setting.equals("frequency")) {
               // frequency of the pull operation
               federatedModels.get(server).frequency = settings.getString(key, "");
            } else if (setting.equals("folder")) {
               // destination folder of the pull operation
               federatedModels.get(server).folder = settings.getString(key, "");
            } else if (setting.equals("bare")) {
               // whether pulled repositories should be bare
               federatedModels.get(server).bare = settings.getBoolean(key, true);
            } else if (setting.equals("mirror")) {
               // are the repositories to be true mirrors of the origin
               federatedModels.get(server).mirror = settings.getBoolean(key, true);
            } else if (setting.equals("mergeAccounts")) {
               // merge remote accounts into local accounts
               federatedModels.get(server).mergeAccounts = settings.getBoolean(key, false);
            } else if (setting.equals("sendStatus")) {
               // send a status acknowledgment to source Gitblit instance
               // at end of git pull
               federatedModels.get(server).sendStatus = settings.getBoolean(key, false);
            } else if (setting.equals("notifyOnError")) {
               // notify administrators on federation pull failures
               federatedModels.get(server).notifyOnError = settings.getBoolean(key, false);
            } else if (setting.equals("exclude")) {
               // excluded repositories
               federatedModels.get(server).exclusions = settings.getStrings(key);
            } else if (setting.equals("include")) {
               // included repositories
               federatedModels.get(server).inclusions = settings.getStrings(key);
            }
         }
         // verify that registrations have a url and a token
         for (FederationModel model : federatedModels.values()) {
            if (StringUtils.isEmpty(model.url)) {
               logger.warn(MessageFormat.format(
                     "Dropping federation registration {0}. Missing url.", model.name));
               continue;
            }
            if (StringUtils.isEmpty(model.token)) {
               logger.warn(MessageFormat.format(
                     "Dropping federation registration {0}. Missing token.", model.name));
               continue;
            }
            // set default frequency if unspecified
            if (StringUtils.isEmpty(model.frequency)) {
               model.frequency = settings.getString(Keys.federation.defaultFrequency,
                     "60 mins");
            }
            federationRegistrations.add(model);
         }
         federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
      }
      return federationRegistrations;
   }
@@ -942,6 +959,29 @@
         }
      }
      return null;
   }
   /**
    * Returns the list of federation sets.
    *
    * @return list of federation sets
    */
   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;
   }
   /**
@@ -1042,13 +1082,13 @@
    * @param proposal
    *            the proposal
    * @param gitblitUrl
    *            the url of your gitblit instance
    *            the url of your gitblit instance to send an email to
    *            administrators
    * @return true if the proposal was submitted
    */
   public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
      // convert proposal to json
      Gson gson = new GsonBuilder().setPrettyPrinting().create();
      String json = gson.toJson(proposal);
      String json = JsonUtils.toJsonString(proposal);
      try {
         // make the proposals folder
@@ -1094,10 +1134,10 @@
                     && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
            }
         });
         Gson gson = new Gson();
         for (File file : files) {
            String json = com.gitblit.utils.FileUtils.readContent(file, null);
            FederationProposal proposal = gson.fromJson(json, FederationProposal.class);
            FederationProposal proposal = JsonUtils.fromJsonString(json,
                  FederationProposal.class);
            list.add(proposal);
         }
      }
@@ -1233,18 +1273,99 @@
   }
   /**
    * Returns the descriptions/comments of the Gitblit config settings.
    *
    * @return Map<String, SettingModel>
    */
   public Map<String, SettingModel> getSettingModels() {
      // ensure that the current values are updated in the setting models
      for (String key : settings.getAllKeys(null)) {
         if (settingModels.containsKey(key)) {
            settingModels.get(key).currentValue = settings.getString(key, "");
         }
      }
      return settingModels;
   }
   /**
    * 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 Map<String, SettingModel> loadSettingModels() {
      Map<String, SettingModel> map = new TreeMap<String, SettingModel>();
      try {
         // Read bundled Gitblit properties to extract setting descriptions.
         // This copy is pristine and only used for populating the setting
         // models map.
         InputStream is = servletContext.getResourceAsStream("/WEB-INF/gitblit.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();
                  map.put(key, 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 map;
   }
   /**
    * Configure the Gitblit singleton with the specified settings source. This
    * source may be file settings (Gitblit GO) or may be web.xml settings
    * (Gitblit WAR).
    * 
    * @param settings
    */
   public void configureContext(IStoredSettings settings) {
   public void configureContext(IStoredSettings settings, boolean startFederation) {
      logger.info("Reading configuration from " + settings.toString());
      this.settings = settings;
      repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "git"));
      logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
      repositoryResolver = new FileResolver<Void>(repositoriesFolder, exportAll);
      serverStatus = new ServerStatus();
      String realm = settings.getString(Keys.realm.userService, "users.properties");
      IUserService loginService = null;
      try {
@@ -1268,12 +1389,14 @@
         loginService = new FileUserService(realmFile);
      }
      setUserService(loginService);
      configureFederation();
      mailExecutor = new MailExecutor(settings);
      if (mailExecutor.isReady()) {
         scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
      } else {
         logger.warn("Mail server is not properly configured.  Mail services disabled.");
      }
      if (startFederation) {
         configureFederation();
      }
   }
@@ -1285,10 +1408,12 @@
    */
   @Override
   public void contextInitialized(ServletContextEvent contextEvent) {
      servletContext = contextEvent.getServletContext();
      settingModels = loadSettingModels();
      if (settings == null) {
         // Gitblit WAR is running in a servlet container
         WebXmlSettings webxmlSettings = new WebXmlSettings(contextEvent.getServletContext());
         configureContext(webxmlSettings);
         configureContext(webxmlSettings, true);
      }
   }