package pt.ist.fenixframework.pstm.repository;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import pt.ist.fenixframework.FenixFramework;
import pt.ist.fenixframework.backend.fenixjvstm.FenixJvstmConfig;
/**
* This class is used when the fenix-framework starts up. It is responsible for
* initializing or updating the repository structure if needed. There are two
* optional configuration parameters that govern the behaviour of this class:
* <code>createRepositoryStructureIfNonExistent</code> and
* <code>updateRepositoryStructureIfNeeded</code>. The former defaults to
* <code>true</code> and controls whether to create the repository structure if
* it doesn't exist. The latter defaults to <code>false</code> and controls
* whether an existing repository structure should be updated to match changes
* in the domain classes.
*/
public class RepositoryBootstrap {
final FenixJvstmConfig config;
public RepositoryBootstrap(FenixJvstmConfig config) {
this.config = config;
}
private void applyInfrastructureChanges(Connection conn) throws SQLException {
if (columnExists(conn, "FF$PERSISTENT_ROOT", "ID_INTERNAL") && !columnExists(conn, "FF$PERSISTENT_ROOT", "OID")) {
executeSqlInstructions(conn, "ALTER TABLE FF$PERSISTENT_ROOT CHANGE COLUMN ID_INTERNAL OID BIGINT");
}
if (columnExists(conn, "FF$PERSISTENT_ROOT", "OID") && !columnExists(conn, "FF$PERSISTENT_ROOT", "OID_NEXT")) {
executeSqlInstructions(
conn,
"ALTER TABLE FF$PERSISTENT_ROOT ADD COLUMN ID_INTERNAL int(11), ADD COLUMN OID_NEXT BIGINT(20), ADD COLUMN OID_PREVIOUS BIGINT(20),ADD COLUMN ROOT_KEY TEXT, ADD COLUMN OID_ROOT_OBJECT BIGINT(20);");
executeSqlInstructions(conn,
"update FF$PERSISTENT_ROOT SET ID_INTERNAL=1, OID_ROOT_OBJECT=ROOT, ROOT_KEY='pt.ist.fenixframework.root' WHERE OID=1;");
}
}
public void updateDataRepositoryStructureIfNeeded() {
Connection connection = null;
try {
connection = getConnection();
Statement statement = null;
ResultSet resultSet = null;
try {
statement = connection.createStatement();
resultSet = statement.executeQuery("SELECT GET_LOCK('FenixFrameworkInit', 100)");
if (!resultSet.next() || (resultSet.getInt(1) != 1)) {
return;
}
} finally {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
}
try {
applyInfrastructureChanges(connection);
if (config.getCreateRepositoryStructureIfNotExists() || config.getUpdateRepositoryStructureIfNeeded()) {
boolean newInfrastructureCreated = false;
if (!infrastructureExists(connection) && config.getCreateRepositoryStructureIfNotExists()) {
if (infrastructureNeedsUpdate(connection)) {
updateInfrastructure(connection);
} else {
createInfrastructure(connection);
newInfrastructureCreated = true;
}
}
if (newInfrastructureCreated || config.getUpdateRepositoryStructureIfNeeded()) {
final String updates = SQLUpdateGenerator.generateSqlUpdates(FenixFramework.getDomainModel(), connection,
null, false);
executeSqlInstructions(connection, updates);
}
}
} finally {
Statement statementUnlock = null;
try {
statementUnlock = connection.createStatement();
statementUnlock.executeUpdate("DO RELEASE_LOCK('FenixFrameworkInit')");
} finally {
if (statementUnlock != null) {
statementUnlock.close();
}
}
}
connection.commit();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// nothing can be done.
}
}
}
}
private void executeSqlInstructions(final Connection connection, final String sqlInstructions) throws SQLException {
for (final String instruction : sqlInstructions.split(";")) {
final String trimmed = instruction.trim();
if (trimmed.length() > 0) {
Statement statement = null;
try {
statement = connection.createStatement();
statement.execute(instruction);
} finally {
if (statement != null) {
statement.close();
}
}
}
}
}
private void executeSqlStream(final Connection connection, final String streamName) throws IOException, SQLException {
final InputStream inputStream = RepositoryBootstrap.class.getResourceAsStream(streamName);
final String sqlInstructions = readFile(new InputStreamReader(inputStream));
executeSqlInstructions(connection, sqlInstructions);
}
private Connection getConnection() throws ClassNotFoundException, SQLException {
final String driverName = "com.mysql.jdbc.Driver";
Class.forName(driverName);
final String url = "jdbc:mysql:" + config.getDbAlias();
final Connection connection = DriverManager.getConnection(url, config.getDbUsername(), config.getDbPassword());
connection.setAutoCommit(false);
return connection;
}
private void createInfrastructure(final Connection connection) throws SQLException, IOException {
executeSqlStream(connection, "/transactional-system-ddl.sql");
executeSqlStream(connection, "/ojb-ddl.sql");
}
private boolean infrastructureExists(final Connection connection) throws SQLException {
return tableExists(connection, "FF$TX_CHANGE_LOGS");
}
private boolean tableExists(final Connection connection, String tableName) throws SQLException {
final DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet resultSet = null;
try {
final String dbName = connection.getCatalog();
resultSet = databaseMetaData.getTables(dbName, "", tableName, new String[] { "TABLE" });
while (resultSet.next()) {
final String existingTableName = resultSet.getString(3);
// we need to use the equalsIgnoreCase here because on
// MS Windows (at least), the name of the tables
// change case
if (tableName.equalsIgnoreCase(existingTableName)) {
return true;
}
}
return false;
} finally {
if (resultSet != null) {
resultSet.close();
}
}
}
private boolean columnExists(Connection connection, String tableName, String colName) throws SQLException {
final DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet resultSet = null;
try {
final String dbName = connection.getCatalog();
resultSet = databaseMetaData.getColumns(dbName, "", tableName, colName);
return resultSet.next();
} finally {
if (resultSet != null) {
resultSet.close();
}
}
}
private String readFile(final InputStreamReader fileReader) throws IOException {
try {
char[] buffer = new char[4096];
final StringBuilder fileContents = new StringBuilder();
for (int n = 0; (n = fileReader.read(buffer)) != -1; fileContents.append(buffer, 0, n))
;
return fileContents.toString();
} finally {
fileReader.close();
}
}
private boolean infrastructureNeedsUpdate(final Connection connection) throws SQLException {
return tableExists(connection, "TX_CHANGE_LOGS");
}
private void updateInfrastructure(final Connection connection) throws SQLException, IOException {
executeSqlStream(connection, "/rename-system-tables.sql");
}
}