/*
* 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/.
* Copyright (c) 2013, MPL CodeInside http://codeinside.ru
*/
package ru.codeinside.gses.liquibase.impl;
import liquibase.database.core.PostgresDatabase;
import liquibase.database.typeconversion.TypeConverter;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.exception.LockException;
import liquibase.lockservice.LockService;
import liquibase.servicelocator.ServiceLocator;
import ru.codeinside.gses.liquibase.impl.DbConfig;
import ru.codeinside.gses.liquibase.types.JPAPostgresTypeConverter;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.sql.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
final class Db extends PostgresDatabase {
private DeferredTxJdbcConnection deferredTxJdbcConnection;
private Connection connection;
private XAConnection xaConnection;
private Db(Connection connection, XAConnection xaConnection, boolean xa) throws DatabaseException {
this.connection = connection;
this.xaConnection = xaConnection;
deferredTxJdbcConnection = new DeferredTxJdbcConnection(this.connection, xa);
setConnection(deferredTxJdbcConnection);
}
static {
final ServiceLocator serviceLocator = ServiceLocator.getInstance();
final Class<JPAPostgresTypeConverter> jpaTypeConverter = JPAPostgresTypeConverter.class;
serviceLocator.addPackageToScan(jpaTypeConverter.getPackage().getName());
if (jpaTypeConverter != serviceLocator.findClass(TypeConverter.class)) {
throw new IllegalStateException("ServiceLocator already cached type converters! Can not patch PostgreSQL types!");
}
}
private static Db create(DbConfig config) throws DatabaseException {
if (config.ds != null) {
try {
return new Db(config.ds.getConnection(), null, config.ds.isWrapperFor(XADataSource.class));
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
XAConnection xaConnection = null;
try {
xaConnection = config.xds.getXAConnection();
return new Db(xaConnection.getConnection(), xaConnection, true);
} catch (SQLException e) {
if (xaConnection != null) {
try {
xaConnection.close();
} catch (SQLException e2) {
Logger.getLogger(Db.class.getName()).log(Level.WARNING, "close", e2);
}
}
throw new DatabaseException(e);
}
}
static <T> T execute(DbConfig config, Query<T> query) throws LiquibaseException {
Db db = create(config);
try {
return query.execute(db);
} finally {
db.close();
}
}
void createTo(Changes changes) throws LiquibaseException {
deferredTxJdbcConnection.setDeferredCommit(true);
try {
dropThenLoad(null);
checkChangeLogTable();
changes.apply(this);
} finally {
deferredTxJdbcConnection.setDeferredCommit(false);
}
deferredTxJdbcConnection.commit();
}
void migrateTo(Changes changes) throws LiquibaseException {
deferredTxJdbcConnection.setDeferredCommit(true);
try {
changes.apply(this);
} finally {
deferredTxJdbcConnection.setDeferredCommit(false);
}
deferredTxJdbcConnection.commit();
}
void dropAndLoad(String sqlScriptFileName) throws DatabaseException {
deferredTxJdbcConnection.setDeferredCommit(true);
try {
dropThenLoad(sqlScriptFileName);
} finally {
deferredTxJdbcConnection.setDeferredCommit(false);
}
//deferredTxJdbcConnection.commit();
}
private void dropThenLoad(String sqlScriptFileName) throws DatabaseException {
try {
List<String> lst = new ArrayList<String>(6);
lst.add("pg_toast");
lst.add("pg_temp_1");
lst.add("pg_toast_temp_1");
lst.add("pg_catalog");
lst.add("information_schema");
lst.add("public");
final DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getSchemas();//connection.prepareStatement("SELECT schema_name FROM information_schema.schemata").executeQuery();
while (resultSet.next()) {
String schemaName = resultSet.getString(1);
if(!lst.contains(schemaName)){
dropDatabaseObjects(schemaName);
connection.createStatement().execute("DROP SCHEMA IF EXISTS " + schemaName + " CASCADE");
}
}
resultSet.close();
dropDatabaseObjects("public");
dropSpecialTables();
if (sqlScriptFileName != null) {
load(sqlScriptFileName);
}
} catch (DatabaseException e) {
throw e;
} catch (SQLException e) {
SQLException n = e;
while (n != null) {
System.err.println(e.getMessage());
n = n.getNextException();
}
System.err.println();
throw new DatabaseException(e);
} catch (Exception e) {
throw new DatabaseException(e);
}
}
public void checkChangeLogTable() throws DatabaseException {
checkDatabaseChangeLogTable(false, null, null);
if (!LockService.getInstance(this).hasChangeLogLock()) {
checkDatabaseChangeLogLockTable();
}
}
private void dropSpecialTables() throws DatabaseException, SQLException {
Statement statement = connection.createStatement();
statement.execute("DROP TABLE IF EXISTS databasechangelog, databasechangeloglock");
}
private void load(String sqlScriptFileName) throws IOException, DatabaseException, SQLException {
String script = readFileToString(new File(sqlScriptFileName), "UTF8");
Statement statement = connection.createStatement();
statement.execute(script);
statement.close();
}
void releaseLock() throws LockException {
begin();
LockService.getInstance(this).releaseLock();
}
void waitForLock() throws LockException {
begin();
LockService.getInstance(this).waitForLock();
}
private void begin() throws LockException {
deferredTxJdbcConnection.setDeferredCommit(false);
try {
deferredTxJdbcConnection.rollback();
} catch (DatabaseException e) {
throw new LockException(e);
}
}
@Override
public void close() throws DatabaseException {
super.close();
if (xaConnection != null) {
try {
xaConnection.close();
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
}
private String readFileToString(File file, String encoding) throws IOException {
InputStream in = null;
try {
in = new FileInputStream(file);
StringWriter sw = new StringWriter();
InputStreamReader in2 = new InputStreamReader(in, encoding);
char[] buffer = new char[1024];
int n = 0;
while (-1 != (n = in2.read(buffer))) {
sw.write(buffer, 0, n);
}
return sw.toString();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ioe) {
// ignore
}
}
}
}