James Moger
2013-11-20 e24670533215141f7146349f782d3c4a2d3ee535
Extract RuntimeManager from GitBlit singleton

Change-Id: I5358389396f816da979ec18a31421c2d2b67b3d9
2 files added
10 files modified
898 ■■■■■ changed files
src/main/java/com/gitblit/DaggerModule.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FederationClient.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FileSettings.java 29 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlit.java 584 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlitServer.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Gitblit.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/IStoredSettings.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IManager.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/IRuntimeManager.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/manager/RuntimeManager.java 205 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/ServerStatus.java 7 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/DaggerModule.java
@@ -28,6 +28,7 @@
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.ISessionManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.manager.RuntimeManager;
import com.gitblit.wicket.GitBlitWebApp;
import com.gitblit.wicket.GitblitWicketFilter;
@@ -42,6 +43,8 @@
 */
@Module(
    injects = {
            IStoredSettings.class,
            // core managers
            IRuntimeManager.class,
            INotificationManager.class,
@@ -84,8 +87,12 @@
        this.gitblit = gitblit;
    }
    @Provides @Singleton IRuntimeManager provideRuntimeManager() {
        return gitblit;
    @Provides @Singleton IStoredSettings provideSettings() {
        return new FileSettings();
    }
    @Provides @Singleton IRuntimeManager provideRuntimeManager(IStoredSettings settings) {
        return new RuntimeManager(settings);
    }
    @Provides @Singleton INotificationManager provideNotificationManager() {
src/main/java/com/gitblit/FederationClient.java
@@ -84,7 +84,7 @@
        // configure the Gitblit singleton for minimal, non-server operation
        GitBlit gitblit = new GitBlit(settings, baseFolder);
        gitblit.configureContext(settings, baseFolder, false);
        gitblit.beforeServletInjection(null); // XXX broken
        FederationPullExecutor executor = new FederationPullExecutor(registrations, params.isDaemon);
        executor.run();
        if (!params.isDaemon) {
src/main/java/com/gitblit/FileSettings.java
@@ -32,7 +32,7 @@
 */
public class FileSettings extends IStoredSettings {
    protected final File propertiesFile;
    protected File propertiesFile;
    private final Properties properties = new Properties();
@@ -40,9 +40,32 @@
    private volatile boolean forceReload;
    public FileSettings(String file) {
    public FileSettings() {
        super(FileSettings.class);
    }
    public FileSettings(String file) {
        this();
        load(file);
    }
    public void load(String file) {
        this.propertiesFile = new File(file);
    }
    /**
     * Merges the provided settings into this instance.  This will also
     * set the target file for this instance IFF it is unset AND the merge
     * source is also a FileSettings.  This is a little sneaky.
     */
    @Override
    public void merge(IStoredSettings settings) {
        super.merge(settings);
        // sneaky: set the target file from the merge source
        if (propertiesFile == null && settings instanceof FileSettings) {
            this.propertiesFile = ((FileSettings) settings).propertiesFile;
        }
    }
    /**
@@ -51,7 +74,7 @@
     */
    @Override
    protected synchronized Properties read() {
        if (propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) {
        if (propertiesFile != null && propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) {
            FileInputStream is = null;
            try {
                Properties props = new Properties();
src/main/java/com/gitblit/GitBlit.java
@@ -47,7 +47,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
@@ -102,6 +101,7 @@
import com.gitblit.git.GitServlet;
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;
@@ -121,7 +121,6 @@
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.SearchResult;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
@@ -151,6 +150,8 @@
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
@@ -165,8 +166,7 @@
 */
@WebListener
public class GitBlit extends DaggerContextListener
                     implements IRuntimeManager,
                                INotificationManager,
                     implements INotificationManager,
                                IUserManager,
                                ISessionManager,
                                IRepositoryManager,
@@ -177,6 +177,10 @@
    private static GitBlit gitblit;
    private final IStoredSettings goSettings;
    private final File goBaseFolder;
    private final List<IManager> managers = new ArrayList<IManager>();
    private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10);
@@ -201,17 +205,11 @@
    private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
    private File baseFolder;
    private File repositoriesFolder;
    private IUserService userService;
    private IStoredSettings settings;
    private ServerSettings settingsModel;
    private ServerStatus serverStatus;
    private MailExecutor mailExecutor;
@@ -221,8 +219,6 @@
    private MirrorExecutor mirrorExecutor;
    private TimeZone timezone;
    private FileBasedConfig projectConfigs;
    private FanoutService fanoutService;
@@ -231,17 +227,19 @@
    public GitBlit() {
        this.goSettings = null;
        this.goBaseFolder = null;
    }
    protected GitBlit(final IUserService userService) {
        this.goSettings = null;
        this.goBaseFolder = null;
        this.userService = userService;
        gitblit = this;
    }
    public GitBlit(IStoredSettings settings, File baseFolder) {
        this.goSettings = settings;
        this.baseFolder = baseFolder;
        this.goBaseFolder = baseFolder;
        gitblit = this;
    }
@@ -259,27 +257,13 @@
        if (managerClass.isAssignableFrom(GitBlit.class)) {
            return (X) gitblit;
        }
        for (IManager manager : gitblit.managers) {
            if (managerClass.isAssignableFrom(manager.getClass())) {
                return (X) manager;
            }
        }
        return null;
    }
    @Override
    public File getBaseFolder() {
        return baseFolder;
    }
    @Override
    public void setBaseFolder(File folder) {
        this.baseFolder = folder;
    }
    /**
     * Returns the boot date of the Gitblit server.
     *
     * @return the boot date of Gitblit
     */
    @Override
    public Date getBootDate() {
        return serverStatus.bootDate;
    }
    /**
@@ -302,71 +286,6 @@
    }
    /**
     * 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 settings.getBoolean(Keys.git.enableGitServlet, true) || (settings.getInteger(Keys.git.daemonPort, 0) > 0);
    }
    /**
     * Returns the preferred timezone for the Gitblit instance.
     *
     * @return a timezone
     */
    @Override
    public TimeZone getTimezone() {
        if (timezone == null) {
            String tzid = settings.getString("web.timezone", null);
            if (StringUtils.isEmpty(tzid)) {
                timezone = TimeZone.getDefault();
                return timezone;
            }
            timezone = TimeZone.getTimeZone(tzid);
        }
        return timezone;
    }
    /**
     * Is Gitblit running in debug mode?
     *
     * @return true if Gitblit is running in debug mode
     */
    @Override
    public boolean isDebugMode() {
        return settings.getBoolean(Keys.web.debugMode, false);
    }
    /**
     * Returns the file object for the specified configuration key.
     *
     * @return the file
     */
    @Override
    public File getFileOrFolder(String key, String defaultFileOrFolder) {
        String fileOrFolder = settings.getString(key, defaultFileOrFolder);
        return getFileOrFolder(fileOrFolder);
    }
    /**
     * Returns the file object which may have it's base-path determined by
     * environment variables for running on a cloud hosting service. All Gitblit
     * file or folder retrievals are (at least initially) funneled through this
     * method so it is the correct point to globally override/alter filesystem
     * access based on environment or some other indicator.
     *
     * @return the file
     */
    @Override
    public File getFileOrFolder(String fileOrFolder) {
        return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$,
                baseFolder, fileOrFolder);
    }
    /**
     * Returns the path of the repositories folder. This method checks to see if
     * Gitblit is running on a cloud service and may return an adjusted path.
     *
@@ -374,7 +293,7 @@
     */
    @Override
    public File getRepositoriesFolder() {
        return getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
        return getManager(IRuntimeManager.class).getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
    }
    /**
@@ -385,7 +304,7 @@
     */
    @Override
    public File getProposalsFolder() {
        return getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
        return getManager(IRuntimeManager.class).getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
    }
    /**
@@ -396,7 +315,7 @@
     */
    @Override
    public File getHooksFolder() {
        return getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy");
        return getManager(IRuntimeManager.class).getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy");
    }
    /**
@@ -407,36 +326,7 @@
     */
    @Override
    public File getGrapesFolder() {
        return getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape");
    }
    /**
     * Returns the runtime settings.
     *
     * @return runtime settings
     */
    @Override
    public IStoredSettings getSettings() {
        return settings;
    }
    /**
     * Updates the runtime settings.
     *
     * @param settings
     * @return true if the update succeeded
     */
    @Override
    public boolean updateSettings(Map<String, String> updatedSettings) {
        return settings.saveSettings(updatedSettings);
    }
    @Override
    public ServerStatus getStatus() {
        // update heap memory status
        serverStatus.heapAllocated = Runtime.getRuntime().totalMemory();
        serverStatus.heapFree = Runtime.getRuntime().freeMemory();
        return serverStatus;
        return getManager(IRuntimeManager.class).getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape");
    }
    /**
@@ -548,7 +438,7 @@
    @Override
    public Collection<GitClientApplication> getClientApplications() {
        // prefer user definitions, if they exist
        File userDefs = new File(baseFolder, "clientapps.json");
        File userDefs = new File(getManager(IRuntimeManager.class).getBaseFolder(), "clientapps.json");
        if (userDefs.exists()) {
            Date lastModified = new Date(userDefs.lastModified());
            if (clientApplications.hasCurrent("user", lastModified)) {
@@ -1912,7 +1802,7 @@
        }
        RepositoryModel model = new RepositoryModel();
        model.isBare = r.isBare();
        File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
        File basePath = getRepositoriesFolder();
        if (model.isBare) {
            model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
        } else {
@@ -2283,7 +2173,7 @@
        if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
            return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
        }
        List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null, getTimezone());
        List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null, getManager(IRuntimeManager.class).getTimezone());
        repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
        return new ArrayList<Metric>(metrics);
    }
@@ -3370,36 +3260,13 @@
    }
    /**
     * Returns the descriptions/comments of the Gitblit config settings.
     *
     * @return SettingsModel
     */
    @Override
    public ServerSettings getSettingsModel() {
        // ensure that the current values are updated in the setting models
        for (String key : settings.getAllKeys(null)) {
            SettingModel setting = settingsModel.get(key);
            if (setting == null) {
                // unreferenced setting, create a setting model
                setting = new SettingModel();
                setting.name = key;
                settingsModel.add(setting);
            }
            setting.currentValue = settings.getString(key, "");
        }
        settingsModel.pushScripts = getAllScripts();
        return settingsModel;
    }
    /**
     * 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 = new ServerSettings();
    private ServerSettings loadSettingModels(ServerSettings settingsModel) {
        settingsModel.supportsCredentialChanges = userService.supportsCredentialChanges();
        settingsModel.supportsDisplayNameChanges = userService.supportsDisplayNameChanges();
        settingsModel.supportsEmailAddressChanges = userService.supportsEmailAddressChanges();
@@ -3458,77 +3325,6 @@
            logger.error("Failed to load resource copy of gitblit.properties");
        }
        return settingsModel;
    }
    /**
     * Configure the Gitblit singleton with the specified settings source. This
     * source may be file settings (Gitblit GO) or may be web.xml settings
     * (Gitblit WAR).
     *
     * @param settings
     */
    public void configureContext(IStoredSettings settings, File folder, boolean startFederation) {
        this.settings = settings;
        this.baseFolder = folder;
        repositoriesFolder = getRepositoriesFolder();
        logger.info("Gitblit base folder     = " + folder.getAbsolutePath());
        logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath());
        logger.info("Gitblit settings        = " + settings.toString());
        // prepare service executors
        mailExecutor = new MailExecutor(settings);
        luceneExecutor = new LuceneExecutor(settings, getManager(IRepositoryManager.class));
        gcExecutor = new GCExecutor(settings, getManager(IRepositoryManager.class));
        mirrorExecutor = new MirrorExecutor(settings, getManager(IRepositoryManager.class));
        // initialize utilities
        String prefix = settings.getString(Keys.git.userRepositoryPrefix, "~");
        ModelUtils.setUserRepoPrefix(prefix);
        // calculate repository list settings checksum for future config changes
        repositoryListSettingsChecksum.set(getRepositoryListSettingsChecksum());
        // build initial repository list
        if (settings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
            logger.info("Identifying available repositories...");
            getRepositoryList();
        }
        logTimezone("JVM", TimeZone.getDefault());
        logTimezone(Constants.NAME, getTimezone());
        serverStatus = new ServerStatus(goSettings != null);
        if (this.userService == null) {
            String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
            IUserService loginService = null;
            try {
                // check to see if this "file" is a login service class
                Class<?> realmClass = Class.forName(realm);
                loginService = (IUserService) realmClass.newInstance();
            } catch (Throwable t) {
                loginService = new GitblitUserService();
            }
            setUserService(loginService);
        }
        // load and cache the project metadata
        projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
        getProjectConfigs();
        configureMailExecutor();
        configureLuceneIndexing();
        configureGarbageCollector();
        configureMirrorExecutor();
        if (startFederation) {
            configureFederation();
        }
        configureJGit();
        configureFanout();
        configureGitDaemon();
        configureCommitCache();
    }
    protected void configureMailExecutor() {
@@ -3642,7 +3438,15 @@
        if (port > 0) {
            try {
                // HACK temporary pending manager separation and injection
                Gitblit gitblit = new Gitblit(this, this, this, this, this, this, this, this);
                Gitblit gitblit = new Gitblit(
                        getManager(IRuntimeManager.class),
                        this,
                        this,
                        this,
                        this,
                        this,
                        this,
                        this);
                gitDaemon = new GitDaemon(gitblit);
                gitDaemon.start();
            } catch (IOException e) {
@@ -3700,13 +3504,6 @@
        return luceneExecutor;
    }
    private void logTimezone(String type, TimeZone zone) {
        SimpleDateFormat df = new SimpleDateFormat("z Z");
        df.setTimeZone(zone);
        String offset = df.format(new Date());
        logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")");
    }
    /**
     * Configure Gitblit from the web.xml, if no configuration has already been
     * specified.
@@ -3715,93 +3512,237 @@
     */
    @Override
    protected void beforeServletInjection(ServletContext context) {
        if (settings == null) {
            // Gitblit is running in a servlet container
        ObjectGraph injector = getInjector(context);
        // 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
            WebXmlSettings webxmlSettings = new WebXmlSettings(context);
            String contextRealPath = context.getRealPath("/");
            File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null;
            String openShift = System.getenv("OPENSHIFT_DATA_DIR");
            if (!StringUtils.isEmpty(openShift)) {
                // Gitblit is running in OpenShift/JBoss
                File base = new File(openShift);
                logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
                // gitblit.properties setting overrides
                File overrideFile = new File(base, "gitblit.properties");
                webxmlSettings.applyOverrides(overrideFile);
                // Copy the included scripts to the configured groovy folder
                String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
                File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
                if (!localScripts.exists()) {
                    File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
                    if (!warScripts.equals(localScripts)) {
                        try {
                            com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
                        } catch (IOException e) {
                            logger.error(MessageFormat.format(
                                    "Failed to copy included Groovy scripts from {0} to {1}",
                                    warScripts, localScripts));
                        }
                    }
                }
                // disable Git daemon on Express - we can't bind 9418 and we
                // can't port-forward to the daemon
                webxmlSettings.overrideSetting(Keys.git.daemonPort, 0);
                // configure context using the web.xml
                configureContext(webxmlSettings, base, true);
            if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) {
                // RedHat OpenShift
                logger.debug("configuring Gitblit Express");
                baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings);
            } else {
                // Gitblit is running in a standard servlet container
                logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
                String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
                if (path.contains(Constants.contextFolder$) && contextFolder == null) {
                    // warn about null contextFolder (issue-199)
                    logger.error("");
                    logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
                            Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
                    logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
                    logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
                    logger.error("");
                }
                try {
                    // try to lookup JNDI env-entry for the baseFolder
                    InitialContext ic = new InitialContext();
                    Context env = (Context) ic.lookup("java:comp/env");
                    String val = (String) env.lookup("baseFolder");
                    if (!StringUtils.isEmpty(val)) {
                        path = val;
                    }
                } catch (NamingException n) {
                    logger.error("Failed to get JNDI env-entry: " + n.getExplanation());
                }
                File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
                base.mkdirs();
                // try to extract the data folder resource to the baseFolder
                File localSettings = new File(base, "gitblit.properties");
                if (!localSettings.exists()) {
                    extractResources(context, "/WEB-INF/data/", base);
                }
                // delegate all config to baseFolder/gitblit.properties file
                FileSettings settings = new FileSettings(localSettings.getAbsolutePath());
                configureContext(settings, base, true);
                // standard WAR
                logger.debug("configuring Gitblit WAR");
                baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings);
            }
            // WAR or Express is likely to be running on a Tomcat.
            // Test for the forward-slash/%2F issue and auto-adjust settings.
            ContainerUtils.CVE_2007_0450.test(settings);
            // Test for Tomcat forward-slash/%2F issue and auto-adjust settings
            ContainerUtils.CVE_2007_0450.test(runtimeSettings);
        }
        settingsModel = loadSettingModels();
        serverStatus.servletContainer = context.getServerInfo();
        // Runtime manager is a container for settings and other parameters
        IRuntimeManager runtime = startManager(injector, IRuntimeManager.class);
        runtime.setBaseFolder(baseFolder);
        runtime.getStatus().isGO = goSettings != null;
        runtime.getStatus().servletContainer = context.getServerInfo();
        repositoriesFolder = getRepositoriesFolder();
        logger.info("Gitblit base folder     = " + baseFolder.getAbsolutePath());
        logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath());
        // prepare service executors
        mailExecutor = new MailExecutor(runtimeSettings);
        luceneExecutor = new LuceneExecutor(runtimeSettings, getManager(IRepositoryManager.class));
        gcExecutor = new GCExecutor(runtimeSettings, getManager(IRepositoryManager.class));
        mirrorExecutor = new MirrorExecutor(runtimeSettings, getManager(IRepositoryManager.class));
        // initialize utilities
        String prefix = runtimeSettings.getString(Keys.git.userRepositoryPrefix, "~");
        ModelUtils.setUserRepoPrefix(prefix);
        // calculate repository list settings checksum for future config changes
        repositoryListSettingsChecksum.set(getRepositoryListSettingsChecksum());
        // build initial repository list
        if (runtimeSettings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
            logger.info("Identifying available repositories...");
            getRepositoryList();
        }
        if (this.userService == null) {
            String realm = runtimeSettings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
            IUserService loginService = null;
            try {
                // check to see if this "file" is a login service class
                Class<?> realmClass = Class.forName(realm);
                loginService = (IUserService) realmClass.newInstance();
            } catch (Throwable t) {
                loginService = new GitblitUserService();
            }
            setUserService(loginService);
        }
        loadSettingModels(runtime.getSettingsModel());
        // load and cache the project metadata
        projectConfigs = new FileBasedConfig(runtime.getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
        getProjectConfigs();
        configureMailExecutor();
        configureLuceneIndexing();
        configureGarbageCollector();
        configureMirrorExecutor();
        if (true/*startFederation*/) {
            configureFederation();
        }
        configureJGit();
        configureFanout();
        configureGitDaemon();
        configureCommitCache();
    }
    /**
     * Configures Gitblit GO
     *
     * @param context
     * @param settings
     * @param baseFolder
     * @param runtimeSettings
     * @return the base folder
     */
    protected File configureGO(
            ServletContext context,
            IStoredSettings goSettings,
            File goBaseFolder,
            IStoredSettings runtimeSettings) {
        // merge the stored settings into the runtime settings
        //
        // if runtimeSettings is also a FileSettings w/o a specified target file,
        // the target file for runtimeSettings is set to "localSettings".
        runtimeSettings.merge(goSettings);
        File base = goBaseFolder;
        return base;
    }
    /**
     * Configures a standard WAR instance of Gitblit.
     *
     * @param context
     * @param webxmlSettings
     * @param contextFolder
     * @param runtimeSettings
     * @return the base folder
     */
    protected File configureWAR(
            ServletContext context,
            WebXmlSettings webxmlSettings,
            File contextFolder,
            IStoredSettings runtimeSettings) {
        // Gitblit is running in a standard servlet container
        logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
        String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
        if (path.contains(Constants.contextFolder$) && contextFolder == null) {
            // warn about null contextFolder (issue-199)
            logger.error("");
            logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
                    Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
            logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
            logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
            logger.error("");
        }
        try {
            // try to lookup JNDI env-entry for the baseFolder
            InitialContext ic = new InitialContext();
            Context env = (Context) ic.lookup("java:comp/env");
            String val = (String) env.lookup("baseFolder");
            if (!StringUtils.isEmpty(val)) {
                path = val;
            }
        } catch (NamingException n) {
            logger.error("Failed to get JNDI env-entry: " + n.getExplanation());
        }
        File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
        base.mkdirs();
        // try to extract the data folder resource to the baseFolder
        File localSettings = new File(base, "gitblit.properties");
        if (!localSettings.exists()) {
            extractResources(context, "/WEB-INF/data/", base);
        }
        // delegate all config to baseFolder/gitblit.properties file
        FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
        // merge the stored settings into the runtime settings
        //
        // if runtimeSettings is also a FileSettings w/o a specified target file,
        // the target file for runtimeSettings is set to "localSettings".
        runtimeSettings.merge(fileSettings);
        return base;
    }
    /**
     * Configures an OpenShift instance of Gitblit.
     *
     * @param context
     * @param webxmlSettings
     * @param contextFolder
     * @param runtimeSettings
     * @return the base folder
     */
    private File configureExpress(
            ServletContext context,
            WebXmlSettings webxmlSettings,
            File contextFolder,
            IStoredSettings runtimeSettings) {
        // Gitblit is running in OpenShift/JBoss
        String openShift = System.getenv("OPENSHIFT_DATA_DIR");
        File base = new File(openShift);
        logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
        // Copy the included scripts to the configured groovy folder
        String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
        File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
        if (!localScripts.exists()) {
            File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
            if (!warScripts.equals(localScripts)) {
                try {
                    com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
                } catch (IOException e) {
                    logger.error(MessageFormat.format(
                            "Failed to copy included Groovy scripts from {0} to {1}",
                            warScripts, localScripts));
                }
            }
        }
        // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty)
        runtimeSettings.merge(webxmlSettings);
        // settings are to be stored in openshift/gitblit.properties
        File localSettings = new File(base, "gitblit.properties");
        FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
        // merge the stored settings into the runtime settings
        //
        // if runtimeSettings is also a FileSettings w/o a specified target file,
        // the target file for runtimeSettings is set to "localSettings".
        runtimeSettings.merge(fileSettings);
        return base;
    }
    protected void extractResources(ServletContext context, String path, File toDir) {
@@ -3858,6 +3799,11 @@
    @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();
        luceneExecutor.close();
        gcExecutor.close();
@@ -4059,6 +4005,14 @@
        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.
src/main/java/com/gitblit/GitBlitServer.java
@@ -409,9 +409,8 @@
            rootContext.setHandler(sh);
        }
        // Setup the GitBlit context
        // Setup the Gitblit context
        GitBlit gitblit = newGitblit(settings, baseFolder);
        gitblit.configureContext(settings, baseFolder, true);
        rootContext.addEventListener(gitblit);
        try {
src/main/java/com/gitblit/Gitblit.java
@@ -33,6 +33,7 @@
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;
@@ -111,6 +112,16 @@
        this.gitblitManager = gitblitManager;
    }
    @Override
    public IManager setup() {
        return this;
    }
    @Override
    public IManager stop() {
        return this;
    }
    /*
     * RUNTIME MANAGER
     */
src/main/java/com/gitblit/IStoredSettings.java
@@ -351,4 +351,13 @@
     * @return true if successful
     */
    public abstract boolean saveSettings(Map<String, String> updatedSettings);
    /**
     * Merge all settings from the settings parameter into this instance.
     *
     * @param settings
     */
    public void merge(IStoredSettings settings) {
        getSettings().putAll(settings.getSettings());
    }
}
src/main/java/com/gitblit/manager/IManager.java
New file
@@ -0,0 +1,23 @@
/*
 * 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 IManager {
    IManager setup();
    IManager stop();
}
src/main/java/com/gitblit/manager/IRuntimeManager.java
@@ -24,7 +24,7 @@
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
public interface IRuntimeManager {
public interface IRuntimeManager extends IManager {
    void setBaseFolder(File folder);
src/main/java/com/gitblit/manager/RuntimeManager.java
New file
@@ -0,0 +1,205 @@
/*
 * 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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel;
import com.gitblit.utils.StringUtils;
public class RuntimeManager implements IRuntimeManager {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final IStoredSettings settings;
    private final ServerStatus serverStatus;
    private TimeZone timezone;
    private File baseFolder;
    private ServerSettings settingsModel;
    public RuntimeManager(IStoredSettings settings) {
        this.settings = settings;
        this.settingsModel = new ServerSettings();
        this.serverStatus = new ServerStatus();
    }
    @Override
    public RuntimeManager setup() {
        logger.info("Gitblit settings        = " + settings.toString());
        logTimezone("JVM", TimeZone.getDefault());
        logTimezone(Constants.NAME, getTimezone());
        return this;
    }
    @Override
    public RuntimeManager stop() {
        return this;
    }
    @Override
    public File getBaseFolder() {
        return baseFolder;
    }
    @Override
    public void setBaseFolder(File folder) {
        this.baseFolder = folder;
    }
    /**
     * Returns the boot date of the Gitblit server.
     *
     * @return the boot date of Gitblit
     */
    @Override
    public Date getBootDate() {
        return serverStatus.bootDate;
    }
    @Override
    public ServerSettings getSettingsModel() {
        // ensure that the current values are updated in the setting models
        for (String key : settings.getAllKeys(null)) {
            SettingModel setting = settingsModel.get(key);
            if (setting == null) {
                // unreferenced setting, create a setting model
                setting = new SettingModel();
                setting.name = key;
                settingsModel.add(setting);
            }
            setting.currentValue = settings.getString(key, "");
        }
//        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 settings.getBoolean(Keys.git.enableGitServlet, true) || (settings.getInteger(Keys.git.daemonPort, 0) > 0);
    }
    /**
     * Returns the preferred timezone for the Gitblit instance.
     *
     * @return a timezone
     */
    @Override
    public TimeZone getTimezone() {
        if (timezone == null) {
            String tzid = settings.getString("web.timezone", null);
            if (StringUtils.isEmpty(tzid)) {
                timezone = TimeZone.getDefault();
                return timezone;
            }
            timezone = TimeZone.getTimeZone(tzid);
        }
        return timezone;
    }
    private void logTimezone(String type, TimeZone zone) {
        SimpleDateFormat df = new SimpleDateFormat("z Z");
        df.setTimeZone(zone);
        String offset = df.format(new Date());
        logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")");
    }
    /**
     * Is Gitblit running in debug mode?
     *
     * @return true if Gitblit is running in debug mode
     */
    @Override
    public boolean isDebugMode() {
        return settings.getBoolean(Keys.web.debugMode, false);
    }
    /**
     * Returns the file object for the specified configuration key.
     *
     * @return the file
     */
    @Override
    public File getFileOrFolder(String key, String defaultFileOrFolder) {
        String fileOrFolder = settings.getString(key, defaultFileOrFolder);
        return getFileOrFolder(fileOrFolder);
    }
    /**
     * Returns the file object which may have it's base-path determined by
     * environment variables for running on a cloud hosting service. All Gitblit
     * file or folder retrievals are (at least initially) funneled through this
     * method so it is the correct point to globally override/alter filesystem
     * access based on environment or some other indicator.
     *
     * @return the file
     */
    @Override
    public File getFileOrFolder(String fileOrFolder) {
        return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$,
                baseFolder, fileOrFolder);
    }
    /**
     * Returns the runtime settings.
     *
     * @return runtime settings
     */
    @Override
    public IStoredSettings getSettings() {
        return settings;
    }
    /**
     * Updates the runtime settings.
     *
     * @param settings
     * @return true if the update succeeded
     */
    @Override
    public boolean updateSettings(Map<String, String> updatedSettings) {
        return settings.saveSettings(updatedSettings);
    }
    @Override
    public ServerStatus getStatus() {
        // update heap memory status
        serverStatus.heapAllocated = Runtime.getRuntime().totalMemory();
        serverStatus.heapFree = Runtime.getRuntime().freeMemory();
        return serverStatus;
    }
}
src/main/java/com/gitblit/models/ServerStatus.java
@@ -39,8 +39,6 @@
    public final String releaseDate;
    public final boolean isGO;
    public final Map<String, String> systemProperties;
    public final long heapMaximum;
@@ -49,13 +47,14 @@
    public volatile long heapFree;
    public boolean isGO;
    public String servletContainer;
    public ServerStatus(boolean isGO) {
    public ServerStatus() {
        this.bootDate = new Date();
        this.version = Constants.getVersion();
        this.releaseDate = Constants.getBuildDate();
        this.isGO = isGO;
        this.heapMaximum = Runtime.getRuntime().maxMemory();
src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
@@ -22,6 +22,7 @@
import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.manager.IManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
@@ -48,7 +49,7 @@
    public MockRuntimeManager(IStoredSettings settings) {
        this.settings = settings;
        this.serverStatus = new ServerStatus(true);
        this.serverStatus = new ServerStatus();
        this.serverStatus.servletContainer = "MockServer";
        this.serverSettings = new ServerSettings();
@@ -130,4 +131,13 @@
        return settings.saveSettings(updatedSettings);
    }
    @Override
    public IManager stop() {
        return this;
    }
    @Override
    public IRuntimeManager setup() {
        return this;
    }
}