blob: 83da44447bfb559929ec8a8203462799ec162bdb [file] [log] [blame]
// Copyright (C) 2015 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.googlesource.gerrit.plugins.verifystatus.init;
import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.pgm.init.api.Section;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.gwtorm.server.StatementExecutor;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.googlesource.gerrit.plugins.verifystatus.server.CiDb;
import com.googlesource.gerrit.plugins.verifystatus.server.schema.CiDataSourceModule;
import com.googlesource.gerrit.plugins.verifystatus.server.schema.CiDataSourceProvider;
import com.googlesource.gerrit.plugins.verifystatus.server.schema.CiDataSourceType;
import com.googlesource.gerrit.plugins.verifystatus.server.schema.CiDataSourceTypeGuesser;
import com.googlesource.gerrit.plugins.verifystatus.server.schema.CiDatabaseModule;
import com.googlesource.gerrit.plugins.verifystatus.server.schema.CurrentSchemaVersion;
import com.googlesource.gerrit.plugins.verifystatus.server.schema.SchemaVersion;
import com.googlesource.gerrit.plugins.verifystatus.server.schema.UpdateUI;
import java.lang.annotation.Annotation;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
@Singleton
public class InitPlugin implements InitStep {
private final ConsoleUI ui;
private final SitePaths site;
private final Section configSection;
private final Injector parent;
private SchemaFactory<CiDb> dbFactory;
private Provider<SchemaVersion> updater;
@Inject
InitPlugin(
Section.Factory sections,
@PluginName String pluginName,
ConsoleUI ui,
SitePaths site,
Injector parent) {
this.ui = ui;
this.site = site;
this.configSection = sections.get("plugin", pluginName);
this.parent = parent;
}
@Override
public void run() throws Exception {
ui.header("SQL Database for CI plugin");
Set<String> allowedValues = Sets.newTreeSet();
Injector i = Guice.createInjector(PRODUCTION, new DatabaseConfigModule(site));
List<Binding<DatabaseConfigInitializer>> dbConfigBindings =
i.findBindingsByType(new TypeLiteral<DatabaseConfigInitializer>() {});
for (Binding<DatabaseConfigInitializer> binding : dbConfigBindings) {
Annotation annotation = binding.getKey().getAnnotation();
if (annotation instanceof Named) {
allowedValues.add(((Named) annotation).value());
}
}
if (!Strings.isNullOrEmpty(configSection.get("dbUrl"))
&& Strings.isNullOrEmpty(configSection.get("dbType"))) {
configSection.set("dbType", "h2");
}
String dbType = configSection.select("Database server type", "dbType", "h2", allowedValues);
DatabaseConfigInitializer dci =
i.getInstance(Key.get(DatabaseConfigInitializer.class, Names.named(dbType.toLowerCase())));
/**
* TODO(davido): We probably don't need that, as CI database would be from the same type as
* ReviewDb. So we expect that the needed libraries were already installed.
*
* <p>if (dci instanceof MySqlInitializer) { libraries.mysqlDriver.downloadRequired(); } else if
* (dci instanceof OracleInitializer) { libraries.oracleDriver.downloadRequired(); } else if
* (dci instanceof DB2Initializer) { libraries.db2Driver.downloadRequired(); }
*/
dci.initConfig(configSection);
}
@Override
public void postRun() throws Exception {
Injector i = buildInjector(parent);
updater = i.getProvider(SchemaVersion.class);
this.dbFactory = i.getInstance(Key.get(new TypeLiteral<SchemaFactory<CiDb>>() {}));
upgradeSchema();
}
private Injector buildInjector(final Injector parent) {
List<Module> modules = new ArrayList<>();
modules.add(
new LifecycleModule() {
@Override
protected void configure() {
// For bootstrap we need to retrieve the ds type first
CiDataSourceTypeGuesser guesser =
parent
.createChildInjector(new CiDataSourceModule())
.getInstance(Key.get(CiDataSourceTypeGuesser.class));
// For the ds type we retrieve the underlying implementation
CiDataSourceType dst =
parent
.createChildInjector(new CiDataSourceModule())
.getInstance(
Key.get(
CiDataSourceType.class, Names.named(guesser.guessDataSourceType())));
// Bind the type to the retrieved instance
bind(CiDataSourceType.class).toInstance(dst);
bind(CiDataSourceProvider.Context.class)
.toInstance(CiDataSourceProvider.Context.MULTI_USER);
bind(Key.get(DataSource.class, Names.named("CiDb")))
.toProvider(CiDataSourceProvider.class)
.in(SINGLETON);
listener().to(CiDataSourceProvider.class);
}
});
modules.add(new CiDatabaseModule());
modules.add(
new AbstractModule() {
@Override
protected void configure() {
bind(SchemaVersion.class).to(SchemaVersion.C);
}
});
return parent.createChildInjector(modules);
}
private void upgradeSchema() throws OrmException {
final List<String> pruneList = new ArrayList<>();
update(
new UpdateUI() {
@Override
public void message(String msg) {
System.err.println(msg);
System.err.flush();
}
@Override
public boolean yesno(boolean def, String msg) {
return ui.yesno(def, msg);
}
@Override
public boolean isBatch() {
return ui.isBatch();
}
@Override
public void pruneSchema(StatementExecutor e, List<String> prune) {
for (String p : prune) {
if (!pruneList.contains(p)) {
pruneList.add(p);
}
}
}
});
if (!pruneList.isEmpty()) {
StringBuilder msg = new StringBuilder();
msg.append("Execute the following SQL to drop unused objects:\n");
msg.append("\n");
for (String sql : pruneList) {
msg.append(" ");
msg.append(sql);
msg.append(";\n");
}
if (ui.isBatch()) {
System.err.print(msg);
System.err.flush();
} else if (ui.yesno(true, "%s\nExecute now", msg)) {
try (JdbcSchema db = (JdbcSchema) dbFactory.open();
JdbcExecutor e = new JdbcExecutor(db)) {
for (String sql : pruneList) {
e.execute(sql);
}
}
}
}
}
public void update(UpdateUI ui) throws OrmException {
try (CiDb db = dbFactory.open()) {
SchemaVersion u = updater.get();
CurrentSchemaVersion version = getSchemaVersion(db);
if (version == null) {
try (JdbcExecutor e = new JdbcExecutor((JdbcSchema) db)) {
((JdbcSchema) db).updateSchema(e);
}
final CurrentSchemaVersion sVer = CurrentSchemaVersion.create();
sVer.versionNbr = SchemaVersion.getBinaryVersion();
db.schemaVersion().insert(Collections.singleton(sVer));
} else {
try {
u.check(ui, version, db);
} catch (SQLException e) {
throw new OrmException("Cannot upgrade schema", e);
}
}
}
}
private CurrentSchemaVersion getSchemaVersion(CiDb db) {
try {
return db.schemaVersion().get(new CurrentSchemaVersion.Key());
} catch (OrmException e) {
return null;
}
}
}