// 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.pgm.util; import static com.google.gerrit.server.config.GerritServerConfigModule.getSecureStoreClassName; import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.Stage.PRODUCTION; import com.google.gerrit.common.Die; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.metrics.DisabledMetricMaker; import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker; import com.google.gerrit.server.LibModuleLoader; 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.git.GitRepositoryManagerModule; import com.google.gerrit.server.notedb.ConfigNotesMigration; import com.google.gerrit.server.schema.DataSourceModule; import com.google.gerrit.server.schema.DataSourceProvider; import com.google.gerrit.server.schema.DataSourceType; import com.google.gerrit.server.schema.DatabaseModule; import com.google.gerrit.server.schema.SchemaModule; import com.google.gerrit.server.securestore.SecureStoreClassName; import com.google.gwtorm.server.OrmException; import com.google.inject.AbstractModule; import com.google.inject.Binding; 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.TypeLiteral; import com.google.inject.name.Named; import com.google.inject.name.Names; import com.google.inject.spi.Message; import com.google.inject.util.Providers; import java.lang.annotation.Annotation; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import org.eclipse.jgit.lib.Config; import org.kohsuke.args4j.Option; public abstract class SiteProgram extends AbstractProgram { @Option( name = "--site-path", aliases = {"-d"}, usage = "Local directory containing site data" ) private void setSitePath(String path) { sitePath = Paths.get(path); } protected Provider<DataSource> dsProvider; private Path sitePath = Paths.get("."); protected SiteProgram() {} protected SiteProgram(Path sitePath) { this.sitePath = sitePath; } protected SiteProgram(Path sitePath, final Provider<DataSource> dsProvider) { this.sitePath = sitePath; this.dsProvider = dsProvider; } /** @return the site path specified on the command line. */ protected Path getSitePath() { return sitePath; } /** Ensures we are running inside of a valid site, otherwise throws a Die. */ protected void mustHaveValidSite() throws Die { if (!Files.exists(sitePath.resolve("etc").resolve("gerrit.config"))) { throw die("not a Gerrit site: '" + getSitePath() + "'\nPerhaps you need to run init first?"); } } /** @return provides database connectivity and site path. */ protected Injector createDbInjector(DataSourceProvider.Context context) { return createDbInjector(false, context); } /** @return provides database connectivity and site path. */ protected Injector createDbInjector( final boolean enableMetrics, final DataSourceProvider.Context context) { final Path sitePath = getSitePath(); final List<Module> modules = new ArrayList<>(); Module sitePathModule = new AbstractModule() { @Override protected void configure() { bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath); bind(String.class) .annotatedWith(SecureStoreClassName.class) .toProvider(Providers.of(getConfiguredSecureStoreClass())); } }; modules.add(sitePathModule); if (enableMetrics) { modules.add(new DropWizardMetricMaker.ApiModule()); } else { modules.add( new AbstractModule() { @Override protected void configure() { bind(MetricMaker.class).to(DisabledMetricMaker.class); } }); } modules.add( new LifecycleModule() { @Override protected void configure() { bind(DataSourceProvider.Context.class).toInstance(context); if (dsProvider != null) { bind(Key.get(DataSource.class, Names.named("ReviewDb"))) .toProvider(dsProvider) .in(SINGLETON); if (LifecycleListener.class.isAssignableFrom(dsProvider.getClass())) { listener().toInstance((LifecycleListener) dsProvider); } } else { bind(Key.get(DataSource.class, Names.named("ReviewDb"))) .toProvider(SiteLibraryBasedDataSourceProvider.class) .in(SINGLETON); listener().to(SiteLibraryBasedDataSourceProvider.class); } } }); Module configModule = new GerritServerConfigModule(); modules.add(configModule); Injector cfgInjector = Guice.createInjector(sitePathModule, configModule); Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class)); String dbType; if (dsProvider != null) { dbType = getDbType(dsProvider); } else { dbType = cfg.getString("database", null, "type"); } if (dbType == null) { throw new ProvisionException("database.type must be defined"); } final DataSourceType dst = Guice.createInjector(new DataSourceModule(), configModule, sitePathModule) .getInstance(Key.get(DataSourceType.class, Names.named(dbType.toLowerCase()))); modules.add( new AbstractModule() { @Override protected void configure() { bind(DataSourceType.class).toInstance(dst); } }); modules.add(new DatabaseModule()); modules.add(new SchemaModule()); modules.add(cfgInjector.getInstance(GitRepositoryManagerModule.class)); modules.add(new ConfigNotesMigration.Module()); modules.addAll(LibModuleLoader.loadModules(cfgInjector)); try { return Guice.createInjector(PRODUCTION, modules); } catch (CreationException ce) { final Message first = ce.getErrorMessages().iterator().next(); Throwable why = first.getCause(); if (why instanceof SQLException) { throw die("Cannot connect to SQL database", why); } if (why instanceof OrmException && why.getCause() != null && "Unable to determine driver URL".equals(why.getMessage())) { why = why.getCause(); if (isCannotCreatePoolException(why)) { throw die("Cannot connect to SQL database", why.getCause()); } throw die("Cannot connect to SQL database", why); } final StringBuilder buf = new StringBuilder(); if (why != null) { buf.append(why.getMessage()); why = why.getCause(); } else { buf.append(first.getMessage()); } while (why != null) { buf.append("\n caused by "); buf.append(why.toString()); why = why.getCause(); } throw die(buf.toString(), new RuntimeException("DbInjector failed", ce)); } } protected final String getConfiguredSecureStoreClass() { return getSecureStoreClassName(sitePath); } private String getDbType(Provider<DataSource> dsProvider) { String dbProductName; try (Connection conn = dsProvider.get().getConnection()) { dbProductName = conn.getMetaData().getDatabaseProductName().toLowerCase(); } catch (SQLException e) { throw new RuntimeException(e); } List<Module> modules = new ArrayList<>(); modules.add( new AbstractModule() { @Override protected void configure() { bind(Path.class).annotatedWith(SitePath.class).toInstance(getSitePath()); } }); modules.add(new GerritServerConfigModule()); modules.add(new DataSourceModule()); Injector i = Guice.createInjector(modules); List<Binding<DataSourceType>> dsTypeBindings = i.findBindingsByType(new TypeLiteral<DataSourceType>() {}); for (Binding<DataSourceType> binding : dsTypeBindings) { Annotation annotation = binding.getKey().getAnnotation(); if (annotation instanceof Named) { if (((Named) annotation).value().toLowerCase().contains(dbProductName)) { return ((Named) annotation).value(); } } } throw new IllegalStateException( String.format( "Cannot guess database type from the database product name '%s'", dbProductName)); } @SuppressWarnings("deprecation") private static boolean isCannotCreatePoolException(Throwable why) { return why instanceof org.apache.commons.dbcp.SQLNestedException && why.getCause() != null && why.getMessage().startsWith("Cannot create PoolableConnectionFactory"); } }