James Moger
2014-07-02 7d3a31514afbe88664081b4ea57cd7939de99014
Extract services manager into a top-level injectable manager
2 files added
16 files modified
821 ■■■■ changed files
src/main/java/com/gitblit/GitBlit.java 181 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/guice/CoreModule.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/guice/WorkQueueProvider.java 57 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/GitblitManager.java 84 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IGitblit.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IRuntimeManager.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IServicesManager.java 75 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/RepositoryManager.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/RuntimeManager.java 51 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/ServicesManager.java 226 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/GitblitContext.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.java 20 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitblitWicketApp.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TicketPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/UserPage.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java 4 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlit.java
@@ -25,10 +25,7 @@
import java.util.Set;
import com.google.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.Transport;
import com.gitblit.manager.GitblitManager;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
@@ -39,9 +36,7 @@
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.manager.ServicesManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.UserModel;
import com.gitblit.tickets.ITicketService;
import com.gitblit.tickets.NullTicketService;
@@ -61,8 +56,6 @@
public class GitBlit extends GitblitManager {
    private final Injector injector;
    private final ServicesManager servicesManager;
    private ITicketService ticketService;
@@ -89,15 +82,11 @@
                federationManager);
        this.injector = Guice.createInjector(getModules());
        this.servicesManager = new ServicesManager(this);
    }
    @Override
    public GitBlit start() {
        super.start();
        logger.info("Starting services manager...");
        servicesManager.start();
        configureTicketService();
        return this;
    }
@@ -105,182 +94,12 @@
    @Override
    public GitBlit stop() {
        super.stop();
        servicesManager.stop();
        ticketService.stop();
        return this;
    }
    @Override
    public boolean isServingRepositories() {
        return servicesManager.isServingRepositories();
    }
    @Override
    public boolean isServingHTTP() {
        return servicesManager.isServingHTTP();
    }
    @Override
    public boolean isServingGIT() {
        return servicesManager.isServingGIT();
    }
    @Override
    public boolean isServingSSH() {
        return servicesManager.isServingSSH();
    }
    protected AbstractModule [] getModules() {
        return new AbstractModule [] { new GitBlitModule()};
    }
    protected boolean acceptPush(Transport byTransport) {
        if (byTransport == null) {
            logger.info("Unknown transport, push rejected!");
            return false;
        }
        Set<Transport> transports = new HashSet<Transport>();
        for (String value : getSettings().getStrings(Keys.git.acceptedPushTransports)) {
            Transport transport = Transport.fromString(value);
            if (transport == null) {
                logger.info(String.format("Ignoring unknown registered transport %s", value));
                continue;
            }
            transports.add(transport);
        }
        if (transports.isEmpty()) {
            // no transports are explicitly specified, all are acceptable
            return true;
        }
        // verify that the transport is permitted
        return transports.contains(byTransport);
    }
    /**
     * 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)) {
                Transport transport = Transport.fromString(request.getScheme());
                if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) {
                    // downgrade the repo permission for this transport
                    // because it is not an acceptable PUSH transport
                    permission = AccessPermission.CLONE;
                }
                list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
            }
        }
        // ssh daemon url
        String sshDaemonUrl = servicesManager.getSshDaemonUrl(request, user, repository);
        if (!StringUtils.isEmpty(sshDaemonUrl)) {
            AccessPermission permission = user.getRepositoryPermission(repository).permission;
            if (permission.exceeds(AccessPermission.NONE)) {
                if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) {
                    // downgrade the repo permission for this transport
                    // because it is not an acceptable PUSH transport
                    permission = AccessPermission.CLONE;
                }
                list.add(new RepositoryUrl(sshDaemonUrl, permission));
            }
        }
        // git daemon url
        String gitDaemonUrl = servicesManager.getGitDaemonUrl(request, user, repository);
        if (!StringUtils.isEmpty(gitDaemonUrl)) {
            AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository);
            if (permission.exceeds(AccessPermission.NONE)) {
                if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) {
                    // downgrade the repo permission for this transport
                    // because it is not an acceptable PUSH transport
                    permission = AccessPermission.CLONE;
                }
                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));
            }
        }
        // sort transports by highest permission and then by transport security
        Collections.sort(list, new Comparator<RepositoryUrl>() {
            @Override
            public int compare(RepositoryUrl o1, RepositoryUrl o2) {
                if (!o1.isExternal() && o2.isExternal()) {
                    // prefer Gitblit over external
                    return -1;
                } else if (o1.isExternal() && !o2.isExternal()) {
                    // prefer Gitblit over external
                    return 1;
                } else if (o1.isExternal() && o2.isExternal()) {
                    // sort by Transport ordinal
                    return o1.transport.compareTo(o2.transport);
                } else if (o1.permission.exceeds(o2.permission)) {
                    // prefer highest permission
                    return -1;
                } else if (o2.permission.exceeds(o1.permission)) {
                    // prefer highest permission
                    return 1;
                }
                // prefer more secure transports
                return o1.transport.compareTo(o2.transport);
            }
        });
        // consider the user's transport preference
        RepositoryUrl preferredUrl = null;
        Transport preferredTransport = user.getPreferences().getTransport();
        if (preferredTransport != null) {
            Iterator<RepositoryUrl> itr = list.iterator();
            while (itr.hasNext()) {
                RepositoryUrl url = itr.next();
                if (url.transport.equals(preferredTransport)) {
                    itr.remove();
                    preferredUrl = url;
                    break;
                }
            }
        }
        if (preferredUrl != null) {
            list.add(0, preferredUrl);
        }
        return list;
    }
    /**
src/main/java/com/gitblit/guice/CoreModule.java
@@ -31,18 +31,21 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.manager.NotificationManager;
import com.gitblit.manager.PluginManager;
import com.gitblit.manager.ProjectManager;
import com.gitblit.manager.RepositoryManager;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.manager.ServicesManager;
import com.gitblit.manager.UserManager;
import com.gitblit.transport.ssh.FileKeyManager;
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.transport.ssh.MemoryKeyManager;
import com.gitblit.transport.ssh.NullKeyManager;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.WorkQueue;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
@@ -59,6 +62,9 @@
        bind(IStoredSettings.class).toInstance(new FileSettings());
        // bind complex providers
        bind(WorkQueue.class).toProvider(WorkQueueProvider.class);
        // core managers
        bind(IRuntimeManager.class).to(RuntimeManager.class).in(Singleton.class);
        bind(IPluginManager.class).to(PluginManager.class).in(Singleton.class);
@@ -71,6 +77,9 @@
        // the monolithic manager
        bind(IGitblit.class).to(GitBlit.class).in(Singleton.class);
        // manager for long-running daemons and services
        bind(IServicesManager.class).to(ServicesManager.class);
    }
    @Provides
src/main/java/com/gitblit/guice/WorkQueueProvider.java
New file
@@ -0,0 +1,57 @@
/*
 * Copyright 2014 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.guice;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.utils.IdGenerator;
import com.gitblit.utils.WorkQueue;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
/**
 * Provides a lazily-instantiated WorkQueue configured from IStoredSettings.
 *
 * @author James Moger
 *
 */
@Singleton
public class WorkQueueProvider implements Provider<WorkQueue> {
    private final IRuntimeManager runtimeManager;
    private volatile WorkQueue workQueue;
    @Inject
    public WorkQueueProvider(IRuntimeManager runtimeManager) {
        this.runtimeManager = runtimeManager;
    }
    @Override
    public synchronized WorkQueue get() {
        if (workQueue != null) {
            return workQueue;
        }
        IStoredSettings settings = runtimeManager.getSettings();
        int defaultThreadPoolSize = settings.getInteger(Keys.execution.defaultThreadPoolSize, 1);
        IdGenerator idGenerator = new IdGenerator();
        workQueue = new WorkQueue(idGenerator, defaultThreadPoolSize);
        return workQueue;
    }
}
src/main/java/com/gitblit/manager/GitblitManager.java
@@ -49,12 +49,10 @@
import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationToken;
import com.gitblit.GitBlitException;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
@@ -68,7 +66,6 @@
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.SearchResult;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
@@ -79,7 +76,6 @@
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.transport.ssh.SshKey;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
@@ -351,66 +347,6 @@
    }
    /**
     * 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));
            }
        }
        // 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) {
        String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
        if (StringUtils.isEmpty(gitblitUrl)) {
            gitblitUrl = HttpUtils.getGitblitURL(request);
        }
        StringBuilder sb = new StringBuilder();
        sb.append(gitblitUrl);
        sb.append(Constants.R_PATH);
        sb.append(repository.name);
        // inject username into repository url if authentication is required
        if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
                && !StringUtils.isEmpty(username)) {
            sb.insert(sb.indexOf("://") + 3, username + "@");
        }
        return sb.toString();
    }
    /**
     * Returns the list of custom client applications to be used for the
     * repository url panel;
     *
@@ -595,26 +531,6 @@
    @Override
    public ServerSettings getSettingsModel() {
        return runtimeManager.getSettingsModel();
    }
    @Override
    public boolean isServingRepositories() {
        return runtimeManager.isServingRepositories();
    }
    @Override
    public boolean isServingHTTP() {
        return runtimeManager.isServingHTTP();
    }
    @Override
    public boolean isServingGIT() {
        return runtimeManager.isServingGIT();
    }
    @Override
    public boolean isServingSSH() {
        return runtimeManager.isServingSSH();
    }
    @Override
src/main/java/com/gitblit/manager/IGitblit.java
@@ -16,14 +16,10 @@
package com.gitblit.manager;
import java.util.Collection;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.gitblit.GitBlitException;
import com.gitblit.models.GitClientApplication;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.tickets.ITicketService;
@@ -38,17 +34,6 @@
                                    IRepositoryManager,
                                    IProjectManager,
                                    IFederationManager {
    /**
     * Returns a list of repository URLs and the user access permission.
     *
     * @param request
     * @param user
     * @param repository
     * @return a list of repository urls
     * @since 1.4.0
     */
    List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository);
    /**
     * Creates a complete user object.
src/main/java/com/gitblit/manager/IRuntimeManager.java
@@ -51,42 +51,6 @@
    Locale getLocale();
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * or if it is merely a repository viewer.
     *
     * @return true if Gitblit is serving repositories
      * @since 1.4.0
     */
    boolean isServingRepositories();
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over HTTP.
     *
     * @return true if Gitblit is serving repositories over HTTP
      * @since 1.6.0
     */
    boolean isServingHTTP();
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over the GIT Daemon protocol.
     *
     * @return true if Gitblit is serving repositories over the GIT Daemon protocol
      * @since 1.6.0
     */
    boolean isServingGIT();
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over the SSH protocol.
     *
     * @return true if Gitblit is serving repositories over the SSH protocol
      * @since 1.6.0
     */
    boolean isServingSSH();
    /**
     * Determine if this Gitblit instance is running in debug mode
     *
     * @return true if Gitblit is running in debug mode
src/main/java/com/gitblit/manager/IServicesManager.java
New file
@@ -0,0 +1,75 @@
/*
 * Copyright 2014 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.util.List;
import javax.servlet.http.HttpServletRequest;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.UserModel;
public interface IServicesManager extends IManager {
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * or if it is merely a repository viewer.
     *
     * @return true if Gitblit is serving repositories
      * @since 1.7.0
     */
    boolean isServingRepositories();
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over HTTP.
     *
     * @return true if Gitblit is serving repositories over HTTP
      * @since 1.7.0
     */
    boolean isServingHTTP();
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over the GIT Daemon protocol.
     *
     * @return true if Gitblit is serving repositories over the GIT Daemon protocol
      * @since 1.7.0
     */
    boolean isServingGIT();
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over the SSH protocol.
     *
     * @return true if Gitblit is serving repositories over the SSH protocol
      * @since 1.7.0
     */
    boolean isServingSSH();
    /**
     * Returns a list of repository URLs and the user access permission.
     *
     * @param request
     * @param user
     * @param repository
     * @return a list of repository urls
     * @since 1.7.0
     */
    List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository);
}
src/main/java/com/gitblit/manager/RepositoryManager.java
@@ -1937,21 +1937,19 @@
    }
    protected void confirmWriteAccess() {
        if (runtimeManager.isServingRepositories()) {
            try {
                if (!getRepositoriesFolder().exists()) {
                    getRepositoriesFolder().mkdirs();
                }
                File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder());
                file.delete();
            } catch (Exception e) {
                logger.error("");
                logger.error(Constants.BORDER2);
                logger.error("Please check filesystem permissions!");
                logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e);
                logger.error(Constants.BORDER2);
                logger.error("");
        try {
            if (!getRepositoriesFolder().exists()) {
                getRepositoriesFolder().mkdirs();
            }
            File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder());
            file.delete();
        } catch (Exception e) {
            logger.error("");
            logger.error(Constants.BORDER2);
            logger.error("Please check filesystem permissions!");
            logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e);
            logger.error(Constants.BORDER2);
            logger.error("");
        }
    }
}
src/main/java/com/gitblit/manager/RuntimeManager.java
@@ -22,8 +22,6 @@
import java.util.Map;
import java.util.TimeZone;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,8 +32,11 @@
import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel;
import com.gitblit.utils.StringUtils;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
@Singleton
public class RuntimeManager implements IRuntimeManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -121,52 +122,6 @@
        }
//        settingsModel.pushScripts = getAllScripts();
        return settingsModel;
    }
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * or if it is merely a repository viewer.
     *
     * @return true if Gitblit is serving repositories
     */
    @Override
    public boolean isServingRepositories() {
        return isServingHTTP()
                || isServingGIT()
                || isServingSSH();
    }
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over the HTTP protocol.
     *
     * @return true if Gitblit is serving repositories over the HTTP protocol
     */
    @Override
    public boolean isServingHTTP() {
        return settings.getBoolean(Keys.git.enableGitServlet, true);
    }
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over the Git Daemon protocol.
     *
     * @return true if Gitblit is serving repositories over the Git Daemon protocol
     */
    @Override
    public boolean isServingGIT() {
        return settings.getInteger(Keys.git.daemonPort, 0) > 0;
    }
    /**
     * Determine if this Gitblit instance is actively serving git repositories
     * over the SSH protocol.
     *
     * @return true if Gitblit is serving repositories over the SSH protocol
     */
    @Override
    public boolean isServingSSH() {
        return settings.getInteger(Keys.git.sshPort, 0) > 0;
    }
    /**
src/main/java/com/gitblit/manager/ServicesManager.java
@@ -18,9 +18,15 @@
import java.io.IOException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -30,9 +36,11 @@
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.Constants.FederationToken;
import com.gitblit.Constants.Transport;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.fanout.FanoutNioService;
@@ -40,14 +48,18 @@
import com.gitblit.fanout.FanoutSocketService;
import com.gitblit.models.FederationModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.UserModel;
import com.gitblit.service.FederationPullService;
import com.gitblit.transport.git.GitDaemon;
import com.gitblit.transport.ssh.SshDaemon;
import com.gitblit.utils.IdGenerator;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.utils.WorkQueue;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
/**
 * Services manager manages long-running services/processes that either have no
@@ -57,19 +69,18 @@
 * @author James Moger
 *
 */
public class ServicesManager implements IManager {
@Singleton
public class ServicesManager implements IServicesManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
    private final Provider<WorkQueue> workQueueProvider;
    private final IStoredSettings settings;
    private final IGitblit gitblit;
    private final IdGenerator idGenerator;
    private final WorkQueue workQueue;
    private FanoutService fanoutService;
@@ -77,12 +88,16 @@
    private SshDaemon sshDaemon;
    public ServicesManager(IGitblit gitblit) {
        this.settings = gitblit.getSettings();
    @Inject
    public ServicesManager(
            Provider<WorkQueue> workQueueProvider,
            IStoredSettings settings,
            IGitblit gitblit) {
        this.workQueueProvider = workQueueProvider;
        this.settings = settings;
        this.gitblit = gitblit;
        int defaultThreadPoolSize = settings.getInteger(Keys.execution.defaultThreadPoolSize, 1);
        this.idGenerator = new IdGenerator();
        this.workQueue = new WorkQueue(idGenerator, defaultThreadPoolSize);
    }
    @Override
@@ -107,24 +122,181 @@
        if (sshDaemon != null) {
            sshDaemon.stop();
        }
        workQueue.stop();
        workQueueProvider.get().stop();
        return this;
    }
    protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
        String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
        if (StringUtils.isEmpty(gitblitUrl)) {
            gitblitUrl = HttpUtils.getGitblitURL(request);
        }
        StringBuilder sb = new StringBuilder();
        sb.append(gitblitUrl);
        sb.append(Constants.R_PATH);
        sb.append(repository.name);
        // inject username into repository url if authentication is required
        if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
                && !StringUtils.isEmpty(username)) {
            sb.insert(sb.indexOf("://") + 3, username + "@");
        }
        return sb.toString();
    }
    /**
     * Returns 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)) {
                Transport transport = Transport.fromString(request.getScheme());
                if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) {
                    // downgrade the repo permission for this transport
                    // because it is not an acceptable PUSH transport
                    permission = AccessPermission.CLONE;
                }
                list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
            }
        }
        // ssh daemon url
        String sshDaemonUrl = getSshDaemonUrl(request, user, repository);
        if (!StringUtils.isEmpty(sshDaemonUrl)) {
            AccessPermission permission = user.getRepositoryPermission(repository).permission;
            if (permission.exceeds(AccessPermission.NONE)) {
                if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) {
                    // downgrade the repo permission for this transport
                    // because it is not an acceptable PUSH transport
                    permission = AccessPermission.CLONE;
                }
                list.add(new RepositoryUrl(sshDaemonUrl, permission));
            }
        }
        // git daemon url
        String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
        if (!StringUtils.isEmpty(gitDaemonUrl)) {
            AccessPermission permission = getGitDaemonAccessPermission(user, repository);
            if (permission.exceeds(AccessPermission.NONE)) {
                if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) {
                    // downgrade the repo permission for this transport
                    // because it is not an acceptable PUSH transport
                    permission = AccessPermission.CLONE;
                }
                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));
            }
        }
        // sort transports by highest permission and then by transport security
        Collections.sort(list, new Comparator<RepositoryUrl>() {
            @Override
            public int compare(RepositoryUrl o1, RepositoryUrl o2) {
                if (!o1.isExternal() && o2.isExternal()) {
                    // prefer Gitblit over external
                    return -1;
                } else if (o1.isExternal() && !o2.isExternal()) {
                    // prefer Gitblit over external
                    return 1;
                } else if (o1.isExternal() && o2.isExternal()) {
                    // sort by Transport ordinal
                    return o1.transport.compareTo(o2.transport);
                } else if (o1.permission.exceeds(o2.permission)) {
                    // prefer highest permission
                    return -1;
                } else if (o2.permission.exceeds(o1.permission)) {
                    // prefer highest permission
                    return 1;
                }
                // prefer more secure transports
                return o1.transport.compareTo(o2.transport);
            }
        });
        // consider the user's transport preference
        RepositoryUrl preferredUrl = null;
        Transport preferredTransport = user.getPreferences().getTransport();
        if (preferredTransport != null) {
            Iterator<RepositoryUrl> itr = list.iterator();
            while (itr.hasNext()) {
                RepositoryUrl url = itr.next();
                if (url.transport.equals(preferredTransport)) {
                    itr.remove();
                    preferredUrl = url;
                    break;
                }
            }
        }
        if (preferredUrl != null) {
            list.add(0, preferredUrl);
        }
        return list;
    }
    /* (non-Javadoc)
     * @see com.gitblit.manager.IServicesManager#isServingRepositories()
     */
    @Override
    public boolean isServingRepositories() {
        return isServingHTTP()
                || isServingGIT()
                || isServingSSH();
    }
    /* (non-Javadoc)
     * @see com.gitblit.manager.IServicesManager#isServingHTTP()
     */
    @Override
    public boolean isServingHTTP() {
        return settings.getBoolean(Keys.git.enableGitServlet, true);
    }
    /* (non-Javadoc)
     * @see com.gitblit.manager.IServicesManager#isServingGIT()
     */
    @Override
    public boolean isServingGIT() {
        return gitDaemon != null && gitDaemon.isRunning();
    }
    /* (non-Javadoc)
     * @see com.gitblit.manager.IServicesManager#isServingSSH()
     */
    @Override
    public boolean isServingSSH() {
        return sshDaemon != null && sshDaemon.isRunning();
    }
@@ -158,6 +330,32 @@
        }
    }
    protected boolean acceptPush(Transport byTransport) {
        if (byTransport == null) {
            logger.info("Unknown transport, push rejected!");
            return false;
        }
        Set<Transport> transports = new HashSet<Transport>();
        for (String value : settings.getStrings(Keys.git.acceptedPushTransports)) {
            Transport transport = Transport.fromString(value);
            if (transport == null) {
                logger.info(String.format("Ignoring unknown registered transport %s", value));
                continue;
            }
            transports.add(transport);
        }
        if (transports.isEmpty()) {
            // no transports are explicitly specified, all are acceptable
            return true;
        }
        // verify that the transport is permitted
        return transports.contains(byTransport);
    }
    protected void configureGitDaemon() {
        int port = settings.getInteger(Keys.git.daemonPort, 0);
        String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
@@ -179,7 +377,7 @@
        String bindInterface = settings.getString(Keys.git.sshBindInterface, "localhost");
        if (port > 0) {
            try {
                sshDaemon = new SshDaemon(gitblit, workQueue);
                sshDaemon = new SshDaemon(gitblit, workQueueProvider.get());
                sshDaemon.start();
            } catch (IOException e) {
                sshDaemon = null;
@@ -285,7 +483,7 @@
     */
    protected String getHostname(HttpServletRequest request) {
        String hostname = request.getServerName();
        String canonicalUrl = gitblit.getSettings().getString(Keys.web.canonicalUrl, null);
        String canonicalUrl = settings.getString(Keys.web.canonicalUrl, null);
        if (!StringUtils.isEmpty(canonicalUrl)) {
            try {
                URI uri = new URI(canonicalUrl);
src/main/java/com/gitblit/servlet/GitblitContext.java
@@ -51,6 +51,7 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.utils.ContainerUtils;
@@ -200,6 +201,7 @@
        startManager(injector, IProjectManager.class);
        startManager(injector, IFederationManager.class);
        startManager(injector, IGitblit.class);
        startManager(injector, IServicesManager.class);
        // start the plugin manager last so that plugins can depend on
        // deterministic access to all other managers in their start() methods
src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -21,9 +21,6 @@
import java.util.Map;
import java.util.TimeZone;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.wicket.Application;
import org.apache.wicket.Request;
import org.apache.wicket.Response;
@@ -46,6 +43,7 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
@@ -92,6 +90,8 @@
import com.gitblit.wicket.pages.TreePage;
import com.gitblit.wicket.pages.UserPage;
import com.gitblit.wicket.pages.UsersPage;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class GitBlitWebApp extends WebApplication implements GitblitWicketApp {
@@ -124,6 +124,8 @@
    private final IGitblit gitblit;
    private final IServicesManager services;
    @Inject
    public GitBlitWebApp(
            IRuntimeManager runtimeManager,
@@ -135,7 +137,8 @@
            IRepositoryManager repositoryManager,
            IProjectManager projectManager,
            IFederationManager federationManager,
            IGitblit gitblit) {
            IGitblit gitblit,
            IServicesManager services) {
        super();
        this.settings = runtimeManager.getSettings();
@@ -149,6 +152,7 @@
        this.projectManager = projectManager;
        this.federationManager = federationManager;
        this.gitblit = gitblit;
        this.services = services;
    }
    @Override
@@ -421,6 +425,14 @@
    }
    /* (non-Javadoc)
     * @see com.gitblit.wicket.Webapp#services()
     */
    @Override
    public IServicesManager services() {
        return services;
    }
    /* (non-Javadoc)
     * @see com.gitblit.wicket.Webapp#tickets()
     */
    @Override
src/main/java/com/gitblit/wicket/GitblitWicketApp.java
@@ -14,6 +14,7 @@
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IServicesManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
@@ -65,6 +66,8 @@
    public abstract IGitblit gitblit();
    public abstract IServicesManager services();
    public abstract ITicketService tickets();
    public abstract TimeZone getTimezone();
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
@@ -55,7 +55,7 @@
        }
        HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
        List<RepositoryUrl> repositoryUrls = app().gitblit().getRepositoryUrls(req, user, repository);
        List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
        RepositoryUrl primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
        String url = primaryUrl != null ? primaryUrl.url : "";
src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -1505,7 +1505,7 @@
     */
    protected RepositoryUrl getRepositoryUrl(UserModel user, RepositoryModel repository) {
        HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
        List<RepositoryUrl> urls = app().gitblit().getRepositoryUrls(req, user, repository);
        List<RepositoryUrl> urls = app().services().getRepositoryUrls(req, user, repository);
        if (ArrayUtils.isEmpty(urls)) {
            return null;
        }
src/main/java/com/gitblit/wicket/pages/UserPage.java
@@ -104,7 +104,7 @@
        if (isMyProfile) {
            addPreferences(user);
            if (app().gitblit().isServingSSH()) {
            if (app().services().isServingSSH()) {
                // show the SSH key management tab
                addSshKeys(user);
            } else {
@@ -248,14 +248,14 @@
                emailMeOnMyTicketChanges).setVisible(app().notifier().isSendingMail()));
        List<Transport> availableTransports = new ArrayList<>();
        if (app().gitblit().isServingSSH()) {
        if (app().services().isServingSSH()) {
            availableTransports.add(Transport.SSH);
        }
        if (app().gitblit().isServingHTTP()) {
        if (app().services().isServingHTTP()) {
            availableTransports.add(Transport.HTTPS);
            availableTransports.add(Transport.HTTP);
        }
        if (app().gitblit().isServingGIT()) {
        if (app().services().isServingGIT()) {
            availableTransports.add(Transport.GIT);
        }
src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
@@ -80,7 +80,7 @@
        HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
        List<RepositoryUrl> repositoryUrls = app().gitblit().getRepositoryUrls(req, user, repository);
        List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
        // grab primary url from the top of the list
        primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
@@ -165,7 +165,7 @@
        if (repository.isMirror) {
            urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",
                    getString("gb.isMirror")));
        } else if (app().gitblit().isServingRepositories()) {
        } else if (app().services().isServingRepositories()) {
            switch (repository.accessRestriction) {
            case NONE:
                urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
@@ -83,26 +83,6 @@
    }
    @Override
    public boolean isServingRepositories() {
        return true;
    }
    @Override
    public boolean isServingHTTP() {
        return true;
    }
    @Override
    public boolean isServingGIT() {
        return true;
    }
    @Override
    public boolean isServingSSH() {
        return true;
    }
    @Override
    public boolean isDebugMode() {
        return true;
    }