Abstract out concepts of DataSourceType and DatabaseConfigInitializer.

Encapsulate data source specific logic into subclasses of DataSourceType
and DatabaseConfigInitializer.  DataSourceType and
DatabaseConfigInitializer are separated because we need them at
different stages (init vs runtime) and they also need to exist in
different projects with different dependencies.

This is a first step toward providing support for different database
platforms as plugins.

Change-Id: I01ea666ef7f682d6137f0e64005d979dd706f67b
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
Signed-off-by: Sasa Zivkov <sasa.zivkov@sap.com>
This commit is contained in:
Sasa Zivkov
2012-06-27 16:44:49 +02:00
committed by Edwin Kempin
parent 05acbe4c0a
commit b53bffe1b9
27 changed files with 798 additions and 222 deletions

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2012 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.init;
/** Abstraction of initializer for the database section */
interface DatabaseConfigInitializer {
/**
* Performs database platform specific configuration steps and writes
* configuration parameters into the given database section
*/
public void initConfig(Section databaseSection);
}

View File

@@ -0,0 +1,41 @@
// Copyright (C) 2012 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.init;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
public class DatabaseConfigModule extends AbstractModule {
private final SitePaths site;
public DatabaseConfigModule(final SitePaths site) {
this.site = site;
}
@Override
protected void configure() {
bind(SitePaths.class).toInstance(site);
bind(DatabaseConfigInitializer.class).annotatedWith(
Names.named("h2")).to(H2Initializer.class);
bind(DatabaseConfigInitializer.class).annotatedWith(
Names.named("jdbc")).to(JDBCInitializer.class);
bind(DatabaseConfigInitializer.class).annotatedWith(
Names.named("mysql")).to(MySqlInitializer.class);
bind(DatabaseConfigInitializer.class).annotatedWith(
Names.named("postgresql")).to(PostgreSQLInitializer.class);
}
}

View File

@@ -0,0 +1,48 @@
// Copyright (C) 2012 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.init;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import java.io.File;
class H2Initializer implements DatabaseConfigInitializer {
private final SitePaths site;
@Inject
H2Initializer(final SitePaths site) {
this.site = site;
}
@Override
public void initConfig(Section databaseSection) {
String path = databaseSection.get("database");
if (path == null) {
path = "db/ReviewDB";
databaseSection.set("database", path);
}
File db = site.resolve(path);
if (db == null) {
throw InitUtil.die("database.database must be supplied for H2");
}
db = db.getParentFile();
if (!db.exists() && !db.mkdirs()) {
throw InitUtil.die("cannot create database.database "
+ db.getAbsolutePath());
}
}
}

View File

@@ -14,17 +14,24 @@
package com.google.gerrit.pgm.init; package com.google.gerrit.pgm.init;
import static com.google.gerrit.pgm.init.InitUtil.die; import static com.google.inject.Stage.PRODUCTION;
import static com.google.gerrit.pgm.init.InitUtil.username;
import static com.google.gerrit.server.schema.DataSourceProvider.Type.H2;
import com.google.gerrit.pgm.util.ConsoleUI; import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.schema.DataSourceProvider; import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import java.io.File; import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/** Initialize the {@code database} configuration section. */ /** Initialize the {@code database} configuration section. */
@Singleton @Singleton
@@ -46,59 +53,28 @@ class InitDatabase implements InitStep {
public void run() { public void run() {
ui.header("SQL Database"); ui.header("SQL Database");
final DataSourceProvider.Type db_type = Set<String> allowedValues = new TreeSet<String>();
database.select("Database server type", "type", H2); 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());
}
}
switch (db_type) { String dbType =
case MYSQL: database.select("Database server type", "type", "h2", allowedValues);
DatabaseConfigInitializer dci =
i.getInstance(Key.get(DatabaseConfigInitializer.class,
Names.named(dbType.toLowerCase())));
if (dci instanceof MySqlInitializer) {
libraries.mysqlDriver.downloadRequired(); libraries.mysqlDriver.downloadRequired();
break;
} }
final boolean userPassAuth; dci.initConfig(database);
switch (db_type) {
case H2: {
userPassAuth = false;
String path = database.get("database");
if (path == null) {
path = "db/ReviewDB";
database.set("database", path);
}
File db = site.resolve(path);
if (db == null) {
throw die("database.database must be supplied for H2");
}
db = db.getParentFile();
if (!db.exists() && !db.mkdirs()) {
throw die("cannot create database.database " + db.getAbsolutePath());
}
break;
}
case JDBC: {
userPassAuth = true;
database.string("Driver class name", "driver", null);
database.string("URL", "url", null);
break;
}
case POSTGRESQL:
case MYSQL: {
userPassAuth = true;
final String defPort = "(" + db_type.toString() + " default)";
database.string("Server hostname", "hostname", "localhost");
database.string("Server port", "port", defPort, true);
database.string("Database name", "database", "reviewdb");
break;
}
default:
throw die("internal bug, database " + db_type + " not supported");
}
if (userPassAuth) {
database.string("Database username", "username", username());
database.password("username", "password");
}
} }
} }

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2012 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.init;
import static com.google.gerrit.pgm.init.InitUtil.username;
class JDBCInitializer implements DatabaseConfigInitializer {
@Override
public void initConfig(Section databaseSection) {
databaseSection.string("Driver class name", "driver", null);
databaseSection.string("URL", "url", null);
databaseSection.string("Database username", "username", username());
databaseSection.password("username", "password");
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2012 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.init;
import static com.google.gerrit.pgm.init.InitUtil.username;
class MySqlInitializer implements DatabaseConfigInitializer {
@Override
public void initConfig(Section databaseSection) {
final String defPort = "(mysql default)";
databaseSection.string("Server hostname", "hostname", "localhost");
databaseSection.string("Server port", "port", defPort, true);
databaseSection.string("Database name", "database", "reviewdb");
databaseSection.string("Database username", "username", username());
databaseSection.password("username", "password");
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2012 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.init;
import static com.google.gerrit.pgm.init.InitUtil.username;
class PostgreSQLInitializer implements DatabaseConfigInitializer {
@Override
public void initConfig(Section databaseSection) {
final String defPort = "(postgresql default)";
databaseSection.string("Server hostname", "hostname", "localhost");
databaseSection.string("Server port", "port", defPort, true);
databaseSection.string("Database name", "database", "reviewdb");
databaseSection.string("Database username", "username", username());
databaseSection.password("username", "password");
}
}

View File

@@ -23,6 +23,7 @@ import com.google.inject.assistedinject.Assisted;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set;
/** Helper to edit a section of the configuration files. */ /** Helper to edit a section of the configuration files. */
class Section { class Section {
@@ -125,6 +126,16 @@ class Section {
return newValue; return newValue;
} }
String select(final String title, final String name, final String dv,
Set<String> allowedValues) {
final String ov = get(name);
String nv = ui.readString(ov != null ? ov : dv, allowedValues, "%s", title);
if (!eq(ov, nv)) {
set(name, nv);
}
return nv;
}
String password(final String username, final String password) { String password(final String username, final String password) {
final String ov = getSecure(password); final String ov = getSecure(password);
@@ -166,6 +177,10 @@ class Section {
} }
} }
String getName() {
return section;
}
private static boolean eq(final String a, final String b) { private static boolean eq(final String a, final String b) {
if (a == null && b == null) { if (a == null && b == null) {
return true; return true;

View File

@@ -20,7 +20,6 @@ import static com.google.gerrit.pgm.init.InitUtil.saveSecure;
import com.google.gerrit.pgm.util.ConsoleUI; import com.google.gerrit.pgm.util.ConsoleUI;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.util.SocketUtil; import com.google.gerrit.server.util.SocketUtil;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@@ -122,7 +121,7 @@ class UpgradeFrom2_0_x implements InitStep {
String url = oldprop.getProperty("url"); String url = oldprop.getProperty("url");
if (url != null && !convertUrl(database, url)) { if (url != null && !convertUrl(database, url)) {
database.set("type", DataSourceProvider.Type.JDBC); database.set("type", "jdbc");
database.set("driver", oldprop.getProperty("driver")); database.set("driver", oldprop.getProperty("driver"));
database.set("url", url); database.set("url", url);
} }
@@ -189,7 +188,7 @@ class UpgradeFrom2_0_x implements InitStep {
if (url.startsWith("jdbc:h2:file:")) { if (url.startsWith("jdbc:h2:file:")) {
url = url.substring("jdbc:h2:file:".length()); url = url.substring("jdbc:h2:file:".length());
database.set("type", DataSourceProvider.Type.H2); database.set("type", "h2");
database.set("database", url); database.set("database", url);
return true; return true;
} }
@@ -202,7 +201,7 @@ class UpgradeFrom2_0_x implements InitStep {
} }
final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0); final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0);
database.set("type", DataSourceProvider.Type.POSTGRESQL); database.set("type", "postgresql");
sethost(database, addr); sethost(database, addr);
database.set("database", url.substring(sl + 1)); database.set("database", url.substring(sl + 1));
setuser(database, username, password); setuser(database, username, password);
@@ -211,7 +210,7 @@ class UpgradeFrom2_0_x implements InitStep {
if (url.startsWith("jdbc:postgresql:")) { if (url.startsWith("jdbc:postgresql:")) {
url = url.substring("jdbc:postgresql:".length()); url = url.substring("jdbc:postgresql:".length());
database.set("type", DataSourceProvider.Type.POSTGRESQL); database.set("type", "postgresql");
database.set("hostname", "localhost"); database.set("hostname", "localhost");
database.set("database", url); database.set("database", url);
setuser(database, username, password); setuser(database, username, password);
@@ -226,7 +225,7 @@ class UpgradeFrom2_0_x implements InitStep {
} }
final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0); final InetSocketAddress addr = SocketUtil.parse(url.substring(0, sl), 0);
database.set("type", DataSourceProvider.Type.MYSQL); database.set("type", "mysql");
sethost(database, addr); sethost(database, addr);
database.set("database", url.substring(sl + 1)); database.set("database", url.substring(sl + 1));
setuser(database, username, password); setuser(database, username, password);

View File

@@ -18,6 +18,7 @@ import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
import java.io.Console; import java.io.Console;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Set;
/** Console based interaction with the invoking user. */ /** Console based interaction with the invoking user. */
public abstract class ConsoleUI { public abstract class ConsoleUI {
@@ -73,6 +74,10 @@ public abstract class ConsoleUI {
/** Prompt the user for a string, suggesting a default, and returning choice. */ /** Prompt the user for a string, suggesting a default, and returning choice. */
public abstract String readString(String def, String fmt, Object... args); public abstract String readString(String def, String fmt, Object... args);
/** Prompt the user to make a choice from an allowed list of values. */
public abstract String readString(String def, Set<String> allowedValues,
String fmt, Object... args);
/** Prompt the user for an integer value, suggesting a default. */ /** Prompt the user for an integer value, suggesting a default. */
public int readInt(int def, String fmt, Object... args) { public int readInt(int def, String fmt, Object... args) {
for (;;) { for (;;) {
@@ -161,6 +166,24 @@ public abstract class ConsoleUI {
return r; return r;
} }
@Override
public String readString(String def, Set<String> allowedValues, String fmt,
Object... args) {
for (;;) {
String r = readString(def, fmt, args);
if (allowedValues.contains(r.toLowerCase())) {
return r.toLowerCase();
}
if (!"?".equals(r)) {
console.printf("error: '%s' is not a valid choice\n", r);
}
console.printf(" Supported options are:\n");
for (final String v : allowedValues) {
console.printf(" %s\n", v.toString().toLowerCase());
}
}
}
@Override @Override
public String password(String fmt, Object... args) { public String password(String fmt, Object... args) {
final String prompt = String.format(fmt, args); final String prompt = String.format(fmt, args);
@@ -241,6 +264,12 @@ public abstract class ConsoleUI {
return def; return def;
} }
@Override
public String readString(String def, Set<String> allowedValues, String fmt,
Object... args) {
return def;
}
@Override @Override
public void waitForUser() { public void waitForUser() {
} }

View File

@@ -18,10 +18,13 @@ import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION; import static com.google.inject.Stage.PRODUCTION;
import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.GerritServerConfigModule; import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath; import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.LocalDiskRepositoryManager; import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.schema.DataSourceModule;
import com.google.gerrit.server.schema.DataSourceProvider; 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.DatabaseModule;
import com.google.gerrit.server.schema.SchemaModule; import com.google.gerrit.server.schema.SchemaModule;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
@@ -34,6 +37,7 @@ import com.google.inject.Module;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import com.google.inject.spi.Message; import com.google.inject.spi.Message;
import org.eclipse.jgit.lib.Config;
import org.kohsuke.args4j.Option; import org.kohsuke.args4j.Option;
import java.io.File; import java.io.File;
@@ -147,12 +151,15 @@ public abstract class SiteProgram extends AbstractProgram {
final File sitePath = getSitePath(); final File sitePath = getSitePath();
final List<Module> modules = new ArrayList<Module>(); final List<Module> modules = new ArrayList<Module>();
modules.add(new AbstractModule() {
Module sitePathModule = new AbstractModule() {
@Override @Override
protected void configure() { protected void configure() {
bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath); bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
} }
}); };
modules.add(sitePathModule);
modules.add(new LifecycleModule() { modules.add(new LifecycleModule() {
@Override @Override
protected void configure() { protected void configure() {
@@ -162,7 +169,21 @@ public abstract class SiteProgram extends AbstractProgram {
listener().to(DataSourceProvider.class); listener().to(DataSourceProvider.class);
} }
}); });
modules.add(new GerritServerConfigModule()); 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 = cfg.getString("database", null, "type");
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 DatabaseModule());
modules.add(new SchemaModule()); modules.add(new SchemaModule());
modules.add(new LocalDiskRepositoryManager.Module()); modules.add(new LocalDiskRepositoryManager.Module());

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2012 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.server.config;
import org.eclipse.jgit.lib.Config;
/** Provides access to one section from {@link Config} */
public class ConfigSection {
private final Config cfg;
private final String section;
public ConfigSection(Config cfg, String section) {
this.cfg = cfg;
this.section = section;
}
public String optional(String name) {
return cfg.getString(section, null, name);
}
public String required(String name) {
return ConfigUtil.getRequired(cfg, null, name);
}
}

View File

@@ -286,6 +286,15 @@ public class ConfigUtil {
} }
} }
public static String getRequired(Config cfg, String section, String name) {
final String v = cfg.getString(section, null, name);
if (v == null || "".equals(v)) {
throw new IllegalArgumentException("No " + section + "." + name
+ " configured");
}
return v;
}
private static boolean match(final String a, final String... cases) { private static boolean match(final String a, final String... cases) {
for (final String b : cases) { for (final String b : cases) {
if (equalsIgnoreCase(a, b)) { if (equalsIgnoreCase(a, b)) {

View File

@@ -0,0 +1,66 @@
// Copyright (C) 2012 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.server.schema;
import com.google.gerrit.reviewdb.server.ReviewDb;
import java.io.IOException;
import java.io.InputStream;
public abstract class BaseDataSourceType implements DataSourceType {
private final String driver;
protected BaseDataSourceType(String driver) {
this.driver = driver;
}
@Override
public final String getDriver() {
return driver;
}
@Override
public boolean usePool() {
return true;
}
@Override
public ScriptRunner getIndexScript() throws IOException {
return getScriptRunner("index_generic.sql");
}
@Override
public ScriptRunner getNextValScript() throws IOException {
return ScriptRunner.NOOP;
}
protected static final ScriptRunner getScriptRunner(String path) throws IOException {
if (path == null) {
return ScriptRunner.NOOP;
}
InputStream in = ReviewDb.class.getResourceAsStream(path);
if (in == null) {
throw new IllegalStateException("SQL script " + path + " not found");
}
ScriptRunner runner;
try {
runner = new ScriptRunner(path, in);
} finally {
in.close();
}
return runner;
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2012 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.server.schema;
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
public class DataSourceModule extends AbstractModule {
@Override
protected void configure() {
bind(DataSourceType.class).annotatedWith(Names.named("h2")).to(H2.class);
bind(DataSourceType.class).annotatedWith(Names.named("jdbc")).to(JDBC.class);
bind(DataSourceType.class).annotatedWith(Names.named("mysql")).to(MySql.class);
bind(DataSourceType.class).annotatedWith(Names.named("postgresql")).to(PostgreSQL.class);
}
}

View File

@@ -14,11 +14,12 @@
package com.google.gerrit.server.schema; package com.google.gerrit.server.schema;
import static com.google.gerrit.server.config.ConfigUtil.getEnum;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.config.ConfigSection;
import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
@@ -31,8 +32,6 @@ import com.google.inject.Singleton;
import org.apache.commons.dbcp.BasicDataSource; import org.apache.commons.dbcp.BasicDataSource;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties; import java.util.Properties;
@@ -46,8 +45,8 @@ public final class DataSourceProvider implements Provider<DataSource>,
@Inject @Inject
DataSourceProvider(final SitePaths site, DataSourceProvider(final SitePaths site,
@GerritServerConfig final Config cfg, Context ctx) { @GerritServerConfig final Config cfg, Context ctx, DataSourceType dst) {
ds = open(site, cfg, ctx); ds = open(site, cfg, ctx, dst);
} }
@Override @Override
@@ -74,100 +73,27 @@ public final class DataSourceProvider implements Provider<DataSource>,
SINGLE_USER, MULTI_USER; SINGLE_USER, MULTI_USER;
} }
public static enum Type {
H2, POSTGRESQL, MYSQL, JDBC;
}
private DataSource open(final SitePaths site, final Config cfg, private DataSource open(final SitePaths site, final Config cfg,
final Context context) { final Context context, final DataSourceType dst) {
Type type = getEnum(cfg, "database", null, "type", Type.values(), null); ConfigSection dbs = new ConfigSection(cfg, "database");
String driver = optional(cfg, "driver"); String driver = dbs.optional("driver");
String url = optional(cfg, "url"); if (Strings.isNullOrEmpty(driver)) {
String username = optional(cfg, "username"); driver = dst.getDriver();
String password = optional(cfg, "password");
if (url == null || url.isEmpty()) {
if (type == null) {
type = Type.H2;
}
switch (type) {
case H2: {
String database = optional(cfg, "database");
if (database == null || database.isEmpty()) {
database = "db/ReviewDB";
}
File db = site.resolve(database);
try {
db = db.getCanonicalFile();
} catch (IOException e) {
db = db.getAbsoluteFile();
}
url = "jdbc:h2:" + db.toURI().toString();
break;
}
case POSTGRESQL: {
final StringBuilder b = new StringBuilder();
b.append("jdbc:postgresql://");
b.append(hostname(optional(cfg, "hostname")));
b.append(port(optional(cfg, "port")));
b.append("/");
b.append(required(cfg, "database"));
url = b.toString();
break;
}
case MYSQL: {
final StringBuilder b = new StringBuilder();
b.append("jdbc:mysql://");
b.append(hostname(optional(cfg, "hostname")));
b.append(port(optional(cfg, "port")));
b.append("/");
b.append(required(cfg, "database"));
url = b.toString();
break;
}
case JDBC:
driver = required(cfg, "driver");
url = required(cfg, "url");
break;
default:
throw new IllegalArgumentException(type + " not supported");
}
} }
if (driver == null || driver.isEmpty()) { String url = dbs.optional("url");
if (url.startsWith("jdbc:h2:")) { if (Strings.isNullOrEmpty(url)) {
driver = "org.h2.Driver"; url = dst.getUrl();
} else if (url.startsWith("jdbc:postgresql:")) {
driver = "org.postgresql.Driver";
} else if (url.startsWith("jdbc:mysql:")) {
driver = "com.mysql.jdbc.Driver";
} else {
throw new IllegalArgumentException("database.driver must be set");
}
} }
String username = dbs.optional("username");
String password = dbs.optional("password");
boolean usePool; boolean usePool;
if (url.startsWith("jdbc:mysql:")) {
// MySQL has given us trouble with the connection pool,
// sometimes the backend disconnects and the pool winds
// up with a stale connection. Fortunately opening up
// a new MySQL connection is usually very fast.
//
usePool = false;
} else {
usePool = true;
}
usePool = cfg.getBoolean("database", "connectionpool", usePool);
if (context == Context.SINGLE_USER) { if (context == Context.SINGLE_USER) {
usePool = false; usePool = false;
} else {
usePool = cfg.getBoolean("database", "connectionpool", dst.usePool());
} }
if (usePool) { if (usePool) {
@@ -207,33 +133,4 @@ public final class DataSourceProvider implements Provider<DataSource>,
} }
} }
} }
private static String hostname(String hostname) {
if (hostname == null || hostname.isEmpty()) {
hostname = "localhost";
} else if (hostname.contains(":") && !hostname.startsWith("[")) {
hostname = "[" + hostname + "]";
}
return hostname;
}
private static String port(String port) {
if (port != null && !port.isEmpty()) {
return ":" + port;
}
return "";
}
private static String optional(final Config config, final String name) {
return config.getString("database", null, name);
}
private static String required(final Config config, final String name) {
final String v = optional(config, name);
if (v == null || "".equals(v)) {
throw new IllegalArgumentException("No database." + name + " configured");
}
return v;
}
} }

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2012 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.server.schema;
import java.io.IOException;
/** Abstraction of a supported database platform */
public interface DataSourceType {
public String getDriver();
public String getUrl();
public boolean usePool();
/**
* Return a ScriptRunner that runs the index script. Must not return
* <code>null</code>, but may return a ScriptRunner that does nothing.
*
* @throws IOException
*/
public ScriptRunner getIndexScript() throws IOException;
/**
* Return a ScriptRunner that runs the nextVal script. Must not return
* <code>null</code>, but may return a ScriptRunner that does nothing.
*
* @throws IOException
*/
public ScriptRunner getNextValScript() throws IOException;
}

View File

@@ -0,0 +1,52 @@
// Copyright (C) 2012 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.server.schema;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
import java.io.File;
import java.io.IOException;
class H2 extends BaseDataSourceType {
protected final Config cfg;
private final SitePaths site;
@Inject
H2(final SitePaths site, @GerritServerConfig final Config cfg) {
super("org.h2.Driver");
this.cfg = cfg;
this.site = site;
}
@Override
public String getUrl() {
String database = cfg.getString("database", null, "database");
if (database == null || database.isEmpty()) {
database = "db/ReviewDB";
}
File db = site.resolve(database);
try {
db = db.getCanonicalFile();
} catch (IOException e) {
db = db.getAbsoluteFile();
}
return "jdbc:h2:" + db.toURI().toString();
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2012 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.server.schema;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
class JDBC extends BaseDataSourceType {
protected final Config cfg;
@Inject
JDBC(@GerritServerConfig final Config cfg) {
super(ConfigUtil.getRequired(cfg, "database", "driver"));
this.cfg = cfg;
}
@Override
public String getUrl() {
return ConfigUtil.getRequired(cfg, "database", "url");
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2012 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.server.schema;
public class JdbcUtil {
public static String hostname(String hostname) {
if (hostname == null || hostname.isEmpty()) {
hostname = "localhost";
} else if (hostname.contains(":") && !hostname.startsWith("[")) {
hostname = "[" + hostname + "]";
}
return hostname;
}
static String port(String port) {
if (port != null && !port.isEmpty()) {
return ":" + port;
}
return "";
}
}

View File

@@ -0,0 +1,59 @@
// Copyright (C) 2012 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.server.schema;
import static com.google.gerrit.server.schema.JdbcUtil.hostname;
import static com.google.gerrit.server.schema.JdbcUtil.port;
import com.google.gerrit.server.config.ConfigSection;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
import java.io.IOException;
class MySql extends BaseDataSourceType {
private Config cfg;
@Inject
public MySql(@GerritServerConfig final Config cfg) {
super("com.mysql.jdbc.Driver");
this.cfg = cfg;
}
@Override
public String getUrl() {
final StringBuilder b = new StringBuilder();
final ConfigSection dbs = new ConfigSection(cfg, "database");
b.append("jdbc:mysql://");
b.append(hostname(dbs.optional("hostname")));
b.append(port(dbs.optional("port")));
b.append("/");
b.append(dbs.required("database"));
return b.toString();
}
@Override
public boolean usePool() {
return false;
}
@Override
public ScriptRunner getNextValScript() throws IOException {
return getScriptRunner("mysql_nextval.sql");
}
}

View File

@@ -0,0 +1,54 @@
// Copyright (C) 2012 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.server.schema;
import static com.google.gerrit.server.schema.JdbcUtil.hostname;
import static com.google.gerrit.server.schema.JdbcUtil.port;
import com.google.gerrit.server.config.ConfigSection;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
import java.io.IOException;
class PostgreSQL extends BaseDataSourceType {
private Config cfg;
@Inject
public PostgreSQL(@GerritServerConfig final Config cfg) {
super("org.postgresql.Driver");
this.cfg = cfg;
}
@Override
public String getUrl() {
final StringBuilder b = new StringBuilder();
final ConfigSection dbc = new ConfigSection(cfg, "database");
b.append("jdbc:postgresql://");
b.append(hostname(dbc.optional("hostname")));
b.append(port(dbc.optional("port")));
b.append("/");
b.append(dbc.required("database"));
return b.toString();
}
@Override
public ScriptRunner getIndexScript() throws IOException {
return getScriptRunner("index_postgres.sql");
}
}

View File

@@ -38,10 +38,6 @@ import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.git.ProjectConfig;
import com.google.gwtorm.jdbc.JdbcExecutor; import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema; import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.schema.sql.DialectH2;
import com.google.gwtorm.schema.sql.DialectMySQL;
import com.google.gwtorm.schema.sql.DialectPostgreSQL;
import com.google.gwtorm.schema.sql.SqlDialect;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -65,11 +61,9 @@ public class SchemaCreator {
private final GitRepositoryManager mgr; private final GitRepositoryManager mgr;
private final AllProjectsName allProjectsName; private final AllProjectsName allProjectsName;
private final PersonIdent serverUser; private final PersonIdent serverUser;
private final DataSourceType dataSourceType;
private final int versionNbr; private final int versionNbr;
private final ScriptRunner index_generic;
private final ScriptRunner index_postgres;
private final ScriptRunner mysql_nextval;
private AccountGroup admin; private AccountGroup admin;
private AccountGroup anonymous; private AccountGroup anonymous;
@@ -81,23 +75,23 @@ public class SchemaCreator {
@Current SchemaVersion version, @Current SchemaVersion version,
GitRepositoryManager mgr, GitRepositoryManager mgr,
AllProjectsName allProjectsName, AllProjectsName allProjectsName,
@GerritPersonIdent PersonIdent au) { @GerritPersonIdent PersonIdent au,
this(site.site_path, version, mgr, allProjectsName, au); DataSourceType dst) {
this(site.site_path, version, mgr, allProjectsName, au, dst);
} }
public SchemaCreator(@SitePath File site, public SchemaCreator(@SitePath File site,
@Current SchemaVersion version, @Current SchemaVersion version,
GitRepositoryManager gitMgr, GitRepositoryManager gitMgr,
AllProjectsName ap, AllProjectsName ap,
@GerritPersonIdent PersonIdent au) { @GerritPersonIdent PersonIdent au,
DataSourceType dst) {
site_path = site; site_path = site;
mgr = gitMgr; mgr = gitMgr;
allProjectsName = ap; allProjectsName = ap;
serverUser = au; serverUser = au;
dataSourceType = dst;
versionNbr = version.getVersionNbr(); versionNbr = version.getVersionNbr();
index_generic = new ScriptRunner("index_generic.sql");
index_postgres = new ScriptRunner("index_postgres.sql");
mysql_nextval = new ScriptRunner("mysql_nextval.sql");
} }
public void create(final ReviewDb db) throws OrmException, IOException, public void create(final ReviewDb db) throws OrmException, IOException,
@@ -123,20 +117,8 @@ public class SchemaCreator {
initWildCardProject(); initWildCardProject();
} }
final SqlDialect d = jdbc.getDialect(); dataSourceType.getIndexScript().run(db);
if (d instanceof DialectH2) { dataSourceType.getNextValScript().run(db);
index_generic.run(db);
} else if (d instanceof DialectMySQL) {
index_generic.run(db);
mysql_nextval.run(db);
} else if (d instanceof DialectPostgreSQL) {
index_postgres.run(db);
} else {
throw new OrmException("Unsupported database " + d.getClass().getName());
}
} }
private AccountGroup newGroup(ReviewDb c, String name, AccountGroup.UUID uuid) private AccountGroup newGroup(ReviewDb c, String name, AccountGroup.UUID uuid)

View File

@@ -19,7 +19,6 @@ import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@@ -34,10 +33,15 @@ class ScriptRunner {
private final String name; private final String name;
private final List<String> commands; private final List<String> commands;
ScriptRunner(final String name) { static final ScriptRunner NOOP = new ScriptRunner(null, null) {
void run(final ReviewDb db) {
};
};
ScriptRunner(final String scriptName, final InputStream script) {
this.name = scriptName;
try { try {
this.name = name; this.commands = script != null ? parse(script) : null;
this.commands = parse(name);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Cannot parse " + name, e); throw new IllegalStateException("Cannot parse " + name, e);
} }
@@ -63,12 +67,7 @@ class ScriptRunner {
} }
} }
private List<String> parse(final String name) throws IOException { private List<String> parse(final InputStream in) throws IOException {
InputStream in = ReviewDb.class.getResourceAsStream(name);
if (in == null) {
throw new FileNotFoundException("SQL script " + name + " not found");
}
BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
try { try {
String delimiter = ";"; String delimiter = ";";

View File

@@ -27,6 +27,7 @@ import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LocalDiskRepositoryManager; import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.testutil.InMemoryDatabase; import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gerrit.testutil.InMemoryH2Type;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory; import com.google.gwtorm.server.SchemaFactory;
import com.google.gwtorm.server.StatementExecutor; import com.google.gwtorm.server.StatementExecutor;
@@ -94,6 +95,8 @@ public class SchemaUpdaterTest extends TestCase {
bind(String.class) // bind(String.class) //
.annotatedWith(AnonymousCowardName.class) // .annotatedWith(AnonymousCowardName.class) //
.toProvider(AnonymousCowardNameProvider.class); .toProvider(AnonymousCowardNameProvider.class);
bind(DataSourceType.class).to(InMemoryH2Type.class);
} }
}).getInstance(SchemaUpdater.class); }).getInstance(SchemaUpdater.class);

View File

@@ -157,7 +157,8 @@ public class InMemoryDatabase implements SchemaFactory<ReviewDb> {
schemaVersion, schemaVersion,
null, null,
new AllProjectsName("Test-Projects"), new AllProjectsName("Test-Projects"),
new PersonIdent("name", "email@site")).create(c); new PersonIdent("name", "email@site"),
new InMemoryH2Type()).create(c);
} catch (IOException e) { } catch (IOException e) {
throw new OrmException("Cannot create in-memory database", e); throw new OrmException("Cannot create in-memory database", e);
} catch (ConfigInvalidException e) { } catch (ConfigInvalidException e) {

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2012 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.testutil;
import com.google.gerrit.server.schema.BaseDataSourceType;
public class InMemoryH2Type extends BaseDataSourceType {
protected InMemoryH2Type() {
super(null);
}
@Override
public String getUrl() {
// not used
throw new UnsupportedOperationException();
}
}