package com.splout.db.engine;
/*
* #%L
* Splout SQL commons
* %%
* Copyright (C) 2012 - 2013 Datasalt Systems S.L.
* %%
* 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.
* #L%
*/
import com.google.common.io.Files;
import com.mysql.management.MysqldResource;
import com.mysql.management.MysqldResourceI;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
/**
* An interface to use MySQL(d) from Java in an embedded way. This actually starts Mysqld in a separate process with its
* own PID.
* <p/>
* Classes used: http://dev.mysql.com/doc/connector-mxj/en/connector-mxj-configuration-java-object.html
*/
public class EmbeddedMySQL {
public static final String DRIVER = "com.mysql.jdbc.Driver";
private final static Log log = LogFactory.getLog(EmbeddedMySQL.class);
public static class EmbeddedMySQLConfig {
public final static int DEFAULT_PORT = 4567;
public final static String DEFAULT_USER = "splout";
public final static String DEFAULT_PASS = "splout";
public final static File DEFAULT_RESIDENT_FOLDER = new File(System.getProperty("java.io.tmpdir"),
"mysql-splout");
final int port;
final String user;
final String pass;
final File residentFolder;
final Map<String, Object> customConfig;
public EmbeddedMySQLConfig() {
this(DEFAULT_PORT, DEFAULT_USER, DEFAULT_PASS, DEFAULT_RESIDENT_FOLDER, null);
}
public EmbeddedMySQLConfig(int port, String user, String pass, File residentFolder,
Map<String, Object> customConfig) {
this.port = port;
this.user = user;
this.pass = pass;
this.residentFolder = residentFolder;
this.customConfig = customConfig;
}
public Map<String, Object> getCustomConfig() {
return customConfig;
}
public String getPass() {
return pass;
}
public int getPort() {
return port;
}
public File getResidentFolder() {
return residentFolder;
}
public String getUser() {
return user;
}
/**
* Normal JDBC connection string to localhost with "createDatabaseIfNotExist"
*/
public String getLocalJDBCConnection(String dbName) {
return "jdbc:mysql://localhost:" + port + "/" + dbName + "?createDatabaseIfNotExist=true";
}
@Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
;
final EmbeddedMySQLConfig config;
MysqldResource resource = null;
public EmbeddedMySQL() {
this(new EmbeddedMySQLConfig());
}
public EmbeddedMySQLConfig getConfig() {
return config;
}
public EmbeddedMySQL(EmbeddedMySQLConfig config) {
this.config = config;
}
/**
* It's ok to call this multiple times (redundant times will be ignored).
*/
public void stop() {
if (resource != null) {
if (resource.isRunning()) {
resource.shutdown();
resource = null;
}
} else {
log.warn("Nothing to stop.");
}
}
/**
* TODO MySQL Hangs if port is busy, we should perform a timeout in a separate thred.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public void start(boolean deleteFilesIfExist) throws IOException, InterruptedException {
if (deleteFilesIfExist && config.residentFolder.exists()) {
File pidFile = new File(config.residentFolder, "data/MysqldResource.pid");
if (pidFile.exists()) {
// Issue "kill -9" if process is still alive
String pid = Files.toString(pidFile, Charset.defaultCharset());
log.info("Killing existing process: " + pid);
Runtime.getRuntime().exec("kill -9 " + pid).waitFor();
}
log.info("Deleting contents of: " + config.residentFolder);
FileUtils.deleteDirectory(config.residentFolder);
}
log.info("Using config: " + config);
MysqldResource mysqldResource = new MysqldResource(config.residentFolder);
Map<String, String> database_options = new HashMap();
database_options.put(MysqldResourceI.PORT, Integer.toString(config.port));
database_options.put(MysqldResourceI.INITIALIZE_USER, "true");
database_options.put(MysqldResourceI.INITIALIZE_USER_NAME, config.user);
database_options.put(MysqldResourceI.INITIALIZE_PASSWORD, config.pass);
database_options.put("innodb-file-per-table", "true");
if (config.customConfig != null) {
for (Map.Entry<String, Object> entry : config.customConfig.entrySet()) {
database_options.put(entry.getKey(), entry.getValue().toString());
}
}
log.info("Using the following MySQL Configuration:");
for (Map.Entry<String, String> entry : database_options.entrySet()) {
log.info("MySQLConf: " + entry.getKey() + " -> " + entry.getValue());
}
// I have to do this checking myself, otherwise in some cases mysqldResource will block undefinitely...
try {
ServerSocket serverSocket = new ServerSocket(config.port);
serverSocket.close();
} catch (IOException e) {
throw new RuntimeException("Port already in use: " + config.port);
}
if (mysqldResource.isRunning()) {
throw new RuntimeException("MySQL already running!");
}
mysqldResource.start("test-mysqld-thread", database_options);
if (!mysqldResource.isRunning()) {
throw new RuntimeException("MySQL did not start successfully!");
}
log.info("MySQL is running.");
resource = mysqldResource;
}
}