/*
* JBoss, Home of Professional Open Source.
* Copyright 2017, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.wildfly.extension.messaging.activemq;
import static java.lang.String.format;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.HashSet;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.activemq.artemis.jdbc.store.sql.SQLProvider;
import org.wildfly.extension.messaging.activemq.logging.MessagingLogger;
/**
* Property-based implementation of a {@link SQLProvider}'s factory.
*
* Properties are stored in a journal-sql.properties in the org.wildfly.extension.messaging-activemq JBoss module.
*
* Dialects specific to a database can be customized by suffixing the property keys with the name of the database.
*
* @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2017 Red Hat inc.
*/
public class PropertySQLProviderFactory implements SQLProvider.Factory {
private static final String ORACLE = "oracle";
String database;
private volatile Properties sql;
/** List of extracted known dialects*/
private final HashSet<String> databaseDialects = new HashSet<>();
public PropertySQLProviderFactory(String database) throws IOException {
this.database = database;
try (InputStream stream = PropertySQLProvider.class.getClassLoader().getResourceAsStream("journal-sql.properties")) {
sql = new Properties();
sql.load(stream);
extractDialects();
}
}
@Override
public SQLProvider create(String tableName, SQLProvider.DatabaseStoreType storeType) {
// WFLY-8307 - Oracle driver does not support lower case for table names
String name = ORACLE.equals(database) ? tableName.toUpperCase() : tableName;
return new PropertySQLProvider(name, storeType);
}
/**
* Read the properties from the journal-sql and extract the database dialects.
*/
private void extractDialects() {
for (Object prop : sql.keySet()) {
int dot = ((String)prop).indexOf('.');
if (dot > 0) {
databaseDialects.add(((String)prop).substring(dot+1));
}
}
}
public void investigateDialect(DataSource dataSource) {
// specifying the database has precedence over detecting it from the data source metadata.
if (database != null) {
return;
}
// no database dialect from configuration, guessing from MetaData
try (Connection connection = dataSource.getConnection()){
DatabaseMetaData metaData = connection.getMetaData();
String dbProduct = metaData.getDatabaseProductName();
database = identifyDialect(dbProduct);
if (database == null) {
MessagingLogger.ROOT_LOGGER.debug("Attempting to guess on driver name.");
database = identifyDialect(metaData.getDriverName());
}
if (dataSource == null) {
MessagingLogger.ROOT_LOGGER.jdbcDatabaseDialectDetectionFailed(databaseDialects.toString());
} else {
MessagingLogger.ROOT_LOGGER.debugf("Detect database dialect as '%s'. If this is incorrect, please specify the correct dialect using the 'database' attribute in your configuration. Supported database dialect strings are %s", database, databaseDialects);
}
} catch (Exception e) {
MessagingLogger.ROOT_LOGGER.debug("Unable to read JDBC metadata.", e);
}
}
private String identifyDialect(String name) {
String unified = null;
if (name != null) {
if (name.toLowerCase().contains("postgres")) {
unified = "postgresql";
} else if (name.toLowerCase().contains("mysql")) {
unified = "mysql";
} else if (name.toLowerCase().contains("db2")) {
unified = "db2";
} else if (name.toLowerCase().contains("derby")) {
unified = "derby";
} else if (name.toLowerCase().contains("hsql") || name.toLowerCase().contains("hypersonic")) {
unified = "hsql";
} else if (name.toLowerCase().contains("h2")) {
unified = "h2";
} else if (name.toLowerCase().contains(ORACLE)) {
unified = ORACLE;
}else if (name.toLowerCase().contains("microsoft")) {
unified = "mssql";
}else if (name.toLowerCase().contains("jconnect")) {
unified = "sybase";
}
}
MessagingLogger.ROOT_LOGGER.debugf("Check dialect for '%s', result is '%s'", name, unified);
return unified;
}
private class PropertySQLProvider implements SQLProvider {
private final String tableName;
public PropertySQLProvider(String tableName, DatabaseStoreType storeType) {
this.tableName = tableName;
}
@Override
public long getMaxBlobSize() {
return Long.valueOf(sql("max-blob-size"));
}
@Override
public String[] getCreateJournalTableSQL() {
return new String[] {
format(sql("create-journal-table"), tableName),
format(sql("create-journal-index"), tableName),
};
}
@Override
public String getInsertJournalRecordsSQL() {
return format(sql("insert-journal-record"), tableName);
}
@Override
public String getSelectJournalRecordsSQL() {
return format(sql("select-journal-record"), tableName);
}
@Override
public String getDeleteJournalRecordsSQL() {
return format(sql("delete-journal-record"), tableName);
}
@Override
public String getDeleteJournalTxRecordsSQL() {
return format(sql("delete-journal-tx-record"), tableName);
}
@Override
public String getTableName() {
return tableName;
}
@Override
public String getCreateFileTableSQL() {
return format(sql("create-file-table"), tableName);
}
@Override
public String getInsertFileSQL() {
return format(sql("insert-file"), tableName);
}
@Override
public String getSelectFileNamesByExtensionSQL() {
return format(sql("select-filenames-by-extension"), tableName);
}
@Override
public String getSelectFileByFileName() {
return format(sql("select-file-by-filename"), tableName);
}
@Override
public String getAppendToLargeObjectSQL() {
return format(sql("append-to-file"), tableName);
}
@Override
public String getReadLargeObjectSQL() {
return format(sql("read-large-object"), tableName);
}
@Override
public String getDeleteFileSQL() {
return format(sql("delete-file"), tableName);
}
@Override
public String getUpdateFileNameByIdSQL() {
return format(sql("update-filename-by-id"), tableName);
}
@Override
public String getCopyFileRecordByIdSQL() {
return format(sql("copy-file-record-by-id"), tableName);
}
@Override
public String getDropFileTableSQL() {
return format(sql("drop-table"), tableName);
}
@Override
public String getCloneFileRecordByIdSQL() {
return format(sql("clone-file-record"), tableName);
}
@Override
public String getCountJournalRecordsSQL() {
return format(sql("count-journal-record"), tableName);
}
@Override
public boolean closeConnectionOnShutdown() {
return Boolean.valueOf(sql("close-connection-on-shutdown"));
}
private String sql(final String key) {
if (database != null) {
String result = sql.getProperty(key + "." + database);
if (result != null) {
return result;
}
}
return sql.getProperty(key);
}
}
}