blob: 7293f35f49f34ecbc5f344182b0a524bf70fa5bd [file] [log] [blame]
// Copyright (C) 2009 The Android Open Source Project
//
// 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.google.gerrit.httpd.init;
import static com.google.inject.Stage.PRODUCTION;
import com.google.common.base.Splitter;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.auth.AuthModule;
import com.google.gerrit.extensions.client.AuthType;
import com.google.gerrit.gpg.GpgModule;
import com.google.gerrit.httpd.AllRequestFilter;
import com.google.gerrit.httpd.GerritAuthModule;
import com.google.gerrit.httpd.GetUserFilter;
import com.google.gerrit.httpd.GitOverHttpModule;
import com.google.gerrit.httpd.H2CacheBasedWebSession;
import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
import com.google.gerrit.httpd.HttpdModule;
import com.google.gerrit.httpd.RequestCleanupFilter;
import com.google.gerrit.httpd.RequestContextFilter;
import com.google.gerrit.httpd.RequestMetricsFilter;
import com.google.gerrit.httpd.RequireSslFilter.RequireSslFilterModule;
import com.google.gerrit.httpd.SetThreadNameFilter;
import com.google.gerrit.httpd.WebModule;
import com.google.gerrit.httpd.WebSshGlueModule;
import com.google.gerrit.httpd.auth.oauth.OAuthModule;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.auth.restapi.OAuthRestModule;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
import com.google.gerrit.httpd.raw.StaticModule;
import com.google.gerrit.index.IndexType;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
import com.google.gerrit.pgm.util.LogFileCompressor.LogFileCompressorModule;
import com.google.gerrit.server.DefaultRefLogIdentityProvider;
import com.google.gerrit.server.LibModuleLoader;
import com.google.gerrit.server.LibModuleType;
import com.google.gerrit.server.ModuleOverloader;
import com.google.gerrit.server.StartupChecks.StartupChecksModule;
import com.google.gerrit.server.account.AccountDeactivator.AccountDeactivatorModule;
import com.google.gerrit.server.account.InternalAccountDirectory.InternalAccountDirectoryModule;
import com.google.gerrit.server.account.externalids.ExternalIdCaseSensitivityMigrator;
import com.google.gerrit.server.api.GerritApiModule;
import com.google.gerrit.server.api.PluginApiModule;
import com.google.gerrit.server.api.projects.ProjectQueryBuilderModule;
import com.google.gerrit.server.audit.AuditModule;
import com.google.gerrit.server.cache.h2.H2CacheModule;
import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
import com.google.gerrit.server.change.ChangeCleanupRunner.ChangeCleanupRunnerModule;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrlModule;
import com.google.gerrit.server.config.DefaultUrlFormatter.DefaultUrlFormatterModule;
import com.google.gerrit.server.config.DownloadConfig;
import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.GerritInstanceNameModule;
import com.google.gerrit.server.config.GerritOptions;
import com.google.gerrit.server.config.GerritRuntime;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SysExecutorModule;
import com.google.gerrit.server.events.EventBroker.EventBrokerModule;
import com.google.gerrit.server.events.StreamEventsApiListener.StreamEventsApiListenerModule;
import com.google.gerrit.server.git.GarbageCollectionModule;
import com.google.gerrit.server.git.GitRepositoryManagerModule;
import com.google.gerrit.server.git.SearchingChangeCacheImpl.SearchingChangeCacheImplModule;
import com.google.gerrit.server.git.SystemReaderInstaller;
import com.google.gerrit.server.git.WorkQueue.WorkQueueModule;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.index.OnlineUpgrader.OnlineUpgraderModule;
import com.google.gerrit.server.index.VersionManager;
import com.google.gerrit.server.index.options.AutoFlush;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier.SignedTokenEmailTokenVerifierModule;
import com.google.gerrit.server.mail.receive.MailReceiver.MailReceiverModule;
import com.google.gerrit.server.mail.send.SmtpEmailSender.SmtpEmailSenderModule;
import com.google.gerrit.server.mime.MimeUtil2Module;
import com.google.gerrit.server.patch.DiffExecutorModule;
import com.google.gerrit.server.permissions.DefaultPermissionBackendModule;
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
import com.google.gerrit.server.plugins.PluginModule;
import com.google.gerrit.server.project.DefaultProjectNameLockManager.DefaultProjectNameLockManagerModule;
import com.google.gerrit.server.restapi.RestApiModule;
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore.JdbcAccountPatchReviewStoreModule;
import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck;
import com.google.gerrit.server.schema.SchemaModule;
import com.google.gerrit.server.securestore.SecureStoreClassName;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.server.ssh.SshAddressesModule;
import com.google.gerrit.server.submit.LocalMergeSuperSetComputation.LocalMergeSuperSetComputationModule;
import com.google.gerrit.server.submit.SubscriptionGraph.SubscriptionGraphModule;
import com.google.gerrit.server.update.SuperprojectUpdateSubmissionListener.SuperprojectUpdateSubmissionListenerModule;
import com.google.gerrit.sshd.SshHostKeyModule;
import com.google.gerrit.sshd.SshKeyCacheImpl;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.SshSessionFactoryInitializer;
import com.google.gerrit.sshd.commands.DefaultCommandModule;
import com.google.gerrit.sshd.commands.ExternalIdCommandsModule;
import com.google.gerrit.sshd.commands.IndexCommandsModule;
import com.google.gerrit.sshd.commands.SequenceCommandsModule;
import com.google.gerrit.sshd.plugin.LfsPluginAuthCommand.LfsPluginAuthCommandModule;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.servlet.GuiceFilter;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.spi.Message;
import com.google.inject.util.Providers;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jgit.lib.Config;
/** Configures the web application environment for Gerrit Code Review. */
public class WebAppInitializer extends GuiceServletContextListener implements Filter {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String GERRIT_SITE_PATH = "gerrit.site_path";
private Path sitePath;
private Injector dbInjector;
private Injector cfgInjector;
private Config config;
private Injector sysInjector;
private Injector webInjector;
private Injector sshInjector;
private LifecycleManager manager;
private GuiceFilter filter;
private ServletContext servletContext;
private IndexType indexType;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
filter.doFilter(req, res, chain);
}
private synchronized void init() {
if (manager == null) {
String path = System.getProperty(GERRIT_SITE_PATH);
if (path != null) {
sitePath = Paths.get(path);
} else {
throw new ProvisionException(GERRIT_SITE_PATH + " must be defined");
}
if (System.getProperty("gerrit.init") != null) {
List<String> pluginsToInstall;
String installPlugins = System.getProperty("gerrit.install_plugins");
if (installPlugins == null) {
pluginsToInstall = null;
} else {
pluginsToInstall =
Splitter.on(",").trimResults().omitEmptyStrings().splitToList(installPlugins);
}
new SiteInitializer(
path,
System.getProperty(GERRIT_SITE_PATH),
new UnzippedDistribution(servletContext),
pluginsToInstall)
.init();
}
try {
cfgInjector = createCfgInjector();
} catch (CreationException ce) {
final Message first = ce.getErrorMessages().iterator().next();
final StringBuilder buf = new StringBuilder();
buf.append(first.getMessage());
Throwable why = first.getCause();
while (why != null) {
buf.append("\n caused by ");
buf.append(why.toString());
why = why.getCause();
}
if (first.getCause() != null) {
buf.append("\n");
buf.append("\nResolve above errors before continuing.");
buf.append("\nComplete stack trace follows:");
}
logger.atSevere().withCause(first.getCause()).log("%s", buf);
throw new CreationException(Collections.singleton(first));
}
dbInjector = createDbInjector();
initIndexType();
config = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
sysInjector = createSysInjector();
if (!sshdOff()) {
sshInjector = createSshInjector();
}
webInjector = createWebInjector();
PluginGuiceEnvironment env = sysInjector.getInstance(PluginGuiceEnvironment.class);
env.setDbCfgInjector(dbInjector, cfgInjector);
if (sshInjector != null) {
env.setSshInjector(sshInjector);
}
env.setHttpInjector(webInjector);
// Push the Provider<HttpServletRequest> down into the canonical
// URL provider. Its optional for that provider, but since we can
// supply one we should do so, in case the administrator has not
// setup the canonical URL in the configuration file.
//
// Note we have to do this manually as Guice failed to do the
// injection here because the HTTP environment is not visible
// to the core server modules.
//
sysInjector
.getInstance(HttpCanonicalWebUrlProvider.class)
.setHttpServletRequest(webInjector.getProvider(HttpServletRequest.class));
filter = webInjector.getInstance(GuiceFilter.class);
manager = new LifecycleManager();
manager.add(dbInjector);
manager.add(cfgInjector);
manager.add(sysInjector);
if (sshInjector != null) {
manager.add(sshInjector);
}
manager.add(webInjector);
}
}
private boolean sshdOff() {
return new SshAddressesModule().provideListenAddresses(config).isEmpty();
}
private Injector createCfgInjector() {
final List<Module> modules = new ArrayList<>();
AbstractModule secureStore = createSecureStoreModule();
modules.add(secureStore);
Module sitePathModule =
new AbstractModule() {
@Override
protected void configure() {
bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
}
};
modules.add(sitePathModule);
Module configModule = new GerritServerConfigModule();
modules.add(configModule);
modules.add(
new LifecycleModule() {
@Override
protected void configure() {
listener().to(SystemReaderInstaller.class);
}
});
modules.add(new DropWizardMetricMaker.ApiModule());
return Guice.createInjector(PRODUCTION, modules);
}
private Injector createDbInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(new SchemaModule());
modules.add(NoteDbSchemaVersionCheck.module());
modules.add(new AuthConfigModule());
return cfgInjector.createChildInjector(
ModuleOverloader.override(
modules, LibModuleLoader.loadModules(cfgInjector, LibModuleType.DB_MODULE_TYPE)));
}
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(new DropWizardMetricMaker.RestModule());
modules.add(new LogFileCompressorModule());
modules.add(new EventBrokerModule());
modules.add(new JdbcAccountPatchReviewStoreModule(config));
modules.add(cfgInjector.getInstance(GitRepositoryManagerModule.class));
modules.add(new StreamEventsApiListenerModule());
modules.add(new SysExecutorModule());
modules.add(new DiffExecutorModule());
modules.add(new MimeUtil2Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
modules.add(new GerritApiModule());
modules.add(new ProjectQueryBuilderModule());
modules.add(new DefaultRefLogIdentityProvider.Module());
modules.add(new PluginApiModule());
modules.add(new SearchingChangeCacheImplModule());
modules.add(new InternalAccountDirectoryModule());
modules.add(new DefaultPermissionBackendModule());
modules.add(new DefaultMemoryCacheModule());
modules.add(new H2CacheModule());
modules.add(cfgInjector.getInstance(MailReceiverModule.class));
modules.add(new SmtpEmailSenderModule());
modules.add(new SignedTokenEmailTokenVerifierModule());
modules.add(new LocalMergeSuperSetComputationModule());
modules.add(new AuditModule());
modules.add(new GpgModule(config));
modules.add(new StartupChecksModule());
// Index module shutdown must happen before work queue shutdown, otherwise
// work queue can get stuck waiting on index futures that will never return.
modules.add(createIndexModule());
modules.add(new PluginModule());
if (VersionManager.getOnlineUpgrade(config)) {
modules.add(new OnlineUpgraderModule());
}
modules.add(new OAuthRestModule());
modules.add(new RestApiModule());
modules.add(new SubscriptionGraphModule());
modules.add(new SuperprojectUpdateSubmissionListenerModule());
modules.add(new WorkQueueModule());
modules.add(new GerritInstanceNameModule());
modules.add(
new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {
return HttpCanonicalWebUrlProvider.class;
}
});
modules.add(new DefaultUrlFormatterModule());
SshSessionFactoryInitializer.init();
modules.add(SshKeyCacheImpl.module());
modules.add(
new AbstractModule() {
@Override
protected void configure() {
bind(GerritOptions.class).toInstance(new GerritOptions(false, false));
bind(GerritRuntime.class).toInstance(GerritRuntime.DAEMON);
}
});
modules.add(new GarbageCollectionModule());
modules.add(new ChangeCleanupRunnerModule());
modules.add(new AccountDeactivatorModule());
modules.add(new DefaultProjectNameLockManagerModule());
modules.add(new ExternalIdCaseSensitivityMigrator.ExternalIdCaseSensitivityMigratorModule());
return dbInjector.createChildInjector(
ModuleOverloader.override(
modules, LibModuleLoader.loadModules(cfgInjector, LibModuleType.SYS_MODULE_TYPE)));
}
private Module createIndexModule() {
if (indexType.isLucene()) {
return LuceneIndexModule.latestVersion(false, AutoFlush.ENABLED);
} else if (indexType.isFake()) {
// Use Reflection so that we can omit the fake index binary in production code. Test code does
// compile the component in.
try {
Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModule");
Method m = clazz.getMethod("latestVersion", boolean.class);
return (Module) m.invoke(null, false);
} catch (NoSuchMethodException
| ClassNotFoundException
| IllegalAccessException
| InvocationTargetException e) {
throw new IllegalStateException("can't create index", e);
}
} else {
throw new IllegalStateException("unsupported index.type = " + indexType);
}
}
private void initIndexType() {
indexType = IndexModule.getIndexType(cfgInjector);
}
private Injector createSshInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(sysInjector.getInstance(SshModule.class));
modules.add(new SshHostKeyModule());
modules.add(
new DefaultCommandModule(
false,
sysInjector.getInstance(DownloadConfig.class),
sysInjector.getInstance(LfsPluginAuthCommandModule.class)));
modules.add(new IndexCommandsModule(sysInjector));
modules.add(new SequenceCommandsModule());
modules.add(new ExternalIdCommandsModule());
return sysInjector.createChildInjector(modules);
}
private Injector createWebInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(RequestContextFilter.module());
modules.add(RequestMetricsFilter.module());
modules.add(sysInjector.getInstance(GerritAuthModule.class));
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
modules.add(sysInjector.getInstance(HttpdModule.class));
modules.add(RequestCleanupFilter.module());
modules.add(SetThreadNameFilter.module());
modules.add(AllRequestFilter.module());
modules.add(sysInjector.getInstance(WebModule.class));
modules.add(sysInjector.getInstance(RequireSslFilterModule.class));
if (sshInjector != null) {
modules.add(sshInjector.getInstance(WebSshGlueModule.class));
} else {
modules.add(new NoSshModule());
}
modules.add(H2CacheBasedWebSession.module());
modules.add(new HttpPluginModule());
AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
if (authConfig.getAuthType() == AuthType.OPENID) {
modules.add(new OpenIdModule());
} else if (authConfig.getAuthType() == AuthType.OAUTH) {
modules.add(new OAuthModule());
}
modules.add(new AuthModule(authConfig));
modules.add(sysInjector.getInstance(GetUserFilter.GetUserFilterModule.class));
// StaticModule contains a "/*" wildcard, place it last.
GerritOptions opts = sysInjector.getInstance(GerritOptions.class);
if (opts.enableMasterFeatures()) {
modules.add(sysInjector.getInstance(StaticModule.class));
}
return sysInjector.createChildInjector(modules);
}
@Override
protected Injector getInjector() {
init();
return webInjector;
}
@Override
public void init(FilterConfig cfg) throws ServletException {
servletContext = cfg.getServletContext();
contextInitialized(new ServletContextEvent(servletContext));
init();
manager.start();
}
@Override
public void destroy() {
if (manager != null) {
manager.stop();
manager = null;
}
}
private AbstractModule createSecureStoreModule() {
return new AbstractModule() {
@Override
public void configure() {
String secureStoreClassName = GerritServerConfigModule.getSecureStoreClassName(sitePath);
bind(String.class)
.annotatedWith(SecureStoreClassName.class)
.toProvider(Providers.of(secureStoreClassName));
}
};
}
}