// Copyright (c) 2003-2014, Jodd Team (jodd.org). All Rights Reserved.
package jodd.db.oom;
import jodd.db.DbQuery;
import jodd.db.DbSession;
import jodd.db.DbUtil;
import jodd.db.oom.mapper.DefaultResultSetMapper;
import jodd.db.oom.mapper.ResultSetMapper;
import jodd.db.oom.sqlgen.ParameterValue;
import jodd.db.type.SqlType;
import jodd.util.StringUtil;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
import static jodd.db.oom.DbOomUtil.initialCollectionSize;
/**
* A simple ORM extension for {@link DbQuery}.
* <p>
* OOM extension may map results to objects in two ways:
* <ul>
* <li><i>auto</i> mode - when result set is mapped to provided types, and</li>
* <li><i>mapped</i> mode - requires explicit mapping definitions.</li>
* </ul>
*/
public class DbOomQuery extends DbQuery {
private static final Logger log = LoggerFactory.getLogger(DbOomQuery.class);
// ---------------------------------------------------------------- default ctors
public DbOomQuery(Connection conn, String sqlString) {
super(conn, sqlString);
}
public static DbOomQuery query(Connection conn, String sqlString) {
return new DbOomQuery(conn, sqlString);
}
public DbOomQuery(DbSession session, String sqlString) {
super(session, sqlString);
}
public static DbOomQuery query(DbSession session, String sqlString) {
return new DbOomQuery(session, sqlString);
}
public DbOomQuery(String sqlString) {
super(sqlString);
}
public static DbOomQuery query(String sqlString) {
return new DbOomQuery(sqlString);
}
// ---------------------------------------------------------------- sqlgen ctors
protected DbSqlGenerator sqlgen;
public DbOomQuery(Connection conn, DbSqlGenerator sqlgen) {
super(conn, sqlgen.generateQuery());
this.sqlgen = sqlgen;
}
public static DbOomQuery query(Connection conn, DbSqlGenerator sqlgen) {
return new DbOomQuery(conn, sqlgen);
}
public DbOomQuery(DbSession session, DbSqlGenerator sqlgen) {
super(session, sqlgen.generateQuery());
this.sqlgen = sqlgen;
}
public static DbOomQuery query(DbSession session, DbSqlGenerator sqlgen) {
return new DbOomQuery(session, sqlgen);
}
public DbOomQuery(DbSqlGenerator sqlgen) {
super(sqlgen.generateQuery());
this.sqlgen = sqlgen;
}
public static DbOomQuery query(DbSqlGenerator sqlgen) {
return new DbOomQuery(sqlgen);
}
// ---------------------------------------------------------------- initialization
protected DbOomManager dbOomManager = DbOomManager.getInstance();
/**
* Returns used ORM manager.
*/
public DbOomManager getManager() {
return dbOomManager;
}
/**
* Prepares the query after initialization. Besides default work, it checks if sql generator
* is used, and if so, generator hints and query parameters will be used for this query.
* Note regarding hints: since hints can be added manually, generators hints will be ignored
* if there exists some manually set hints.
*/
@Override
protected void prepareQuery() {
super.prepareQuery();
if (sqlgen == null) {
return;
}
if (hints == null) {
String[] joinHints = sqlgen.getJoinHints();
if (joinHints != null) {
withHints(joinHints);
}
}
// insert parameters
Map<String, ParameterValue> parameters = sqlgen.getQueryParameters();
if (parameters == null) {
return;
}
for (Map.Entry<String, ParameterValue> entry : parameters.entrySet()) {
String paramName = entry.getKey();
ParameterValue param = entry.getValue();
DbEntityColumnDescriptor dec = param.getColumnDescriptor();
if (dec == null) {
setObject(paramName, param.getValue());
} else {
resolveColumnDbSqlType(connection, dec);
setObject(paramName, param.getValue(), dec.getSqlTypeClass(), dec.getDbSqlType());
}
}
}
/**
* Resolves column db sql type and populates it in column descriptor if missing.
*/
protected void resolveColumnDbSqlType(Connection connection, DbEntityColumnDescriptor dec) {
if (dec.dbSqlType != SqlType.DB_SQLTYPE_UNKNOWN) {
return;
}
ResultSet rs = null;
DbEntityDescriptor ded = dec.getDbEntityDescriptor();
try {
DatabaseMetaData dmd = connection.getMetaData();
rs = dmd.getColumns(null, ded.getSchemaName(), ded.getTableName(), dec.getColumnName());
if (rs.next()) {
dec.dbSqlType = rs.getInt("DATA_TYPE");
} else {
dec.dbSqlType = SqlType.DB_SQLTYPE_NOT_AVAILABLE;
if (log.isWarnEnabled()) {
log.warn("Column SQL type not available: " + ded.toString() + '.' + dec.getColumnName());
}
}
} catch (SQLException sex) {
dec.dbSqlType = SqlType.DB_SQLTYPE_NOT_AVAILABLE;
if (log.isWarnEnabled()) {
log.warn("Column SQL type not resolved: " + ded.toString() + '.' + dec.getColumnName(), sex);
}
} finally {
DbUtil.close(rs);
}
}
// ---------------------------------------------------------------- join hints
protected String[] hints;
protected JoinHintResolver hintResolver = dbOomManager.getHintResolver();
/**
* Specifies hints for the query. Provided string is
* split on ',' separator.
*/
public DbOomQuery withHints(String hint) {
this.hints = StringUtil.splitc(hint, ',');
return this;
}
/**
* Specifies multiple hints for the query.
*/
public DbOomQuery withHints(String... hints) {
this.hints = hints;
return this;
}
/**
* Prepares a row (array of rows mapped object) using hints.
* Returns either single object or objects array.
*/
protected Object resolveRowResults(Object[] row) {
row = hintResolver.join(row, hints);
return row.length == 1 ? row[0] : row;
}
// ---------------------------------------------------------------- result set
protected boolean cacheEntities = dbOomManager.isCacheEntitiesInResultSet();
/**
* Defines if entities should be cached in {@link ResultSetMapper}.
* Overrides default value in {@link DbOomManager}.
*/
public DbOomQuery cacheEntities(boolean cacheEntities) {
this.cacheEntities = cacheEntities;
return this;
}
/**
* Executes the query and returns {@link #createResultSetMapper(java.sql.ResultSet) builded ResultSet mapper}.
*/
protected ResultSetMapper executeAndBuildResultSetMapper() {
ResultSet resultSet = execute();
return createResultSetMapper(resultSet);
}
/**
* Factory for result sets mapper.
*/
protected ResultSetMapper createResultSetMapper(ResultSet resultSet) {
Map<String, ColumnData> columnAliases = sqlgen != null ? sqlgen.getColumnData() : null;
return new DefaultResultSetMapper(resultSet, columnAliases, cacheEntities, this);
}
// ---------------------------------------------------------------- db list
protected boolean entityAwareMode = dbOomManager.isEntityAwareMode();
/**
* Defines entity-aware mode for entities tracking in result collection.
* @see DbOomManager#setEntityAwareMode(boolean)
*/
public DbOomQuery entityAwareMode(boolean entityAware) {
if (entityAware) {
this.cacheEntities = true;
}
this.entityAwareMode = entityAware;
return this;
}
// ---------------------------------------------------------------- iterator
public <T> Iterator<T> iterate(Class... types) {
return iterate(types, autoClose);
}
public <T> Iterator<T> iterate() {
return iterate(null, autoClose);
}
protected <T> Iterator<T> iterate(Class[] types, boolean close) {
return new DbListIterator<T>(this, types, close);
}
// ---------------------------------------------------------------- list
public <T> List<T> list(Class... types) {
return list(types, -1, autoClose);
}
public <T> List<T> list() {
return list(null, -1, autoClose);
}
public <T> List<T> list(int max, Class... types) {
return list(types, max, autoClose);
}
public <T> List<T> list(int max) {
return list(null, max, autoClose);
}
/**
* Iterates result set, maps rows to classes and populates resulting array list.
* @param types mapping types
* @param max max number of rows to collect, <code>-1</code> for all
* @param close <code>true</code> if query is closed at the end, otherwise <code>false</code>.
* @return list of mapped entities or array of entities
*/
@SuppressWarnings({"unchecked"})
protected <T> List<T> list(Class[] types, int max, boolean close) {
List<T> result = new ArrayList<T>(initialCollectionSize(max));
ResultSetMapper rsm = executeAndBuildResultSetMapper();
if (types == null) {
types = rsm.resolveTables();
}
Object previousElement = null;
while (rsm.next()) {
Object[] objects = rsm.parseObjects(types);
Object row = resolveRowResults(objects);
int size = result.size();
T newElement = (T) row;
if (entityAwareMode && size > 0) {
if (previousElement != null && newElement != null) {
boolean equals;
if (newElement.getClass().isArray()) {
equals = Arrays.equals((Object[]) previousElement, (Object[]) newElement);
} else {
equals = previousElement.equals(newElement);
}
if (equals) {
continue;
}
}
}
if (size == max) {
break;
}
result.add(newElement);
previousElement = newElement;
}
close(rsm, close);
return result;
}
// ---------------------------------------------------------------- set
public <T> Set<T> listSet(Class... types) {
return listSet(types, -1, autoClose);
}
public <T> Set<T> listSet() {
return listSet(null, -1, autoClose);
}
public <T> Set<T> listSet(int max, Class... types) {
return listSet(types, max, autoClose);
}
public <T> Set<T> listSet(int max) {
return listSet(null, max, autoClose);
}
@SuppressWarnings({"unchecked"})
protected <T> Set<T> listSet(Class[] types, int max, boolean close) {
Set<T> result = new LinkedHashSet<T>(initialCollectionSize(max));
ResultSetMapper rsm = executeAndBuildResultSetMapper();
if (types == null) {
types = rsm.resolveTables();
}
Object previousElement = null;
while (rsm.next()) {
Object[] objects = rsm.parseObjects(types);
Object row = resolveRowResults(objects);
int size = result.size();
T newElement = (T) row;
if (entityAwareMode && size > 0) {
if (previousElement != null && newElement != null) {
boolean equals;
if (newElement.getClass().isArray()) {
equals = Arrays.equals((Object[]) previousElement, (Object[]) newElement);
} else {
equals = previousElement.equals(newElement);
}
if (equals) {
continue;
}
}
}
if (size == max) {
break;
}
result.add(newElement);
previousElement = newElement;
}
close(rsm, close);
return result;
}
// ---------------------------------------------------------------- find
public <T> T find(Class... types) {
return find(types, autoClose, null);
}
public <T> T find() {
return find(null, autoClose, null);
}
protected <T> T find(Class[] types, boolean close, ResultSet resultSet) {
if (resultSet == null) {
resultSet = execute();
}
ResultSetMapper rsm = createResultSetMapper(resultSet);
Iterator<T> iterator = new DbListIterator<T>(this, types, rsm, false);
T result = null;
if (iterator.hasNext()) {
result = iterator.next();
}
close(rsm, close);
return result;
}
// ---------------------------------------------------------------- generated columns
/**
* Finds generated key column of given type.
*/
public <T> T findGeneratedKey(Class<T> type) {
return find(new Class[] {type}, false, getGeneratedColumns());
}
/**
* Finds generated columns.
*/
public Object findGeneratedColumns(Class... types) {
return find(types, false, getGeneratedColumns());
}
// ---------------------------------------------------------------- util
/**
* {@inheritDoc}
*/
@Override
public DbOomQuery autoClose() {
super.autoClose();
return this;
}
/**
* Closes results set or whole query.
*/
protected void close(ResultSetMapper rsm, boolean closeQuery) {
if (closeQuery == true) {
close();
} else {
closeResultSet(rsm.getResultSet());
}
}
}