James Moger
2011-10-22 16038ce2adf5f06f75230150f75d110a804414c1
commit | author | age
f13c4c 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
fc948c 16 package com.gitblit;
JM 17
b75734 18 import java.io.BufferedReader;
fc948c 19 import java.io.File;
831469 20 import java.io.FileFilter;
f97bf0 21 import java.io.IOException;
b75734 22 import java.io.InputStream;
JM 23 import java.io.InputStreamReader;
dd6f08 24 import java.lang.reflect.Field;
166e6a 25 import java.text.MessageFormat;
fc948c 26 import java.util.ArrayList;
8f73a7 27 import java.util.Arrays;
00afd7 28 import java.util.Collections;
8c9a20 29 import java.util.HashMap;
fc948c 30 import java.util.List;
8c9a20 31 import java.util.Map;
JM 32 import java.util.Map.Entry;
831469 33 import java.util.concurrent.ConcurrentHashMap;
JM 34 import java.util.concurrent.Executors;
35 import java.util.concurrent.ScheduledExecutorService;
36 import java.util.concurrent.TimeUnit;
dd6f08 37 import java.util.concurrent.atomic.AtomicInteger;
fc948c 38
831469 39 import javax.mail.Message;
JM 40 import javax.mail.MessagingException;
b75734 41 import javax.servlet.ServletContext;
87cc1e 42 import javax.servlet.ServletContextEvent;
JM 43 import javax.servlet.ServletContextListener;
85c2e6 44 import javax.servlet.http.Cookie;
fc948c 45
85c2e6 46 import org.apache.wicket.protocol.http.WebResponse;
fc948c 47 import org.eclipse.jgit.errors.RepositoryNotFoundException;
JM 48 import org.eclipse.jgit.lib.Repository;
5c2841 49 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
f97bf0 50 import org.eclipse.jgit.lib.StoredConfig;
f5d0ad 51 import org.eclipse.jgit.transport.resolver.FileResolver;
892570 52 import org.eclipse.jgit.transport.resolver.RepositoryResolver;
JM 53 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
f5d0ad 54 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
5c2841 55 import org.eclipse.jgit.util.FS;
8a2e9c 56 import org.eclipse.jgit.util.FileUtils;
fc948c 57 import org.slf4j.Logger;
JM 58 import org.slf4j.LoggerFactory;
59
dfb889 60 import com.gitblit.Constants.AccessRestrictionType;
831469 61 import com.gitblit.Constants.FederationRequest;
JM 62 import com.gitblit.Constants.FederationStrategy;
63 import com.gitblit.Constants.FederationToken;
64 import com.gitblit.models.FederationModel;
65 import com.gitblit.models.FederationProposal;
31abc2 66 import com.gitblit.models.FederationSet;
4d44cf 67 import com.gitblit.models.Metric;
JM 68 import com.gitblit.models.ObjectCache;
1f9dae 69 import com.gitblit.models.RepositoryModel;
b75734 70 import com.gitblit.models.ServerStatus;
JM 71 import com.gitblit.models.SettingModel;
84c1d5 72 import com.gitblit.models.ServerSettings;
1f9dae 73 import com.gitblit.models.UserModel;
72633d 74 import com.gitblit.utils.ByteFormat;
f6740d 75 import com.gitblit.utils.FederationUtils;
fc948c 76 import com.gitblit.utils.JGitUtils;
93f0b1 77 import com.gitblit.utils.JsonUtils;
4d44cf 78 import com.gitblit.utils.MetricUtils;
00afd7 79 import com.gitblit.utils.StringUtils;
fc948c 80
892570 81 /**
JM 82  * GitBlit is the servlet context listener singleton that acts as the core for
83  * the web ui and the servlets. This class is either directly instantiated by
84  * the GitBlitServer class (Gitblit GO) or is reflectively instantiated from the
85  * definition in the web.xml file (Gitblit WAR).
86  * 
87  * This class is the central logic processor for Gitblit. All settings, user
88  * object, and repository object operations pass through this class.
89  * 
90  * Repository Resolution. There are two pathways for finding repositories. One
91  * pathway, for web ui display and repository authentication & authorization, is
92  * within this class. The other pathway is through the standard GitServlet.
93  * 
94  * @author James Moger
95  * 
96  */
87cc1e 97 public class GitBlit implements ServletContextListener {
fc948c 98
5450d0 99     private static GitBlit gitblit;
fc948c 100
JM 101     private final Logger logger = LoggerFactory.getLogger(GitBlit.class);
102
831469 103     private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
JM 104
105     private final List<FederationModel> federationRegistrations = Collections
106             .synchronizedList(new ArrayList<FederationModel>());
107
108     private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
4d44cf 109
JM 110     private final ObjectCache<Long> repositorySizeCache = new ObjectCache<Long>();
111
112     private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();
831469 113
892570 114     private RepositoryResolver<Void> repositoryResolver;
fc948c 115
b75734 116     private ServletContext servletContext;
JM 117
166e6a 118     private File repositoriesFolder;
fc948c 119
5450d0 120     private boolean exportAll = true;
fc948c 121
85c2e6 122     private IUserService userService;
fc948c 123
892570 124     private IStoredSettings settings;
b75734 125
84c1d5 126     private ServerSettings settingsModel;
b75734 127
JM 128     private ServerStatus serverStatus;
831469 129
JM 130     private MailExecutor mailExecutor;
87cc1e 131
5450d0 132     public GitBlit() {
JM 133         if (gitblit == null) {
892570 134             // set the static singleton reference
5450d0 135             gitblit = this;
JM 136         }
87cc1e 137     }
JM 138
892570 139     /**
JM 140      * Returns the Gitblit singleton.
141      * 
142      * @return gitblit singleton
143      */
2a7306 144     public static GitBlit self() {
5450d0 145         if (gitblit == null) {
892570 146             new GitBlit();
5450d0 147         }
JM 148         return gitblit;
831469 149     }
JM 150
151     /**
152      * Determine if this is the GO variant of Gitblit.
153      * 
154      * @return true if this is the GO variant of Gitblit.
155      */
156     public static boolean isGO() {
157         return self().settings instanceof FileSettings;
2a7306 158     }
JM 159
892570 160     /**
JM 161      * Returns the boolean value for the specified key. If the key does not
162      * exist or the value for the key can not be interpreted as a boolean, the
163      * defaultValue is returned.
164      * 
165      * @see IStoredSettings.getBoolean(String, boolean)
166      * @param key
167      * @param defaultValue
168      * @return key value or defaultValue
169      */
2a7306 170     public static boolean getBoolean(String key, boolean defaultValue) {
892570 171         return self().settings.getBoolean(key, defaultValue);
2a7306 172     }
JM 173
892570 174     /**
JM 175      * Returns the integer value for the specified key. If the key does not
176      * exist or the value for the key can not be interpreted as an integer, the
177      * defaultValue is returned.
178      * 
179      * @see IStoredSettings.getInteger(String key, int defaultValue)
180      * @param key
181      * @param defaultValue
182      * @return key value or defaultValue
183      */
2a7306 184     public static int getInteger(String key, int defaultValue) {
892570 185         return self().settings.getInteger(key, defaultValue);
2a7306 186     }
JM 187
892570 188     /**
7e5ee5 189      * Returns the char value for the specified key. If the key does not exist
JM 190      * or the value for the key can not be interpreted as a character, the
191      * defaultValue is returned.
192      * 
193      * @see IStoredSettings.getChar(String key, char defaultValue)
194      * @param key
195      * @param defaultValue
196      * @return key value or defaultValue
197      */
198     public static char getChar(String key, char defaultValue) {
199         return self().settings.getChar(key, defaultValue);
200     }
dd6f08 201
7e5ee5 202     /**
892570 203      * Returns the string value for the specified key. If the key does not exist
JM 204      * or the value for the key can not be interpreted as a string, the
205      * defaultValue is returned.
206      * 
207      * @see IStoredSettings.getString(String key, String defaultValue)
208      * @param key
209      * @param defaultValue
210      * @return key value or defaultValue
211      */
2a7306 212     public static String getString(String key, String defaultValue) {
892570 213         return self().settings.getString(key, defaultValue);
2a7306 214     }
JM 215
892570 216     /**
JM 217      * Returns a list of space-separated strings from the specified key.
218      * 
219      * @see IStoredSettings.getStrings(String key)
220      * @param name
221      * @return list of strings
222      */
2a7306 223     public static List<String> getStrings(String key) {
892570 224         return self().settings.getStrings(key);
2a7306 225     }
892570 226
JM 227     /**
228      * Returns the list of keys whose name starts with the specified prefix. If
229      * the prefix is null or empty, all key names are returned.
230      * 
231      * @see IStoredSettings.getAllKeys(String key)
232      * @param startingWith
233      * @return list of keys
234      */
2a7306 235
JM 236     public static List<String> getAllKeys(String startingWith) {
892570 237         return self().settings.getAllKeys(startingWith);
fc948c 238     }
155bf7 239
892570 240     /**
JM 241      * Is Gitblit running in debug mode?
242      * 
243      * @return true if Gitblit is running in debug mode
244      */
8c9a20 245     public static boolean isDebugMode() {
892570 246         return self().settings.getBoolean(Keys.web.debugMode, false);
b75734 247     }
JM 248
249     public ServerStatus getStatus() {
250         // update heap memory status
251         serverStatus.heapAllocated = Runtime.getRuntime().totalMemory();
252         serverStatus.heapFree = Runtime.getRuntime().freeMemory();
253         return serverStatus;
87cc1e 254     }
JM 255
892570 256     /**
JM 257      * Returns the list of non-Gitblit clone urls. This allows Gitblit to
258      * advertise alternative urls for Git client repository access.
259      * 
260      * @param repositoryName
261      * @return list of non-gitblit clone urls
262      */
8a2e9c 263     public List<String> getOtherCloneUrls(String repositoryName) {
JM 264         List<String> cloneUrls = new ArrayList<String>();
892570 265         for (String url : settings.getStrings(Keys.web.otherUrls)) {
8a2e9c 266             cloneUrls.add(MessageFormat.format(url, repositoryName));
JM 267         }
268         return cloneUrls;
fc948c 269     }
JM 270
892570 271     /**
JM 272      * Set the user service. The user service authenticates all users and is
273      * responsible for managing user permissions.
274      * 
275      * @param userService
276      */
85c2e6 277     public void setUserService(IUserService userService) {
JM 278         logger.info("Setting up user service " + userService.toString());
279         this.userService = userService;
63ee41 280         this.userService.setup(settings);
fc948c 281     }
JM 282
892570 283     /**
JM 284      * Authenticate a user based on a username and password.
285      * 
286      * @see IUserService.authenticate(String, char[])
287      * @param username
288      * @param password
289      * @return a user object or null
290      */
511554 291     public UserModel authenticate(String username, char[] password) {
831469 292         if (StringUtils.isEmpty(username)) {
JM 293             // can not authenticate empty username
294             return null;
295         }
296         String pw = new String(password);
297         if (StringUtils.isEmpty(pw)) {
298             // can not authenticate empty password
299             return null;
300         }
301
302         // check to see if this is the federation user
303         if (canFederate()) {
304             if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
305                 List<String> tokens = getFederationTokens();
306                 if (tokens.contains(pw)) {
307                     // the federation user is an administrator
308                     UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
309                     federationUser.canAdmin = true;
310                     return federationUser;
311                 }
312             }
313         }
314
315         // delegate authentication to the user service
85c2e6 316         if (userService == null) {
fc948c 317             return null;
JM 318         }
85c2e6 319         return userService.authenticate(username, password);
JM 320     }
321
892570 322     /**
JM 323      * Authenticate a user based on their cookie.
324      * 
325      * @param cookies
326      * @return a user object or null
327      */
85c2e6 328     public UserModel authenticate(Cookie[] cookies) {
JM 329         if (userService == null) {
330             return null;
331         }
332         if (userService.supportsCookies()) {
333             if (cookies != null && cookies.length > 0) {
334                 for (Cookie cookie : cookies) {
335                     if (cookie.getName().equals(Constants.NAME)) {
336                         String value = cookie.getValue();
337                         return userService.authenticate(value.toCharArray());
338                     }
339                 }
340             }
341         }
342         return null;
343     }
344
892570 345     /**
JM 346      * Sets a cookie for the specified user.
347      * 
348      * @param response
349      * @param user
350      */
85c2e6 351     public void setCookie(WebResponse response, UserModel user) {
JM 352         if (userService == null) {
353             return;
354         }
355         if (userService.supportsCookies()) {
356             Cookie userCookie;
357             if (user == null) {
358                 // clear cookie for logout
359                 userCookie = new Cookie(Constants.NAME, "");
360             } else {
361                 // set cookie for login
362                 char[] cookie = userService.getCookie(user);
363                 userCookie = new Cookie(Constants.NAME, new String(cookie));
364                 userCookie.setMaxAge(Integer.MAX_VALUE);
365             }
366             userCookie.setPath("/");
367             response.addCookie(userCookie);
368         }
fc948c 369     }
JM 370
892570 371     /**
JM 372      * Returns the list of all users available to the login service.
373      * 
374      * @see IUserService.getAllUsernames()
375      * @return list of all usernames
376      */
f98825 377     public List<String> getAllUsernames() {
85c2e6 378         List<String> names = new ArrayList<String>(userService.getAllUsernames());
00afd7 379         Collections.sort(names);
JM 380         return names;
8a2e9c 381     }
JM 382
892570 383     /**
JM 384      * Delete the user object with the specified username
385      * 
386      * @see IUserService.deleteUser(String)
387      * @param username
388      * @return true if successful
389      */
8a2e9c 390     public boolean deleteUser(String username) {
85c2e6 391         return userService.deleteUser(username);
f98825 392     }
dfb889 393
892570 394     /**
JM 395      * Retrieve the user object for the specified username.
396      * 
397      * @see IUserService.getUserModel(String)
398      * @param username
399      * @return a user object or null
400      */
f98825 401     public UserModel getUserModel(String username) {
85c2e6 402         UserModel user = userService.getUserModel(username);
dfb889 403         return user;
f98825 404     }
8a2e9c 405
892570 406     /**
JM 407      * Returns the list of all users who are allowed to bypass the access
408      * restriction placed on the specified repository.
409      * 
410      * @see IUserService.getUsernamesForRepositoryRole(String)
411      * @param repository
412      * @return list of all usernames that can bypass the access restriction
413      */
f98825 414     public List<String> getRepositoryUsers(RepositoryModel repository) {
892570 415         return userService.getUsernamesForRepositoryRole(repository.name);
f98825 416     }
8a2e9c 417
892570 418     /**
JM 419      * Sets the list of all uses who are allowed to bypass the access
420      * restriction placed on the specified repository.
421      * 
422      * @see IUserService.setUsernamesForRepositoryRole(String, List<String>)
423      * @param repository
424      * @param usernames
425      * @return true if successful
426      */
f98825 427     public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
892570 428         return userService.setUsernamesForRepositoryRole(repository.name, repositoryUsers);
dfb889 429     }
JM 430
892570 431     /**
JM 432      * Adds/updates a complete user object keyed by username. This method allows
433      * for renaming a user.
434      * 
435      * @see IUserService.updateUserModel(String, UserModel)
436      * @param username
437      * @param user
438      * @param isCreate
439      * @throws GitBlitException
440      */
441     public void updateUserModel(String username, UserModel user, boolean isCreate)
2a7306 442             throws GitBlitException {
16038c 443         if (!username.equalsIgnoreCase(user.username)) {
JM 444             if (userService.getUserModel(user.username) != null) {
445                 throw new GitBlitException(
446                         MessageFormat
447                                 .format("Failed to rename ''{0}'' because ''{1}'' already exists.",
448                                         username, user.username));
449             }
450         }
85c2e6 451         if (!userService.updateUserModel(username, user)) {
dfb889 452             throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
JM 453         }
454     }
455
892570 456     /**
4d44cf 457      * Clears all the cached data for the specified repository.
JM 458      * 
459      * @param repositoryName
460      */
461     public void clearRepositoryCache(String repositoryName) {
462         repositorySizeCache.remove(repositoryName);
463         repositoryMetricsCache.remove(repositoryName);
464     }
465
466     /**
892570 467      * Returns the list of all repositories available to Gitblit. This method
JM 468      * does not consider user access permissions.
469      * 
470      * @return list of all repositories
471      */
166e6a 472     public List<String> getRepositoryList() {
2a7306 473         return JGitUtils.getRepositoryList(repositoriesFolder, exportAll,
892570 474                 settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true));
166e6a 475     }
155bf7 476
892570 477     /**
JM 478      * Returns the JGit repository for the specified name.
479      * 
480      * @param repositoryName
481      * @return repository or null
482      */
166e6a 483     public Repository getRepository(String repositoryName) {
JM 484         Repository r = null;
485         try {
486             r = repositoryResolver.open(null, repositoryName);
487         } catch (RepositoryNotFoundException e) {
488             r = null;
1f9dae 489             logger.error("GitBlit.getRepository(String) failed to find "
JM 490                     + new File(repositoriesFolder, repositoryName).getAbsolutePath());
892570 491         } catch (ServiceNotAuthorizedException e) {
JM 492             r = null;
493             logger.error("GitBlit.getRepository(String) failed to find "
494                     + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
166e6a 495         } catch (ServiceNotEnabledException e) {
JM 496             r = null;
892570 497             logger.error("GitBlit.getRepository(String) failed to find "
JM 498                     + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
166e6a 499         }
JM 500         return r;
501     }
dfb889 502
892570 503     /**
JM 504      * Returns the list of repository models that are accessible to the user.
505      * 
506      * @param user
507      * @return list of repository models accessible to user
508      */
511554 509     public List<RepositoryModel> getRepositoryModels(UserModel user) {
166e6a 510         List<String> list = getRepositoryList();
JM 511         List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();
512         for (String repo : list) {
dfb889 513             RepositoryModel model = getRepositoryModel(user, repo);
JM 514             if (model != null) {
515                 repositories.add(model);
516             }
166e6a 517         }
3d293a 518         if (getBoolean(Keys.web.showRepositorySizes, true)) {
JM 519             int repoCount = 0;
520             long startTime = System.currentTimeMillis();
521             ByteFormat byteFormat = new ByteFormat();
522             for (RepositoryModel model : repositories) {
523                 if (!model.skipSizeCalculation) {
524                     repoCount++;
525                     model.size = byteFormat.format(calculateSize(model));
526                 }
527             }
528             long duration = System.currentTimeMillis() - startTime;
529             logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs",
530                     repoCount, duration));
531         }
166e6a 532         return repositories;
JM 533     }
8a2e9c 534
892570 535     /**
JM 536      * Returns a repository model if the repository exists and the user may
537      * access the repository.
538      * 
539      * @param user
540      * @param repositoryName
541      * @return repository model or null
542      */
511554 543     public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
dfb889 544         RepositoryModel model = getRepositoryModel(repositoryName);
85c2e6 545         if (model == null) {
JM 546             return null;
547         }
dfb889 548         if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
d0d438 549             if (user != null && user.canAccessRepository(model.name)) {
dfb889 550                 return model;
JM 551             }
552             return null;
553         } else {
554             return model;
555         }
556     }
557
892570 558     /**
JM 559      * Returns the repository model for the specified repository. This method
560      * does not consider user access permissions.
561      * 
562      * @param repositoryName
563      * @return repository model or null
564      */
166e6a 565     public RepositoryModel getRepositoryModel(String repositoryName) {
JM 566         Repository r = getRepository(repositoryName);
1f9dae 567         if (r == null) {
JM 568             return null;
569         }
166e6a 570         RepositoryModel model = new RepositoryModel();
JM 571         model.name = repositoryName;
bc9d4a 572         model.hasCommits = JGitUtils.hasCommits(r);
ed21d2 573         model.lastChange = JGitUtils.getLastChange(r, null);
166e6a 574         StoredConfig config = JGitUtils.readConfig(r);
JM 575         if (config != null) {
00afd7 576             model.description = getConfig(config, "description", "");
JM 577             model.owner = getConfig(config, "owner", "");
578             model.useTickets = getConfig(config, "useTickets", false);
579             model.useDocs = getConfig(config, "useDocs", false);
2a7306 580             model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
JM 581                     "accessRestriction", null));
00afd7 582             model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
JM 583             model.isFrozen = getConfig(config, "isFrozen", false);
a1ea87 584             model.showReadme = getConfig(config, "showReadme", false);
3d293a 585             model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
831469 586             model.federationStrategy = FederationStrategy.fromName(getConfig(config,
JM 587                     "federationStrategy", null));
8f73a7 588             model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
JM 589                     "gitblit", null, "federationSets")));
831469 590             model.isFederated = getConfig(config, "isFederated", false);
JM 591             model.origin = config.getString("remote", "origin", "url");
166e6a 592         }
JM 593         r.close();
594         return model;
00afd7 595     }
8a2e9c 596
892570 597     /**
4d44cf 598      * Returns the size in bytes of the repository. Gitblit caches the
JM 599      * repository sizes to reduce the performance penalty of recursive
600      * calculation. The cache is updated if the repository has been changed
601      * since the last calculation.
5c2841 602      * 
JM 603      * @param model
604      * @return size in bytes
605      */
606     public long calculateSize(RepositoryModel model) {
4d44cf 607         if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
JM 608             return repositorySizeCache.getObject(model.name);
609         }
5c2841 610         File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED);
4d44cf 611         long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
JM 612         repositorySizeCache.updateObject(model.name, model.lastChange, size);
613         return size;
5c2841 614     }
JM 615
616     /**
dd6f08 617      * Ensure that a cached repository is completely closed and its resources
JM 618      * are properly released.
619      * 
620      * @param repositoryName
621      */
622     private void closeRepository(String repositoryName) {
623         Repository repository = getRepository(repositoryName);
624         // assume 2 uses in case reflection fails
625         int uses = 2;
626         try {
627             // The FileResolver caches repositories which is very useful
628             // for performance until you want to delete a repository.
629             // I have to use reflection to call close() the correct
630             // number of times to ensure that the object and ref databases
631             // are properly closed before I can delete the repository from
632             // the filesystem.
633             Field useCnt = Repository.class.getDeclaredField("useCnt");
634             useCnt.setAccessible(true);
635             uses = ((AtomicInteger) useCnt.get(repository)).get();
636         } catch (Exception e) {
637             logger.warn(MessageFormat
638                     .format("Failed to reflectively determine use count for repository {0}",
639                             repositoryName), e);
640         }
641         if (uses > 0) {
642             logger.info(MessageFormat
643                     .format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases",
644                             repositoryName, uses, uses));
645             for (int i = 0; i < uses; i++) {
646                 repository.close();
647             }
648         }
649     }
650
651     /**
4d44cf 652      * Returns the metrics for the default branch of the specified repository.
JM 653      * This method builds a metrics cache. The cache is updated if the
654      * repository is updated. A new copy of the metrics list is returned on each
655      * call so that modifications to the list are non-destructive.
656      * 
657      * @param model
658      * @param repository
659      * @return a new array list of metrics
660      */
661     public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
662         if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
663             return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
664         }
665         List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null);
666         repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
667         return new ArrayList<Metric>(metrics);
668     }
669
670     /**
671      * Returns the gitblit string value for the specified key. If key is not
892570 672      * set, returns defaultValue.
JM 673      * 
674      * @param config
675      * @param field
676      * @param defaultValue
677      * @return field value or defaultValue
678      */
00afd7 679     private String getConfig(StoredConfig config, String field, String defaultValue) {
JM 680         String value = config.getString("gitblit", null, field);
681         if (StringUtils.isEmpty(value)) {
682             return defaultValue;
683         }
684         return value;
685     }
8a2e9c 686
892570 687     /**
JM 688      * Returns the gitblit boolean vlaue for the specified key. If key is not
689      * set, returns defaultValue.
690      * 
691      * @param config
692      * @param field
693      * @param defaultValue
694      * @return field value or defaultValue
695      */
00afd7 696     private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
JM 697         return config.getBoolean("gitblit", field, defaultValue);
166e6a 698     }
JM 699
892570 700     /**
JM 701      * Creates/updates the repository model keyed by reopsitoryName. Saves all
702      * repository settings in .git/config. This method allows for renaming
703      * repositories and will update user access permissions accordingly.
704      * 
705      * All repositories created by this method are bare and automatically have
706      * .git appended to their names, which is the standard convention for bare
707      * repositories.
708      * 
709      * @param repositoryName
710      * @param repository
711      * @param isCreate
712      * @throws GitBlitException
713      */
714     public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
2a7306 715             boolean isCreate) throws GitBlitException {
f5d0ad 716         Repository r = null;
JM 717         if (isCreate) {
a3bde6 718             // ensure created repository name ends with .git
168566 719             if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
a3bde6 720                 repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
JM 721             }
166e6a 722             if (new File(repositoriesFolder, repository.name).exists()) {
2a7306 723                 throw new GitBlitException(MessageFormat.format(
JM 724                         "Can not create repository ''{0}'' because it already exists.",
725                         repository.name));
166e6a 726             }
dfb889 727             // create repository
f5d0ad 728             logger.info("create repository " + repository.name);
168566 729             r = JGitUtils.createRepository(repositoriesFolder, repository.name);
f5d0ad 730         } else {
8a2e9c 731             // rename repository
JM 732             if (!repositoryName.equalsIgnoreCase(repository.name)) {
16038c 733                 if (!repository.name.toLowerCase().endsWith(
JM 734                         org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
735                     repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
736                 }
737                 if (new File(repositoriesFolder, repository.name).exists()) {
738                     throw new GitBlitException(
739                             MessageFormat
740                                     .format("Failed to rename ''{0}'' because ''{1}'' already exists.",
741                                             repositoryName, repository.name));
742                 }
dd6f08 743                 closeRepository(repositoryName);
8a2e9c 744                 File folder = new File(repositoriesFolder, repositoryName);
JM 745                 File destFolder = new File(repositoriesFolder, repository.name);
746                 if (destFolder.exists()) {
2a7306 747                     throw new GitBlitException(
JM 748                             MessageFormat
749                                     .format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.",
750                                             repositoryName, repository.name));
8a2e9c 751                 }
JM 752                 if (!folder.renameTo(destFolder)) {
2a7306 753                     throw new GitBlitException(MessageFormat.format(
JM 754                             "Failed to rename repository ''{0}'' to ''{1}''.", repositoryName,
755                             repository.name));
8a2e9c 756                 }
JM 757                 // rename the roles
85c2e6 758                 if (!userService.renameRepositoryRole(repositoryName, repository.name)) {
2a7306 759                     throw new GitBlitException(MessageFormat.format(
JM 760                             "Failed to rename repository permissions ''{0}'' to ''{1}''.",
761                             repositoryName, repository.name));
8a2e9c 762                 }
4d44cf 763
JM 764                 // clear the cache
765                 clearRepositoryCache(repositoryName);
8a2e9c 766             }
JM 767
f5d0ad 768             // load repository
JM 769             logger.info("edit repository " + repository.name);
770             try {
771                 r = repositoryResolver.open(null, repository.name);
772             } catch (RepositoryNotFoundException e) {
773                 logger.error("Repository not found", e);
892570 774             } catch (ServiceNotAuthorizedException e) {
JM 775                 logger.error("Service not authorized", e);
f5d0ad 776             } catch (ServiceNotEnabledException e) {
JM 777                 logger.error("Service not enabled", e);
778             }
f97bf0 779         }
JM 780
f5d0ad 781         // update settings
2a7306 782         if (r != null) {
831469 783             updateConfiguration(r, repository);
2a7306 784             r.close();
831469 785         }
JM 786     }
787
788     /**
789      * Updates the Gitblit configuration for the specified repository.
790      * 
791      * @param r
792      *            the Git repository
793      * @param repository
794      *            the Gitblit repository model
795      */
796     public void updateConfiguration(Repository r, RepositoryModel repository) {
797         StoredConfig config = JGitUtils.readConfig(r);
798         config.setString("gitblit", null, "description", repository.description);
799         config.setString("gitblit", null, "owner", repository.owner);
800         config.setBoolean("gitblit", null, "useTickets", repository.useTickets);
801         config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
802         config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.name());
803         config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
804         config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
805         config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
3d293a 806         config.setBoolean("gitblit", null, "skipSizeCalculation", repository.skipSizeCalculation);
8f73a7 807         config.setStringList("gitblit", null, "federationSets", repository.federationSets);
831469 808         config.setString("gitblit", null, "federationStrategy",
JM 809                 repository.federationStrategy.name());
810         config.setBoolean("gitblit", null, "isFederated", repository.isFederated);
811         try {
812             config.save();
813         } catch (IOException e) {
814             logger.error("Failed to save repository config!", e);
f97bf0 815         }
f5d0ad 816     }
JM 817
892570 818     /**
JM 819      * Deletes the repository from the file system and removes the repository
820      * permission from all repository users.
821      * 
822      * @param model
823      * @return true if successful
824      */
8a2e9c 825     public boolean deleteRepositoryModel(RepositoryModel model) {
JM 826         return deleteRepository(model.name);
827     }
828
892570 829     /**
JM 830      * Deletes the repository from the file system and removes the repository
831      * permission from all repository users.
832      * 
833      * @param repositoryName
834      * @return true if successful
835      */
8a2e9c 836     public boolean deleteRepository(String repositoryName) {
JM 837         try {
dd6f08 838             closeRepository(repositoryName);
8a2e9c 839             File folder = new File(repositoriesFolder, repositoryName);
JM 840             if (folder.exists() && folder.isDirectory()) {
892570 841                 FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
85c2e6 842                 if (userService.deleteRepositoryRole(repositoryName)) {
8a2e9c 843                     return true;
JM 844                 }
845             }
4d44cf 846
JM 847             // clear the repository cache
848             clearRepositoryCache(repositoryName);
8a2e9c 849         } catch (Throwable t) {
JM 850             logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
851         }
852         return false;
853     }
854
892570 855     /**
JM 856      * Returns an html version of the commit message with any global or
857      * repository-specific regular expression substitution applied.
858      * 
859      * @param repositoryName
860      * @param text
861      * @return html version of the commit message
862      */
8c9a20 863     public String processCommitMessage(String repositoryName, String text) {
JM 864         String html = StringUtils.breakLinesForHtml(text);
865         Map<String, String> map = new HashMap<String, String>();
866         // global regex keys
892570 867         if (settings.getBoolean(Keys.regex.global, false)) {
JM 868             for (String key : settings.getAllKeys(Keys.regex.global)) {
8c9a20 869                 if (!key.equals(Keys.regex.global)) {
JM 870                     String subKey = key.substring(key.lastIndexOf('.') + 1);
892570 871                     map.put(subKey, settings.getString(key, ""));
8c9a20 872                 }
JM 873             }
874         }
875
876         // repository-specific regex keys
892570 877         List<String> keys = settings.getAllKeys(Keys.regex._ROOT + "."
8c9a20 878                 + repositoryName.toLowerCase());
JM 879         for (String key : keys) {
880             String subKey = key.substring(key.lastIndexOf('.') + 1);
892570 881             map.put(subKey, settings.getString(key, ""));
8c9a20 882         }
JM 883
884         for (Entry<String, String> entry : map.entrySet()) {
885             String definition = entry.getValue().trim();
886             String[] chunks = definition.split("!!!");
887             if (chunks.length == 2) {
888                 html = html.replaceAll(chunks[0], chunks[1]);
889             } else {
890                 logger.warn(entry.getKey()
891                         + " improperly formatted.  Use !!! to separate match from replacement: "
892                         + definition);
893             }
894         }
895         return html;
896     }
897
892570 898     /**
831469 899      * Returns Gitblit's scheduled executor service for scheduling tasks.
JM 900      * 
901      * @return scheduledExecutor
902      */
903     public ScheduledExecutorService executor() {
904         return scheduledExecutor;
905     }
906
907     public static boolean canFederate() {
2c32fd 908         String passphrase = getString(Keys.federation.passphrase, "");
JM 909         return !StringUtils.isEmpty(passphrase);
831469 910     }
JM 911
912     /**
913      * Configures this Gitblit instance to pull any registered federated gitblit
914      * instances.
915      */
916     private void configureFederation() {
2c32fd 917         boolean validPassphrase = true;
JM 918         String passphrase = settings.getString(Keys.federation.passphrase, "");
919         if (StringUtils.isEmpty(passphrase)) {
920             logger.warn("Federation passphrase is blank! This server can not be PULLED from.");
921             validPassphrase = false;
831469 922         }
2c32fd 923         if (validPassphrase) {
8f73a7 924             // standard tokens
831469 925             for (FederationToken tokenType : FederationToken.values()) {
JM 926                 logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
927                         getFederationToken(tokenType)));
8f73a7 928             }
JM 929
930             // federation set tokens
931             for (String set : settings.getStrings(Keys.federation.sets)) {
932                 logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
933                         getFederationToken(set)));
831469 934             }
JM 935         }
936
937         // Schedule the federation executor
938         List<FederationModel> registrations = getFederationRegistrations();
939         if (registrations.size() > 0) {
f6740d 940             FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
JM 941             scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
831469 942         }
JM 943     }
944
945     /**
946      * Returns the list of federated gitblit instances that this instance will
947      * try to pull.
948      * 
949      * @return list of registered gitblit instances
950      */
951     public List<FederationModel> getFederationRegistrations() {
952         if (federationRegistrations.isEmpty()) {
f6740d 953             federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
831469 954         }
JM 955         return federationRegistrations;
956     }
957
958     /**
959      * Retrieve the specified federation registration.
960      * 
961      * @param name
962      *            the name of the registration
963      * @return a federation registration
964      */
965     public FederationModel getFederationRegistration(String url, String name) {
966         // check registrations
967         for (FederationModel r : getFederationRegistrations()) {
968             if (r.name.equals(name) && r.url.equals(url)) {
969                 return r;
970             }
971         }
972
973         // check the results
974         for (FederationModel r : getFederationResultRegistrations()) {
975             if (r.name.equals(name) && r.url.equals(url)) {
976                 return r;
977             }
978         }
979         return null;
980     }
981
982     /**
31abc2 983      * Returns the list of federation sets.
JM 984      * 
985      * @return list of federation sets
986      */
987     public List<FederationSet> getFederationSets(String gitblitUrl) {
988         List<FederationSet> list = new ArrayList<FederationSet>();
989         // generate standard tokens
990         for (FederationToken type : FederationToken.values()) {
991             FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
992             fset.repositories = getRepositories(gitblitUrl, fset.token);
993             list.add(fset);
994         }
995         // generate tokens for federation sets
996         for (String set : settings.getStrings(Keys.federation.sets)) {
997             FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
998                     getFederationToken(set));
999             fset.repositories = getRepositories(gitblitUrl, fset.token);
1000             list.add(fset);
1001         }
1002         return list;
1003     }
1004
1005     /**
831469 1006      * Returns the list of possible federation tokens for this Gitblit instance.
JM 1007      * 
1008      * @return list of federation tokens
1009      */
1010     public List<String> getFederationTokens() {
1011         List<String> tokens = new ArrayList<String>();
8f73a7 1012         // generate standard tokens
831469 1013         for (FederationToken type : FederationToken.values()) {
JM 1014             tokens.add(getFederationToken(type));
8f73a7 1015         }
JM 1016         // generate tokens for federation sets
1017         for (String set : settings.getStrings(Keys.federation.sets)) {
1018             tokens.add(getFederationToken(set));
831469 1019         }
JM 1020         return tokens;
1021     }
1022
1023     /**
1024      * Returns the specified federation token for this Gitblit instance.
1025      * 
1026      * @param type
1027      * @return a federation token
1028      */
1029     public String getFederationToken(FederationToken type) {
8f73a7 1030         return getFederationToken(type.name());
JM 1031     }
1032
1033     /**
1034      * Returns the specified federation token for this Gitblit instance.
1035      * 
1036      * @param value
1037      * @return a federation token
1038      */
1039     public String getFederationToken(String value) {
2c32fd 1040         String passphrase = settings.getString(Keys.federation.passphrase, "");
8f73a7 1041         return StringUtils.getSHA1(passphrase + "-" + value);
831469 1042     }
JM 1043
1044     /**
1045      * Compares the provided token with this Gitblit instance's tokens and
1046      * determines if the requested permission may be granted to the token.
1047      * 
1048      * @param req
1049      * @param token
1050      * @return true if the request can be executed
1051      */
1052     public boolean validateFederationRequest(FederationRequest req, String token) {
1053         String all = getFederationToken(FederationToken.ALL);
1054         String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
1055         String jur = getFederationToken(FederationToken.REPOSITORIES);
1056         switch (req) {
1057         case PULL_REPOSITORIES:
1058             return token.equals(all) || token.equals(unr) || token.equals(jur);
1059         case PULL_USERS:
1060             return token.equals(all) || token.equals(unr);
1061         case PULL_SETTINGS:
1062             return token.equals(all);
1063         }
1064         return false;
1065     }
1066
1067     /**
1068      * Acknowledge and cache the status of a remote Gitblit instance.
1069      * 
1070      * @param identification
1071      *            the identification of the pulling Gitblit instance
1072      * @param registration
1073      *            the registration from the pulling Gitblit instance
1074      * @return true if acknowledged
1075      */
1076     public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
1077         // reset the url to the identification of the pulling Gitblit instance
1078         registration.url = identification;
1079         String id = identification;
1080         if (!StringUtils.isEmpty(registration.folder)) {
1081             id += "-" + registration.folder;
1082         }
1083         federationPullResults.put(id, registration);
1084         return true;
1085     }
1086
1087     /**
1088      * Returns the list of registration results.
1089      * 
1090      * @return the list of registration results
1091      */
1092     public List<FederationModel> getFederationResultRegistrations() {
1093         return new ArrayList<FederationModel>(federationPullResults.values());
1094     }
1095
1096     /**
1097      * Submit a federation proposal. The proposal is cached locally and the
1098      * Gitblit administrator(s) are notified via email.
1099      * 
1100      * @param proposal
1101      *            the proposal
1102      * @param gitblitUrl
4aafd4 1103      *            the url of your gitblit instance to send an email to
JM 1104      *            administrators
831469 1105      * @return true if the proposal was submitted
JM 1106      */
1107     public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
1108         // convert proposal to json
93f0b1 1109         String json = JsonUtils.toJsonString(proposal);
831469 1110
JM 1111         try {
1112             // make the proposals folder
1113             File proposalsFolder = new File(getString(Keys.federation.proposalsFolder, "proposals")
1114                     .trim());
1115             proposalsFolder.mkdirs();
1116
1117             // cache json to a file
1118             File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
1119             com.gitblit.utils.FileUtils.writeContent(file, json);
1120         } catch (Exception e) {
1121             logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
1122         }
1123
1124         // send an email, if possible
1125         try {
1126             Message message = mailExecutor.createMessageForAdministrators();
1127             if (message != null) {
1128                 message.setSubject("Federation proposal from " + proposal.url);
1129                 message.setText("Please review the proposal @ " + gitblitUrl + "/proposal/"
1130                         + proposal.token);
1131                 mailExecutor.queue(message);
1132             }
1133         } catch (Throwable t) {
1134             logger.error("Failed to notify administrators of proposal", t);
1135         }
1136         return true;
1137     }
1138
1139     /**
1140      * Returns the list of pending federation proposals
1141      * 
1142      * @return list of federation proposals
1143      */
1144     public List<FederationProposal> getPendingFederationProposals() {
1145         List<FederationProposal> list = new ArrayList<FederationProposal>();
1146         File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
1147         if (folder.exists()) {
1148             File[] files = folder.listFiles(new FileFilter() {
1149                 @Override
1150                 public boolean accept(File file) {
1151                     return file.isFile()
1152                             && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
1153                 }
1154             });
1155             for (File file : files) {
1156                 String json = com.gitblit.utils.FileUtils.readContent(file, null);
31abc2 1157                 FederationProposal proposal = JsonUtils.fromJsonString(json,
JM 1158                         FederationProposal.class);
831469 1159                 list.add(proposal);
JM 1160             }
1161         }
1162         return list;
1163     }
1164
1165     /**
dd9ae7 1166      * Get repositories for the specified token.
JM 1167      * 
1168      * @param gitblitUrl
1169      *            the base url of this gitblit instance
1170      * @param token
1171      *            the federation token
1172      * @return a map of <cloneurl, RepositoryModel>
1173      */
1174     public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
1175         Map<String, String> federationSets = new HashMap<String, String>();
1176         for (String set : getStrings(Keys.federation.sets)) {
1177             federationSets.put(getFederationToken(set), set);
1178         }
1179
1180         // Determine the Gitblit clone url
1181         StringBuilder sb = new StringBuilder();
1182         sb.append(gitblitUrl);
1183         sb.append(Constants.GIT_PATH);
1184         sb.append("{0}");
1185         String cloneUrl = sb.toString();
1186
1187         // Retrieve all available repositories
1188         UserModel user = new UserModel(Constants.FEDERATION_USER);
1189         user.canAdmin = true;
1190         List<RepositoryModel> list = getRepositoryModels(user);
1191
1192         // create the [cloneurl, repositoryModel] map
1193         Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
1194         for (RepositoryModel model : list) {
1195             // by default, setup the url for THIS repository
1196             String url = MessageFormat.format(cloneUrl, model.name);
1197             switch (model.federationStrategy) {
1198             case EXCLUDE:
1199                 // skip this repository
1200                 continue;
1201             case FEDERATE_ORIGIN:
1202                 // federate the origin, if it is defined
1203                 if (!StringUtils.isEmpty(model.origin)) {
1204                     url = model.origin;
1205                 }
1206                 break;
1207             }
1208
1209             if (federationSets.containsKey(token)) {
1210                 // include repositories only for federation set
1211                 String set = federationSets.get(token);
1212                 if (model.federationSets.contains(set)) {
1213                     repositories.put(url, model);
1214                 }
1215             } else {
1216                 // standard federation token for ALL
1217                 repositories.put(url, model);
1218             }
1219         }
1220         return repositories;
1221     }
1222
1223     /**
1224      * Creates a proposal from the token.
1225      * 
1226      * @param gitblitUrl
1227      *            the url of this Gitblit instance
1228      * @param token
1229      * @return a potential proposal
1230      */
1231     public FederationProposal createFederationProposal(String gitblitUrl, String token) {
1232         FederationToken tokenType = FederationToken.REPOSITORIES;
1233         for (FederationToken type : FederationToken.values()) {
1234             if (token.equals(getFederationToken(type))) {
1235                 tokenType = type;
1236                 break;
1237             }
1238         }
1239         Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
1240         FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
1241                 repositories);
1242         return proposal;
1243     }
1244
1245     /**
831469 1246      * Returns the proposal identified by the supplied token.
JM 1247      * 
1248      * @param token
1249      * @return the specified proposal or null
1250      */
1251     public FederationProposal getPendingFederationProposal(String token) {
1252         List<FederationProposal> list = getPendingFederationProposals();
1253         for (FederationProposal proposal : list) {
1254             if (proposal.token.equals(token)) {
1255                 return proposal;
1256             }
1257         }
1258         return null;
1259     }
1260
1261     /**
1262      * Deletes a pending federation proposal.
1263      * 
1264      * @param a
1265      *            proposal
1266      * @return true if the proposal was deleted
1267      */
1268     public boolean deletePendingFederationProposal(FederationProposal proposal) {
1269         File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
1270         File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
1271         return file.delete();
1272     }
1273
1274     /**
1275      * Notify the administrators by email.
1276      * 
1277      * @param subject
1278      * @param message
1279      */
1280     public void notifyAdministrators(String subject, String message) {
1281         try {
1282             Message mail = mailExecutor.createMessageForAdministrators();
1283             if (mail != null) {
1284                 mail.setSubject(subject);
1285                 mail.setText(message);
1286                 mailExecutor.queue(mail);
1287             }
1288         } catch (MessagingException e) {
1289             logger.error("Messaging error", e);
1290         }
1291     }
1292
1293     /**
b75734 1294      * Returns the descriptions/comments of the Gitblit config settings.
JM 1295      * 
84c1d5 1296      * @return SettingsModel
b75734 1297      */
84c1d5 1298     public ServerSettings getSettingsModel() {
b75734 1299         // ensure that the current values are updated in the setting models
84c1d5 1300         settingsModel.updateCurrentValues(settings);
JM 1301         return settingsModel;
b75734 1302     }
JM 1303
1304     /**
1305      * Parse the properties file and aggregate all the comments by the setting
1306      * key. A setting model tracks the current value, the default value, the
1307      * description of the setting and and directives about the setting.
1308      * 
1309      * @return Map<String, SettingModel>
1310      */
84c1d5 1311     private ServerSettings loadSettingModels() {
JM 1312         ServerSettings settingsModel = new ServerSettings();
b75734 1313         try {
JM 1314             // Read bundled Gitblit properties to extract setting descriptions.
1315             // This copy is pristine and only used for populating the setting
1316             // models map.
1317             InputStream is = servletContext.getResourceAsStream("/WEB-INF/gitblit.properties");
1318             BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
1319             StringBuilder description = new StringBuilder();
1320             SettingModel setting = new SettingModel();
1321             String line = null;
1322             while ((line = propertiesReader.readLine()) != null) {
1323                 if (line.length() == 0) {
1324                     description.setLength(0);
1325                     setting = new SettingModel();
1326                 } else {
1327                     if (line.charAt(0) == '#') {
1328                         if (line.length() > 1) {
1329                             String text = line.substring(1).trim();
1330                             if (SettingModel.CASE_SENSITIVE.equals(text)) {
1331                                 setting.caseSensitive = true;
1332                             } else if (SettingModel.RESTART_REQUIRED.equals(text)) {
1333                                 setting.restartRequired = true;
1334                             } else if (SettingModel.SPACE_DELIMITED.equals(text)) {
1335                                 setting.spaceDelimited = true;
1336                             } else if (text.startsWith(SettingModel.SINCE)) {
1337                                 try {
1338                                     setting.since = text.split(" ")[1];
1339                                 } catch (Exception e) {
1340                                     setting.since = text;
1341                                 }
1342                             } else {
1343                                 description.append(text);
1344                                 description.append('\n');
1345                             }
1346                         }
1347                     } else {
1348                         String[] kvp = line.split("=", 2);
1349                         String key = kvp[0].trim();
1350                         setting.name = key;
1351                         setting.defaultValue = kvp[1].trim();
1352                         setting.currentValue = setting.defaultValue;
1353                         setting.description = description.toString().trim();
84c1d5 1354                         settingsModel.add(setting);
b75734 1355                         description.setLength(0);
JM 1356                         setting = new SettingModel();
1357                     }
1358                 }
1359             }
1360             propertiesReader.close();
1361         } catch (NullPointerException e) {
1362             logger.error("Failed to find resource copy of gitblit.properties");
1363         } catch (IOException e) {
1364             logger.error("Failed to load resource copy of gitblit.properties");
1365         }
84c1d5 1366         return settingsModel;
b75734 1367     }
JM 1368
1369     /**
892570 1370      * Configure the Gitblit singleton with the specified settings source. This
JM 1371      * source may be file settings (Gitblit GO) or may be web.xml settings
1372      * (Gitblit WAR).
1373      * 
1374      * @param settings
1375      */
f6740d 1376     public void configureContext(IStoredSettings settings, boolean startFederation) {
1f9dae 1377         logger.info("Reading configuration from " + settings.toString());
892570 1378         this.settings = settings;
5450d0 1379         repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "git"));
JM 1380         logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
166e6a 1381         repositoryResolver = new FileResolver<Void>(repositoriesFolder, exportAll);
b75734 1382         serverStatus = new ServerStatus();
85c2e6 1383         String realm = settings.getString(Keys.realm.userService, "users.properties");
JM 1384         IUserService loginService = null;
5450d0 1385         try {
892570 1386             // check to see if this "file" is a login service class
5450d0 1387             Class<?> realmClass = Class.forName(realm);
85c2e6 1388             if (IUserService.class.isAssignableFrom(realmClass)) {
JM 1389                 loginService = (IUserService) realmClass.newInstance();
5450d0 1390             }
JM 1391         } catch (Throwable t) {
892570 1392             // not a login service class or class could not be instantiated.
JM 1393             // try to use default file login service
5450d0 1394             File realmFile = new File(realm);
JM 1395             if (!realmFile.exists()) {
1396                 try {
1397                     realmFile.createNewFile();
1398                 } catch (IOException x) {
1399                     logger.error(
1400                             MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x);
1401                 }
1402             }
85c2e6 1403             loginService = new FileUserService(realmFile);
5450d0 1404         }
85c2e6 1405         setUserService(loginService);
831469 1406         mailExecutor = new MailExecutor(settings);
JM 1407         if (mailExecutor.isReady()) {
1408             scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
1409         } else {
1410             logger.warn("Mail server is not properly configured.  Mail services disabled.");
f6740d 1411         }
JM 1412         if (startFederation) {
1413             configureFederation();
831469 1414         }
87cc1e 1415     }
JM 1416
892570 1417     /**
JM 1418      * Configure Gitblit from the web.xml, if no configuration has already been
1419      * specified.
1420      * 
1421      * @see ServletContextListener.contextInitialize(ServletContextEvent)
1422      */
87cc1e 1423     @Override
JM 1424     public void contextInitialized(ServletContextEvent contextEvent) {
b75734 1425         servletContext = contextEvent.getServletContext();
84c1d5 1426         settingsModel = loadSettingModels();
892570 1427         if (settings == null) {
JM 1428             // Gitblit WAR is running in a servlet container
87cc1e 1429             WebXmlSettings webxmlSettings = new WebXmlSettings(contextEvent.getServletContext());
f6740d 1430             configureContext(webxmlSettings, true);
87cc1e 1431         }
JM 1432     }
1433
892570 1434     /**
JM 1435      * Gitblit is being shutdown either because the servlet container is
1436      * shutting down or because the servlet container is re-deploying Gitblit.
1437      */
87cc1e 1438     @Override
JM 1439     public void contextDestroyed(ServletContextEvent contextEvent) {
f339f5 1440         logger.info("Gitblit context destroyed by servlet container.");
831469 1441         scheduledExecutor.shutdownNow();
87cc1e 1442     }
fc948c 1443 }