/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.upgrade;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.olat.core.logging.StartupException;
import org.olat.core.util.StringHelper;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.xml.XStreamHelper;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
/**
*
* Description:<br>
* Upgrade the database
*
* <P>
* Initial Date: 8 sept. 2011 <br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*/
public class DatabaseUpgradeManager extends UpgradeManagerImpl {
private String dbVendor;
private boolean autoUpgradeDatabase = true;
protected UpgradesDefinitions olatUpgradesDefinitions;
public DatabaseUpgradeManager() {
INSTALLED_UPGRADES_XML = "installed_database_upgrades.xml";
}
public String getDbVendor() {
return dbVendor;
}
/**
* [user by Spring]
* @param dbVendor
*/
public void setDbVendor(String dbVendor) {
this.dbVendor = dbVendor;
}
/**
* [used by spring]
* @param autoUpgradeDatabase
*/
public void setAutoUpgradeDatabase(boolean autoUpgradeDatabase) {
this.autoUpgradeDatabase = autoUpgradeDatabase;
}
/**
* [used by Spring]
* @param olatUpgradesDefinitions
*/
public void setOlatUpgradesDefinitions(UpgradesDefinitions olatUpgradesDefinitions) {
this.olatUpgradesDefinitions = olatUpgradesDefinitions;
}
@Override
public void init() {
// load upgrades using spring framework
upgrades = upgradesDefinitions.getUpgrades();
// load history of previous upgrades using xstream
initUpgradesHistories();
if (autoUpgradeDatabase) {
runAlterDbStatements();
} else {
logInfo("Auto upgrade of the database is disabled. Make sure you do it manually by applying the " +
"alter*.sql scripts and adding an entry to system/installed_upgrades.xml file.");
}
}
/**
* @see org.olat.upgrade.UpgradeManager#runAlterDbStatements()
*/
public void runAlterDbStatements() {
Dialect dialect;
//only run upgrades on mysql or postgresql
if (getDbVendor().contains("mysql")) dialect = Dialect.mysql;
else if (getDbVendor().contains("postgresql")) dialect = Dialect.postgresql;
else return;
Statement statement = null;
try {
logAudit("+--------------------------------------------------------------+");
logAudit("+... Pure database upgrade: starting alter DB statements ...+");
logAudit("+ If it fails, do it manually by applying the content of the alter_X_to_Y.sql files.+");
logAudit("+ For each file you upgraded to add an entry like this to the [pathToOlat]/olatdata/system/installed_database_upgrades.xml: +");
logAudit("+ <entry><string>Database update</string><boolean>true</boolean></entry>+");
logAudit("+--------------------------------------------------------------+");
statement = getDataSource().getConnection().createStatement();
Iterator<OLATUpgrade> iter = upgrades.iterator();
OLATUpgrade upgrade = null;
while (iter.hasNext()) {
upgrade = iter.next();
String alterDbStatementsFilename = upgrade.getAlterDbStatements();
if (alterDbStatementsFilename != null) {
UpgradeHistoryData uhd = getUpgradesHistory(upgrade.getVersion());
if (uhd == null) {
// has never been called, initialize
uhd = new UpgradeHistoryData();
}
if (!uhd.getBooleanDataValue(OLATUpgrade.TASK_DP_UPGRADE)) {
loadAndExecuteSqlStatements(statement, alterDbStatementsFilename, dialect);
uhd.setBooleanDataValue(OLATUpgrade.TASK_DP_UPGRADE, true);
setUpgradesHistory(uhd, upgrade.getVersion());
logAudit("Successfully executed alter DB statements for Version::" + upgrade.getVersion());
}
}
}
} catch (SQLException e) {
logError("Could not upgrade your database! Please do it manually and add ", e);
throw new StartupException("Could not execute alter db statements. Please do it manually.", e);
} catch (Throwable e) {
logWarn("Error executing alter DB statements::", e);
abort(e);
} finally {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e2){
logWarn("Could not close sql statement", e2);
throw new StartupException("Could not close sql statements.", e2);
}
}
}
/**
* load file with alter statements and add to batch
* @param statements
* @param alterDbStatements
*/
private void loadAndExecuteSqlStatements(Statement statement, String alterDbStatements, Dialect dialect) {
try {
Resource setupDatabaseFile = new ClassPathResource("/database/"+dialect+"/"+alterDbStatements);
if (!setupDatabaseFile.exists()) {
throw new StartupException("The database upgrade file was not found on the classpath: "+"/database/"+dialect+"/"+alterDbStatements);
}
InputStream in = setupDatabaseFile.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String strLine;
StringBuilder sb = new StringBuilder();
//Read File Line By Line
while ((strLine = br.readLine()) != null) {
if (strLine.length() > 1 && (!strLine.startsWith("--") && !strLine.startsWith("#"))) {
sb.append(strLine.trim()).append(' ');
}
}
StringTokenizer tokenizer = new StringTokenizer(sb.toString(), ";");
String sql = null;
while (tokenizer.hasMoreTokens()) {
try {
String token = tokenizer.nextToken();
if(!StringHelper.containsNonWhitespace(token)) {
continue;
}
sql = token + ";".toLowerCase();
if (sql.startsWith("update") || sql.startsWith("delete") || sql.startsWith("alter") || sql.startsWith("insert")) {
statement.executeUpdate(sql);
} else {
statement.execute(sql);
}
logInfo("Successfully upgraded database with the following sql: "+sql);
} catch (SQLException e) {
if(isErrorFatal(dialect, sql, e)) {
throw new StartupException("Fatal error trying to update database.", e);
}
} catch (Exception e) {
//handle non sql errors
logError("Could not upgrade your database!",e);
throw new StartupException("Could not add alter db statements to batch.", e);
}
}
in.close();
} catch (FileNotFoundException e1) {
logError("could not find deleteDatabase.sql file!", e1);
abort(e1);
} catch (IOException e) {
logError("could not read deleteDatabase.sql file!", e);
abort(e);
}
}
private boolean isErrorFatal(Dialect dialect, String sql, SQLException e) {
if(dialect == Dialect.mysql) {
return isMysqlErrorFatal(sql, e);
} else if(dialect == Dialect.postgresql) {
return isPostgresqlErrorFatal(sql, e);
}
return false;
}
private boolean isMysqlErrorFatal(String sql, SQLException e) {
logError("Error while trying to upgrade the database with:("+sql+"). We will continue with upgrading but check the errors manually! Error says:", e);
return false;
}
private boolean isPostgresqlErrorFatal(String sql, SQLException e) {
logError("Error while trying to upgrade the database with:("+sql+"). We will continue with upgrading but check the errors manually! Error says:", e);
return false;
}
@SuppressWarnings("unchecked")
protected void initUpgradesHistories() {
File upgradesDir = new File(WebappHelper.getUserDataRoot(), SYSTEM_DIR);
upgradesDir.mkdirs();
File upgradesHistoriesFile = new File(upgradesDir, INSTALLED_UPGRADES_XML);
File stdUpgradesHistoriesFile = new File(upgradesDir, "installed_upgrades.xml");
boolean newInstance = !upgradesHistoriesFile.exists() && !stdUpgradesHistoriesFile.exists();
if(newInstance) {
upgradesHistories = new HashMap<String, UpgradeHistoryData>();
// Fill the history
for(OLATUpgrade upgrade:upgrades) {
UpgradeHistoryData uhd = new UpgradeHistoryData();
uhd.setBooleanDataValue(OLATUpgrade.TASK_DP_UPGRADE, true);
uhd.setInstallationComplete(true);
setUpgradesHistory(uhd, upgrade.getVersion());
}
logInfo("This looks like a new install, will not do any database upgrades.");
} else {
if (upgradesHistoriesFile.exists()) {
upgradesHistories = (Map<String, UpgradeHistoryData>) XStreamHelper.readObject(upgradesHistoriesFile);
}
if (upgradesHistories == null) {
upgradesHistories = new HashMap<String, UpgradeHistoryData>();
}
if (stdUpgradesHistoriesFile.exists()) {
Set<String> versions = new HashSet<String>();
for(OLATUpgrade upgrade:upgradesDefinitions.getUpgrades()) {
versions.add(upgrade.getVersion());
}
Map<String, UpgradeHistoryData> stdUpgradesHistories = (Map<String, UpgradeHistoryData>) XStreamHelper.readObject(stdUpgradesHistoriesFile);
for(Map.Entry<String, UpgradeHistoryData> entry: stdUpgradesHistories.entrySet()) {
String version = entry.getKey();
UpgradeHistoryData data = entry.getValue();
boolean updated = data.getBooleanDataValue("Database update");
if(versions.contains(version) && updated && !upgradesHistories.containsKey(version)) {
upgradesHistories.put(version, data);
}
}
}
}
}
@Override
public void doPreSystemInitUpgrades() {
//
}
@Override
public void doPostSystemInitUpgrades() {
//
}
private enum Dialect {
mysql,
postgresql
}
}