James Moger
2013-11-21 269c5043ab8f66f67d5719ac5149a436ca1baa2b
Extract Federation, Gitblit and Services manager from GitBlit singleton

Change-Id: I2b2f361a868c8eedf4b6df5939e7dfac2d5f92a9
4 files added
15 files modified
3431 ■■■■ changed files
src/main/java/com/gitblit/DaggerModule.java 45 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FederationClient.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FederationPullExecutor.java 1019 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlit.java 1106 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Gitblit.java 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/FederationManager.java 454 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/GitblitManager.java 463 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IFederationManager.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IGitblitManager.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IManager.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IServicesManager.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/NotificationManager.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/ProjectManager.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/RepositoryManager.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/RuntimeManager.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/ServicesManager.java 188 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/SessionManager.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/UserManager.java 6 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/DaggerModule.java
@@ -20,18 +20,22 @@
import org.apache.wicket.protocol.http.WebApplication;
import com.gitblit.git.GitServlet;
import com.gitblit.manager.FederationManager;
import com.gitblit.manager.GitblitManager;
import com.gitblit.manager.IFederationManager;
import com.gitblit.manager.IGitblitManager;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.manager.NotificationManager;
import com.gitblit.manager.ProjectManager;
import com.gitblit.manager.RepositoryManager;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.ServicesManager;
import com.gitblit.manager.SessionManager;
import com.gitblit.manager.UserManager;
import com.gitblit.wicket.GitBlitWebApp;
@@ -47,6 +51,7 @@
 *
 */
@Module(
    library = true,
    injects = {
            IStoredSettings.class,
@@ -59,6 +64,7 @@
            IProjectManager.class,
            IGitblitManager.class,
            IFederationManager.class,
            IServicesManager.class,
            // the monolithic manager
            Gitblit.class,
@@ -84,13 +90,6 @@
    }
)
public class DaggerModule {
    final GitBlit gitblit;
    // HACK but necessary for now
    public DaggerModule(GitBlit gitblit) {
        this.gitblit = gitblit;
    }
    @Provides @Singleton IStoredSettings provideSettings() {
        return new FileSettings();
@@ -137,12 +136,28 @@
                repositoryManager);
    }
    @Provides @Singleton IGitblitManager provideGitblitManager() {
        return gitblit;
    @Provides @Singleton IFederationManager provideFederationManager(
            IRuntimeManager runtimeManager,
            INotificationManager notificationManager,
            IUserManager userManager,
            IRepositoryManager repositoryManager) {
        return new FederationManager(
                runtimeManager,
                notificationManager,
                userManager,
                repositoryManager);
    }
    @Provides @Singleton IFederationManager provideFederationManager() {
        return gitblit;
    @Provides @Singleton IGitblitManager provideGitblitManager(
            IRuntimeManager runtimeManager,
            IUserManager userManager,
            IRepositoryManager repositoryManager) {
        return new GitblitManager(
                runtimeManager,
                userManager,
                repositoryManager);
    }
    @Provides @Singleton Gitblit provideGitblit(
@@ -162,8 +177,12 @@
                sessionManager,
                repositoryManager,
                projectManager,
                federationManager,
                gitblitManager);
                gitblitManager,
                federationManager);
    }
    @Provides @Singleton IServicesManager provideServicesManager(Gitblit gitblit) {
        return new ServicesManager(gitblit);
    }
    @Provides @Singleton WebApplication provideWebApplication(
src/main/java/com/gitblit/FederationClient.java
@@ -23,6 +23,11 @@
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.gitblit.manager.FederationManager;
import com.gitblit.manager.NotificationManager;
import com.gitblit.manager.RepositoryManager;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.UserManager;
import com.gitblit.models.FederationModel;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.StringUtils;
@@ -53,7 +58,7 @@
        }
        File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile);
        IStoredSettings settings = new FileSettings(regFile.getAbsolutePath());
        FileSettings settings = new FileSettings(regFile.getAbsolutePath());
        List<FederationModel> registrations = new ArrayList<FederationModel>();
        if (StringUtils.isEmpty(params.url)) {
            registrations.addAll(FederationUtils.getFederationRegistrations(settings));
@@ -83,14 +88,23 @@
        }
        // configure the Gitblit singleton for minimal, non-server operation
        GitBlit gitblit = new GitBlit(settings, baseFolder);
        gitblit.beforeServletInjection(null); // XXX broken
        FederationPullExecutor executor = new FederationPullExecutor(registrations, params.isDaemon);
        executor.run();
        if (!params.isDaemon) {
            System.out.println("Finished.");
            System.exit(0);
        }
        RuntimeManager runtime = new RuntimeManager(settings);
        runtime.setBaseFolder(baseFolder);
        NotificationManager notifications = new NotificationManager(settings).start();
        UserManager users = new UserManager(runtime).start();
        RepositoryManager repositories = new RepositoryManager(runtime, users).start();
        FederationManager federation = new FederationManager(runtime, notifications, users, repositories).start();
        FederationPullExecutor puller = new FederationPullExecutor(federation.getFederationRegistrations()) {
            @Override
            public void reschedule(FederationModel registration) {
                // NOOP
            }
        };
        puller.run();
        System.out.println("Finished.");
        System.exit(0);
    }
    private static void usage(JCommander jc, ParameterException t) {
@@ -115,9 +129,6 @@
        @Parameter(names = { "--registrations" }, description = "Gitblit Federation Registrations File", required = false)
        public String registrationsFile = "${baseFolder}/federation.properties";
        @Parameter(names = { "--daemon" }, description = "Runs in daemon mode to schedule and pull repositories", required = false)
        public boolean isDaemon;
        @Parameter(names = { "--url" }, description = "URL of Gitblit instance to mirror from", required = false)
        public String url;
src/main/java/com/gitblit/FederationPullExecutor.java
@@ -1,533 +1,486 @@
/*
 * Copyright 2011 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit;
import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.FederationPullStatus;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.manager.IGitblitManager;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.models.FederationModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JGitUtils.CloneResult;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
/**
 * FederationPullExecutor pulls repository updates and, optionally, user
 * accounts and server settings from registered Gitblit instances.
 */
public class FederationPullExecutor implements Runnable {
    private final Logger logger = LoggerFactory.getLogger(FederationPullExecutor.class);
    private final List<FederationModel> registrations;
    private final boolean isDaemon;
    /**
     * Constructor for specifying a single federation registration. This
     * constructor is used to schedule the next pull execution.
     *
     * @param registration
     */
    private FederationPullExecutor(FederationModel registration) {
        this(Arrays.asList(registration), true);
    }
    /**
     * Constructor to specify a group of federation registrations. This is
     * normally used at startup to pull and then schedule the next update based
     * on each registrations frequency setting.
     *
     * @param registrations
     * @param isDaemon
     *            if true, registrations are rescheduled in perpetuity. if
     *            false, the federation pull operation is executed once.
     */
    public FederationPullExecutor(List<FederationModel> registrations, boolean isDaemon) {
        this.registrations = registrations;
        this.isDaemon = isDaemon;
    }
    /**
     * Run method for this pull executor.
     */
    @Override
    public void run() {
        for (FederationModel registration : registrations) {
            FederationPullStatus was = registration.getLowestStatus();
            try {
                Date now = new Date(System.currentTimeMillis());
                pull(registration);
                sendStatusAcknowledgment(registration);
                registration.lastPull = now;
                FederationPullStatus is = registration.getLowestStatus();
                if (is.ordinal() < was.ordinal()) {
                    // the status for this registration has downgraded
                    logger.warn("Federation pull status of {0} is now {1}", registration.name,
                            is.name());
                    if (registration.notifyOnError) {
                        String message = "Federation pull of " + registration.name + " @ "
                                + registration.url + " is now at " + is.name();
                        INotificationManager mailManager = GitBlit.getManager(INotificationManager.class);
                        mailManager
                                .sendMailToAdministrators(
                                        "Pull Status of " + registration.name + " is " + is.name(),
                                        message);
                    }
                }
            } catch (Throwable t) {
                logger.error(MessageFormat.format(
                        "Failed to pull from federated gitblit ({0} @ {1})", registration.name,
                        registration.url), t);
            } finally {
                if (isDaemon) {
                    schedule(registration);
                }
            }
        }
    }
    /**
     * Mirrors a repository and, optionally, the server's users, and/or
     * configuration settings from a origin Gitblit instance.
     *
     * @param registration
     * @throws Exception
     */
    private void pull(FederationModel registration) throws Exception {
        Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration,
                true);
        String registrationFolder = registration.folder.toLowerCase().trim();
        // confirm valid characters in server alias
        Character c = StringUtils.findInvalidCharacter(registrationFolder);
        if (c != null) {
            logger.error(MessageFormat
                    .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!",
                            c, registrationFolder, registration.name));
            return;
        }
        IRepositoryManager repositoryManager = GitBlit.getManager(IRepositoryManager.class);
        File repositoriesFolder = repositoryManager.getRepositoriesFolder();
        File registrationFolderFile = new File(repositoriesFolder, registrationFolder);
        registrationFolderFile.mkdirs();
        // Clone/Pull the repository
        for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) {
            String cloneUrl = entry.getKey();
            RepositoryModel repository = entry.getValue();
            if (!repository.hasCommits) {
                logger.warn(MessageFormat.format(
                        "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.",
                        repository.name, registration.name, registration.url));
                registration.updateStatus(repository, FederationPullStatus.SKIPPED);
                continue;
            }
            // Determine local repository name
            String repositoryName;
            if (StringUtils.isEmpty(registrationFolder)) {
                repositoryName = repository.name;
            } else {
                repositoryName = registrationFolder + "/" + repository.name;
            }
            if (registration.bare) {
                // bare repository, ensure .git suffix
                if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
                    repositoryName += DOT_GIT_EXT;
                }
            } else {
                // normal repository, strip .git suffix
                if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
                    repositoryName = repositoryName.substring(0,
                            repositoryName.indexOf(DOT_GIT_EXT));
                }
            }
            // confirm that the origin of any pre-existing repository matches
            // the clone url
            String fetchHead = null;
            Repository existingRepository = repositoryManager.getRepository(repositoryName);
            if (existingRepository == null && repositoryManager.isCollectingGarbage(repositoryName)) {
                logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName));
                continue;
            }
            if (existingRepository != null) {
                StoredConfig config = existingRepository.getConfig();
                config.load();
                String origin = config.getString("remote", "origin", "url");
                RevCommit commit = JGitUtils.getCommit(existingRepository,
                        org.eclipse.jgit.lib.Constants.FETCH_HEAD);
                if (commit != null) {
                    fetchHead = commit.getName();
                }
                existingRepository.close();
                if (!origin.startsWith(registration.url)) {
                    logger.warn(MessageFormat
                            .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.",
                                    repository.name, registration.name, registration.url));
                    registration.updateStatus(repository, FederationPullStatus.SKIPPED);
                    continue;
                }
            }
            // clone/pull this repository
            CredentialsProvider credentials = new UsernamePasswordCredentialsProvider(
                    Constants.FEDERATION_USER, registration.token);
            logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}",
                    repository.name, registration.name, registration.url));
            CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name,
                    cloneUrl, registration.bare, credentials);
            Repository r = repositoryManager.getRepository(repositoryName);
            RepositoryModel rm = repositoryManager.getRepositoryModel(repositoryName);
            repository.isFrozen = registration.mirror;
            if (result.createdRepository) {
                // default local settings
                repository.federationStrategy = FederationStrategy.EXCLUDE;
                repository.isFrozen = registration.mirror;
                repository.showRemoteBranches = !registration.mirror;
                logger.info(MessageFormat.format("     cloning {0}", repository.name));
                registration.updateStatus(repository, FederationPullStatus.MIRRORED);
            } else {
                // fetch and update
                boolean fetched = false;
                RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD);
                String newFetchHead = commit.getName();
                fetched = fetchHead == null || !fetchHead.equals(newFetchHead);
                if (registration.mirror) {
                    // mirror
                    if (fetched) {
                        // update local branches to match the remote tracking branches
                        for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) {
                            if (ref.displayName.startsWith("origin/")) {
                                String branch = org.eclipse.jgit.lib.Constants.R_HEADS
                                        + ref.displayName.substring(ref.displayName.indexOf('/') + 1);
                                String hash = ref.getReferencedObjectId().getName();
                                JGitUtils.setBranchRef(r, branch, hash);
                                logger.info(MessageFormat.format("     resetting {0} of {1} to {2}", branch,
                                        repository.name, hash));
                            }
                        }
                        String newHead;
                        if (StringUtils.isEmpty(repository.HEAD)) {
                            newHead = newFetchHead;
                        } else {
                            newHead = repository.HEAD;
                        }
                        JGitUtils.setHEADtoRef(r, newHead);
                        logger.info(MessageFormat.format("     resetting HEAD of {0} to {1}",
                                repository.name, newHead));
                        registration.updateStatus(repository, FederationPullStatus.MIRRORED);
                    } else {
                        // indicate no commits pulled
                        registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
                    }
                } else {
                    // non-mirror
                    if (fetched) {
                        // indicate commits pulled to origin/master
                        registration.updateStatus(repository, FederationPullStatus.PULLED);
                    } else {
                        // indicate no commits pulled
                        registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
                    }
                }
                // preserve local settings
                repository.isFrozen = rm.isFrozen;
                repository.federationStrategy = rm.federationStrategy;
                // merge federation sets
                Set<String> federationSets = new HashSet<String>();
                if (rm.federationSets != null) {
                    federationSets.addAll(rm.federationSets);
                }
                if (repository.federationSets != null) {
                    federationSets.addAll(repository.federationSets);
                }
                repository.federationSets = new ArrayList<String>(federationSets);
                // merge indexed branches
                Set<String> indexedBranches = new HashSet<String>();
                if (rm.indexedBranches != null) {
                    indexedBranches.addAll(rm.indexedBranches);
                }
                if (repository.indexedBranches != null) {
                    indexedBranches.addAll(repository.indexedBranches);
                }
                repository.indexedBranches = new ArrayList<String>(indexedBranches);
            }
            // only repositories that are actually _cloned_ from the origin
            // Gitblit repository are marked as federated. If the origin
            // is from somewhere else, these repositories are not considered
            // "federated" repositories.
            repository.isFederated = cloneUrl.startsWith(registration.url);
            repositoryManager.updateConfiguration(r, repository);
            r.close();
        }
        IUserManager userManager = GitBlit.getManager(IUserManager.class);
        IGitblitManager gitblitManager = GitBlit.getManager(IGitblitManager.class);
        IUserService userService = null;
        try {
            // Pull USERS
            // TeamModels are automatically pulled because they are contained
            // within the UserModel. The UserService creates unknown teams
            // and updates existing teams.
            Collection<UserModel> users = FederationUtils.getUsers(registration);
            if (users != null && users.size() > 0) {
                File realmFile = new File(registrationFolderFile, registration.name + "_users.conf");
                realmFile.delete();
                userService = new ConfigUserService(realmFile);
                for (UserModel user : users) {
                    userService.updateUserModel(user.username, user);
                    // merge the origin permissions and origin accounts into
                    // the user accounts of this Gitblit instance
                    if (registration.mergeAccounts) {
                        // reparent all repository permissions if the local
                        // repositories are stored within subfolders
                        if (!StringUtils.isEmpty(registrationFolder)) {
                            if (user.permissions != null) {
                                // pulling from >= 1.2 version
                                Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
                                user.permissions.clear();
                                for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
                                    user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue());
                                }
                            } else {
                                // pulling from <= 1.1 version
                                List<String> permissions = new ArrayList<String>(user.repositories);
                                user.repositories.clear();
                                for (String permission : permissions) {
                                    user.addRepositoryPermission(registrationFolder + "/" + permission);
                                }
                            }
                        }
                        // insert new user or update local user
                        UserModel localUser = userManager.getUserModel(user.username);
                        if (localUser == null) {
                            // create new local user
                            gitblitManager.updateUserModel(user.username, user, true);
                        } else {
                            // update repository permissions of local user
                            if (user.permissions != null) {
                                // pulling from >= 1.2 version
                                Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
                                for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
                                    localUser.setRepositoryPermission(entry.getKey(), entry.getValue());
                                }
                            } else {
                                // pulling from <= 1.1 version
                                for (String repository : user.repositories) {
                                    localUser.addRepositoryPermission(repository);
                                }
                            }
                            localUser.password = user.password;
                            localUser.canAdmin = user.canAdmin;
                            gitblitManager.updateUserModel(localUser.username, localUser, false);
                        }
                        for (String teamname : userManager.getAllTeamNames()) {
                            TeamModel team = userManager.getTeamModel(teamname);
                            if (user.isTeamMember(teamname) && !team.hasUser(user.username)) {
                                // new team member
                                team.addUser(user.username);
                                userManager.updateTeamModel(teamname, team);
                            } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) {
                                // remove team member
                                team.removeUser(user.username);
                                userManager.updateTeamModel(teamname, team);
                            }
                            // update team repositories
                            TeamModel remoteTeam = user.getTeam(teamname);
                            if (remoteTeam != null) {
                                if (remoteTeam.permissions != null) {
                                    // pulling from >= 1.2
                                    for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){
                                        team.setRepositoryPermission(entry.getKey(), entry.getValue());
                                    }
                                    userManager.updateTeamModel(teamname, team);
                                } else if(!ArrayUtils.isEmpty(remoteTeam.repositories)) {
                                    // pulling from <= 1.1
                                    team.addRepositoryPermissions(remoteTeam.repositories);
                                    userManager.updateTeamModel(teamname, team);
                                }
                            }
                        }
                    }
                }
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve USERS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
        try {
            // Pull TEAMS
            // We explicitly pull these even though they are embedded in
            // UserModels because it is possible to use teams to specify
            // mailing lists or push scripts without specifying users.
            if (userService != null) {
                Collection<TeamModel> teams = FederationUtils.getTeams(registration);
                if (teams != null && teams.size() > 0) {
                    for (TeamModel team : teams) {
                        userService.updateTeamModel(team);
                    }
                }
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve TEAMS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
        try {
            // Pull SETTINGS
            Map<String, String> settings = FederationUtils.getSettings(registration);
            if (settings != null && settings.size() > 0) {
                Properties properties = new Properties();
                properties.putAll(settings);
                FileOutputStream os = new FileOutputStream(new File(registrationFolderFile,
                        registration.name + "_" + Constants.PROPERTIES_FILE));
                properties.store(os, null);
                os.close();
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
        try {
            // Pull SCRIPTS
            Map<String, String> scripts = FederationUtils.getScripts(registration);
            if (scripts != null && scripts.size() > 0) {
                for (Map.Entry<String, String> script : scripts.entrySet()) {
                    String scriptName = script.getKey();
                    if (scriptName.endsWith(".groovy")) {
                        scriptName = scriptName.substring(0, scriptName.indexOf(".groovy"));
                    }
                    File file = new File(registrationFolderFile, registration.name + "_"
                            + scriptName + ".groovy");
                    FileUtils.writeContent(file, script.getValue());
                }
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
    }
    /**
     * Sends a status acknowledgment to the origin Gitblit instance. This
     * includes the results of the federated pull.
     *
     * @param registration
     * @throws Exception
     */
    private void sendStatusAcknowledgment(FederationModel registration) throws Exception {
        if (!registration.sendStatus) {
            // skip status acknowledgment
            return;
        }
        InetAddress addr = InetAddress.getLocalHost();
        IStoredSettings settings = GitBlit.getManager(IRuntimeManager.class).getSettings();
        String federationName = settings.getString(Keys.federation.name, null);
        if (StringUtils.isEmpty(federationName)) {
            federationName = addr.getHostName();
        }
        FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration);
        logger.info(MessageFormat.format("Pull status sent to {0}", registration.url));
    }
    /**
     * Schedules the next check of the federated Gitblit instance.
     *
     * @param registration
     */
    private void schedule(FederationModel registration) {
        // schedule the next pull
        int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency);
        registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L));
        GitBlit.self().executor()
                .schedule(new FederationPullExecutor(registration), mins, TimeUnit.MINUTES);
        logger.info(MessageFormat.format(
                "Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}",
                registration.name, registration.url, registration.nextPull));
    }
}
package com.gitblit;
import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.FederationPullStatus;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.models.FederationModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JGitUtils.CloneResult;
import com.gitblit.utils.StringUtils;
public abstract class FederationPullExecutor implements Runnable {
    Logger logger = LoggerFactory.getLogger(getClass());
    Gitblit gitblit;
    private final List<FederationModel> registrations;
    /**
     * Constructor for specifying a single federation registration. This
     * constructor is used to schedule the next pull execution.
     *
     * @param provider
     * @param registration
     */
    public FederationPullExecutor(FederationModel registration) {
        this(Arrays.asList(registration));
    }
    /**
     * Constructor to specify a group of federation registrations. This is
     * normally used at startup to pull and then schedule the next update based
     * on each registrations frequency setting.
     *
     * @param provider
     * @param registrations
     * @param isDaemon
     *            if true, registrations are rescheduled in perpetuity. if
     *            false, the federation pull operation is executed once.
     */
    public FederationPullExecutor(List<FederationModel> registrations) {
        this.registrations = registrations;
    }
    public abstract void reschedule(FederationModel registration);
    /**
     * Run method for this pull executor.
     */
    @Override
    public void run() {
        for (FederationModel registration : registrations) {
            FederationPullStatus was = registration.getLowestStatus();
            try {
                Date now = new Date(System.currentTimeMillis());
                pull(registration);
                sendStatusAcknowledgment(registration);
                registration.lastPull = now;
                FederationPullStatus is = registration.getLowestStatus();
                if (is.ordinal() < was.ordinal()) {
                    // the status for this registration has downgraded
                    logger.warn("Federation pull status of {0} is now {1}", registration.name,
                            is.name());
                    if (registration.notifyOnError) {
                        String message = "Federation pull of " + registration.name + " @ "
                                + registration.url + " is now at " + is.name();
                        gitblit.sendMailToAdministrators(
                                "Pull Status of " + registration.name + " is " + is.name(),
                                message);
                    }
                }
            } catch (Throwable t) {
                logger.error(MessageFormat.format(
                        "Failed to pull from federated gitblit ({0} @ {1})", registration.name,
                        registration.url), t);
            } finally {
                reschedule(registration);
            }
        }
    }
    /**
     * Mirrors a repository and, optionally, the server's users, and/or
     * configuration settings from a origin Gitblit instance.
     *
     * @param registration
     * @throws Exception
     */
    private void pull(FederationModel registration) throws Exception {
        Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration,
                true);
        String registrationFolder = registration.folder.toLowerCase().trim();
        // confirm valid characters in server alias
        Character c = StringUtils.findInvalidCharacter(registrationFolder);
        if (c != null) {
            logger.error(MessageFormat
                    .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!",
                            c, registrationFolder, registration.name));
            return;
        }
        File repositoriesFolder = gitblit.getRepositoriesFolder();
        File registrationFolderFile = new File(repositoriesFolder, registrationFolder);
        registrationFolderFile.mkdirs();
        // Clone/Pull the repository
        for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) {
            String cloneUrl = entry.getKey();
            RepositoryModel repository = entry.getValue();
            if (!repository.hasCommits) {
                logger.warn(MessageFormat.format(
                        "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.",
                        repository.name, registration.name, registration.url));
                registration.updateStatus(repository, FederationPullStatus.SKIPPED);
                continue;
            }
            // Determine local repository name
            String repositoryName;
            if (StringUtils.isEmpty(registrationFolder)) {
                repositoryName = repository.name;
            } else {
                repositoryName = registrationFolder + "/" + repository.name;
            }
            if (registration.bare) {
                // bare repository, ensure .git suffix
                if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
                    repositoryName += DOT_GIT_EXT;
                }
            } else {
                // normal repository, strip .git suffix
                if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
                    repositoryName = repositoryName.substring(0,
                            repositoryName.indexOf(DOT_GIT_EXT));
                }
            }
            // confirm that the origin of any pre-existing repository matches
            // the clone url
            String fetchHead = null;
            Repository existingRepository = gitblit.getRepository(repositoryName);
            if (existingRepository == null && gitblit.isCollectingGarbage(repositoryName)) {
                logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName));
                continue;
            }
            if (existingRepository != null) {
                StoredConfig config = existingRepository.getConfig();
                config.load();
                String origin = config.getString("remote", "origin", "url");
                RevCommit commit = JGitUtils.getCommit(existingRepository,
                        org.eclipse.jgit.lib.Constants.FETCH_HEAD);
                if (commit != null) {
                    fetchHead = commit.getName();
                }
                existingRepository.close();
                if (!origin.startsWith(registration.url)) {
                    logger.warn(MessageFormat
                            .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.",
                                    repository.name, registration.name, registration.url));
                    registration.updateStatus(repository, FederationPullStatus.SKIPPED);
                    continue;
                }
            }
            // clone/pull this repository
            CredentialsProvider credentials = new UsernamePasswordCredentialsProvider(
                    Constants.FEDERATION_USER, registration.token);
            logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}",
                    repository.name, registration.name, registration.url));
            CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name,
                    cloneUrl, registration.bare, credentials);
            Repository r = gitblit.getRepository(repositoryName);
            RepositoryModel rm = gitblit.getRepositoryModel(repositoryName);
            repository.isFrozen = registration.mirror;
            if (result.createdRepository) {
                // default local settings
                repository.federationStrategy = FederationStrategy.EXCLUDE;
                repository.isFrozen = registration.mirror;
                repository.showRemoteBranches = !registration.mirror;
                logger.info(MessageFormat.format("     cloning {0}", repository.name));
                registration.updateStatus(repository, FederationPullStatus.MIRRORED);
            } else {
                // fetch and update
                boolean fetched = false;
                RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD);
                String newFetchHead = commit.getName();
                fetched = fetchHead == null || !fetchHead.equals(newFetchHead);
                if (registration.mirror) {
                    // mirror
                    if (fetched) {
                        // update local branches to match the remote tracking branches
                        for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) {
                            if (ref.displayName.startsWith("origin/")) {
                                String branch = org.eclipse.jgit.lib.Constants.R_HEADS
                                        + ref.displayName.substring(ref.displayName.indexOf('/') + 1);
                                String hash = ref.getReferencedObjectId().getName();
                                JGitUtils.setBranchRef(r, branch, hash);
                                logger.info(MessageFormat.format("     resetting {0} of {1} to {2}", branch,
                                        repository.name, hash));
                            }
                        }
                        String newHead;
                        if (StringUtils.isEmpty(repository.HEAD)) {
                            newHead = newFetchHead;
                        } else {
                            newHead = repository.HEAD;
                        }
                        JGitUtils.setHEADtoRef(r, newHead);
                        logger.info(MessageFormat.format("     resetting HEAD of {0} to {1}",
                                repository.name, newHead));
                        registration.updateStatus(repository, FederationPullStatus.MIRRORED);
                    } else {
                        // indicate no commits pulled
                        registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
                    }
                } else {
                    // non-mirror
                    if (fetched) {
                        // indicate commits pulled to origin/master
                        registration.updateStatus(repository, FederationPullStatus.PULLED);
                    } else {
                        // indicate no commits pulled
                        registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
                    }
                }
                // preserve local settings
                repository.isFrozen = rm.isFrozen;
                repository.federationStrategy = rm.federationStrategy;
                // merge federation sets
                Set<String> federationSets = new HashSet<String>();
                if (rm.federationSets != null) {
                    federationSets.addAll(rm.federationSets);
                }
                if (repository.federationSets != null) {
                    federationSets.addAll(repository.federationSets);
                }
                repository.federationSets = new ArrayList<String>(federationSets);
                // merge indexed branches
                Set<String> indexedBranches = new HashSet<String>();
                if (rm.indexedBranches != null) {
                    indexedBranches.addAll(rm.indexedBranches);
                }
                if (repository.indexedBranches != null) {
                    indexedBranches.addAll(repository.indexedBranches);
                }
                repository.indexedBranches = new ArrayList<String>(indexedBranches);
            }
            // only repositories that are actually _cloned_ from the origin
            // Gitblit repository are marked as federated. If the origin
            // is from somewhere else, these repositories are not considered
            // "federated" repositories.
            repository.isFederated = cloneUrl.startsWith(registration.url);
            gitblit.updateConfiguration(r, repository);
            r.close();
        }
        IUserService userService = null;
        try {
            // Pull USERS
            // TeamModels are automatically pulled because they are contained
            // within the UserModel. The UserService creates unknown teams
            // and updates existing teams.
            Collection<UserModel> users = FederationUtils.getUsers(registration);
            if (users != null && users.size() > 0) {
                File realmFile = new File(registrationFolderFile, registration.name + "_users.conf");
                realmFile.delete();
                userService = new ConfigUserService(realmFile);
                for (UserModel user : users) {
                    userService.updateUserModel(user.username, user);
                    // merge the origin permissions and origin accounts into
                    // the user accounts of this Gitblit instance
                    if (registration.mergeAccounts) {
                        // reparent all repository permissions if the local
                        // repositories are stored within subfolders
                        if (!StringUtils.isEmpty(registrationFolder)) {
                            if (user.permissions != null) {
                                // pulling from >= 1.2 version
                                Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
                                user.permissions.clear();
                                for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
                                    user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue());
                                }
                            } else {
                                // pulling from <= 1.1 version
                                List<String> permissions = new ArrayList<String>(user.repositories);
                                user.repositories.clear();
                                for (String permission : permissions) {
                                    user.addRepositoryPermission(registrationFolder + "/" + permission);
                                }
                            }
                        }
                        // insert new user or update local user
                        UserModel localUser = gitblit.getUserModel(user.username);
                        if (localUser == null) {
                            // create new local user
                            gitblit.updateUserModel(user.username, user, true);
                        } else {
                            // update repository permissions of local user
                            if (user.permissions != null) {
                                // pulling from >= 1.2 version
                                Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
                                for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
                                    localUser.setRepositoryPermission(entry.getKey(), entry.getValue());
                                }
                            } else {
                                // pulling from <= 1.1 version
                                for (String repository : user.repositories) {
                                    localUser.addRepositoryPermission(repository);
                                }
                            }
                            localUser.password = user.password;
                            localUser.canAdmin = user.canAdmin;
                            gitblit.updateUserModel(localUser.username, localUser, false);
                        }
                        for (String teamname : gitblit.getAllTeamNames()) {
                            TeamModel team = gitblit.getTeamModel(teamname);
                            if (user.isTeamMember(teamname) && !team.hasUser(user.username)) {
                                // new team member
                                team.addUser(user.username);
                                gitblit.updateTeamModel(teamname, team);
                            } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) {
                                // remove team member
                                team.removeUser(user.username);
                                gitblit.updateTeamModel(teamname, team);
                            }
                            // update team repositories
                            TeamModel remoteTeam = user.getTeam(teamname);
                            if (remoteTeam != null) {
                                if (remoteTeam.permissions != null) {
                                    // pulling from >= 1.2
                                    for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){
                                        team.setRepositoryPermission(entry.getKey(), entry.getValue());
                                    }
                                    gitblit.updateTeamModel(teamname, team);
                                } else if (!ArrayUtils.isEmpty(remoteTeam.repositories)) {
                                    // pulling from <= 1.1
                                    team.addRepositoryPermissions(remoteTeam.repositories);
                                    gitblit.updateTeamModel(teamname, team);
                                }
                            }
                        }
                    }
                }
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve USERS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
        try {
            // Pull TEAMS
            // We explicitly pull these even though they are embedded in
            // UserModels because it is possible to use teams to specify
            // mailing lists or push scripts without specifying users.
            if (userService != null) {
                Collection<TeamModel> teams = FederationUtils.getTeams(registration);
                if (teams != null && teams.size() > 0) {
                    for (TeamModel team : teams) {
                        userService.updateTeamModel(team);
                    }
                }
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve TEAMS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
        try {
            // Pull SETTINGS
            Map<String, String> settings = FederationUtils.getSettings(registration);
            if (settings != null && settings.size() > 0) {
                Properties properties = new Properties();
                properties.putAll(settings);
                FileOutputStream os = new FileOutputStream(new File(registrationFolderFile,
                        registration.name + "_" + Constants.PROPERTIES_FILE));
                properties.store(os, null);
                os.close();
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
        try {
            // Pull SCRIPTS
            Map<String, String> scripts = FederationUtils.getScripts(registration);
            if (scripts != null && scripts.size() > 0) {
                for (Map.Entry<String, String> script : scripts.entrySet()) {
                    String scriptName = script.getKey();
                    if (scriptName.endsWith(".groovy")) {
                        scriptName = scriptName.substring(0, scriptName.indexOf(".groovy"));
                    }
                    File file = new File(registrationFolderFile, registration.name + "_"
                            + scriptName + ".groovy");
                    FileUtils.writeContent(file, script.getValue());
                }
            }
        } catch (ForbiddenException e) {
            // ignore forbidden exceptions
        } catch (IOException e) {
            logger.warn(MessageFormat.format(
                    "Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})",
                    registration.name, registration.url), e);
        }
    }
    /**
     * Sends a status acknowledgment to the origin Gitblit instance. This
     * includes the results of the federated pull.
     *
     * @param registration
     * @throws Exception
     */
    private void sendStatusAcknowledgment(FederationModel registration) throws Exception {
        if (!registration.sendStatus) {
            // skip status acknowledgment
            return;
        }
        InetAddress addr = InetAddress.getLocalHost();
        String federationName = gitblit.getSettings().getString(Keys.federation.name, null);
        if (StringUtils.isEmpty(federationName)) {
            federationName = addr.getHostName();
        }
        FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration);
        logger.info(MessageFormat.format("Pull status sent to {0}", registration.url));
    }
}
src/main/java/com/gitblit/GitBlit.java
@@ -15,50 +15,25 @@
 */
package com.gitblit;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
import org.apache.wicket.resource.ContextRelativeResource;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.slf4j.Logger;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationToken;
import com.gitblit.dagger.DaggerContextListener;
import com.gitblit.fanout.FanoutNioService;
import com.gitblit.fanout.FanoutService;
import com.gitblit.fanout.FanoutSocketService;
import com.gitblit.git.GitDaemon;
import com.gitblit.git.GitServlet;
import com.gitblit.manager.IFederationManager;
import com.gitblit.manager.IGitblitManager;
@@ -67,80 +42,54 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.GitClientApplication;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.SettingModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.ContainerUtils;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitblitWicketFilter;
import com.gitblit.wicket.WicketUtils;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import dagger.ObjectGraph;
/**
 * GitBlit is the servlet context listener singleton that acts as the core for
 * the web ui and the servlets. This class is either directly instantiated by
 * the GitBlitServer class (Gitblit GO) or is reflectively instantiated by the
 * servlet 3 container (Gitblit WAR or Express).
 * This class is the main entry point for the entire webapp.  It is a singleton
 * created manually by Gitblit GO or dynamically by the WAR/Express servlet
 * container.  This class instantiates and starts all managers followed by
 * instantiating and registering all servlets and filters.
 *
 * This class is the central logic processor for Gitblit. All settings, user
 * object, and repository object operations pass through this class.
 * Leveraging Servlet 3 and Dagger static dependency injection allows Gitblit to
 * be modular and completely code-driven rather then relying on the fragility of
 * a web.xml descriptor and the static & monolithic design previously used.
 *
 * @author James Moger
 *
 */
@WebListener
public class GitBlit extends DaggerContextListener
                     implements IFederationManager,
                                IGitblitManager {
public class GitBlit extends DaggerContextListener {
    private static GitBlit gitblit;
    private final List<IManager> managers = new ArrayList<IManager>();
    private final IStoredSettings goSettings;
    private final File goBaseFolder;
    private final List<IManager> managers = new ArrayList<IManager>();
    private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10);
    private final List<FederationModel> federationRegistrations = Collections
            .synchronizedList(new ArrayList<FederationModel>());
    private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
    private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
    private IStoredSettings settings;
    private FanoutService fanoutService;
    private GitDaemon gitDaemon;
    /**
     * Construct a Gitblit WAR/Express context.
     */
    public GitBlit() {
        this.goSettings = null;
        this.goBaseFolder = null;
        gitblit = this;
    }
    /**
     * Construct a Gitblit GO context.
     *
     * @param settings
     * @param baseFolder
     */
    public GitBlit(IStoredSettings settings, File baseFolder) {
        this.goSettings = settings;
        this.goBaseFolder = baseFolder;
@@ -148,20 +97,13 @@
    }
    /**
     * Returns the Gitblit singleton.
     * This method is only used for unit and integration testing.
     *
     * @return gitblit singleton
     * @param managerClass
     * @return a manager
     */
    public static GitBlit self() {
        return gitblit;
    }
    @SuppressWarnings("unchecked")
    public static <X> X getManager(Class<X> managerClass) {
        if (managerClass.isAssignableFrom(GitBlit.class)) {
            return (X) gitblit;
        }
    public static <X extends IManager> X getManager(Class<X> managerClass) {
        for (IManager manager : gitblit.managers) {
            if (managerClass.isAssignableFrom(manager.getClass())) {
                return (X) manager;
@@ -171,786 +113,15 @@
    }
    /**
     * Returns the path of the proposals folder. This method checks to see if
     * Gitblit is running on a cloud service and may return an adjusted path.
     *
     * @return the proposals folder path
     * Returns Gitblit's Dagger injection modules.
     */
    @Override
    public File getProposalsFolder() {
        return getManager(IRuntimeManager.class).getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
    protected Object [] getModules() {
        return new Object [] { new DaggerModule() };
    }
    /**
     * Returns a list of repository URLs and the user access permission.
     *
     * @param request
     * @param user
     * @param repository
     * @return a list of repository urls
     */
    @Override
    public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
        if (user == null) {
            user = UserModel.ANONYMOUS;
        }
        String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
        List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
        // http/https url
        if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
            AccessPermission permission = user.getRepositoryPermission(repository).permission;
            if (permission.exceeds(AccessPermission.NONE)) {
                list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
            }
        }
        // git daemon url
        String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
        if (!StringUtils.isEmpty(gitDaemonUrl)) {
            AccessPermission permission = getGitDaemonAccessPermission(user, repository);
            if (permission.exceeds(AccessPermission.NONE)) {
                list.add(new RepositoryUrl(gitDaemonUrl, permission));
            }
        }
        // add all other urls
        // {0} = repository
        // {1} = username
        for (String url : settings.getStrings(Keys.web.otherUrls)) {
            if (url.contains("{1}")) {
                // external url requires username, only add url IF we have one
                if(!StringUtils.isEmpty(username)) {
                    list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
                }
            } else {
                // external url does not require username
                list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
            }
        }
        return list;
    }
    protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
        StringBuilder sb = new StringBuilder();
        sb.append(HttpUtils.getGitblitURL(request));
        sb.append(Constants.GIT_PATH);
        sb.append(repository.name);
        // inject username into repository url if authentication is required
        if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
                && !StringUtils.isEmpty(username)) {
            sb.insert(sb.indexOf("://") + 3, username + "@");
        }
        return sb.toString();
    }
    protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
        if (gitDaemon != null) {
            String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
            if (bindInterface.equals("localhost")
                    && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
                // git daemon is bound to localhost and the request is from elsewhere
                return null;
            }
            if (user.canClone(repository)) {
                String servername = request.getServerName();
                String url = gitDaemon.formatUrl(servername, repository.name);
                return url;
            }
        }
        return null;
    }
    protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
        if (gitDaemon != null && user.canClone(repository)) {
            AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
            if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
                if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
                    // can not authenticate clone via anonymous git protocol
                    gitDaemonPermission = AccessPermission.NONE;
                } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
                    // can not authenticate push via anonymous git protocol
                    gitDaemonPermission = AccessPermission.CLONE;
                } else {
                    // normal user permission
                }
            }
            return gitDaemonPermission;
        }
        return AccessPermission.NONE;
    }
    /**
     * Returns the list of custom client applications to be used for the
     * repository url panel;
     *
     * @return a collection of client applications
     */
    @Override
    public Collection<GitClientApplication> getClientApplications() {
        // prefer user definitions, if they exist
        File userDefs = new File(getManager(IRuntimeManager.class).getBaseFolder(), "clientapps.json");
        if (userDefs.exists()) {
            Date lastModified = new Date(userDefs.lastModified());
            if (clientApplications.hasCurrent("user", lastModified)) {
                return clientApplications.getObject("user");
            } else {
                // (re)load user definitions
                try {
                    InputStream is = new FileInputStream(userDefs);
                    Collection<GitClientApplication> clients = readClientApplications(is);
                    is.close();
                    if (clients != null) {
                        clientApplications.updateObject("user", lastModified, clients);
                        return clients;
                    }
                } catch (IOException e) {
                    logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
                }
            }
        }
        // no user definitions, use system definitions
        if (!clientApplications.hasCurrent("system", new Date(0))) {
            try {
                InputStream is = getClass().getResourceAsStream("/clientapps.json");
                Collection<GitClientApplication> clients = readClientApplications(is);
                is.close();
                if (clients != null) {
                    clientApplications.updateObject("system", new Date(0), clients);
                }
            } catch (IOException e) {
                logger.error("Failed to deserialize clientapps.json resource!", e);
            }
        }
        return clientApplications.getObject("system");
    }
    private Collection<GitClientApplication> readClientApplications(InputStream is) {
        try {
            Type type = new TypeToken<Collection<GitClientApplication>>() {
            }.getType();
            InputStreamReader reader = new InputStreamReader(is);
            Gson gson = JsonUtils.gson();
            Collection<GitClientApplication> links = gson.fromJson(reader, type);
            return links;
        } catch (JsonIOException e) {
            logger.error("Error deserializing client applications!", e);
        } catch (JsonSyntaxException e) {
            logger.error("Error deserializing client applications!", e);
        }
        return null;
    }
    /**
     * Open a file resource using the Servlet container.
     * @param file to open
     * @return InputStream of the opened file
     * @throws ResourceStreamNotFoundException
     */
    public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException {
        ContextRelativeResource res = WicketUtils.getResource(file);
        return res.getResourceStream().getInputStream();
    }
    @Override
    public UserModel getFederationUser() {
        // the federation user is an administrator
        UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
        federationUser.canAdmin = true;
        return federationUser;
    }
    /**
     * Adds/updates a complete user object keyed by username. This method allows
     * for renaming a user.
     *
     * @see IUserService.updateUserModel(String, UserModel)
     * @param username
     * @param user
     * @param isCreate
     * @throws GitBlitException
     */
    @Override
    public void updateUserModel(String username, UserModel user, boolean isCreate)
            throws GitBlitException {
        if (!username.equalsIgnoreCase(user.username)) {
            if (getManager(IUserManager.class).getUserModel(user.username) != null) {
                throw new GitBlitException(MessageFormat.format(
                        "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
                        user.username));
            }
            // rename repositories and owner fields for all repositories
            for (RepositoryModel model : getManager(IRepositoryManager.class).getRepositoryModels(user)) {
                if (model.isUsersPersonalRepository(username)) {
                    // personal repository
                    model.addOwner(user.username);
                    String oldRepositoryName = model.name;
                    model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length());
                    model.projectPath = user.getPersonalPath();
                    getManager(IRepositoryManager.class).updateRepositoryModel(oldRepositoryName, model, false);
                } else if (model.isOwner(username)) {
                    // common/shared repo
                    model.addOwner(user.username);
                    getManager(IRepositoryManager.class).updateRepositoryModel(model.name, model, false);
                }
            }
        }
        if (!getManager(IUserManager.class).updateUserModel(username, user)) {
            throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
        }
    }
    /**
     * Updates the TeamModel object for the specified name.
     *
     * @param teamname
     * @param team
     * @param isCreate
     */
    @Override
    public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
            throws GitBlitException {
        if (!teamname.equalsIgnoreCase(team.name)) {
            if (getManager(IUserManager.class).getTeamModel(team.name) != null) {
                throw new GitBlitException(MessageFormat.format(
                        "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
                        team.name));
            }
        }
        if (!getManager(IUserManager.class).updateTeamModel(teamname, team)) {
            throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
        }
    }
    /**
     * Returns Gitblit's scheduled executor service for scheduling tasks.
     *
     * @return scheduledExecutor
     */
    public ScheduledExecutorService executor() {
        return scheduledExecutor;
    }
    @Override
    public boolean canFederate() {
        String passphrase = settings.getString(Keys.federation.passphrase, "");
        return !StringUtils.isEmpty(passphrase);
    }
    /**
     * Configures this Gitblit instance to pull any registered federated gitblit
     * instances.
     */
    private void configureFederation() {
        boolean validPassphrase = true;
        String passphrase = settings.getString(Keys.federation.passphrase, "");
        if (StringUtils.isEmpty(passphrase)) {
            logger.warn("Federation passphrase is blank! This server can not be PULLED from.");
            validPassphrase = false;
        }
        if (validPassphrase) {
            // standard tokens
            for (FederationToken tokenType : FederationToken.values()) {
                logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
                        getFederationToken(tokenType)));
            }
            // federation set tokens
            for (String set : settings.getStrings(Keys.federation.sets)) {
                logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
                        getFederationToken(set)));
            }
        }
        // Schedule the federation executor
        List<FederationModel> registrations = getFederationRegistrations();
        if (registrations.size() > 0) {
            FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
            scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
        }
    }
    /**
     * Returns the list of federated gitblit instances that this instance will
     * try to pull.
     *
     * @return list of registered gitblit instances
     */
    @Override
    public List<FederationModel> getFederationRegistrations() {
        if (federationRegistrations.isEmpty()) {
            federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
        }
        return federationRegistrations;
    }
    /**
     * Retrieve the specified federation registration.
     *
     * @param name
     *            the name of the registration
     * @return a federation registration
     */
    @Override
    public FederationModel getFederationRegistration(String url, String name) {
        // check registrations
        for (FederationModel r : getFederationRegistrations()) {
            if (r.name.equals(name) && r.url.equals(url)) {
                return r;
            }
        }
        // check the results
        for (FederationModel r : getFederationResultRegistrations()) {
            if (r.name.equals(name) && r.url.equals(url)) {
                return r;
            }
        }
        return null;
    }
    /**
     * Returns the list of federation sets.
     *
     * @return list of federation sets
     */
    @Override
    public List<FederationSet> getFederationSets(String gitblitUrl) {
        List<FederationSet> list = new ArrayList<FederationSet>();
        // generate standard tokens
        for (FederationToken type : FederationToken.values()) {
            FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
            fset.repositories = getRepositories(gitblitUrl, fset.token);
            list.add(fset);
        }
        // generate tokens for federation sets
        for (String set : settings.getStrings(Keys.federation.sets)) {
            FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
                    getFederationToken(set));
            fset.repositories = getRepositories(gitblitUrl, fset.token);
            list.add(fset);
        }
        return list;
    }
    /**
     * Returns the list of possible federation tokens for this Gitblit instance.
     *
     * @return list of federation tokens
     */
    @Override
    public List<String> getFederationTokens() {
        List<String> tokens = new ArrayList<String>();
        // generate standard tokens
        for (FederationToken type : FederationToken.values()) {
            tokens.add(getFederationToken(type));
        }
        // generate tokens for federation sets
        for (String set : settings.getStrings(Keys.federation.sets)) {
            tokens.add(getFederationToken(set));
        }
        return tokens;
    }
    /**
     * Returns the specified federation token for this Gitblit instance.
     *
     * @param type
     * @return a federation token
     */
    @Override
    public String getFederationToken(FederationToken type) {
        return getFederationToken(type.name());
    }
    /**
     * Returns the specified federation token for this Gitblit instance.
     *
     * @param value
     * @return a federation token
     */
    @Override
    public String getFederationToken(String value) {
        String passphrase = settings.getString(Keys.federation.passphrase, "");
        return StringUtils.getSHA1(passphrase + "-" + value);
    }
    /**
     * Compares the provided token with this Gitblit instance's tokens and
     * determines if the requested permission may be granted to the token.
     *
     * @param req
     * @param token
     * @return true if the request can be executed
     */
    @Override
    public boolean validateFederationRequest(FederationRequest req, String token) {
        String all = getFederationToken(FederationToken.ALL);
        String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
        String jur = getFederationToken(FederationToken.REPOSITORIES);
        switch (req) {
        case PULL_REPOSITORIES:
            return token.equals(all) || token.equals(unr) || token.equals(jur);
        case PULL_USERS:
        case PULL_TEAMS:
            return token.equals(all) || token.equals(unr);
        case PULL_SETTINGS:
        case PULL_SCRIPTS:
            return token.equals(all);
        default:
            break;
        }
        return false;
    }
    /**
     * Acknowledge and cache the status of a remote Gitblit instance.
     *
     * @param identification
     *            the identification of the pulling Gitblit instance
     * @param registration
     *            the registration from the pulling Gitblit instance
     * @return true if acknowledged
     */
    @Override
    public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
        // reset the url to the identification of the pulling Gitblit instance
        registration.url = identification;
        String id = identification;
        if (!StringUtils.isEmpty(registration.folder)) {
            id += "-" + registration.folder;
        }
        federationPullResults.put(id, registration);
        return true;
    }
    /**
     * Returns the list of registration results.
     *
     * @return the list of registration results
     */
    @Override
    public List<FederationModel> getFederationResultRegistrations() {
        return new ArrayList<FederationModel>(federationPullResults.values());
    }
    /**
     * Submit a federation proposal. The proposal is cached locally and the
     * Gitblit administrator(s) are notified via email.
     *
     * @param proposal
     *            the proposal
     * @param gitblitUrl
     *            the url of your gitblit instance to send an email to
     *            administrators
     * @return true if the proposal was submitted
     */
    @Override
    public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
        // convert proposal to json
        String json = JsonUtils.toJsonString(proposal);
        try {
            // make the proposals folder
            File proposalsFolder = getProposalsFolder();
            proposalsFolder.mkdirs();
            // cache json to a file
            File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
            com.gitblit.utils.FileUtils.writeContent(file, json);
        } catch (Exception e) {
            logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
        }
        // send an email, if possible
        getManager(INotificationManager.class).sendMailToAdministrators("Federation proposal from " + proposal.url,
                "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token);
        return true;
    }
    /**
     * Returns the list of pending federation proposals
     *
     * @return list of federation proposals
     */
    @Override
    public List<FederationProposal> getPendingFederationProposals() {
        List<FederationProposal> list = new ArrayList<FederationProposal>();
        File folder = getProposalsFolder();
        if (folder.exists()) {
            File[] files = folder.listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return file.isFile()
                            && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
                }
            });
            for (File file : files) {
                String json = com.gitblit.utils.FileUtils.readContent(file, null);
                FederationProposal proposal = JsonUtils.fromJsonString(json,
                        FederationProposal.class);
                list.add(proposal);
            }
        }
        return list;
    }
    /**
     * Get repositories for the specified token.
     *
     * @param gitblitUrl
     *            the base url of this gitblit instance
     * @param token
     *            the federation token
     * @return a map of <cloneurl, RepositoryModel>
     */
    @Override
    public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
        Map<String, String> federationSets = new HashMap<String, String>();
        for (String set : settings.getStrings(Keys.federation.sets)) {
            federationSets.put(getFederationToken(set), set);
        }
        // Determine the Gitblit clone url
        StringBuilder sb = new StringBuilder();
        sb.append(gitblitUrl);
        sb.append(Constants.GIT_PATH);
        sb.append("{0}");
        String cloneUrl = sb.toString();
        // Retrieve all available repositories
        UserModel user = getFederationUser();
        List<RepositoryModel> list = getManager(IRepositoryManager.class).getRepositoryModels(user);
        // create the [cloneurl, repositoryModel] map
        Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
        for (RepositoryModel model : list) {
            // by default, setup the url for THIS repository
            String url = MessageFormat.format(cloneUrl, model.name);
            switch (model.federationStrategy) {
            case EXCLUDE:
                // skip this repository
                continue;
            case FEDERATE_ORIGIN:
                // federate the origin, if it is defined
                if (!StringUtils.isEmpty(model.origin)) {
                    url = model.origin;
                }
                break;
            default:
                break;
            }
            if (federationSets.containsKey(token)) {
                // include repositories only for federation set
                String set = federationSets.get(token);
                if (model.federationSets.contains(set)) {
                    repositories.put(url, model);
                }
            } else {
                // standard federation token for ALL
                repositories.put(url, model);
            }
        }
        return repositories;
    }
    /**
     * Creates a proposal from the token.
     *
     * @param gitblitUrl
     *            the url of this Gitblit instance
     * @param token
     * @return a potential proposal
     */
    @Override
    public FederationProposal createFederationProposal(String gitblitUrl, String token) {
        FederationToken tokenType = FederationToken.REPOSITORIES;
        for (FederationToken type : FederationToken.values()) {
            if (token.equals(getFederationToken(type))) {
                tokenType = type;
                break;
            }
        }
        Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
        FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
                repositories);
        return proposal;
    }
    /**
     * Returns the proposal identified by the supplied token.
     *
     * @param token
     * @return the specified proposal or null
     */
    @Override
    public FederationProposal getPendingFederationProposal(String token) {
        List<FederationProposal> list = getPendingFederationProposals();
        for (FederationProposal proposal : list) {
            if (proposal.token.equals(token)) {
                return proposal;
            }
        }
        return null;
    }
    /**
     * Deletes a pending federation proposal.
     *
     * @param a
     *            proposal
     * @return true if the proposal was deleted
     */
    @Override
    public boolean deletePendingFederationProposal(FederationProposal proposal) {
        File folder = getProposalsFolder();
        File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
        return file.delete();
    }
    /**
     * Parse the properties file and aggregate all the comments by the setting
     * key. A setting model tracks the current value, the default value, the
     * description of the setting and and directives about the setting.
     *
     * @return Map<String, SettingModel>
     */
    private ServerSettings loadSettingModels(ServerSettings settingsModel) {
        // this entire "supports" concept will go away with user service refactoring
        UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT);
        externalUser.password = Constants.EXTERNAL_ACCOUNT;
        IUserManager userManager = getManager(IUserManager.class);
        settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser);
        settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser);
        settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser);
        settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser);
        try {
            // Read bundled Gitblit properties to extract setting descriptions.
            // This copy is pristine and only used for populating the setting
            // models map.
            InputStream is = getClass().getResourceAsStream("/reference.properties");
            BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
            StringBuilder description = new StringBuilder();
            SettingModel setting = new SettingModel();
            String line = null;
            while ((line = propertiesReader.readLine()) != null) {
                if (line.length() == 0) {
                    description.setLength(0);
                    setting = new SettingModel();
                } else {
                    if (line.charAt(0) == '#') {
                        if (line.length() > 1) {
                            String text = line.substring(1).trim();
                            if (SettingModel.CASE_SENSITIVE.equals(text)) {
                                setting.caseSensitive = true;
                            } else if (SettingModel.RESTART_REQUIRED.equals(text)) {
                                setting.restartRequired = true;
                            } else if (SettingModel.SPACE_DELIMITED.equals(text)) {
                                setting.spaceDelimited = true;
                            } else if (text.startsWith(SettingModel.SINCE)) {
                                try {
                                    setting.since = text.split(" ")[1];
                                } catch (Exception e) {
                                    setting.since = text;
                                }
                            } else {
                                description.append(text);
                                description.append('\n');
                            }
                        }
                    } else {
                        String[] kvp = line.split("=", 2);
                        String key = kvp[0].trim();
                        setting.name = key;
                        setting.defaultValue = kvp[1].trim();
                        setting.currentValue = setting.defaultValue;
                        setting.description = description.toString().trim();
                        settingsModel.add(setting);
                        description.setLength(0);
                        setting = new SettingModel();
                    }
                }
            }
            propertiesReader.close();
        } catch (NullPointerException e) {
            logger.error("Failed to find resource copy of gitblit.properties");
        } catch (IOException e) {
            logger.error("Failed to load resource copy of gitblit.properties");
        }
        return settingsModel;
    }
    protected void configureFanout() {
        // startup Fanout PubSub service
        if (settings.getInteger(Keys.fanout.port, 0) > 0) {
            String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
            int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
            boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
            int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
            if (useNio) {
                if (StringUtils.isEmpty(bindInterface)) {
                    fanoutService = new FanoutNioService(port);
                } else {
                    fanoutService = new FanoutNioService(bindInterface, port);
                }
            } else {
                if (StringUtils.isEmpty(bindInterface)) {
                    fanoutService = new FanoutSocketService(port);
                } else {
                    fanoutService = new FanoutSocketService(bindInterface, port);
                }
            }
            fanoutService.setConcurrentConnectionLimit(limit);
            fanoutService.setAllowAllChannelAnnouncements(false);
            fanoutService.start();
        }
    }
    protected void configureGitDaemon() {
        int port = settings.getInteger(Keys.git.daemonPort, 0);
        String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
        if (port > 0) {
            try {
                // HACK temporary pending manager separation and injection
                Gitblit gitblit = new Gitblit(
                        getManager(IRuntimeManager.class),
                        getManager(INotificationManager.class),
                        getManager(IUserManager.class),
                        getManager(ISessionManager.class),
                        getManager(IRepositoryManager.class),
                        getManager(IProjectManager.class),
                        this,
                        this);
                gitDaemon = new GitDaemon(gitblit);
                gitDaemon.start();
            } catch (IOException e) {
                gitDaemon = null;
                logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e);
            }
        }
    }
    protected final Logger getLogger() {
        return logger;
    }
    protected final ScheduledExecutorService getScheduledExecutor() {
        return scheduledExecutor;
    }
    /**
     * Configure Gitblit from the web.xml, if no configuration has already been
     * specified.
     *
     * @see ServletContextListener.contextInitialize(ServletContextEvent)
     * Prepare runtime settings and start all manager instances.
     */
    @Override
    protected void beforeServletInjection(ServletContext context) {
@@ -958,12 +129,10 @@
        // create the runtime settings object
        IStoredSettings runtimeSettings = injector.get(IStoredSettings.class);
        this.settings = runtimeSettings; // XXX remove me eventually
        final File baseFolder;
        if (goSettings != null) {
            // Gitblit GO
            logger.debug("configuring Gitblit GO");
            baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings);
        } else {
            // servlet container
@@ -973,11 +142,9 @@
            if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) {
                // RedHat OpenShift
                logger.debug("configuring Gitblit Express");
                baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings);
            } else {
                // standard WAR
                logger.debug("configuring Gitblit WAR");
                baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings);
            }
@@ -985,27 +152,85 @@
            ContainerUtils.CVE_2007_0450.test(runtimeSettings);
        }
        // Runtime manager is a container for settings and other parameters
        IRuntimeManager runtime = startManager(injector, IRuntimeManager.class);
        // Manually configure IRuntimeManager
        logManager(IRuntimeManager.class);
        IRuntimeManager runtime = injector.get(IRuntimeManager.class);
        runtime.setBaseFolder(baseFolder);
        runtime.getStatus().isGO = goSettings != null;
        runtime.getStatus().servletContainer = context.getServerInfo();
        runtime.start();
        managers.add(runtime);
        // start all other managers
        startManager(injector, INotificationManager.class);
        startManager(injector, IUserManager.class);
        startManager(injector, ISessionManager.class);
        startManager(injector, IRepositoryManager.class);
        startManager(injector, IProjectManager.class);
        startManager(injector, IGitblitManager.class);
        startManager(injector, IFederationManager.class);
        startManager(injector, IServicesManager.class);
        logger.info("Gitblit base folder     = " + baseFolder.getAbsolutePath());
        logger.info("");
        logger.info("All managers started.");
        logger.info("");
    }
        loadSettingModels(runtime.getSettingsModel());
    protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) {
        logManager(clazz);
        X x = injector.get(clazz);
        x.start();
        managers.add(x);
        return x;
    }
        if (true/*startFederation*/) {
            configureFederation();
    protected void logManager(Class<? extends IManager> clazz) {
        logger.info("");
        logger.info("----[{}]----", clazz.getName());
    }
    /**
     * Instantiate and inject all filters and servlets into the container using
     * the servlet 3 specification.
     */
    @Override
    protected void injectServlets(ServletContext context) {
        // access restricted servlets
        serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class);
        serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class);
        serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class);
        serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class);
        serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class);
        // servlets
        serve(context, Constants.FEDERATION_PATH, FederationServlet.class);
        serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class);
        serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class);
        file(context, "/robots.txt", RobotsTxtServlet.class);
        file(context, "/logo.png", LogoServlet.class);
        // optional force basic authentication
        filter(context, "/*", EnforceAuthenticationFilter.class, null);
        // Wicket
        String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ",");
        Map<String, String> params = new HashMap<String, String>();
        params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*");
        params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore);
        filter(context, "/*", GitblitWicketFilter.class, params);
    }
    /**
     * Gitblit is being shutdown either because the servlet container is
     * shutting down or because the servlet container is re-deploying Gitblit.
     */
    @Override
    protected void destroyContext(ServletContext context) {
        logger.info("Gitblit context destroyed by servlet container.");
        for (IManager manager : managers) {
            logger.debug("stopping {}", manager.getClass().getSimpleName());
            manager.stop();
        }
        configureFanout();
        configureGitDaemon();
    }
    /**
@@ -1022,6 +247,8 @@
            IStoredSettings goSettings,
            File goBaseFolder,
            IStoredSettings runtimeSettings) {
        logger.debug("configuring Gitblit GO");
        // merge the stored settings into the runtime settings
        //
@@ -1049,6 +276,7 @@
            IStoredSettings runtimeSettings) {
        // Gitblit is running in a standard servlet container
        logger.debug("configuring Gitblit WAR");
        logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
        String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
@@ -1112,6 +340,7 @@
            IStoredSettings runtimeSettings) {
        // Gitblit is running in OpenShift/JBoss
        logger.debug("configuring Gitblit Express");
        String openShift = System.getenv("OPENSHIFT_DATA_DIR");
        File base = new File(openShift);
        logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
@@ -1193,142 +422,5 @@
                }
            }
        }
    }
    /**
     * Gitblit is being shutdown either because the servlet container is
     * shutting down or because the servlet container is re-deploying Gitblit.
     */
    @Override
    protected void destroyContext(ServletContext context) {
        logger.info("Gitblit context destroyed by servlet container.");
        for (IManager manager : managers) {
            logger.debug("stopping {}", manager.getClass().getSimpleName());
            manager.stop();
        }
        scheduledExecutor.shutdownNow();
        if (fanoutService != null) {
            fanoutService.stop();
        }
        if (gitDaemon != null) {
            gitDaemon.stop();
        }
    }
    /**
     * Creates a personal fork of the specified repository. The clone is view
     * restricted by default and the owner of the source repository is given
     * access to the clone.
     *
     * @param repository
     * @param user
     * @return the repository model of the fork, if successful
     * @throws GitBlitException
     */
    @Override
    public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
        String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
        String fromUrl = MessageFormat.format("file://{0}/{1}", getManager(IRepositoryManager.class).getRepositoriesFolder().getAbsolutePath(), repository.name);
        // clone the repository
        try {
            JGitUtils.cloneRepository(getManager(IRepositoryManager.class).getRepositoriesFolder(), cloneName, fromUrl, true, null);
        } catch (Exception e) {
            throw new GitBlitException(e);
        }
        // create a Gitblit repository model for the clone
        RepositoryModel cloneModel = repository.cloneAs(cloneName);
        // owner has REWIND/RW+ permissions
        cloneModel.addOwner(user.username);
        getManager(IRepositoryManager.class).updateRepositoryModel(cloneName, cloneModel, false);
        // add the owner of the source repository to the clone's access list
        if (!ArrayUtils.isEmpty(repository.owners)) {
            for (String owner : repository.owners) {
                UserModel originOwner = getManager(IUserManager.class).getUserModel(owner);
                if (originOwner != null) {
                    originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
                    updateUserModel(originOwner.username, originOwner, false);
                }
            }
        }
        // grant origin's user list clone permission to fork
        List<String> users = getManager(IRepositoryManager.class).getRepositoryUsers(repository);
        List<UserModel> cloneUsers = new ArrayList<UserModel>();
        for (String name : users) {
            if (!name.equalsIgnoreCase(user.username)) {
                UserModel cloneUser = getManager(IUserManager.class).getUserModel(name);
                if (cloneUser.canClone(repository)) {
                    // origin user can clone origin, grant clone access to fork
                    cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
                }
                cloneUsers.add(cloneUser);
            }
        }
        getManager(IUserManager.class).updateUserModels(cloneUsers);
        // grant origin's team list clone permission to fork
        List<String> teams = getManager(IRepositoryManager.class).getRepositoryTeams(repository);
        List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
        for (String name : teams) {
            TeamModel cloneTeam = getManager(IUserManager.class).getTeamModel(name);
            if (cloneTeam.canClone(repository)) {
                // origin team can clone origin, grant clone access to fork
                cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
            }
            cloneTeams.add(cloneTeam);
        }
        getManager(IUserManager.class).updateTeamModels(cloneTeams);
        // add this clone to the cached model
        getManager(IRepositoryManager.class).addToCachedRepositoryList(cloneModel);
        return cloneModel;
    }
    @Override
    protected Object [] getModules() {
        return new Object [] { new DaggerModule(this) };
    }
    protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) {
        logger.debug("injecting and starting {}", clazz.getSimpleName());
        X x = injector.get(clazz);
        x.setup();
        managers.add(x);
        return x;
    }
    /**
     * Instantiate and inject all filters and servlets into the container using
     * the servlet 3 specification.
     */
    @Override
    protected void injectServlets(ServletContext context) {
        // access restricted servlets
        serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class);
        serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class);
        serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class);
        serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class);
        serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class);
        // servlets
        serve(context, Constants.FEDERATION_PATH, FederationServlet.class);
        serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class);
        serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class);
        file(context, "/robots.txt", RobotsTxtServlet.class);
        file(context, "/logo.png", LogoServlet.class);
        // optional force basic authentication
        filter(context, "/*", EnforceAuthenticationFilter.class, null);
        // Wicket
        String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ",");
        Map<String, String> params = new HashMap<String, String>();
        params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*");
        params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore);
        filter(context, "/*", GitblitWicketFilter.class, params);
    }
}
src/main/java/com/gitblit/Gitblit.java
@@ -22,8 +22,6 @@
import java.util.Map;
import java.util.TimeZone;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -33,7 +31,6 @@
import com.gitblit.Constants.FederationToken;
import com.gitblit.manager.IFederationManager;
import com.gitblit.manager.IGitblitManager;
import com.gitblit.manager.IManager;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
@@ -65,7 +62,6 @@
 * @author James Moger
 *
 */
@Singleton
public class Gitblit implements IRuntimeManager,
                                INotificationManager,
                                IUserManager,
@@ -87,11 +83,10 @@
    private final IProjectManager projectManager;
    private final IFederationManager federationManager;
    private final IGitblitManager gitblitManager;
    @Inject
    private final IFederationManager federationManager;
    public Gitblit(
            IRuntimeManager runtimeManager,
            INotificationManager notificationManager,
@@ -99,8 +94,8 @@
            ISessionManager sessionManager,
            IRepositoryManager repositoryManager,
            IProjectManager projectManager,
            IFederationManager federationManager,
            IGitblitManager gitblitManager) {
            IGitblitManager gitblitManager,
            IFederationManager federationManager) {
        this.runtimeManager = runtimeManager;
        this.notificationManager = notificationManager;
@@ -108,17 +103,17 @@
        this.sessionManager = sessionManager;
        this.repositoryManager = repositoryManager;
        this.projectManager = projectManager;
        this.federationManager = federationManager;
        this.gitblitManager = gitblitManager;
        this.federationManager = federationManager;
    }
    @Override
    public IManager setup() {
    public Gitblit start() {
        return this;
    }
    @Override
    public IManager stop() {
    public Gitblit stop() {
        return this;
    }
src/main/java/com/gitblit/manager/FederationManager.java
New file
@@ -0,0 +1,454 @@
/*
 * Copyright 2013 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.manager;
import java.io.File;
import java.io.FileFilter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationToken;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.StringUtils;
/**
 * Federation manager controls all aspects of handling federation sets, tokens,
 * and proposals.
 *
 * @author James Moger
 *
 */
public class FederationManager implements IFederationManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final List<FederationModel> federationRegistrations = Collections
            .synchronizedList(new ArrayList<FederationModel>());
    private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
    private final IStoredSettings settings;
    private final IRuntimeManager runtimeManager;
    private final INotificationManager notificationManager;
    private final IRepositoryManager repositoryManager;
    public FederationManager(
            IRuntimeManager runtimeManager,
            INotificationManager notificationManager,
            IUserManager userManager,
            IRepositoryManager repositoryManager) {
        this.settings = runtimeManager.getSettings();
        this.runtimeManager = runtimeManager;
        this.notificationManager = notificationManager;
        this.repositoryManager = repositoryManager;
    }
    @Override
    public FederationManager start() {
        return this;
    }
    @Override
    public FederationManager stop() {
        return this;
    }
    /**
     * Returns the path of the proposals folder. This method checks to see if
     * Gitblit is running on a cloud service and may return an adjusted path.
     *
     * @return the proposals folder path
     */
    @Override
    public File getProposalsFolder() {
        return runtimeManager.getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
    }
    @Override
    public UserModel getFederationUser() {
        // the federation user is an administrator
        UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
        federationUser.canAdmin = true;
        return federationUser;
    }
    @Override
    public boolean canFederate() {
        String passphrase = settings.getString(Keys.federation.passphrase, "");
        return !StringUtils.isEmpty(passphrase);
    }
    /**
     * Returns the list of federated gitblit instances that this instance will
     * try to pull.
     *
     * @return list of registered gitblit instances
     */
    @Override
    public List<FederationModel> getFederationRegistrations() {
        if (federationRegistrations.isEmpty()) {
            federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
        }
        return federationRegistrations;
    }
    /**
     * Retrieve the specified federation registration.
     *
     * @param name
     *            the name of the registration
     * @return a federation registration
     */
    @Override
    public FederationModel getFederationRegistration(String url, String name) {
        // check registrations
        for (FederationModel r : getFederationRegistrations()) {
            if (r.name.equals(name) && r.url.equals(url)) {
                return r;
            }
        }
        // check the results
        for (FederationModel r : getFederationResultRegistrations()) {
            if (r.name.equals(name) && r.url.equals(url)) {
                return r;
            }
        }
        return null;
    }
    /**
     * Returns the list of federation sets.
     *
     * @return list of federation sets
     */
    @Override
    public List<FederationSet> getFederationSets(String gitblitUrl) {
        List<FederationSet> list = new ArrayList<FederationSet>();
        // generate standard tokens
        for (FederationToken type : FederationToken.values()) {
            FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
            fset.repositories = getRepositories(gitblitUrl, fset.token);
            list.add(fset);
        }
        // generate tokens for federation sets
        for (String set : settings.getStrings(Keys.federation.sets)) {
            FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
                    getFederationToken(set));
            fset.repositories = getRepositories(gitblitUrl, fset.token);
            list.add(fset);
        }
        return list;
    }
    /**
     * Returns the list of possible federation tokens for this Gitblit instance.
     *
     * @return list of federation tokens
     */
    @Override
    public List<String> getFederationTokens() {
        List<String> tokens = new ArrayList<String>();
        // generate standard tokens
        for (FederationToken type : FederationToken.values()) {
            tokens.add(getFederationToken(type));
        }
        // generate tokens for federation sets
        for (String set : settings.getStrings(Keys.federation.sets)) {
            tokens.add(getFederationToken(set));
        }
        return tokens;
    }
    /**
     * Returns the specified federation token for this Gitblit instance.
     *
     * @param type
     * @return a federation token
     */
    @Override
    public String getFederationToken(FederationToken type) {
        return getFederationToken(type.name());
    }
    /**
     * Returns the specified federation token for this Gitblit instance.
     *
     * @param value
     * @return a federation token
     */
    @Override
    public String getFederationToken(String value) {
        String passphrase = settings.getString(Keys.federation.passphrase, "");
        return StringUtils.getSHA1(passphrase + "-" + value);
    }
    /**
     * Compares the provided token with this Gitblit instance's tokens and
     * determines if the requested permission may be granted to the token.
     *
     * @param req
     * @param token
     * @return true if the request can be executed
     */
    @Override
    public boolean validateFederationRequest(FederationRequest req, String token) {
        String all = getFederationToken(FederationToken.ALL);
        String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
        String jur = getFederationToken(FederationToken.REPOSITORIES);
        switch (req) {
        case PULL_REPOSITORIES:
            return token.equals(all) || token.equals(unr) || token.equals(jur);
        case PULL_USERS:
        case PULL_TEAMS:
            return token.equals(all) || token.equals(unr);
        case PULL_SETTINGS:
        case PULL_SCRIPTS:
            return token.equals(all);
        default:
            break;
        }
        return false;
    }
    /**
     * Acknowledge and cache the status of a remote Gitblit instance.
     *
     * @param identification
     *            the identification of the pulling Gitblit instance
     * @param registration
     *            the registration from the pulling Gitblit instance
     * @return true if acknowledged
     */
    @Override
    public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
        // reset the url to the identification of the pulling Gitblit instance
        registration.url = identification;
        String id = identification;
        if (!StringUtils.isEmpty(registration.folder)) {
            id += "-" + registration.folder;
        }
        federationPullResults.put(id, registration);
        return true;
    }
    /**
     * Returns the list of registration results.
     *
     * @return the list of registration results
     */
    @Override
    public List<FederationModel> getFederationResultRegistrations() {
        return new ArrayList<FederationModel>(federationPullResults.values());
    }
    /**
     * Submit a federation proposal. The proposal is cached locally and the
     * Gitblit administrator(s) are notified via email.
     *
     * @param proposal
     *            the proposal
     * @param gitblitUrl
     *            the url of your gitblit instance to send an email to
     *            administrators
     * @return true if the proposal was submitted
     */
    @Override
    public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
        // convert proposal to json
        String json = JsonUtils.toJsonString(proposal);
        try {
            // make the proposals folder
            File proposalsFolder = getProposalsFolder();
            proposalsFolder.mkdirs();
            // cache json to a file
            File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
            com.gitblit.utils.FileUtils.writeContent(file, json);
        } catch (Exception e) {
            logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
        }
        // send an email, if possible
        notificationManager.sendMailToAdministrators("Federation proposal from " + proposal.url,
                "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token);
        return true;
    }
    /**
     * Returns the list of pending federation proposals
     *
     * @return list of federation proposals
     */
    @Override
    public List<FederationProposal> getPendingFederationProposals() {
        List<FederationProposal> list = new ArrayList<FederationProposal>();
        File folder = getProposalsFolder();
        if (folder.exists()) {
            File[] files = folder.listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return file.isFile()
                            && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
                }
            });
            for (File file : files) {
                String json = com.gitblit.utils.FileUtils.readContent(file, null);
                FederationProposal proposal = JsonUtils.fromJsonString(json,
                        FederationProposal.class);
                list.add(proposal);
            }
        }
        return list;
    }
    /**
     * Get repositories for the specified token.
     *
     * @param gitblitUrl
     *            the base url of this gitblit instance
     * @param token
     *            the federation token
     * @return a map of <cloneurl, RepositoryModel>
     */
    @Override
    public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
        Map<String, String> federationSets = new HashMap<String, String>();
        for (String set : settings.getStrings(Keys.federation.sets)) {
            federationSets.put(getFederationToken(set), set);
        }
        // Determine the Gitblit clone url
        StringBuilder sb = new StringBuilder();
        sb.append(gitblitUrl);
        sb.append(Constants.GIT_PATH);
        sb.append("{0}");
        String cloneUrl = sb.toString();
        // Retrieve all available repositories
        UserModel user = getFederationUser();
        List<RepositoryModel> list = repositoryManager.getRepositoryModels(user);
        // create the [cloneurl, repositoryModel] map
        Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
        for (RepositoryModel model : list) {
            // by default, setup the url for THIS repository
            String url = MessageFormat.format(cloneUrl, model.name);
            switch (model.federationStrategy) {
            case EXCLUDE:
                // skip this repository
                continue;
            case FEDERATE_ORIGIN:
                // federate the origin, if it is defined
                if (!StringUtils.isEmpty(model.origin)) {
                    url = model.origin;
                }
                break;
            default:
                break;
            }
            if (federationSets.containsKey(token)) {
                // include repositories only for federation set
                String set = federationSets.get(token);
                if (model.federationSets.contains(set)) {
                    repositories.put(url, model);
                }
            } else {
                // standard federation token for ALL
                repositories.put(url, model);
            }
        }
        return repositories;
    }
    /**
     * Creates a proposal from the token.
     *
     * @param gitblitUrl
     *            the url of this Gitblit instance
     * @param token
     * @return a potential proposal
     */
    @Override
    public FederationProposal createFederationProposal(String gitblitUrl, String token) {
        FederationToken tokenType = FederationToken.REPOSITORIES;
        for (FederationToken type : FederationToken.values()) {
            if (token.equals(getFederationToken(type))) {
                tokenType = type;
                break;
            }
        }
        Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
        FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
                repositories);
        return proposal;
    }
    /**
     * Returns the proposal identified by the supplied token.
     *
     * @param token
     * @return the specified proposal or null
     */
    @Override
    public FederationProposal getPendingFederationProposal(String token) {
        List<FederationProposal> list = getPendingFederationProposals();
        for (FederationProposal proposal : list) {
            if (proposal.token.equals(token)) {
                return proposal;
            }
        }
        return null;
    }
    /**
     * Deletes a pending federation proposal.
     *
     * @param a
     *            proposal
     * @return true if the proposal was deleted
     */
    @Override
    public boolean deletePendingFederationProposal(FederationProposal proposal) {
        File folder = getProposalsFolder();
        File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
        return file.delete();
    }
}
src/main/java/com/gitblit/manager/GitblitManager.java
New file
@@ -0,0 +1,463 @@
/*
 * Copyright 2013 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.manager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.GitBlitException;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.GitClientApplication;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.SettingModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
public class GitblitManager implements IGitblitManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
    private final IStoredSettings settings;
    private final IRuntimeManager runtimeManager;
    private final IUserManager userManager;
    private final IRepositoryManager repositoryManager;
    public GitblitManager(
            IRuntimeManager runtimeManager,
            IUserManager userManager,
            IRepositoryManager repositoryManager) {
        this.settings = runtimeManager.getSettings();
        this.runtimeManager = runtimeManager;
        this.userManager = userManager;
        this.repositoryManager = repositoryManager;
    }
    @Override
    public GitblitManager start() {
        loadSettingModels(runtimeManager.getSettingsModel());
        return this;
    }
    @Override
    public GitblitManager stop() {
        return this;
    }
    /**
     * Parse the properties file and aggregate all the comments by the setting
     * key. A setting model tracks the current value, the default value, the
     * description of the setting and and directives about the setting.
     *
     * @return Map<String, SettingModel>
     */
    private void loadSettingModels(ServerSettings settingsModel) {
        // this entire "supports" concept will go away with user service refactoring
        UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT);
        externalUser.password = Constants.EXTERNAL_ACCOUNT;
        settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser);
        settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser);
        settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser);
        settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser);
        try {
            // Read bundled Gitblit properties to extract setting descriptions.
            // This copy is pristine and only used for populating the setting
            // models map.
            InputStream is = getClass().getResourceAsStream("/reference.properties");
            BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
            StringBuilder description = new StringBuilder();
            SettingModel setting = new SettingModel();
            String line = null;
            while ((line = propertiesReader.readLine()) != null) {
                if (line.length() == 0) {
                    description.setLength(0);
                    setting = new SettingModel();
                } else {
                    if (line.charAt(0) == '#') {
                        if (line.length() > 1) {
                            String text = line.substring(1).trim();
                            if (SettingModel.CASE_SENSITIVE.equals(text)) {
                                setting.caseSensitive = true;
                            } else if (SettingModel.RESTART_REQUIRED.equals(text)) {
                                setting.restartRequired = true;
                            } else if (SettingModel.SPACE_DELIMITED.equals(text)) {
                                setting.spaceDelimited = true;
                            } else if (text.startsWith(SettingModel.SINCE)) {
                                try {
                                    setting.since = text.split(" ")[1];
                                } catch (Exception e) {
                                    setting.since = text;
                                }
                            } else {
                                description.append(text);
                                description.append('\n');
                            }
                        }
                    } else {
                        String[] kvp = line.split("=", 2);
                        String key = kvp[0].trim();
                        setting.name = key;
                        setting.defaultValue = kvp[1].trim();
                        setting.currentValue = setting.defaultValue;
                        setting.description = description.toString().trim();
                        settingsModel.add(setting);
                        description.setLength(0);
                        setting = new SettingModel();
                    }
                }
            }
            propertiesReader.close();
        } catch (NullPointerException e) {
            logger.error("Failed to find resource copy of gitblit.properties");
        } catch (IOException e) {
            logger.error("Failed to load resource copy of gitblit.properties");
        }
    }
    /**
     * Returns a list of repository URLs and the user access permission.
     *
     * @param request
     * @param user
     * @param repository
     * @return a list of repository urls
     */
    @Override
    public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
        if (user == null) {
            user = UserModel.ANONYMOUS;
        }
        String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
        List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
        // http/https url
        if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
            AccessPermission permission = user.getRepositoryPermission(repository).permission;
            if (permission.exceeds(AccessPermission.NONE)) {
                list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
            }
        }
        // git daemon url
        String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
        if (!StringUtils.isEmpty(gitDaemonUrl)) {
            AccessPermission permission = getGitDaemonAccessPermission(user, repository);
            if (permission.exceeds(AccessPermission.NONE)) {
                list.add(new RepositoryUrl(gitDaemonUrl, permission));
            }
        }
        // add all other urls
        // {0} = repository
        // {1} = username
        for (String url : settings.getStrings(Keys.web.otherUrls)) {
            if (url.contains("{1}")) {
                // external url requires username, only add url IF we have one
                if (!StringUtils.isEmpty(username)) {
                    list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
                }
            } else {
                // external url does not require username
                list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
            }
        }
        return list;
    }
    protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
        StringBuilder sb = new StringBuilder();
        sb.append(HttpUtils.getGitblitURL(request));
        sb.append(Constants.GIT_PATH);
        sb.append(repository.name);
        // inject username into repository url if authentication is required
        if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
                && !StringUtils.isEmpty(username)) {
            sb.insert(sb.indexOf("://") + 3, username + "@");
        }
        return sb.toString();
    }
    protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
//        if (gitDaemon != null) {
//            String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
//            if (bindInterface.equals("localhost")
//                    && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
//                // git daemon is bound to localhost and the request is from elsewhere
//                return null;
//            }
//            if (user.canClone(repository)) {
//                String servername = request.getServerName();
//                String url = gitDaemon.formatUrl(servername, repository.name);
//                return url;
//            }
//        }
        return null;
    }
    protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
//        if (gitDaemon != null && user.canClone(repository)) {
//            AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
//            if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
//                if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
//                    // can not authenticate clone via anonymous git protocol
//                    gitDaemonPermission = AccessPermission.NONE;
//                } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
//                    // can not authenticate push via anonymous git protocol
//                    gitDaemonPermission = AccessPermission.CLONE;
//                } else {
//                    // normal user permission
//                }
//            }
//            return gitDaemonPermission;
//        }
        return AccessPermission.NONE;
    }
    /**
     * Returns the list of custom client applications to be used for the
     * repository url panel;
     *
     * @return a collection of client applications
     */
    @Override
    public Collection<GitClientApplication> getClientApplications() {
        // prefer user definitions, if they exist
        File userDefs = new File(runtimeManager.getBaseFolder(), "clientapps.json");
        if (userDefs.exists()) {
            Date lastModified = new Date(userDefs.lastModified());
            if (clientApplications.hasCurrent("user", lastModified)) {
                return clientApplications.getObject("user");
            } else {
                // (re)load user definitions
                try {
                    InputStream is = new FileInputStream(userDefs);
                    Collection<GitClientApplication> clients = readClientApplications(is);
                    is.close();
                    if (clients != null) {
                        clientApplications.updateObject("user", lastModified, clients);
                        return clients;
                    }
                } catch (IOException e) {
                    logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
                }
            }
        }
        // no user definitions, use system definitions
        if (!clientApplications.hasCurrent("system", new Date(0))) {
            try {
                InputStream is = getClass().getResourceAsStream("/clientapps.json");
                Collection<GitClientApplication> clients = readClientApplications(is);
                is.close();
                if (clients != null) {
                    clientApplications.updateObject("system", new Date(0), clients);
                }
            } catch (IOException e) {
                logger.error("Failed to deserialize clientapps.json resource!", e);
            }
        }
        return clientApplications.getObject("system");
    }
    private Collection<GitClientApplication> readClientApplications(InputStream is) {
        try {
            Type type = new TypeToken<Collection<GitClientApplication>>() {
            }.getType();
            InputStreamReader reader = new InputStreamReader(is);
            Gson gson = JsonUtils.gson();
            Collection<GitClientApplication> links = gson.fromJson(reader, type);
            return links;
        } catch (JsonIOException e) {
            logger.error("Error deserializing client applications!", e);
        } catch (JsonSyntaxException e) {
            logger.error("Error deserializing client applications!", e);
        }
        return null;
    }
    /**
     * Creates a personal fork of the specified repository. The clone is view
     * restricted by default and the owner of the source repository is given
     * access to the clone.
     *
     * @param repository
     * @param user
     * @return the repository model of the fork, if successful
     * @throws GitBlitException
     */
    @Override
    public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
        String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
        String fromUrl = MessageFormat.format("file://{0}/{1}", repositoryManager.getRepositoriesFolder().getAbsolutePath(), repository.name);
        // clone the repository
        try {
            JGitUtils.cloneRepository(repositoryManager.getRepositoriesFolder(), cloneName, fromUrl, true, null);
        } catch (Exception e) {
            throw new GitBlitException(e);
        }
        // create a Gitblit repository model for the clone
        RepositoryModel cloneModel = repository.cloneAs(cloneName);
        // owner has REWIND/RW+ permissions
        cloneModel.addOwner(user.username);
        repositoryManager.updateRepositoryModel(cloneName, cloneModel, false);
        // add the owner of the source repository to the clone's access list
        if (!ArrayUtils.isEmpty(repository.owners)) {
            for (String owner : repository.owners) {
                UserModel originOwner = userManager.getUserModel(owner);
                if (originOwner != null) {
                    originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
                    updateUserModel(originOwner.username, originOwner, false);
                }
            }
        }
        // grant origin's user list clone permission to fork
        List<String> users = repositoryManager.getRepositoryUsers(repository);
        List<UserModel> cloneUsers = new ArrayList<UserModel>();
        for (String name : users) {
            if (!name.equalsIgnoreCase(user.username)) {
                UserModel cloneUser = userManager.getUserModel(name);
                if (cloneUser.canClone(repository)) {
                    // origin user can clone origin, grant clone access to fork
                    cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
                }
                cloneUsers.add(cloneUser);
            }
        }
        userManager.updateUserModels(cloneUsers);
        // grant origin's team list clone permission to fork
        List<String> teams = repositoryManager.getRepositoryTeams(repository);
        List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
        for (String name : teams) {
            TeamModel cloneTeam = userManager.getTeamModel(name);
            if (cloneTeam.canClone(repository)) {
                // origin team can clone origin, grant clone access to fork
                cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
            }
            cloneTeams.add(cloneTeam);
        }
        userManager.updateTeamModels(cloneTeams);
        // add this clone to the cached model
        repositoryManager.addToCachedRepositoryList(cloneModel);
        return cloneModel;
    }
    /**
     * Adds/updates a complete user object keyed by username. This method allows
     * for renaming a user.
     *
     * @see IUserService.updateUserModel(String, UserModel)
     * @param username
     * @param user
     * @param isCreate
     * @throws GitBlitException
     */
    @Override
    public void updateUserModel(String username, UserModel user, boolean isCreate)
            throws GitBlitException {
        if (!username.equalsIgnoreCase(user.username)) {
            if (userManager.getUserModel(user.username) != null) {
                throw new GitBlitException(MessageFormat.format(
                        "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
                        user.username));
            }
            // rename repositories and owner fields for all repositories
            for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) {
                if (model.isUsersPersonalRepository(username)) {
                    // personal repository
                    model.addOwner(user.username);
                    String oldRepositoryName = model.name;
                    model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length());
                    model.projectPath = user.getPersonalPath();
                    repositoryManager.updateRepositoryModel(oldRepositoryName, model, false);
                } else if (model.isOwner(username)) {
                    // common/shared repo
                    model.addOwner(user.username);
                    repositoryManager.updateRepositoryModel(model.name, model, false);
                }
            }
        }
        if (!userManager.updateUserModel(username, user)) {
            throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
        }
    }
    /**
     * Updates the TeamModel object for the specified name.
     *
     * @param teamname
     * @param team
     * @param isCreate
     */
    @Override
    public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
            throws GitBlitException {
        if (!teamname.equalsIgnoreCase(team.name)) {
            if (userManager.getTeamModel(team.name) != null) {
                throw new GitBlitException(MessageFormat.format(
                        "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
                        team.name));
            }
        }
        if (!userManager.updateTeamModel(teamname, team)) {
            throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
        }
    }
}
src/main/java/com/gitblit/manager/IFederationManager.java
@@ -27,7 +27,7 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
public interface IFederationManager {
public interface IFederationManager extends IManager {
    /**
     * Returns the path of the proposals folder. This method checks to see if
src/main/java/com/gitblit/manager/IGitblitManager.java
@@ -27,7 +27,7 @@
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
public interface IGitblitManager {
public interface IGitblitManager extends IManager {
    /**
     * Returns a list of repository URLs and the user access permission.
src/main/java/com/gitblit/manager/IManager.java
@@ -17,7 +17,8 @@
public interface IManager {
    IManager setup();
    IManager start();
    IManager stop();
}
src/main/java/com/gitblit/manager/IServicesManager.java
New file
@@ -0,0 +1,21 @@
/*
 * Copyright 2013 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.manager;
public interface IServicesManager extends IManager {
}
src/main/java/com/gitblit/manager/NotificationManager.java
@@ -58,18 +58,19 @@
    }
    @Override
    public IManager setup() {
    public NotificationManager start() {
        if (mailExecutor.isReady()) {
            logger.info("Mail executor is scheduled to process the message queue every 2 minutes.");
            scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
            int period = 2;
            logger.info("Mail service will process the queue every {} minutes.", period);
            scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, period, TimeUnit.MINUTES);
        } else {
            logger.warn("Mail server is not properly configured.  Mail services disabled.");
            logger.warn("Mail service disabled.");
        }
        return this;
    }
    @Override
    public IManager stop() {
    public NotificationManager stop() {
        scheduledExecutor.shutdownNow();
        return this;
    }
src/main/java/com/gitblit/manager/ProjectManager.java
@@ -80,7 +80,7 @@
    }
    @Override
    public IManager setup() {
    public ProjectManager start() {
        // load and cache the project metadata
        projectConfigs = new FileBasedConfig(runtimeManager.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
        getProjectConfigs();
@@ -89,7 +89,7 @@
    }
    @Override
    public IManager stop() {
    public ProjectManager stop() {
        return this;
    }
src/main/java/com/gitblit/manager/RepositoryManager.java
@@ -135,8 +135,8 @@
    }
    @Override
    public IManager setup() {
        logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath());
    public RepositoryManager start() {
        logger.info("Repositories folder : {}", repositoriesFolder.getAbsolutePath());
        // initialize utilities
        String prefix = settings.getString(Keys.git.userRepositoryPrefix, "~");
@@ -147,7 +147,7 @@
        // build initial repository list
        if (settings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
            logger.info("Identifying available repositories...");
            logger.info("Identifying repositories...");
            getRepositoryList();
        }
@@ -161,7 +161,7 @@
    }
    @Override
    public IManager stop() {
    public RepositoryManager stop() {
        scheduledExecutor.shutdownNow();
        luceneExecutor.close();
        gcExecutor.close();
@@ -1645,15 +1645,16 @@
    protected void configureLuceneIndexing() {
        luceneExecutor = new LuceneExecutor(settings, this);
        scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2,  TimeUnit.MINUTES);
        logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes.");
        int period = 2;
        scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, period,  TimeUnit.MINUTES);
        logger.info("Lucene will process indexed branches every {} minutes.", period);
    }
    protected void configureGarbageCollector() {
        // schedule gc engine
        gcExecutor = new GCExecutor(settings, this);
        if (gcExecutor.isReady()) {
            logger.info("GC executor is scheduled to scan repositories every 24 hours.");
            logger.info("Garbage Collector (GC) will scan repositories every 24 hours.");
            Calendar c = Calendar.getInstance();
            c.set(Calendar.HOUR_OF_DAY, settings.getInteger(Keys.git.garbageCollectionHour, 0));
            c.set(Calendar.MINUTE, 0);
@@ -1673,6 +1674,8 @@
            }
            logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when));
            scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60 * 24, TimeUnit.MINUTES);
        } else {
            logger.info("Garbage Collector (GC) is disabled.");
        }
    }
@@ -1685,8 +1688,10 @@
            }
            int delay = 1;
            scheduledExecutor.scheduleAtFixedRate(mirrorExecutor, delay, mins,  TimeUnit.MINUTES);
            logger.info("Mirror executor is scheduled to fetch updates every {} minutes.", mins);
            logger.info("Mirror service will fetch updates every {} minutes.", mins);
            logger.info("Next scheduled mirror fetch is in {} minutes", delay);
        } else {
            logger.info("Mirror service is disabled.");
        }
    }
@@ -1717,12 +1722,12 @@
    protected void configureCommitCache() {
        int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14);
        if (daysToCache <= 0) {
            logger.info("commit cache disabled");
            logger.info("Commit cache is disabled");
        } else {
            long start = System.nanoTime();
            long repoCount = 0;
            long commitCount = 0;
            logger.info(MessageFormat.format("preparing {0} day commit cache. please wait...", daysToCache));
            logger.info(MessageFormat.format("Preparing {0} day commit cache. please wait...", daysToCache));
            CommitCache.instance().setCacheDays(daysToCache);
            Date cutoff = CommitCache.instance().getCutoffDate();
            for (String repositoryName : getRepositoryList()) {
src/main/java/com/gitblit/manager/RuntimeManager.java
@@ -40,23 +40,29 @@
    private final ServerStatus serverStatus;
    private TimeZone timezone;
    private final ServerSettings settingsModel;
    private File baseFolder;
    private ServerSettings settingsModel;
    private TimeZone timezone;
    public RuntimeManager(IStoredSettings settings) {
        this(settings, null);
    }
    public RuntimeManager(IStoredSettings settings, File baseFolder) {
        this.settings = settings;
        this.settingsModel = new ServerSettings();
        this.serverStatus = new ServerStatus();
        this.baseFolder = baseFolder == null ? new File("") : baseFolder;
    }
    @Override
    public RuntimeManager setup() {
        logger.info("Gitblit settings        = " + settings.toString());
        logTimezone("JVM", TimeZone.getDefault());
        logTimezone(Constants.NAME, getTimezone());
    public RuntimeManager start() {
        logger.info("Basefolder  : " + baseFolder.getAbsolutePath());
        logger.info("Settings    : " + settings.toString());
        logTimezone("JVM timezone: ", TimeZone.getDefault());
        logTimezone("App timezone: ", getTimezone());
        return this;
    }
@@ -121,7 +127,7 @@
    @Override
    public TimeZone getTimezone() {
        if (timezone == null) {
            String tzid = settings.getString("web.timezone", null);
            String tzid = settings.getString(Keys.web.timezone, null);
            if (StringUtils.isEmpty(tzid)) {
                timezone = TimeZone.getDefault();
                return timezone;
@@ -135,7 +141,7 @@
        SimpleDateFormat df = new SimpleDateFormat("z Z");
        df.setTimeZone(zone);
        String offset = df.format(new Date());
        logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")");
        logger.info("{}{} ({})", new Object [] { type, zone.getID(), offset });
    }
    /**
src/main/java/com/gitblit/manager/ServicesManager.java
New file
@@ -0,0 +1,188 @@
/*
 * Copyright 2013 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.manager;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.FederationToken;
import com.gitblit.FederationPullExecutor;
import com.gitblit.Gitblit;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.fanout.FanoutNioService;
import com.gitblit.fanout.FanoutService;
import com.gitblit.fanout.FanoutSocketService;
import com.gitblit.git.GitDaemon;
import com.gitblit.models.FederationModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
/**
 * Services manager manages long-running services/processes that either have no
 * direct relation to other managers OR require really high-level manager
 * integration (i.e. a Gitblit instance).
 *
 * @author James Moger
 *
 */
public class ServicesManager implements IServicesManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
    private final IStoredSettings settings;
    private final Gitblit gitblit;
    private FanoutService fanoutService;
    private GitDaemon gitDaemon;
    public ServicesManager(Gitblit gitblit) {
        this.settings = gitblit.getSettings();
        this.gitblit = gitblit;
    }
    @Override
    public ServicesManager start() {
        configureFederation();
        configureFanout();
        configureGitDaemon();
        return this;
    }
    @Override
    public ServicesManager stop() {
        scheduledExecutor.shutdownNow();
        if (fanoutService != null) {
            fanoutService.stop();
        }
        if (gitDaemon != null) {
            gitDaemon.stop();
        }
        return this;
    }
    protected void configureFederation() {
        boolean validPassphrase = true;
        String passphrase = settings.getString(Keys.federation.passphrase, "");
        if (StringUtils.isEmpty(passphrase)) {
            logger.info("Federation passphrase is blank! This server can not be PULLED from.");
            validPassphrase = false;
        }
        if (validPassphrase) {
            // standard tokens
            for (FederationToken tokenType : FederationToken.values()) {
                logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
                        gitblit.getFederationToken(tokenType)));
            }
            // federation set tokens
            for (String set : settings.getStrings(Keys.federation.sets)) {
                logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
                        gitblit.getFederationToken(set)));
            }
        }
        // Schedule or run the federation executor
        List<FederationModel> registrations = gitblit.getFederationRegistrations();
        if (registrations.size() > 0) {
            FederationPuller executor = new FederationPuller(registrations);
            scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
        }
    }
    protected void configureGitDaemon() {
        int port = settings.getInteger(Keys.git.daemonPort, 0);
        String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
        if (port > 0) {
            try {
                gitDaemon = new GitDaemon(gitblit);
                gitDaemon.start();
            } catch (IOException e) {
                gitDaemon = null;
                logger.error(MessageFormat.format("Failed to start Git Daemon on {0}:{1,number,0}", bindInterface, port), e);
            }
        } else {
            logger.info("Git Daemon is disabled.");
        }
    }
    protected void configureFanout() {
        // startup Fanout PubSub service
        if (settings.getInteger(Keys.fanout.port, 0) > 0) {
            String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
            int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
            boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
            int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
            if (useNio) {
                if (StringUtils.isEmpty(bindInterface)) {
                    fanoutService = new FanoutNioService(port);
                } else {
                    fanoutService = new FanoutNioService(bindInterface, port);
                }
            } else {
                if (StringUtils.isEmpty(bindInterface)) {
                    fanoutService = new FanoutSocketService(port);
                } else {
                    fanoutService = new FanoutSocketService(bindInterface, port);
                }
            }
            fanoutService.setConcurrentConnectionLimit(limit);
            fanoutService.setAllowAllChannelAnnouncements(false);
            fanoutService.start();
        } else {
            logger.info("Fanout PubSub service is disabled.");
        }
    }
    private class FederationPuller extends FederationPullExecutor {
        public FederationPuller(FederationModel registration) {
            super(Arrays.asList(registration));
        }
        public FederationPuller(List<FederationModel> registrations) {
            super(registrations);
        }
        @Override
        public void reschedule(FederationModel registration) {
            // schedule the next pull
            int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency);
            registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L));
            scheduledExecutor.schedule(new FederationPuller(registration), mins, TimeUnit.MINUTES);
            logger.info(MessageFormat.format(
                    "Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}",
                    registration.name, registration.url, registration.nextPull));
        }
    }
}
src/main/java/com/gitblit/manager/SessionManager.java
@@ -65,7 +65,7 @@
    }
    @Override
    public IManager setup() {
    public SessionManager start() {
        List<String> services = settings.getStrings("realm.authenticationServices");
        for (String service : services) {
            // TODO populate authentication services here
@@ -74,7 +74,7 @@
    }
    @Override
    public IManager stop() {
    public SessionManager stop() {
        return this;
    }
src/main/java/com/gitblit/manager/UserManager.java
@@ -64,13 +64,13 @@
     * @param userService
     */
    public void setUserService(IUserService userService) {
        logger.info("Setting up user service " + userService.toString());
        logger.info("UserService: " + userService.toString());
        this.userService = userService;
        this.userService.setup(runtimeManager);
    }
    @Override
    public IManager setup() {
    public UserManager start() {
        if (this.userService == null) {
            String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
            IUserService service = null;
@@ -114,7 +114,7 @@
    }
    @Override
    public IManager stop() {
    public UserManager stop() {
        return this;
    }
src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
@@ -137,7 +137,7 @@
    }
    @Override
    public IRuntimeManager setup() {
    public IRuntimeManager start() {
        return this;
    }
}