James Moger
2011-10-21 b75734f0600c333d70a3659af82be54caf3cfd3e
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;
b75734 33 import java.util.TreeMap;
831469 34 import java.util.concurrent.ConcurrentHashMap;
JM 35 import java.util.concurrent.Executors;
36 import java.util.concurrent.ScheduledExecutorService;
37 import java.util.concurrent.TimeUnit;
dd6f08 38 import java.util.concurrent.atomic.AtomicInteger;
fc948c 39
831469 40 import javax.mail.Message;
JM 41 import javax.mail.MessagingException;
b75734 42 import javax.servlet.ServletContext;
87cc1e 43 import javax.servlet.ServletContextEvent;
JM 44 import javax.servlet.ServletContextListener;
85c2e6 45 import javax.servlet.http.Cookie;
fc948c 46
85c2e6 47 import org.apache.wicket.protocol.http.WebResponse;
fc948c 48 import org.eclipse.jgit.errors.RepositoryNotFoundException;
JM 49 import org.eclipse.jgit.lib.Repository;
5c2841 50 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
f97bf0 51 import org.eclipse.jgit.lib.StoredConfig;
f5d0ad 52 import org.eclipse.jgit.transport.resolver.FileResolver;
892570 53 import org.eclipse.jgit.transport.resolver.RepositoryResolver;
JM 54 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
f5d0ad 55 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
5c2841 56 import org.eclipse.jgit.util.FS;
8a2e9c 57 import org.eclipse.jgit.util.FileUtils;
fc948c 58 import org.slf4j.Logger;
JM 59 import org.slf4j.LoggerFactory;
60
dfb889 61 import com.gitblit.Constants.AccessRestrictionType;
831469 62 import com.gitblit.Constants.FederationRequest;
JM 63 import com.gitblit.Constants.FederationStrategy;
64 import com.gitblit.Constants.FederationToken;
65 import com.gitblit.models.FederationModel;
66 import com.gitblit.models.FederationProposal;
31abc2 67 import com.gitblit.models.FederationSet;
4d44cf 68 import com.gitblit.models.Metric;
JM 69 import com.gitblit.models.ObjectCache;
1f9dae 70 import com.gitblit.models.RepositoryModel;
b75734 71 import com.gitblit.models.ServerStatus;
JM 72 import com.gitblit.models.SettingModel;
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
JM 126     private Map<String, SettingModel> settingModels;
127
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 {
85c2e6 443         if (!userService.updateUserModel(username, user)) {
dfb889 444             throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
JM 445         }
446     }
447
892570 448     /**
4d44cf 449      * Clears all the cached data for the specified repository.
JM 450      * 
451      * @param repositoryName
452      */
453     public void clearRepositoryCache(String repositoryName) {
454         repositorySizeCache.remove(repositoryName);
455         repositoryMetricsCache.remove(repositoryName);
456     }
457
458     /**
892570 459      * Returns the list of all repositories available to Gitblit. This method
JM 460      * does not consider user access permissions.
461      * 
462      * @return list of all repositories
463      */
166e6a 464     public List<String> getRepositoryList() {
2a7306 465         return JGitUtils.getRepositoryList(repositoriesFolder, exportAll,
892570 466                 settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true));
166e6a 467     }
155bf7 468
892570 469     /**
JM 470      * Returns the JGit repository for the specified name.
471      * 
472      * @param repositoryName
473      * @return repository or null
474      */
166e6a 475     public Repository getRepository(String repositoryName) {
JM 476         Repository r = null;
477         try {
478             r = repositoryResolver.open(null, repositoryName);
479         } catch (RepositoryNotFoundException e) {
480             r = null;
1f9dae 481             logger.error("GitBlit.getRepository(String) failed to find "
JM 482                     + new File(repositoriesFolder, repositoryName).getAbsolutePath());
892570 483         } catch (ServiceNotAuthorizedException e) {
JM 484             r = null;
485             logger.error("GitBlit.getRepository(String) failed to find "
486                     + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
166e6a 487         } catch (ServiceNotEnabledException e) {
JM 488             r = null;
892570 489             logger.error("GitBlit.getRepository(String) failed to find "
JM 490                     + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e);
166e6a 491         }
JM 492         return r;
493     }
dfb889 494
892570 495     /**
JM 496      * Returns the list of repository models that are accessible to the user.
497      * 
498      * @param user
499      * @return list of repository models accessible to user
500      */
511554 501     public List<RepositoryModel> getRepositoryModels(UserModel user) {
166e6a 502         List<String> list = getRepositoryList();
JM 503         List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();
504         for (String repo : list) {
dfb889 505             RepositoryModel model = getRepositoryModel(user, repo);
JM 506             if (model != null) {
507                 repositories.add(model);
508             }
166e6a 509         }
3d293a 510         if (getBoolean(Keys.web.showRepositorySizes, true)) {
JM 511             int repoCount = 0;
512             long startTime = System.currentTimeMillis();
513             ByteFormat byteFormat = new ByteFormat();
514             for (RepositoryModel model : repositories) {
515                 if (!model.skipSizeCalculation) {
516                     repoCount++;
517                     model.size = byteFormat.format(calculateSize(model));
518                 }
519             }
520             long duration = System.currentTimeMillis() - startTime;
521             logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs",
522                     repoCount, duration));
523         }
166e6a 524         return repositories;
JM 525     }
8a2e9c 526
892570 527     /**
JM 528      * Returns a repository model if the repository exists and the user may
529      * access the repository.
530      * 
531      * @param user
532      * @param repositoryName
533      * @return repository model or null
534      */
511554 535     public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
dfb889 536         RepositoryModel model = getRepositoryModel(repositoryName);
85c2e6 537         if (model == null) {
JM 538             return null;
539         }
dfb889 540         if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
d0d438 541             if (user != null && user.canAccessRepository(model.name)) {
dfb889 542                 return model;
JM 543             }
544             return null;
545         } else {
546             return model;
547         }
548     }
549
892570 550     /**
JM 551      * Returns the repository model for the specified repository. This method
552      * does not consider user access permissions.
553      * 
554      * @param repositoryName
555      * @return repository model or null
556      */
166e6a 557     public RepositoryModel getRepositoryModel(String repositoryName) {
JM 558         Repository r = getRepository(repositoryName);
1f9dae 559         if (r == null) {
JM 560             return null;
561         }
166e6a 562         RepositoryModel model = new RepositoryModel();
JM 563         model.name = repositoryName;
bc9d4a 564         model.hasCommits = JGitUtils.hasCommits(r);
ed21d2 565         model.lastChange = JGitUtils.getLastChange(r, null);
166e6a 566         StoredConfig config = JGitUtils.readConfig(r);
JM 567         if (config != null) {
00afd7 568             model.description = getConfig(config, "description", "");
JM 569             model.owner = getConfig(config, "owner", "");
570             model.useTickets = getConfig(config, "useTickets", false);
571             model.useDocs = getConfig(config, "useDocs", false);
2a7306 572             model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
JM 573                     "accessRestriction", null));
00afd7 574             model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
JM 575             model.isFrozen = getConfig(config, "isFrozen", false);
a1ea87 576             model.showReadme = getConfig(config, "showReadme", false);
3d293a 577             model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
831469 578             model.federationStrategy = FederationStrategy.fromName(getConfig(config,
JM 579                     "federationStrategy", null));
8f73a7 580             model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
JM 581                     "gitblit", null, "federationSets")));
831469 582             model.isFederated = getConfig(config, "isFederated", false);
JM 583             model.origin = config.getString("remote", "origin", "url");
166e6a 584         }
JM 585         r.close();
586         return model;
00afd7 587     }
8a2e9c 588
892570 589     /**
4d44cf 590      * Returns the size in bytes of the repository. Gitblit caches the
JM 591      * repository sizes to reduce the performance penalty of recursive
592      * calculation. The cache is updated if the repository has been changed
593      * since the last calculation.
5c2841 594      * 
JM 595      * @param model
596      * @return size in bytes
597      */
598     public long calculateSize(RepositoryModel model) {
4d44cf 599         if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
JM 600             return repositorySizeCache.getObject(model.name);
601         }
5c2841 602         File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED);
4d44cf 603         long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
JM 604         repositorySizeCache.updateObject(model.name, model.lastChange, size);
605         return size;
5c2841 606     }
JM 607
608     /**
dd6f08 609      * Ensure that a cached repository is completely closed and its resources
JM 610      * are properly released.
611      * 
612      * @param repositoryName
613      */
614     private void closeRepository(String repositoryName) {
615         Repository repository = getRepository(repositoryName);
616         // assume 2 uses in case reflection fails
617         int uses = 2;
618         try {
619             // The FileResolver caches repositories which is very useful
620             // for performance until you want to delete a repository.
621             // I have to use reflection to call close() the correct
622             // number of times to ensure that the object and ref databases
623             // are properly closed before I can delete the repository from
624             // the filesystem.
625             Field useCnt = Repository.class.getDeclaredField("useCnt");
626             useCnt.setAccessible(true);
627             uses = ((AtomicInteger) useCnt.get(repository)).get();
628         } catch (Exception e) {
629             logger.warn(MessageFormat
630                     .format("Failed to reflectively determine use count for repository {0}",
631                             repositoryName), e);
632         }
633         if (uses > 0) {
634             logger.info(MessageFormat
635                     .format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases",
636                             repositoryName, uses, uses));
637             for (int i = 0; i < uses; i++) {
638                 repository.close();
639             }
640         }
641     }
642
643     /**
4d44cf 644      * Returns the metrics for the default branch of the specified repository.
JM 645      * This method builds a metrics cache. The cache is updated if the
646      * repository is updated. A new copy of the metrics list is returned on each
647      * call so that modifications to the list are non-destructive.
648      * 
649      * @param model
650      * @param repository
651      * @return a new array list of metrics
652      */
653     public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
654         if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
655             return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
656         }
657         List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null);
658         repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
659         return new ArrayList<Metric>(metrics);
660     }
661
662     /**
663      * Returns the gitblit string value for the specified key. If key is not
892570 664      * set, returns defaultValue.
JM 665      * 
666      * @param config
667      * @param field
668      * @param defaultValue
669      * @return field value or defaultValue
670      */
00afd7 671     private String getConfig(StoredConfig config, String field, String defaultValue) {
JM 672         String value = config.getString("gitblit", null, field);
673         if (StringUtils.isEmpty(value)) {
674             return defaultValue;
675         }
676         return value;
677     }
8a2e9c 678
892570 679     /**
JM 680      * Returns the gitblit boolean vlaue for the specified key. If key is not
681      * set, returns defaultValue.
682      * 
683      * @param config
684      * @param field
685      * @param defaultValue
686      * @return field value or defaultValue
687      */
00afd7 688     private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
JM 689         return config.getBoolean("gitblit", field, defaultValue);
166e6a 690     }
JM 691
892570 692     /**
JM 693      * Creates/updates the repository model keyed by reopsitoryName. Saves all
694      * repository settings in .git/config. This method allows for renaming
695      * repositories and will update user access permissions accordingly.
696      * 
697      * All repositories created by this method are bare and automatically have
698      * .git appended to their names, which is the standard convention for bare
699      * repositories.
700      * 
701      * @param repositoryName
702      * @param repository
703      * @param isCreate
704      * @throws GitBlitException
705      */
706     public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
2a7306 707             boolean isCreate) throws GitBlitException {
f5d0ad 708         Repository r = null;
JM 709         if (isCreate) {
a3bde6 710             // ensure created repository name ends with .git
168566 711             if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
a3bde6 712                 repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
JM 713             }
166e6a 714             if (new File(repositoriesFolder, repository.name).exists()) {
2a7306 715                 throw new GitBlitException(MessageFormat.format(
JM 716                         "Can not create repository ''{0}'' because it already exists.",
717                         repository.name));
166e6a 718             }
dfb889 719             // create repository
f5d0ad 720             logger.info("create repository " + repository.name);
168566 721             r = JGitUtils.createRepository(repositoriesFolder, repository.name);
f5d0ad 722         } else {
8a2e9c 723             // rename repository
JM 724             if (!repositoryName.equalsIgnoreCase(repository.name)) {
dd6f08 725                 closeRepository(repositoryName);
8a2e9c 726                 File folder = new File(repositoriesFolder, repositoryName);
JM 727                 File destFolder = new File(repositoriesFolder, repository.name);
728                 if (destFolder.exists()) {
2a7306 729                     throw new GitBlitException(
JM 730                             MessageFormat
731                                     .format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.",
732                                             repositoryName, repository.name));
8a2e9c 733                 }
JM 734                 if (!folder.renameTo(destFolder)) {
2a7306 735                     throw new GitBlitException(MessageFormat.format(
JM 736                             "Failed to rename repository ''{0}'' to ''{1}''.", repositoryName,
737                             repository.name));
8a2e9c 738                 }
JM 739                 // rename the roles
85c2e6 740                 if (!userService.renameRepositoryRole(repositoryName, repository.name)) {
2a7306 741                     throw new GitBlitException(MessageFormat.format(
JM 742                             "Failed to rename repository permissions ''{0}'' to ''{1}''.",
743                             repositoryName, repository.name));
8a2e9c 744                 }
4d44cf 745
JM 746                 // clear the cache
747                 clearRepositoryCache(repositoryName);
8a2e9c 748             }
JM 749
f5d0ad 750             // load repository
JM 751             logger.info("edit repository " + repository.name);
752             try {
753                 r = repositoryResolver.open(null, repository.name);
754             } catch (RepositoryNotFoundException e) {
755                 logger.error("Repository not found", e);
892570 756             } catch (ServiceNotAuthorizedException e) {
JM 757                 logger.error("Service not authorized", e);
f5d0ad 758             } catch (ServiceNotEnabledException e) {
JM 759                 logger.error("Service not enabled", e);
760             }
f97bf0 761         }
JM 762
f5d0ad 763         // update settings
2a7306 764         if (r != null) {
831469 765             updateConfiguration(r, repository);
2a7306 766             r.close();
831469 767         }
JM 768     }
769
770     /**
771      * Updates the Gitblit configuration for the specified repository.
772      * 
773      * @param r
774      *            the Git repository
775      * @param repository
776      *            the Gitblit repository model
777      */
778     public void updateConfiguration(Repository r, RepositoryModel repository) {
779         StoredConfig config = JGitUtils.readConfig(r);
780         config.setString("gitblit", null, "description", repository.description);
781         config.setString("gitblit", null, "owner", repository.owner);
782         config.setBoolean("gitblit", null, "useTickets", repository.useTickets);
783         config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
784         config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.name());
785         config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
786         config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
787         config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
3d293a 788         config.setBoolean("gitblit", null, "skipSizeCalculation", repository.skipSizeCalculation);
8f73a7 789         config.setStringList("gitblit", null, "federationSets", repository.federationSets);
831469 790         config.setString("gitblit", null, "federationStrategy",
JM 791                 repository.federationStrategy.name());
792         config.setBoolean("gitblit", null, "isFederated", repository.isFederated);
793         try {
794             config.save();
795         } catch (IOException e) {
796             logger.error("Failed to save repository config!", e);
f97bf0 797         }
f5d0ad 798     }
JM 799
892570 800     /**
JM 801      * Deletes the repository from the file system and removes the repository
802      * permission from all repository users.
803      * 
804      * @param model
805      * @return true if successful
806      */
8a2e9c 807     public boolean deleteRepositoryModel(RepositoryModel model) {
JM 808         return deleteRepository(model.name);
809     }
810
892570 811     /**
JM 812      * Deletes the repository from the file system and removes the repository
813      * permission from all repository users.
814      * 
815      * @param repositoryName
816      * @return true if successful
817      */
8a2e9c 818     public boolean deleteRepository(String repositoryName) {
JM 819         try {
dd6f08 820             closeRepository(repositoryName);
8a2e9c 821             File folder = new File(repositoriesFolder, repositoryName);
JM 822             if (folder.exists() && folder.isDirectory()) {
892570 823                 FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
85c2e6 824                 if (userService.deleteRepositoryRole(repositoryName)) {
8a2e9c 825                     return true;
JM 826                 }
827             }
4d44cf 828
JM 829             // clear the repository cache
830             clearRepositoryCache(repositoryName);
8a2e9c 831         } catch (Throwable t) {
JM 832             logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
833         }
834         return false;
835     }
836
892570 837     /**
JM 838      * Returns an html version of the commit message with any global or
839      * repository-specific regular expression substitution applied.
840      * 
841      * @param repositoryName
842      * @param text
843      * @return html version of the commit message
844      */
8c9a20 845     public String processCommitMessage(String repositoryName, String text) {
JM 846         String html = StringUtils.breakLinesForHtml(text);
847         Map<String, String> map = new HashMap<String, String>();
848         // global regex keys
892570 849         if (settings.getBoolean(Keys.regex.global, false)) {
JM 850             for (String key : settings.getAllKeys(Keys.regex.global)) {
8c9a20 851                 if (!key.equals(Keys.regex.global)) {
JM 852                     String subKey = key.substring(key.lastIndexOf('.') + 1);
892570 853                     map.put(subKey, settings.getString(key, ""));
8c9a20 854                 }
JM 855             }
856         }
857
858         // repository-specific regex keys
892570 859         List<String> keys = settings.getAllKeys(Keys.regex._ROOT + "."
8c9a20 860                 + repositoryName.toLowerCase());
JM 861         for (String key : keys) {
862             String subKey = key.substring(key.lastIndexOf('.') + 1);
892570 863             map.put(subKey, settings.getString(key, ""));
8c9a20 864         }
JM 865
866         for (Entry<String, String> entry : map.entrySet()) {
867             String definition = entry.getValue().trim();
868             String[] chunks = definition.split("!!!");
869             if (chunks.length == 2) {
870                 html = html.replaceAll(chunks[0], chunks[1]);
871             } else {
872                 logger.warn(entry.getKey()
873                         + " improperly formatted.  Use !!! to separate match from replacement: "
874                         + definition);
875             }
876         }
877         return html;
878     }
879
892570 880     /**
831469 881      * Returns Gitblit's scheduled executor service for scheduling tasks.
JM 882      * 
883      * @return scheduledExecutor
884      */
885     public ScheduledExecutorService executor() {
886         return scheduledExecutor;
887     }
888
889     public static boolean canFederate() {
2c32fd 890         String passphrase = getString(Keys.federation.passphrase, "");
JM 891         return !StringUtils.isEmpty(passphrase);
831469 892     }
JM 893
894     /**
895      * Configures this Gitblit instance to pull any registered federated gitblit
896      * instances.
897      */
898     private void configureFederation() {
2c32fd 899         boolean validPassphrase = true;
JM 900         String passphrase = settings.getString(Keys.federation.passphrase, "");
901         if (StringUtils.isEmpty(passphrase)) {
902             logger.warn("Federation passphrase is blank! This server can not be PULLED from.");
903             validPassphrase = false;
831469 904         }
2c32fd 905         if (validPassphrase) {
8f73a7 906             // standard tokens
831469 907             for (FederationToken tokenType : FederationToken.values()) {
JM 908                 logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
909                         getFederationToken(tokenType)));
8f73a7 910             }
JM 911
912             // federation set tokens
913             for (String set : settings.getStrings(Keys.federation.sets)) {
914                 logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
915                         getFederationToken(set)));
831469 916             }
JM 917         }
918
919         // Schedule the federation executor
920         List<FederationModel> registrations = getFederationRegistrations();
921         if (registrations.size() > 0) {
f6740d 922             FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
JM 923             scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
831469 924         }
JM 925     }
926
927     /**
928      * Returns the list of federated gitblit instances that this instance will
929      * try to pull.
930      * 
931      * @return list of registered gitblit instances
932      */
933     public List<FederationModel> getFederationRegistrations() {
934         if (federationRegistrations.isEmpty()) {
f6740d 935             federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
831469 936         }
JM 937         return federationRegistrations;
938     }
939
940     /**
941      * Retrieve the specified federation registration.
942      * 
943      * @param name
944      *            the name of the registration
945      * @return a federation registration
946      */
947     public FederationModel getFederationRegistration(String url, String name) {
948         // check registrations
949         for (FederationModel r : getFederationRegistrations()) {
950             if (r.name.equals(name) && r.url.equals(url)) {
951                 return r;
952             }
953         }
954
955         // check the results
956         for (FederationModel r : getFederationResultRegistrations()) {
957             if (r.name.equals(name) && r.url.equals(url)) {
958                 return r;
959             }
960         }
961         return null;
962     }
963
964     /**
31abc2 965      * Returns the list of federation sets.
JM 966      * 
967      * @return list of federation sets
968      */
969     public List<FederationSet> getFederationSets(String gitblitUrl) {
970         List<FederationSet> list = new ArrayList<FederationSet>();
971         // generate standard tokens
972         for (FederationToken type : FederationToken.values()) {
973             FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
974             fset.repositories = getRepositories(gitblitUrl, fset.token);
975             list.add(fset);
976         }
977         // generate tokens for federation sets
978         for (String set : settings.getStrings(Keys.federation.sets)) {
979             FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
980                     getFederationToken(set));
981             fset.repositories = getRepositories(gitblitUrl, fset.token);
982             list.add(fset);
983         }
984         return list;
985     }
986
987     /**
831469 988      * Returns the list of possible federation tokens for this Gitblit instance.
JM 989      * 
990      * @return list of federation tokens
991      */
992     public List<String> getFederationTokens() {
993         List<String> tokens = new ArrayList<String>();
8f73a7 994         // generate standard tokens
831469 995         for (FederationToken type : FederationToken.values()) {
JM 996             tokens.add(getFederationToken(type));
8f73a7 997         }
JM 998         // generate tokens for federation sets
999         for (String set : settings.getStrings(Keys.federation.sets)) {
1000             tokens.add(getFederationToken(set));
831469 1001         }
JM 1002         return tokens;
1003     }
1004
1005     /**
1006      * Returns the specified federation token for this Gitblit instance.
1007      * 
1008      * @param type
1009      * @return a federation token
1010      */
1011     public String getFederationToken(FederationToken type) {
8f73a7 1012         return getFederationToken(type.name());
JM 1013     }
1014
1015     /**
1016      * Returns the specified federation token for this Gitblit instance.
1017      * 
1018      * @param value
1019      * @return a federation token
1020      */
1021     public String getFederationToken(String value) {
2c32fd 1022         String passphrase = settings.getString(Keys.federation.passphrase, "");
8f73a7 1023         return StringUtils.getSHA1(passphrase + "-" + value);
831469 1024     }
JM 1025
1026     /**
1027      * Compares the provided token with this Gitblit instance's tokens and
1028      * determines if the requested permission may be granted to the token.
1029      * 
1030      * @param req
1031      * @param token
1032      * @return true if the request can be executed
1033      */
1034     public boolean validateFederationRequest(FederationRequest req, String token) {
1035         String all = getFederationToken(FederationToken.ALL);
1036         String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
1037         String jur = getFederationToken(FederationToken.REPOSITORIES);
1038         switch (req) {
1039         case PULL_REPOSITORIES:
1040             return token.equals(all) || token.equals(unr) || token.equals(jur);
1041         case PULL_USERS:
1042             return token.equals(all) || token.equals(unr);
1043         case PULL_SETTINGS:
1044             return token.equals(all);
1045         }
1046         return false;
1047     }
1048
1049     /**
1050      * Acknowledge and cache the status of a remote Gitblit instance.
1051      * 
1052      * @param identification
1053      *            the identification of the pulling Gitblit instance
1054      * @param registration
1055      *            the registration from the pulling Gitblit instance
1056      * @return true if acknowledged
1057      */
1058     public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
1059         // reset the url to the identification of the pulling Gitblit instance
1060         registration.url = identification;
1061         String id = identification;
1062         if (!StringUtils.isEmpty(registration.folder)) {
1063             id += "-" + registration.folder;
1064         }
1065         federationPullResults.put(id, registration);
1066         return true;
1067     }
1068
1069     /**
1070      * Returns the list of registration results.
1071      * 
1072      * @return the list of registration results
1073      */
1074     public List<FederationModel> getFederationResultRegistrations() {
1075         return new ArrayList<FederationModel>(federationPullResults.values());
1076     }
1077
1078     /**
1079      * Submit a federation proposal. The proposal is cached locally and the
1080      * Gitblit administrator(s) are notified via email.
1081      * 
1082      * @param proposal
1083      *            the proposal
1084      * @param gitblitUrl
4aafd4 1085      *            the url of your gitblit instance to send an email to
JM 1086      *            administrators
831469 1087      * @return true if the proposal was submitted
JM 1088      */
1089     public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
1090         // convert proposal to json
93f0b1 1091         String json = JsonUtils.toJsonString(proposal);
831469 1092
JM 1093         try {
1094             // make the proposals folder
1095             File proposalsFolder = new File(getString(Keys.federation.proposalsFolder, "proposals")
1096                     .trim());
1097             proposalsFolder.mkdirs();
1098
1099             // cache json to a file
1100             File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
1101             com.gitblit.utils.FileUtils.writeContent(file, json);
1102         } catch (Exception e) {
1103             logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
1104         }
1105
1106         // send an email, if possible
1107         try {
1108             Message message = mailExecutor.createMessageForAdministrators();
1109             if (message != null) {
1110                 message.setSubject("Federation proposal from " + proposal.url);
1111                 message.setText("Please review the proposal @ " + gitblitUrl + "/proposal/"
1112                         + proposal.token);
1113                 mailExecutor.queue(message);
1114             }
1115         } catch (Throwable t) {
1116             logger.error("Failed to notify administrators of proposal", t);
1117         }
1118         return true;
1119     }
1120
1121     /**
1122      * Returns the list of pending federation proposals
1123      * 
1124      * @return list of federation proposals
1125      */
1126     public List<FederationProposal> getPendingFederationProposals() {
1127         List<FederationProposal> list = new ArrayList<FederationProposal>();
1128         File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
1129         if (folder.exists()) {
1130             File[] files = folder.listFiles(new FileFilter() {
1131                 @Override
1132                 public boolean accept(File file) {
1133                     return file.isFile()
1134                             && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
1135                 }
1136             });
1137             for (File file : files) {
1138                 String json = com.gitblit.utils.FileUtils.readContent(file, null);
31abc2 1139                 FederationProposal proposal = JsonUtils.fromJsonString(json,
JM 1140                         FederationProposal.class);
831469 1141                 list.add(proposal);
JM 1142             }
1143         }
1144         return list;
1145     }
1146
1147     /**
dd9ae7 1148      * Get repositories for the specified token.
JM 1149      * 
1150      * @param gitblitUrl
1151      *            the base url of this gitblit instance
1152      * @param token
1153      *            the federation token
1154      * @return a map of <cloneurl, RepositoryModel>
1155      */
1156     public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
1157         Map<String, String> federationSets = new HashMap<String, String>();
1158         for (String set : getStrings(Keys.federation.sets)) {
1159             federationSets.put(getFederationToken(set), set);
1160         }
1161
1162         // Determine the Gitblit clone url
1163         StringBuilder sb = new StringBuilder();
1164         sb.append(gitblitUrl);
1165         sb.append(Constants.GIT_PATH);
1166         sb.append("{0}");
1167         String cloneUrl = sb.toString();
1168
1169         // Retrieve all available repositories
1170         UserModel user = new UserModel(Constants.FEDERATION_USER);
1171         user.canAdmin = true;
1172         List<RepositoryModel> list = getRepositoryModels(user);
1173
1174         // create the [cloneurl, repositoryModel] map
1175         Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
1176         for (RepositoryModel model : list) {
1177             // by default, setup the url for THIS repository
1178             String url = MessageFormat.format(cloneUrl, model.name);
1179             switch (model.federationStrategy) {
1180             case EXCLUDE:
1181                 // skip this repository
1182                 continue;
1183             case FEDERATE_ORIGIN:
1184                 // federate the origin, if it is defined
1185                 if (!StringUtils.isEmpty(model.origin)) {
1186                     url = model.origin;
1187                 }
1188                 break;
1189             }
1190
1191             if (federationSets.containsKey(token)) {
1192                 // include repositories only for federation set
1193                 String set = federationSets.get(token);
1194                 if (model.federationSets.contains(set)) {
1195                     repositories.put(url, model);
1196                 }
1197             } else {
1198                 // standard federation token for ALL
1199                 repositories.put(url, model);
1200             }
1201         }
1202         return repositories;
1203     }
1204
1205     /**
1206      * Creates a proposal from the token.
1207      * 
1208      * @param gitblitUrl
1209      *            the url of this Gitblit instance
1210      * @param token
1211      * @return a potential proposal
1212      */
1213     public FederationProposal createFederationProposal(String gitblitUrl, String token) {
1214         FederationToken tokenType = FederationToken.REPOSITORIES;
1215         for (FederationToken type : FederationToken.values()) {
1216             if (token.equals(getFederationToken(type))) {
1217                 tokenType = type;
1218                 break;
1219             }
1220         }
1221         Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
1222         FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
1223                 repositories);
1224         return proposal;
1225     }
1226
1227     /**
831469 1228      * Returns the proposal identified by the supplied token.
JM 1229      * 
1230      * @param token
1231      * @return the specified proposal or null
1232      */
1233     public FederationProposal getPendingFederationProposal(String token) {
1234         List<FederationProposal> list = getPendingFederationProposals();
1235         for (FederationProposal proposal : list) {
1236             if (proposal.token.equals(token)) {
1237                 return proposal;
1238             }
1239         }
1240         return null;
1241     }
1242
1243     /**
1244      * Deletes a pending federation proposal.
1245      * 
1246      * @param a
1247      *            proposal
1248      * @return true if the proposal was deleted
1249      */
1250     public boolean deletePendingFederationProposal(FederationProposal proposal) {
1251         File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
1252         File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
1253         return file.delete();
1254     }
1255
1256     /**
1257      * Notify the administrators by email.
1258      * 
1259      * @param subject
1260      * @param message
1261      */
1262     public void notifyAdministrators(String subject, String message) {
1263         try {
1264             Message mail = mailExecutor.createMessageForAdministrators();
1265             if (mail != null) {
1266                 mail.setSubject(subject);
1267                 mail.setText(message);
1268                 mailExecutor.queue(mail);
1269             }
1270         } catch (MessagingException e) {
1271             logger.error("Messaging error", e);
1272         }
1273     }
1274
1275     /**
b75734 1276      * Returns the descriptions/comments of the Gitblit config settings.
JM 1277      * 
1278      * @return Map<String, SettingModel>
1279      */
1280     public Map<String, SettingModel> getSettingModels() {
1281         // ensure that the current values are updated in the setting models
1282         for (String key : settings.getAllKeys(null)) {
1283             if (settingModels.containsKey(key)) {
1284                 settingModels.get(key).currentValue = settings.getString(key, "");
1285             }
1286         }
1287         return settingModels;
1288     }
1289
1290     /**
1291      * Parse the properties file and aggregate all the comments by the setting
1292      * key. A setting model tracks the current value, the default value, the
1293      * description of the setting and and directives about the setting.
1294      * 
1295      * @return Map<String, SettingModel>
1296      */
1297     private Map<String, SettingModel> loadSettingModels() {
1298         Map<String, SettingModel> map = new TreeMap<String, SettingModel>();
1299         try {
1300             // Read bundled Gitblit properties to extract setting descriptions.
1301             // This copy is pristine and only used for populating the setting
1302             // models map.
1303             InputStream is = servletContext.getResourceAsStream("/WEB-INF/gitblit.properties");
1304             BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
1305             StringBuilder description = new StringBuilder();
1306             SettingModel setting = new SettingModel();
1307             String line = null;
1308             while ((line = propertiesReader.readLine()) != null) {
1309                 if (line.length() == 0) {
1310                     description.setLength(0);
1311                     setting = new SettingModel();
1312                 } else {
1313                     if (line.charAt(0) == '#') {
1314                         if (line.length() > 1) {
1315                             String text = line.substring(1).trim();
1316                             if (SettingModel.CASE_SENSITIVE.equals(text)) {
1317                                 setting.caseSensitive = true;
1318                             } else if (SettingModel.RESTART_REQUIRED.equals(text)) {
1319                                 setting.restartRequired = true;
1320                             } else if (SettingModel.SPACE_DELIMITED.equals(text)) {
1321                                 setting.spaceDelimited = true;
1322                             } else if (text.startsWith(SettingModel.SINCE)) {
1323                                 try {
1324                                     setting.since = text.split(" ")[1];
1325                                 } catch (Exception e) {
1326                                     setting.since = text;
1327                                 }
1328                             } else {
1329                                 description.append(text);
1330                                 description.append('\n');
1331                             }
1332                         }
1333                     } else {
1334                         String[] kvp = line.split("=", 2);
1335                         String key = kvp[0].trim();
1336                         setting.name = key;
1337                         setting.defaultValue = kvp[1].trim();
1338                         setting.currentValue = setting.defaultValue;
1339                         setting.description = description.toString().trim();
1340                         map.put(key, setting);
1341                         description.setLength(0);
1342                         setting = new SettingModel();
1343                     }
1344                 }
1345             }
1346             propertiesReader.close();
1347         } catch (NullPointerException e) {
1348             logger.error("Failed to find resource copy of gitblit.properties");
1349         } catch (IOException e) {
1350             logger.error("Failed to load resource copy of gitblit.properties");
1351         }
1352         return map;
1353     }
1354
1355     /**
892570 1356      * Configure the Gitblit singleton with the specified settings source. This
JM 1357      * source may be file settings (Gitblit GO) or may be web.xml settings
1358      * (Gitblit WAR).
1359      * 
1360      * @param settings
1361      */
f6740d 1362     public void configureContext(IStoredSettings settings, boolean startFederation) {
1f9dae 1363         logger.info("Reading configuration from " + settings.toString());
892570 1364         this.settings = settings;
5450d0 1365         repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "git"));
JM 1366         logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
166e6a 1367         repositoryResolver = new FileResolver<Void>(repositoriesFolder, exportAll);
b75734 1368         serverStatus = new ServerStatus();
85c2e6 1369         String realm = settings.getString(Keys.realm.userService, "users.properties");
JM 1370         IUserService loginService = null;
5450d0 1371         try {
892570 1372             // check to see if this "file" is a login service class
5450d0 1373             Class<?> realmClass = Class.forName(realm);
85c2e6 1374             if (IUserService.class.isAssignableFrom(realmClass)) {
JM 1375                 loginService = (IUserService) realmClass.newInstance();
5450d0 1376             }
JM 1377         } catch (Throwable t) {
892570 1378             // not a login service class or class could not be instantiated.
JM 1379             // try to use default file login service
5450d0 1380             File realmFile = new File(realm);
JM 1381             if (!realmFile.exists()) {
1382                 try {
1383                     realmFile.createNewFile();
1384                 } catch (IOException x) {
1385                     logger.error(
1386                             MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x);
1387                 }
1388             }
85c2e6 1389             loginService = new FileUserService(realmFile);
5450d0 1390         }
85c2e6 1391         setUserService(loginService);
831469 1392         mailExecutor = new MailExecutor(settings);
JM 1393         if (mailExecutor.isReady()) {
1394             scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
1395         } else {
1396             logger.warn("Mail server is not properly configured.  Mail services disabled.");
f6740d 1397         }
JM 1398         if (startFederation) {
1399             configureFederation();
831469 1400         }
87cc1e 1401     }
JM 1402
892570 1403     /**
JM 1404      * Configure Gitblit from the web.xml, if no configuration has already been
1405      * specified.
1406      * 
1407      * @see ServletContextListener.contextInitialize(ServletContextEvent)
1408      */
87cc1e 1409     @Override
JM 1410     public void contextInitialized(ServletContextEvent contextEvent) {
b75734 1411         servletContext = contextEvent.getServletContext();
JM 1412         settingModels = loadSettingModels();
892570 1413         if (settings == null) {
JM 1414             // Gitblit WAR is running in a servlet container
87cc1e 1415             WebXmlSettings webxmlSettings = new WebXmlSettings(contextEvent.getServletContext());
f6740d 1416             configureContext(webxmlSettings, true);
87cc1e 1417         }
JM 1418     }
1419
892570 1420     /**
JM 1421      * Gitblit is being shutdown either because the servlet container is
1422      * shutting down or because the servlet container is re-deploying Gitblit.
1423      */
87cc1e 1424     @Override
JM 1425     public void contextDestroyed(ServletContextEvent contextEvent) {
f339f5 1426         logger.info("Gitblit context destroyed by servlet container.");
831469 1427         scheduledExecutor.shutdownNow();
87cc1e 1428     }
fc948c 1429 }