package org.rr.jeborker.db;
import static org.rr.commons.utils.StringUtil.EMPTY;
import static org.rr.jeborker.app.preferences.PreferenceStoreFactory.PREFERENCE_KEYS.JEBOORKER_DB_VERSION_KEY;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import org.h2.jdbc.JdbcResultSet;
import org.h2.result.LocalResult;
import org.rr.commons.collection.ICloseableList;
import org.rr.commons.collection.IteratorList;
import org.rr.commons.log.LoggerFactory;
import org.rr.commons.utils.ReflectionFailureException;
import org.rr.commons.utils.ReflectionUtils;
import org.rr.commons.utils.StringUtil;
import org.rr.jeborker.Jeboorker;
import org.rr.jeborker.app.preferences.APreferenceStore;
import org.rr.jeborker.app.preferences.PreferenceStoreFactory;
import com.j256.ormlite.dao.BaseDaoImpl;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.dao.GenericRawResults;
import com.j256.ormlite.jdbc.JdbcDatabaseResults;
import com.j256.ormlite.jdbc.JdbcPooledConnectionSource;
import com.j256.ormlite.stmt.RawRowMapperImpl;
import com.j256.ormlite.stmt.Where;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.table.TableInfo;
import com.j256.ormlite.table.TableUtils;
class H2DBManager extends DefaultDBManager {
protected JdbcPooledConnectionSource initDatabase() {
PreferenceStoreFactory.getPreferenceStore(PreferenceStoreFactory.SYSTEM_STORE);
String configPath = APreferenceStore.getConfigDirectory();
try {
Class.forName("org.h2.Driver");
JdbcPooledConnectionSource connection = new JdbcPooledConnectionSource("jdbc:h2:" + configPath + "h2db;MULTI_THREADED=TRUE;TRACE_LEVEL_FILE=0;OPTIMIZE_UPDATE=false");
connection.setUsername("sa");
connection.setPassword(EMPTY);
setConnectionPool(connection);
createDatabaseIfNecessary(connection);
return connection;
} catch (Exception e) {
LoggerFactory.log(Level.SEVERE, this, "could not init database in " + configPath, e);
}
return null;
}
private void createDatabaseIfNecessary(JdbcPooledConnectionSource connection) throws SQLException {
APreferenceStore dbPreferenceStore = PreferenceStoreFactory.getPreferenceStore(JEBOORKER_DB_VERSION_KEY);
String jbDbVersion = dbPreferenceStore.getEntryAsString(JEBOORKER_DB_VERSION_KEY);
if (jbDbVersion == null) {
prepareFullTextIndices();
for (Class<?> entity : KNOWN_CLASSES) {
TableUtils.createTableIfNotExists(connection, entity);
initFullTextIndices(entity);
}
reCreateFullTextIndices();
dbPreferenceStore.addEntryAsString(JEBOORKER_DB_VERSION_KEY, Jeboorker.getAppVersion());
}
}
private void reCreateFullTextIndices() throws SQLException {
JdbcPooledConnectionSource connectionPool = getConnectionPool();
DatabaseConnection connection = null;
try {
connection = connectionPool.getReadWriteConnection();
connection.executeStatement("CALL FT_REINDEX()", DatabaseConnection.DEFAULT_RESULT_FLAGS);
} finally {
if (connection != null) {
connectionPool.releaseConnection(connection);
}
}
}
private void prepareFullTextIndices() throws SQLException {
JdbcPooledConnectionSource connectionPool = getConnectionPool();
DatabaseConnection connection = null;
try {
connection = connectionPool.getReadWriteConnection();
connection.executeStatement("CREATE ALIAS IF NOT EXISTS FT_INIT FOR \"org.h2.fulltext.FullText.init\"", DatabaseConnection.DEFAULT_RESULT_FLAGS);
connection.executeStatement("CALL FT_INIT()", DatabaseConnection.DEFAULT_RESULT_FLAGS);
} finally {
if (connection != null) {
connectionPool.releaseConnection(connection);
}
}
}
private void initFullTextIndices(Class<?> entity) throws SQLException {
JdbcPooledConnectionSource connectionPool = getConnectionPool();
DatabaseConnection connection = null;
try {
connection = connectionPool.getReadWriteConnection();
connection.executeStatement("CALL FT_CREATE_INDEX('PUBLIC', '" + entity.getSimpleName().toUpperCase() + "', NULL)",
DatabaseConnection.DEFAULT_RESULT_FLAGS);
} finally {
if (connection != null) {
connectionPool.releaseConnection(connection);
}
}
}
public synchronized <T> ICloseableList<T> queryFullTextSearch(Class<T> cls, Where<T, T> where, List<String> keywords, List<Field> orderFields,
OrderDirection orderDirection) {
try {
StringBuilder sql = new StringBuilder();
String tableName = cls.getSimpleName().toUpperCase();
sql.append("SELECT A.* FROM ").append(tableName).append(" A ");
boolean searchTableAppended = appendFulltextQueryTable(keywords, tableName, sql);
if (searchTableAppended) {
sql.append(" WHERE A.FILE = B.KEYS AND ");
boolean queryConditionAppended = appendFulltextQueryCondition(keywords, tableName, sql);
if(queryConditionAppended) {
sql.append(" AND ");
}
} else {
sql.append(" WHERE ");
}
try {
sql.append(where.getStatement()).append(' ');
} catch(IllegalStateException e) {
sql.append("true = true ");
LoggerFactory.getLogger().log(Level.INFO, "No Where clause", e);
}
appendOrderFields(orderFields, orderDirection, sql);
String sqlString = sql.toString();
Dao<T, T> createDao = DaoManager.createDao(getConnectionPool(), cls);
GenericRawResults<T> queryRaw = createDao.queryRaw(sqlString, new RawRowMapperImpl<T, T>(new TableInfo<T, T>(getConnectionPool(),
(BaseDaoImpl<T, T>) createDao, cls)), new String[0]);
Iterator<T> iterator = queryRaw.closeableIterator();
int rowCount = getRowCount(iterator);
return new IteratorList<T>(iterator, rowCount);
} catch (Exception e) {
LoggerFactory.log(Level.SEVERE, this, "Failed to execute query", e);
return new IteratorList<T>(new ArrayList<T>(0).iterator(), 0);
}
}
private <T> int getRowCount(Iterator<T> iterator) throws ReflectionFailureException {
JdbcDatabaseResults results = (JdbcDatabaseResults) ReflectionUtils.getFieldValue(iterator, "results", false);
JdbcResultSet resultSet = (JdbcResultSet) ReflectionUtils.getFieldValue(results, "resultSet", false);
LocalResult localResult = (LocalResult) ReflectionUtils.getFieldValue(resultSet, "result", false);
return localResult.getRowCount();
}
private void appendOrderFields(List<Field> orderFields, OrderDirection orderDirection, StringBuilder sql) {
if (!orderFields.isEmpty()) {
sql.append("ORDER BY ");
for (Field orderField : orderFields) {
sql.append("A.").append(orderField.getName()).append(" ").append(orderDirection.getDirectionString()).append(", ");
}
sql.setLength(sql.length() - 2);
}
sql.append(' ');
}
private boolean appendFulltextQueryTable(List<String> keywords, String tableName, StringBuilder sql) {
if (!keywords.isEmpty()) {
sql.append(", (");
for (int i = 0; i < keywords.size(); i++) {
if (i > 0) {
sql.append(" union ");
}
String keyword = keywords.get(i);
if (keyword.contains(":")) {
keyword = keyword.substring(keyword.indexOf(':') + 1);
}
keyword = StringUtil.escapeSql(keyword);
sql.append("select * from FT_SEARCH_DATA('").append(keyword).append("', 0, 0) B where B.TABLE='").append(tableName).append("'");
}
sql.append(") B");
return true;
}
return false;
}
private boolean appendFulltextQueryCondition(List<String> keywords, String tableName, StringBuilder sql) {
if (!keywords.isEmpty()) {
StringBuilder localSQL = new StringBuilder();
boolean append = false;
String orString = " OR ";
localSQL.append(" (");
for (String keyword : keywords) {
if (keyword.contains(":")) {
String searchColumn = keyword.substring(0, keyword.indexOf(':')).toUpperCase();
keyword = keyword.substring(keyword.indexOf(':') + 1).toUpperCase();
keyword = StringUtil.escapeSql(keyword);
localSQL.append("upper(").append(searchColumn).append(") like '%").append(keyword).append("%'").append(orString);
append = true;
}
}
if(localSQL.length() > orString.length() && localSQL.substring(localSQL.length() - orString.length(), localSQL.length()).equals(orString)) {
localSQL.setLength(localSQL.length() - orString.length());
}
localSQL.append(") ");
if(append) {
sql.append(localSQL);
}
return append;
}
return false;
}
}