/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.util.databasechange;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.dbunit.DatabaseUnitException;
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.ext.h2.H2DataTypeFactory;
import org.dbunit.operation.DatabaseOperation;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
/**
* Allows to test database upgrade. It accepts initialDatabasePath which should point to the h2
* liqubaseConnection that will be used for upgrade.
*/
public class DatabaseUpgradeTestUtil {
private final Connection connection;
private final Database liqubaseConnection;
private final DatabaseConnection dbUnitConnection;
private final File tempDir;
private final File tempDBFile;
private final String connectionUrl;
public DatabaseUpgradeTestUtil(String initialDatabasePath) throws IOException, SQLException {
InputStream databaseInputStream = getClass().getResourceAsStream(initialDatabasePath);
tempDir = File.createTempFile("openmrs-tests-temp-", "");
tempDir.delete();
tempDir.mkdir();
tempDir.deleteOnExit();
tempDBFile = new File(tempDir, "openmrs.h2.db");
tempDBFile.delete();
try {
tempDBFile.createNewFile();
}
catch (IOException e) {
tempDir.delete();
throw e;
}
tempDBFile.deleteOnExit();
FileOutputStream tempDBOutputStream = new FileOutputStream(tempDBFile);
try {
IOUtils.copy(databaseInputStream, tempDBOutputStream);
databaseInputStream.close();
tempDBOutputStream.close();
}
catch (IOException e) {
tempDBFile.delete();
tempDir.delete();
throw e;
}
finally {
IOUtils.closeQuietly(databaseInputStream);
IOUtils.closeQuietly(tempDBOutputStream);
}
String databaseUrl = tempDir.getAbsolutePath().replace("\\", "/") + "/openmrs";
connectionUrl = "jdbc:h2:" + databaseUrl + ";AUTO_RECONNECT=TRUE;DB_CLOSE_DELAY=-1";
connection = DriverManager.getConnection(connectionUrl, "sa", "sa");
connection.setAutoCommit(true);
try {
liqubaseConnection = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(
new JdbcConnection(connection));
liqubaseConnection.setDatabaseChangeLogTableName("LIQUIBASECHANGELOG");
liqubaseConnection.setDatabaseChangeLogLockTableName("LIQUIBASECHANGELOGLOCK");
}
catch (LiquibaseException e) {
tempDir.delete();
tempDBFile.delete();
throw new SQLException(e);
}
try {
dbUnitConnection = new DatabaseConnection(connection);
dbUnitConnection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new H2DataTypeFactory());
}
catch (DatabaseUnitException e) {
tempDir.delete();
tempDBFile.delete();
throw new SQLException(e);
}
}
public void close() throws SQLException {
try {
connection.close();
}
finally {
tempDBFile.delete();
tempDir.delete();
}
}
public Connection getConnection() {
return connection;
}
public void executeDataset(String path) throws IOException, SQLException {
InputStream inputStream = getClass().getResourceAsStream(path);
ReplacementDataSet replacementDataSet = null;
try {
replacementDataSet = new ReplacementDataSet(new FlatXmlDataSet(new InputStreamReader(inputStream), false, true,
false));
inputStream.close();
}
catch (DataSetException e) {
throw new IOException(e);
}
finally {
IOUtils.closeQuietly(inputStream);
}
replacementDataSet.addReplacementObject("[NULL]", null);
try {
DatabaseOperation.REFRESH.execute(dbUnitConnection, replacementDataSet);
connection.commit();
}
catch (DatabaseUnitException e) {
throw new IOException(e);
}
}
public List<Map<String, String>> select(String tableName, String where, String columnName, String... columnNames)
throws SQLException {
String[] allColumnNames = ArrayUtils.addAll(new String[] { columnName }, columnNames);
String sql = "select " + StringUtils.join(allColumnNames, ", ") + " from " + tableName;
if (!StringUtils.isBlank(where)) {
sql += " where " + where;
}
PreparedStatement query = connection.prepareStatement(sql);
ResultSet resultSet = query.executeQuery();
List<Map<String, String>> results = new ArrayList<Map<String, String>>();
while (resultSet.next()) {
Map<String, String> columns = new HashMap<String, String>();
results.add(columns);
for (int i = 0; i < allColumnNames.length; i++) {
Object object = resultSet.getObject(i + 1);
columns.put(allColumnNames[i], object != null ? object.toString() : null);
}
}
query.close();
return results;
}
public void insertGlobalProperty(String globalProperty, String value) throws SQLException {
PreparedStatement insert = connection
.prepareStatement("insert into global_property (property, property_value, uuid) values (?, ?, ?)");
insert.setString(1, globalProperty);
insert.setString(2, value);
insert.setString(3, UUID.randomUUID().toString());
insert.executeUpdate();
insert.close();
connection.commit();
}
public void upgrade() throws IOException, SQLException {
upgrade("liquibase-update-to-latest.xml");
}
public void upgrade(String filename) throws IOException, SQLException {
try {
Liquibase liquibase = new Liquibase(filename, new ClassLoaderResourceAccessor(getClass()
.getClassLoader()), liqubaseConnection);
liquibase.update(null);
connection.commit();
}
catch (LiquibaseException e) {
throw new IOException(e);
}
}
public SessionFactory buildSessionFactory() {
Configuration config = new Configuration().configure();
//H2 version we use behaves differently from H2Dialect in Hibernate so we provide our implementation
config.setProperty(Environment.DIALECT, H2LessStrictDialect.class.getName());
config.setProperty(Environment.URL, connectionUrl);
config.setProperty(Environment.DRIVER, "org.h2.Driver");
config.setProperty(Environment.USER, "sa");
config.setProperty(Environment.PASS, "sa");
config.setProperty(Environment.USE_SECOND_LEVEL_CACHE, "false");
config.setProperty(Environment.USE_QUERY_CACHE, "false");
//Let's validate HBMs against the actual schema
config.setProperty(Environment.HBM2DDL_AUTO, "validate");
return config.buildSessionFactory();
}
}