| // 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.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()); |
| |
| 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"); |
| } |
| } |