This closes #961

This commit is contained in:
Martyn Taylor 2017-01-16 15:36:33 +00:00
commit 490bec9d92
4 changed files with 122 additions and 15 deletions

View File

@ -22,9 +22,12 @@ import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.activemq.artemis.jdbc.store.sql.SQLProvider;
import org.apache.activemq.artemis.journal.ActiveMQJournalLogger;
@ -61,7 +64,7 @@ public abstract class AbstractJDBCDriver {
this.sqlProvider = provider;
}
public void start() throws Exception {
public void start() throws SQLException {
connect();
createSchema();
prepareStatements();
@ -69,7 +72,12 @@ public abstract class AbstractJDBCDriver {
public void stop() throws SQLException {
if (sqlProvider.closeConnectionOnShutdown()) {
connection.close();
try {
connection.close();
} catch (SQLException e) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e));
throw e;
}
}
}
@ -77,58 +85,100 @@ public abstract class AbstractJDBCDriver {
protected abstract void createSchema() throws SQLException;
protected void createTable(String... schemaSqls) throws SQLException {
protected final void createTable(String... schemaSqls) throws SQLException {
createTableIfNotExists(connection, sqlProvider.getTableName(), schemaSqls);
}
protected void connect() throws Exception {
private void connect() throws SQLException {
if (dataSource != null) {
connection = dataSource.getConnection();
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e));
throw e;
}
} else {
try {
Driver dbDriver = getDriver(jdbcDriverClass);
if (jdbcDriverClass == null || jdbcDriverClass.isEmpty()) {
throw new IllegalStateException("jdbcDriverClass is null or empty!");
}
if (jdbcConnectionUrl == null || jdbcConnectionUrl.isEmpty()) {
throw new IllegalStateException("jdbcConnectionUrl is null or empty!");
}
final Driver dbDriver = getDriver(jdbcDriverClass);
connection = dbDriver.connect(jdbcConnectionUrl, new Properties());
if (connection == null) {
throw new IllegalStateException("the driver: " + jdbcDriverClass + " isn't able to connect to the requested url: " + jdbcConnectionUrl);
}
} catch (SQLException e) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e));
ActiveMQJournalLogger.LOGGER.error("Unable to connect to database using URL: " + jdbcConnectionUrl);
throw new RuntimeException("Error connecting to database", e);
throw e;
}
}
}
public void destroy() throws Exception {
final String dropTableSql = "DROP TABLE " + sqlProvider.getTableName();
try {
connection.setAutoCommit(false);
try (Statement statement = connection.createStatement()) {
statement.executeUpdate("DROP TABLE " + sqlProvider.getTableName());
statement.executeUpdate(dropTableSql);
}
connection.commit();
} catch (SQLException e) {
connection.rollback();
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e, dropTableSql));
try {
connection.rollback();
} catch (SQLException rollbackEx) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), rollbackEx, dropTableSql));
throw rollbackEx;
}
throw e;
}
}
private static void createTableIfNotExists(Connection connection, String tableName, String... sqls) throws SQLException {
private static void createTableIfNotExists(Connection connection,
String tableName,
String... sqls) throws SQLException {
logger.tracef("Validating if table %s didn't exist before creating", tableName);
try {
connection.setAutoCommit(false);
try (ResultSet rs = connection.getMetaData().getTables(null, null, tableName, null)) {
if (rs != null && !rs.next()) {
logger.tracef("Table %s did not exist, creating it with SQL=%s", tableName, Arrays.toString(sqls));
if (logger.isTraceEnabled()) {
logger.tracef("Table %s did not exist, creating it with SQL=%s", tableName, Arrays.toString(sqls));
}
final SQLWarning sqlWarning = rs.getWarnings();
if (sqlWarning != null) {
logger.warn(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), sqlWarning));
}
try (Statement statement = connection.createStatement()) {
for (String sql : sqls) {
statement.executeUpdate(sql);
final SQLWarning statementSqlWarning = statement.getWarnings();
if (statementSqlWarning != null) {
logger.warn(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), statementSqlWarning, sql));
}
}
}
}
}
connection.commit();
} catch (SQLException e) {
connection.rollback();
final String sqlStatements = Stream.of(sqls).collect(Collectors.joining("\n"));
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e, sqlStatements));
try {
connection.rollback();
} catch (SQLException rollbackEx) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), rollbackEx, sqlStatements));
throw rollbackEx;
}
throw e;
}
}
private Driver getDriver(String className) throws Exception {
private Driver getDriver(String className) {
try {
Driver driver = (Driver) Class.forName(className).newInstance();

View File

@ -16,6 +16,8 @@
*/
package org.apache.activemq.artemis.jdbc.store.drivers;
import java.sql.SQLException;
import org.apache.activemq.artemis.jdbc.store.drivers.derby.DerbySQLProvider;
import org.apache.activemq.artemis.jdbc.store.drivers.mysql.MySQLSQLProvider;
import org.apache.activemq.artemis.jdbc.store.drivers.postgres.PostgresSQLProvider;
@ -63,4 +65,59 @@ public class JDBCUtils {
return factory.create(tableName);
}
/**
* Append to {@code errorMessage} a detailed description of the provided {@link SQLException}.<br/>
* The information appended are:
* <ul>
* <li>SQL STATEMENTS</li>
* <li>SQL EXCEPTIONS details ({@link SQLException#getSQLState},
* {@link SQLException#getErrorCode} and {@link SQLException#getMessage}) of the linked list ({@link SQLException#getNextException}) of exceptions</li>
* </ul>
*
* @param errorMessage the target where append the exceptions details
* @param exception the SQL exception (or warning)
* @param sqlStatements the SQL statements related to the {@code exception}
* @return {@code errorMessage}
*/
public static StringBuilder appendSQLExceptionDetails(StringBuilder errorMessage,
SQLException exception,
CharSequence sqlStatements) {
errorMessage.append("\nSQL STATEMENTS: \n").append(sqlStatements);
return appendSQLExceptionDetails(errorMessage, exception);
}
/**
* Append to {@code errorMessage} a detailed description of the provided {@link SQLException}.<br/>
* The information appended are:
* <ul>
* <li>SQL EXCEPTIONS details ({@link SQLException#getSQLState},
* {@link SQLException#getErrorCode} and {@link SQLException#getMessage}) of the linked list ({@link SQLException#getNextException}) of exceptions</li>
* </ul>
*
* @param errorMessage the target where append the exceptions details
* @param exception the SQL exception (or warning)
* @return {@code errorMessage}
*/
public static StringBuilder appendSQLExceptionDetails(StringBuilder errorMessage, SQLException exception) {
errorMessage.append("\nSQL EXCEPTIONS: ");
SQLException nextEx = exception;
int level = 0;
do {
errorMessage.append('\n');
for (int i = 0; i < level; i++) {
errorMessage.append(' ');
}
formatSqlException(errorMessage, nextEx);
nextEx = exception.getNextException();
level++;
} while (nextEx != null);
return errorMessage;
}
private static StringBuilder formatSqlException(StringBuilder errorMessage, SQLException exception) {
final String sqlState = exception.getSQLState();
final int errorCode = exception.getErrorCode();
final String message = exception.getMessage();
return errorMessage.append("SQLState: ").append(sqlState).append(" ErrorCode: ").append(errorCode).append(" Message: ").append(message);
}
}

View File

@ -80,7 +80,7 @@ public class JDBCSequentialFileFactory implements SequentialFileFactory, ActiveM
started = true;
}
} catch (Exception e) {
ActiveMQJournalLogger.LOGGER.error("Could not start file factory, unable to connect to database");
ActiveMQJournalLogger.LOGGER.error("Could not start file factory, unable to connect to database", e);
started = false;
}
}

View File

@ -107,7 +107,7 @@ public class JDBCJournalImpl extends AbstractJDBCDriver implements Journal {
}
@Override
public void start() throws Exception {
public void start() throws SQLException {
super.start();
syncTimer = new JDBCJournalSync(scheduledExecutorService, completeExecutor, SYNC_DELAY, TimeUnit.MILLISECONDS, this);
started = true;