/*
* Copyright 2011 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package com.griefcraft.sql;
import com.griefcraft.lwc.LWC;
import com.griefcraft.scripting.ModuleException;
import com.griefcraft.util.Statistics;
import com.griefcraft.util.Updater;
import com.griefcraft.util.config.Configuration;
import org.bukkit.Bukkit;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public abstract class Database {
public enum Type {
MySQL("mysql.jar"), //
SQLite("sqlite.jar"), //
NONE("nil"); //
private String driver;
Type(String driver) {
this.driver = driver;
}
public String getDriver() {
return driver;
}
/**
* Match the given string to a database type
*
* @param str
* @return
*/
public static Type matchType(String str) {
for (Type type : values()) {
if (type.toString().equalsIgnoreCase(str)) {
return type;
}
}
return null;
}
}
/**
* The database engine being used for this connection
*/
public Type currentType;
/**
* Store cached prepared statements.
* <p/>
* Since SQLite JDBC doesn't cache them.. we do it ourselves :S
*/
private Map<String, PreparedStatement> statementCache = new HashMap<String, PreparedStatement>();
/**
* The connection to the database
*/
protected Connection connection = null;
/**
* The default database engine being used. This is set via config
*
* @default SQLite
*/
public static Type DefaultType = Type.NONE;
/**
* If we are connected to sqlite
*/
private boolean connected = false;
/**
* If the database has been loaded
*/
protected boolean loaded = false;
/**
* The database prefix (only if we're using MySQL.)
*/
protected String prefix = "";
/**
* If the high level statement cache should be used. If this is false, already cached statements are ignored
*/
private boolean useStatementCache = true;
public Database() {
currentType = DefaultType;
prefix = LWC.getInstance().getConfiguration().getString("database.prefix", "");
if (prefix == null) {
prefix = "";
}
}
public Database(Type currentType) {
this();
this.currentType = currentType;
}
/**
* Ping the database to keep the connection alive
*/
public void pingDatabase() {
Statement stmt = null;
try {
stmt = connection.createStatement();
stmt.executeQuery("SELECT 1;");
stmt.close();
} catch (SQLException e) {
log("Keepalive packet (ping) failed!");
e.printStackTrace();
} finally {
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) { }
}
}
/**
* Set the value of auto commit
*
* @param autoCommit
* @return TRUE if successful, FALSE if exception was thrown
*/
public boolean setAutoCommit(boolean autoCommit) {
try {
// Commit the database if we are setting auto commit back to true
if (autoCommit) {
connection.commit();
}
connection.setAutoCommit(autoCommit);
return true;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
/**
* @return the table prefix
*/
public String getPrefix() {
return prefix;
}
/**
* Print an exception to stdout
*
* @param exception
*/
protected void printException(Exception exception) {
throw new ModuleException(exception);
}
/**
* Connect to MySQL
*
* @return if the connection was succesful
*/
public boolean connect() throws Exception {
if (connection != null) {
return true;
}
if (currentType == null || currentType == Type.NONE) {
log("Invalid database engine");
return false;
}
// load the database jar
ClassLoader classLoader;
if (currentType == Type.SQLite) {
classLoader = new URLClassLoader(new URL[]{new URL("jar:file:" + new File(Updater.DEST_LIBRARY_FOLDER + currentType.getDriver()).getPath() + "!/")});
} else {
classLoader = Bukkit.getServer().getClass().getClassLoader();
}
// What class should we try to load?
String className = "";
if (currentType == Type.MySQL) {
className = "com.mysql.jdbc.Driver";
} else {
className = "org.sqlite.JDBC";
}
// Load the driver class
Driver driver = (Driver) classLoader.loadClass(className).newInstance();
// Create the properties to pass to the driver
Properties properties = new Properties();
// if we're using mysql, append the database info
if (currentType == Type.MySQL) {
LWC lwc = LWC.getInstance();
properties.put("autoReconnect", "true");
properties.put("user", lwc.getConfiguration().getString("database.username"));
properties.put("password", lwc.getConfiguration().getString("database.password"));
}
// Connect to the database
try {
connection = driver.connect("jdbc:" + currentType.toString().toLowerCase() + ":" + getDatabasePath(), properties);
connected = true;
return true;
} catch (SQLException e) {
log("Failed to connect to " + currentType + ": " + e.getErrorCode() + " - " + e.getMessage());
if (e.getCause() != null) {
log("Connection failure cause: " + e.getCause().getMessage());
}
return false;
}
}
public void dispose() {
statementCache.clear();
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
/**
* @return the connection to the database
*/
public Connection getConnection() {
return connection;
}
/**
* @return the path where the database file should be saved
*/
public String getDatabasePath() {
Configuration lwcConfiguration = LWC.getInstance().getConfiguration();
if (currentType == Type.MySQL) {
return "//" + lwcConfiguration.getString("database.host") + "/" + lwcConfiguration.getString("database.database");
}
return lwcConfiguration.getString("database.path");
}
/**
* @return the database engine type
*/
public Type getType() {
return currentType;
}
/**
* Load the database
*/
public abstract void load();
/**
* Log a string to stdout
*
* @param str The string to log
*/
public void log(String str) {
LWC.getInstance().log(str);
}
/**
* Prepare a statement unless it's already cached (and if so, just return it)
*
* @param sql
* @return
*/
public PreparedStatement prepare(String sql) {
return prepare(sql, false);
}
/**
* Prepare a statement unless it's already cached (and if so, just return it)
*
* @param sql
* @param returnGeneratedKeys
* @return
*/
public PreparedStatement prepare(String sql, boolean returnGeneratedKeys) {
if (connection == null) {
return null;
}
if (useStatementCache && statementCache.containsKey(sql)) {
Statistics.addQuery();
return statementCache.get(sql);
}
try {
PreparedStatement preparedStatement;
if (returnGeneratedKeys) {
preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
} else {
preparedStatement = connection.prepareStatement(sql);
}
statementCache.put(sql, preparedStatement);
Statistics.addQuery();
return preparedStatement;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* Add a column to a table
*
* @param table
* @param column
*/
public boolean addColumn(String table, String column, String type) {
return executeUpdateNoException("ALTER TABLE " + table + " ADD " + column + " " + type);
}
/**
* Add a column to a table
*
* @param table
* @param column
*/
public boolean dropColumn(String table, String column) {
return executeUpdateNoException("ALTER TABLE " + table + " DROP COLUMN " + column);
}
/**
* Rename a table
*
* @param table
* @param newName
*/
public boolean renameTable(String table, String newName) {
return executeUpdateNoException("ALTER TABLE " + table + " RENAME TO " + newName);
}
/**
* Drop a table
*
* @param table
*/
public boolean dropTable(String table) {
return executeUpdateNoException("DROP TABLE " + table);
}
/**
* Execute an update, ignoring any exceptions
*
* @param query
* @return true if an exception was thrown
*/
public boolean executeUpdateNoException(String query) {
Statement statement = null;
boolean exception = false;
try {
statement = connection.createStatement();
statement.executeUpdate(query);
} catch (SQLException e) {
exception = true;
} finally {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
}
}
return exception;
}
/**
* @return true if connected to the database
*/
public boolean isConnected() {
return connected;
}
/**
* Returns true if the high level statement cache should be used. If this is false, already cached statements are ignored
*
* @return
*/
public boolean useStatementCache() {
return useStatementCache;
}
/**
* Set if the high level statement cache should be used.
*
* @param useStatementCache
* @return
*/
public void setUseStatementCache(boolean useStatementCache) {
this.useStatementCache = useStatementCache;
}
}