package net.sourceforge.javautil.database.mysql;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
import net.sourceforge.javautil.classloader.impl.ClassContext;
import net.sourceforge.javautil.classloader.impl.StandardClassLoaderHeiarchy;
import net.sourceforge.javautil.classloader.source.InternalZipClassSource;
import net.sourceforge.javautil.classloader.source.ZipClassSource;
import net.sourceforge.javautil.common.ThreadUtil;
import net.sourceforge.javautil.common.VirtualArtifactUtil;
import net.sourceforge.javautil.common.exception.ThrowableManagerRegistry;
import net.sourceforge.javautil.common.io.IVirtualFile;
import net.sourceforge.javautil.common.io.IModifiableInputSource.MemoryInputSource;
import net.sourceforge.javautil.common.io.impl.SystemDirectory;
import net.sourceforge.javautil.common.io.impl.SystemFile;
import net.sourceforge.javautil.common.password.IPassword;
import net.sourceforge.javautil.common.reflection.proxy.ReflectiveObjectProxy;
import net.sourceforge.javautil.database.DatabaseServerAbstract;
import net.sourceforge.javautil.datasource.IDataSourceDescriptor;
import net.sourceforge.javautil.datasource.DataSourceDescriptorStandard;
import net.sourceforge.javautil.lifecycle.ILifecycle.PhaseType;
import net.sourceforge.javautil.lifecycle.annotation.Phase;
/**
* The standard implementation for using MXJ for emebedded mysql.<br/><br/>
*
* The actual MXJ jar and packaged mysqld binary and initialization folders are not
* distributed with this library and were not currently available
* via maven when this was last updated.<br/><br/>
*
* You must provide in the class path the packaged jar with the binary
* and other resources as distributed by MySQL
* <a href="http://dev.mysql.com/downloads/connector/mxj/5.0.html">here</a>.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: MysqlEmbeddedDatabaseServer.java 2477 2010-10-25 03:42:34Z ponderator $
*/
public class MysqlEmbeddedDatabaseServer extends DatabaseServerAbstract {
/**
* @return A special internal classloader for loading the MXJ classes
*/
protected static ClassLoader createMysqlMXJClassLoader (IVirtualFile mxjJar, IVirtualFile mxjDbFileJar) {
return new ClassContext(new StandardClassLoaderHeiarchy(),
new ZipClassSource(mxjJar.getURL()),
new ZipClassSource(mxjDbFileJar.getURL())
);
}
protected final PrintStream outputStream;
protected final PrintStream errorStream;
protected final String username;
protected final IPassword password;
protected final int port;
protected final boolean skipInnoDb;
protected final File processIdFile;
protected final File portFile;
protected final File dataDirectory;
protected final File baseDirectory;
protected final ClassLoader mxjLoader;
protected final String mxjVersion;
protected MysqldResource resource;
protected boolean running = false;
protected String maxAllowedPacket = "256M";
/**
* This assumes the use of the current thread level context class loader.
*
* @see MysqlEmbeddedDatabaseServer#MysqlEmbeddedDatabaseServer(ClassLoader, SystemDirectory, PrintStream, PrintStream, String, IPassword, int, boolean)M
*/
public MysqlEmbeddedDatabaseServer(String mxjVersion, SystemDirectory root, PrintStream outputStream, PrintStream errorStream,
String username, IPassword password, int port, boolean skipInnoDb) {
this(Thread.currentThread().getContextClassLoader(), mxjVersion, root, outputStream, errorStream, username, password, port, skipInnoDb);
}
public MysqlEmbeddedDatabaseServer(
IVirtualFile mxjJar,
IVirtualFile mxjDbFilesJar,
String mxjVersion,
SystemDirectory root, PrintStream outputStream,
PrintStream errorStream, String username, IPassword password,
int port, boolean skipInnoDb
) {
this(createMysqlMXJClassLoader(mxjJar, mxjDbFilesJar), mxjVersion, root, outputStream, errorStream, username, password, port, skipInnoDb);
}
/**
* @param mxjLoader The class loader for accessing the MXJ connector and db files
* @param root The root directory of the server
* @param outputStream The output stream for server output
* @param errorStream The error stream for server errors
* @param username The username, or null if no authentication should be used
* @param password The password
* @param port The port
* @param skipInnoDb True if innodb should be skipped, otherwise false
*/
public MysqlEmbeddedDatabaseServer(ClassLoader mxjLoader, String mxjVersion, SystemDirectory root, PrintStream outputStream, PrintStream errorStream,
String username, IPassword password, int port, boolean skipInnoDb) {
super("Mysql Embedded Server", root);
this.mxjLoader = mxjLoader;
this.mxjVersion = mxjVersion;
this.outputStream = outputStream == null ? System.out : outputStream;
this.errorStream = errorStream == null ? System.err : errorStream;
this.username = username;
this.password = password;
this.port = port;
this.skipInnoDb = skipInnoDb;
File rootDirectory = this.root.getRealArtifact();
this.baseDirectory = new File(rootDirectory, "base");
this.dataDirectory = new File(rootDirectory, "data");
this.processIdFile = new File(dataDirectory, "MysqldResource.pid");
this.portFile = new File(dataDirectory, "MysqldResource.port");
}
/**
* @return The setting passed to the --max_allowed_packet parameter passed to the server.
*/
public String getMaxAllowedPacket() { return maxAllowedPacket; }
/**
* If this is set after the server is started, the server will need to be restarted in order for the
* new setting to be put into effect.
*
* @see #getMaxAllowedPacket()
*/
public void setMaxAllowedPacket(String maxAllowedPacket) { this.maxAllowedPacket = maxAllowedPacket; }
/**
* @return The output stream being used for the server
*/
public PrintStream getOutputStream() { return outputStream; }
/**
* @return The error stream being used for the server
*/
public PrintStream getErrorStream() { return errorStream; }
/**
* @return The username the server was setup with, or null if no authentication is being used
*/
public String getUsername() { return username; }
/**
* @return The password the server was setup with, or null if no authentication is being used
*/
public IPassword getPassword() { return password; }
/**
* @return The port the server was setup with
*/
public int getPort() { return port; }
/**
* @return True if this server will skip innodb initialization, otherwise false
*/
public boolean isSkipInnoDb() { return skipInnoDb; }
/**
* @return The proccess id file used by the server
*/
public File getProcessIdFile() { return processIdFile; }
/**
* @return The port file used by the server
*/
public File getPortFile() { return portFile; }
/**
* @return The data directory for this server
*/
public File getDataDirectory() { return dataDirectory; }
/**
* @return The base server directory
*/
public File getBaseDirectory() { return baseDirectory; }
public boolean isDatabaseExists(String name) { return new File(this.dataDirectory, name).exists(); }
public Connection createConnection(String databaseName, String username, IPassword password) throws SQLException {
String url = this.createConnectionURL(databaseName);
return username == null ? DriverManager.getConnection(url) : DriverManager.getConnection(url, username, new String(password.getPassword()));
}
public boolean createDatabase(String name, String username, IPassword password) {
File databaseDir = new File(this.dataDirectory, name);
if (databaseDir.exists()) return false;
return databaseDir.mkdir();
}
public IDataSourceDescriptor createDescriptor(String name, String databaseName, String username) {
return new DataSourceDescriptorStandard(name, "com.mysql.jdbc.Driver", this.createConnectionURL(databaseName), username);
}
public MysqlConnectionPoolDataSource createDataSource(String databaseName, String username, IPassword password) throws SQLException {
MysqlConnectionPoolDataSource pooled = new MysqlConnectionPoolDataSource();
pooled.setUser(username);
pooled.setPassword(new String(password.getPassword()));
pooled.setDatabaseName(databaseName);
pooled.setPort(this.port);
pooled.setLocalSocketAddress("localhost");
return pooled;
}
@Phase(value=PhaseType.START) private void startMysql() {
this.resource = ReflectiveObjectProxy.newInstance(
Thread.currentThread().getContextClassLoader(),
mxjLoader, MysqldResource.class,
baseDirectory, dataDirectory, mxjVersion, this.outputStream, this.errorStream
);
this.resource.setKillDelay(5);
if (this.resource.isRunning()) { this.resource.shutdown(); }
if (this.processIdFile.exists()) this.processIdFile.delete();
if (this.portFile.exists()) this.portFile.delete();
Map<String, String> options = new HashMap<String, String>();
options.put("port", String.valueOf( port ));
options.put("initialize-user", username == null ? "false" : "true");
if (this.username != null) {
options.put("initialize-user.user", username);
options.put("initialize-user.password", new String(password.getPassword()));
}
if (this.maxAllowedPacket != null) {
options.put("max_allowed_packet", this.maxAllowedPacket);
}
options.put("skip-innodb", String.valueOf( this.skipInnoDb ));
this.resource.start("Mysql Embedded Thread", options, true);
ThreadUtil.sleep(500);
if (!this.resource.isRunning())
throw new IllegalStateException("Mysql Instance not running after start()");
}
@Phase(value=PhaseType.STOP) private void stopMysql() {
try {
if (this.resource.isRunning()) {
this.resource.shutdown();
}
this.running = false;
} catch (Throwable t) {
ThrowableManagerRegistry.caught(t);
}
}
/**
* Create a connection URL for the database
*/
protected String createConnectionURL (String databaseName) {
return "jdbc:mysql://localhost:" + port + "/" + databaseName + (this.maxAllowedPacket != null ? "?maxAllowedPacket=" + this.maxAllowedPacket : "");
}
}