Commit c81a84af authored by Matt Tucker's avatar Matt Tucker Committed by matt

Added support for plugin db schema support (JM-673).

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@3879 b35dd754-fafc-0310-a699-88a17e54d16e
parent e4a70aae
/**
* $Revision$
* $Date$
*
* Copyright (C) 2006 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.database;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.wildfire.container.Plugin;
import org.jivesoftware.wildfire.container.PluginManager;
import org.jivesoftware.wildfire.XMPPServer;
import java.sql.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Arrays;
/**
* Manages database schemas for Wildfire and Wildfire plugins. The manager uses the
* jiveVersion database table to figure out which database schema is currently installed
* and then attempts to automatically apply database schema changes as necessary.<p>
*
* Running database schemas automatically requires appropriate database permissions.
* Without those permissions, the automatic installation/upgrade process will fail
* and users will be prompted to apply database changes manually.
*
* @see DbConnectionManager#getSchemaManager()
*
* @author Matt Tucker
*/
public class SchemaManager {
private static final String CHECK_VERSION_OLD =
"SELECT minorVersion FROM jiveVersion";
private static final String CHECK_VERSION =
"SELECT version FROM jiveVersion WHERE name=?";
/**
* Current Wildfire database schema version.
*/
private static final int DATABASE_VERSION = 9;
/**
* Creates a new Schema manager.
*/
SchemaManager() {
}
/**
* Checks the Wildfire database schema to ensure that it's installed and up to date.
* If the schema isn't present or up to date, an automatic update will be attempted.
*
* @param con a connection to the database.
* @return true if database schema checked out fine, or was automatically installed
* or updated successfully.
*/
public boolean checkWildfireSchema(Connection con) {
try {
return checkSchema(con, "wildfire", DATABASE_VERSION,
new ResourceLoader() {
public InputStream loadResource(String resourceName) {
return SchemaManager.class.getResourceAsStream("/database/" + resourceName);
}
});
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("upgrade.database.failure"), e);
System.out.println(LocaleUtils.getLocalizedString("upgrade.database.failure"));
}
return false;
}
/**
* Checks the plugin's database schema (if one is required) to ensure that it's
* installed and up to date. If the schema isn't present or up to date, an automatic
* update will be attempted.
*
* @param plugin the plugin.
* @return true if database schema checked out fine, or was automatically installed
* or updated successfully.
*/
public boolean checkPluginSchema(final Plugin plugin) {
final PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
String schemaKey = pluginManager.getDatabaseKey(plugin);
int schemaVersion = pluginManager.getDatabaseVersion(plugin);
// If the schema key or database version aren't defined, then the plugin doesn't
// need database tables.
if (schemaKey == null || schemaVersion == -1) {
return false;
}
Connection con = null;
try {
con = DbConnectionManager.getConnection();
return checkSchema(con, schemaKey, schemaVersion, new ResourceLoader() {
public InputStream loadResource(String resourceName) {
return pluginManager.getPluginClassloader(
plugin).getClassLoader().getResourceAsStream( resourceName);
}
});
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("upgrade.database.failure"), e);
System.out.println(LocaleUtils.getLocalizedString("upgrade.database.failure"));
}
finally {
DbConnectionManager.closeConnection(con);
}
return false;
}
/**
* Checks to see if the database needs to be upgraded. This method should be
* called once every time the application starts up.
*
* @param con the database connection to use to check the schema with.
* @param schemaKey the database schema key (name).
* @param requiredVersion the version that the schema should be at.
* @param resourceLoader a resource loader that knows how to load schema files.
* @throws Exception if an error occured.
*/
private boolean checkSchema(Connection con, String schemaKey, int requiredVersion,
ResourceLoader resourceLoader) throws Exception
{
int currentVersion = -1;
PreparedStatement pstmt = null;
try {
pstmt = con.prepareStatement(CHECK_VERSION);
pstmt.setString(1, schemaKey);
ResultSet rs = pstmt.executeQuery();
rs.next();
currentVersion = rs.getInt(1);
rs.close();
}
catch (SQLException sqle) {
// Releases of Wildfire before 2.6.0 stored a major and minor version
// number so the normal check for version can fail. Check for the
// version using the old format in that case.
try {
if (pstmt != null) {
pstmt.close();
}
pstmt = con.prepareStatement(CHECK_VERSION_OLD);
ResultSet rs = pstmt.executeQuery();
rs.next();
currentVersion = rs.getInt(1);
rs.close();
}
catch (SQLException sqle2) {
// The database schema must not be installed.
}
}
finally {
try {
if (pstmt != null) {
pstmt.close();
}
}
catch (Exception e) {
Log.error(e);
}
}
// If already up to date, return.
if (currentVersion == requiredVersion) {
return false;
}
// If the database schema isn't installed at all, we need to install it.
else if (currentVersion == -1) {
Log.error(LocaleUtils.getLocalizedString("upgrade.database.missing_schema",
Arrays.asList(schemaKey)));
System.out.println(LocaleUtils.getLocalizedString("upgrade.database.missing_schema",
Arrays.asList(schemaKey)));
// Resource will be like "/database/wildfire_hsqldb.sql"
String resourceName = schemaKey + "_" +
DbConnectionManager.getDatabaseType() + ".sql";
InputStream resource = resourceLoader.loadResource(resourceName);
if (resource == null) {
return false;
}
try {
executeSQLScript(con, resource);
}
catch (Exception e) {
Log.error(e);
return false;
}
finally {
try {
resource.close();
}
catch (Exception e) {
// Ignore.
}
}
Log.info(LocaleUtils.getLocalizedString("upgrade.database.success"));
System.out.println(LocaleUtils.getLocalizedString("upgrade.database.success"));
return true;
}
// Must have a version of the schema that needs to be upgraded.
else {
// The database is an old version that needs to be upgraded.
Log.info(LocaleUtils.getLocalizedString("upgrade.database.old_schema",
Arrays.asList(currentVersion, schemaKey, requiredVersion)));
System.out.println(LocaleUtils.getLocalizedString("upgrade.database.old_schema",
Arrays.asList(currentVersion, schemaKey, requiredVersion)));
// If the database type is unknown, we don't know how to upgrade it.
if (DbConnectionManager.getDatabaseType() == DbConnectionManager.DatabaseType.unknown) {
Log.info(LocaleUtils.getLocalizedString("upgrade.database.unknown_db"));
System.out.println(LocaleUtils.getLocalizedString("upgrade.database.unknown_db"));
return false;
}
// Upgrade scripts for interbase are not maintained.
else if (DbConnectionManager.getDatabaseType() == DbConnectionManager.DatabaseType.interbase) {
Log.info(LocaleUtils.getLocalizedString("upgrade.database.interbase_db"));
System.out.println(LocaleUtils.getLocalizedString("upgrade.database.interbase_db"));
return false;
}
// Run all upgrade scripts until we're up to the latest schema.
for (int i=currentVersion+1; i <= DATABASE_VERSION; i++) {
// Resource will be like "/database/upgrade/6/wildfire_hsqldb.sql"
String resourceName = "/database/upgrade/" + i + "/" + schemaKey + "_" +
DbConnectionManager.getDatabaseType() + ".sql";
InputStream resource = resourceLoader.loadResource(resourceName);
if (resource == null) {
// If the resource is null, the specific upgrade number is not available.
continue;
}
try {
executeSQLScript(con, resource);
}
catch (Exception e) {
Log.error(e);
return false;
}
finally {
try {
resource.close();
}
catch (Exception e) {
// Ignore.
}
}
}
Log.info(LocaleUtils.getLocalizedString("upgrade.database.success"));
System.out.println(LocaleUtils.getLocalizedString("upgrade.database.success"));
return true;
}
}
/**
* Executes a SQL script.
*
* @param con database connection.
* @param resource an input stream for the script to execute.
* @throws IOException if an IOException occurs.
* @throws SQLException if an SQLException occurs.
*/
private static void executeSQLScript(Connection con, InputStream resource) throws IOException,
SQLException
{
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(resource));
boolean done = false;
while (!done) {
StringBuilder command = new StringBuilder();
while (true) {
String line = in.readLine();
if (line == null) {
done = true;
break;
}
// Ignore comments and blank lines.
if (isSQLCommandPart(line)) {
command.append(line);
}
if (line.endsWith(";")) {
break;
}
}
// Send command to database.
if (!done && !command.toString().equals("")) {
Statement stmt = con.createStatement();
stmt.execute(command.toString());
stmt.close();
}
}
}
finally {
if (in != null) {
try {
in.close();
}
catch (Exception e) {
// Ignore.
}
}
}
}
private static abstract class ResourceLoader {
public abstract InputStream loadResource(String resourceName);
}
/**
* Returns true if a line from a SQL schema is a valid command part.
*
* @param line the line of the schema.
* @return true if a valid command part.
*/
private static boolean isSQLCommandPart(String line) {
line = line.trim();
if (line.equals("")) {
return false;
}
// Check to see if the line is a comment. Valid comment types:
// "//" is HSQLDB
// "--" is DB2 and Postgres
// "#" is MySQL
// "REM" is Oracle
// "/*" is SQLServer
return !(line.startsWith("//") || line.startsWith("--") || line.startsWith("#") ||
line.startsWith("REM") || line.startsWith("/*") || line.startsWith("*"));
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment