/*
* This software is distributed under the terms of the FSF
* Gnu Lesser General Public License (see lgpl.txt).
*
* This program is distributed WITHOUT ANY WARRANTY. See the
* GNU General Public License for more details.
*/
package com.scooterframework.orm.activerecord;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import com.scooterframework.common.exception.ObjectCreationException;
import com.scooterframework.common.exception.RequiredDataMissingException;
import com.scooterframework.common.util.Converters;
import com.scooterframework.common.util.StringUtil;
import com.scooterframework.common.util.Util;
import com.scooterframework.orm.sqldataexpress.config.DatabaseConfig;
import com.scooterframework.orm.sqldataexpress.exception.BaseSQLException;
import com.scooterframework.orm.sqldataexpress.object.ColumnInfo;
import com.scooterframework.orm.sqldataexpress.object.OmniDTO;
import com.scooterframework.orm.sqldataexpress.object.RowData;
import com.scooterframework.orm.sqldataexpress.object.RowInfo;
import com.scooterframework.orm.sqldataexpress.object.TableData;
import com.scooterframework.orm.sqldataexpress.processor.DataProcessor;
import com.scooterframework.orm.sqldataexpress.processor.DataProcessorTypes;
import com.scooterframework.orm.sqldataexpress.service.SqlService;
import com.scooterframework.orm.sqldataexpress.service.SqlServiceClient;
import com.scooterframework.orm.sqldataexpress.service.SqlServiceConfig;
import com.scooterframework.orm.sqldataexpress.util.SqlConstants;
/**
* <p>
* TableGateway class implements Table Data Gateway pattern. TableGateway
* handles access to all records of a table or view for a domain model: selects,
* updates, deletes.
* </p>
*
* <p>
* There is no callback involved in methods in this class. To enable callbacks
* when deleting or updating a set of records, you can first retrieve them and
* then use the record instance's delete() or update() method which has
* callbacks.
* </p>
*
* @author (Fei) John Chen
*/
public class TableGateway {
private Class<? extends ActiveRecord> clazz;
private ActiveRecord home;
private ModelCacheClient modelCacheClient;
// /**
// * Constructs an instance of TableGateway.
// *
// * @param modelClazz a domain model class type
// */
// TableGateway(Class<? extends ActiveRecord> modelClazz) {
// this.clazz = modelClazz;
// this.home = ActiveRecordUtil.getHomeInstance(modelClazz);
// }
/**
* Constructs an instance of TableGateway.
*
* @param modelHome
* a domain model home instance
*/
TableGateway(ActiveRecord modelHome) {
if (modelHome == null)
throw new IllegalArgumentException("modelHome is null.");
if (!modelHome.isHomeInstance())
throw new IllegalArgumentException("modelHome must be a home instance.");
this.clazz = modelHome.getClass();
this.home = modelHome;
modelCacheClient = new ModelCacheClient(modelHome);
}
/**
* Returns the underlining home instance of this gateway.
*/
public ActiveRecord getHomeInstance() {
return home;
}
/**
* Returns the underlining model class type of this gateway.
*/
public Class<? extends ActiveRecord> getModelClass() {
return clazz;
}
/**
* Returns the ModelCacheClient instance
*/
public ModelCacheClient getModelCacheClient() {
return modelCacheClient;
}
/**
*
* QueryBuilder related
*
*/
/**
* <p>
* Setup where clause.
* </p>
*
* @param conditionsSQL
* a valid SQL query where clause string
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder where(String conditionsSQL) {
return (new QueryBuilder(this)).where(conditionsSQL);
}
/**
* <p>
* Setup where clause.
* </p>
*
* @param conditionsSQL
* a valid SQL query where clause string
* @param conditionsSQLData
* an array of data for the <tt>conditionsSQL</tt> string
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder where(String conditionsSQL,
Map<String, Object> conditionsSQLData) {
return (new QueryBuilder(this)).where(conditionsSQL, conditionsSQLData);
}
/**
* <p>
* Setup where clause.
* </p>
*
* @param conditionsSQL
* a valid SQL query where clause string
* @param conditionsSQLData
* an array of data for the <tt>conditionsSQL</tt> string
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder where(String conditionsSQL, Object[] conditionsSQLData) {
return (new QueryBuilder(this)).where(conditionsSQL, conditionsSQLData);
}
/**
* <p>
* Setup associated models for eager loading.
* </p>
*
* @param includes
* a string of associated models
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder includes(String includes) {
return (new QueryBuilder(this)).includes(includes);
}
/**
* <p>
* Setup associated models for eager loading.
* </p>
*
* @param includes
* a string of associated models
* @param joinType
* type of join
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder includes(String includes, String joinType) {
return (new QueryBuilder(this)).includes(includes, joinType);
}
/**
* <p>
* Setup associated models for eager loading.
* </p>
*
* <p>
* If <tt>strict</tt> is true, then child records can only be accessed
* through their parent.
* </p>
*
* @param includes
* a string of associated models
* @param strict
* true if strict
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder includes(String includes, boolean strict) {
return (new QueryBuilder(this)).includes(includes, strict);
}
/**
* <p>
* Setup group-by clause.
* </p>
*
* @param groupBy
* a valid SQL query group-by clause string
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder groupBy(String groupBy) {
return (new QueryBuilder(this)).groupBy(groupBy);
}
/**
* <p>
* Setup having clause.
* </p>
*
* @param having
* a valid SQL query having clause string
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder having(String having) {
return (new QueryBuilder(this)).having(having);
}
/**
* <p>
* Setup group-by clause.
* </p>
*
* @param orderBy
* a valid SQL query order-by clause string
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder orderBy(String orderBy) {
return (new QueryBuilder(this)).orderBy(orderBy);
}
/**
* <p>
* Setup limit for number of records per retrieval.
* </p>
*
* @param limit
* number of records for each retrieval
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder limit(int limit) {
return (new QueryBuilder(this)).limit(limit);
}
/**
* <p>
* Setup number of records to skip.
* </p>
*
* @param offset
* number of records to skip
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder offset(int offset) {
return (new QueryBuilder(this)).offset(offset);
}
/**
* <p>
* Setup current page number. All records in previous pages are skipped.
* </p>
*
* @param page
* current page number
* @return current <tt>QueryBuilder</tt> instance
*/
public QueryBuilder page(int page) {
return (new QueryBuilder(this)).page(page);
}
/**
*
* FIND related
*
*/
/**
* Finds the record with the given id, assuming ID is the primary key
* column.
*
* If there is no column name like "ID", an exception will be thrown.
*
* @param id
* the id of the record
* @return the ActiveRecord associated with the <tt>id</tt>
*/
public ActiveRecord findById(long id) {
return findById(Long.valueOf(id));
}
/**
* Finds the record with the given id, assuming ID is the primary key
* column.
*
* If there is no column name like "ID", an exception will be thrown.
*
* @param id
* the id of the record
* @return the ActiveRecord associated with the <tt>id</tt>
*/
public ActiveRecord findById(Object id) {
if (!home.getRowInfo().isValidColumnName("ID")) {
throw new IllegalArgumentException("There is no column name as ID");
}
ActiveRecord ar = null;
Map<String, Object> inputs = new HashMap<String, Object>();
inputs.put("1", id);
inputs = addMoreProperties(inputs, null);
String findSQL = "SELECT * FROM " + home.getTableName() + " WHERE id = ?";
Object cacheKey = null;
if (modelCacheClient.useCache("findById")) {
cacheKey = modelCacheClient.getCacheKey("findById", findSQL, inputs);
ar = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (ar != null) return ar;
}
try {
OmniDTO returnTO = getSqlService().execute(inputs,
DataProcessorTypes.DIRECT_SQL_STATEMENT_PROCESSOR, findSQL);
RowData tmpRd = returnTO.getTableData(findSQL).getRow(0);
if (tmpRd != null) {
ar = (ActiveRecord) createNewInstance();
ar.populateDataFromDatabase(tmpRd);
if (modelCacheClient.useCache("findById")) {
modelCacheClient.getCache().put(cacheKey, ar);
}
}
} catch (Exception ex) {
throw new BaseSQLException(ex);
}
return ar;
}
/**
* Finds the record with the given <tt>restfulId</tt>.
*
* See
* {@link com.scooterframework.orm.activerecord.ActiveRecord#getRestfulId()}
* for definition of RESTfulId.
*
* If there is no primary key, a null record is returned.
*
* @param restfulId
* the RESTful id of the record
* @return the ActiveRecord associated with the <tt>restfulId</tt>
*/
public ActiveRecord findByRESTfulId(String restfulId) {
Map<String, Object> pkMap = convertToPrimaryKeyDataMap(restfulId);
if (pkMap == null) return null;
ActiveRecord record = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findByRESTfulId")) {
cacheKey = modelCacheClient.getCacheKey("findByRESTfulId", restfulId);
record = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (record != null) return record;
}
record = findFirst(pkMap);
if (record != null) {
if (modelCacheClient.useCache("findByRESTfulId")) {
modelCacheClient.getCache().put(cacheKey, record);
}
}
return record;
}
/**
* Finds the record with the given <tt>pkString</tt>. This method is the
* same as <tt>findByRESTfulId(String restfulId)</tt> method.
*
* See
* {@link com.scooterframework.orm.activerecord.ActiveRecord#getRestfulId()}
* for definition of RESTfulId which is the same as the primary key string.
*
* If there is no primary key, a null record is returned.
*
* @param pkString
* primary key string
* @return the ActiveRecord associated with the <tt>restfulId</tt>
*/
public ActiveRecord findByPK(String pkString) {
ActiveRecord record = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findByPK")) {
cacheKey = modelCacheClient.getCacheKey("findByPK", pkString);
record = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (record != null) return record;
}
record = findByRESTfulId(pkString);
if (record != null) {
if (modelCacheClient.useCache("findByPK")) {
modelCacheClient.getCache().put(cacheKey, record);
}
}
return record;
}
/**
* <p>
* Finds all the records that satisfy the SQL query.
* </p>
*
* @param sql
* a valid SQL query string
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAllBySQL(String sql) {
return findAllBySQL(sql, null);
}
/**
* <p>
* Finds all the records that satisfy the SQL query.
* </p>
*
* @param sql
* a valid SQL query string
* @param inputs
* a map of name and value pairs
* @return a list of ActiveRecord objects
*/
@SuppressWarnings("unchecked")
public List<ActiveRecord> findAllBySQL(String sql, Map<String, Object> inputs) {
List<ActiveRecord> list = null;
inputs = addMoreProperties(inputs, null);
Object cacheKey = null;
if (modelCacheClient.useCache("findAllBySQL")) {
cacheKey = modelCacheClient.getCacheKey("findAllBySQL", sql, inputs);
list = (List<ActiveRecord>) modelCacheClient.getCache().get(cacheKey);
if (list != null) return list;
}
try {
OmniDTO returnTO = getSqlService().execute(inputs,
DataProcessorTypes.DIRECT_SQL_STATEMENT_PROCESSOR, sql);
if (returnTO != null) {
TableData rt = returnTO.getTableData(sql);
if (rt != null) {
int records = rt.getTableSize();
if (records > 0) {
list = new ArrayList<ActiveRecord>();
for (int i = 0; i < records; i++) {
ActiveRecord newRecord = (ActiveRecord) createNewInstance();
newRecord.populateDataFromDatabase(rt.getRow(i));
list.add(newRecord);
}
if (modelCacheClient.useCache("findAllBySQL")) {
modelCacheClient.getCache().put(cacheKey, list);
}
}
}
}
} catch (Exception ex) {
throw new BaseSQLException(ex);
}
return (list != null) ? list : (new ArrayList<ActiveRecord>());
}
/**
* <p>
* Finds all the records that satisfy the SQL corresponding to the SQL key.
* </p>
*
* @param sqlKey
* a key to a SQL string defined in <tt>sql.properties</tt> file
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAllBySQLKey(String sqlKey) {
return findAllBySQLKey(sqlKey, null);
}
/**
* <p>
* Finds all the records that satisfy the SQL corresponding to the SQL key.
* </p>
*
* @param sqlKey
* a key to a SQL string defined in <tt>sql.properties</tt> file
* @param inputs
* a map of name and value pairs
* @return a list of ActiveRecord objects
*/
@SuppressWarnings("unchecked")
public List<ActiveRecord> findAllBySQLKey(String sqlKey,
Map<String, Object> inputs) {
List<ActiveRecord> list = null;
inputs = addMoreProperties(inputs, null);
Object cacheKey = null;
if (modelCacheClient.useCache("findAllBySQLKey")) {
cacheKey = modelCacheClient.getCacheKey("findAllBySQLKey", sqlKey, inputs);
list = (List<ActiveRecord>) modelCacheClient.getCache().get(cacheKey);
if (list != null) return list;
}
try {
OmniDTO returnTO = getSqlService().execute(inputs,
DataProcessorTypes.NAMED_SQL_STATEMENT_PROCESSOR, sqlKey);
if (returnTO != null) {
TableData rt = returnTO.getTableData(sqlKey);
if (rt != null) {
int records = rt.getTableSize();
if (records > 0) {
list = new ArrayList<ActiveRecord>();
for (int i = 0; i < records; i++) {
ActiveRecord newRecord = (ActiveRecord) createNewInstance();
newRecord.populateDataFromDatabase(rt.getRow(i));
list.add(newRecord);
}
if (modelCacheClient.useCache("findAllBySQLKey")) {
modelCacheClient.getCache().put(cacheKey, list);
}
}
}
}
} catch (Exception ex) {
throw new BaseSQLException(ex);
}
return (list != null) ? list : (new ArrayList<ActiveRecord>());
}
/**
* <p>
* Finds the first record that satisfy the conditions.
* </p>
*
* <p>
* This is a dynamic finder method. See
* {@link com.scooterframework.orm.activerecord.ActiveRecord ActiveRecord}
* class for dynamic finder examples.
* </p>
*
* @param columns
* a string of column names linked by "_and_".
* @param values
* an Object[] array
* @return ActiveRecord
*/
public ActiveRecord findFirstBy(String columns, Object[] values) {
ActiveRecord record = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findFirstBy")) {
cacheKey = modelCacheClient.getCacheKey("findFirstBy", columns, values);
record = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (record != null) return record;
}
List<ActiveRecord> all = findAllBy(columns, values);
if (all != null && all.size() > 0) {
record = (ActiveRecord) all.get(0);
if (record != null) {
if (modelCacheClient.useCache("findFirstBy")) {
modelCacheClient.getCache().put(cacheKey, record);
}
}
}
return record;
}
/**
* <p>
* Finds the last record that satisfy the conditions.
* </p>
*
* <p>
* This is a dynamic finder method. See
* {@link com.scooterframework.orm.activerecord.ActiveRecord ActiveRecord}
* class for dynamic finder examples.
* </p>
*
* @param columns
* a string of column names linked by "_and_".
* @param values
* an Object[] array
* @return ActiveRecord
*/
public ActiveRecord findLastBy(String columns, Object[] values) {
ActiveRecord record = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findLastBy")) {
cacheKey = modelCacheClient.getCacheKey("findLastBy", columns, values);
record = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (record != null) return record;
}
List<ActiveRecord> all = findAllBy(columns, values);
if (all != null && all.size() > 0) {
record = (ActiveRecord) all.get(all.size() - 1);
if (record != null) {
if (modelCacheClient.useCache("findLastBy")) {
modelCacheClient.getCache().put(cacheKey, record);
}
}
}
return record;
}
/**
* <p>
* Finds all the records that satisfy the conditions.
* </p>
*
* <p>
* This is a dynamic finder method. See
* {@link com.scooterframework.orm.activerecord.ActiveRecord ActiveRecord}
* class for dynamic finder examples.
* </p>
*
* @param columns
* a string of column names linked by "_and_".
* @param values
* an Object[] array
* @return List of ActiveRecord objects
*/
public List<ActiveRecord> findAllBy(String columns, Object[] values) {
return findAllBy(columns, values, (Map<String, String>) null);
}
/**
* <p>
* Finds all the records that satisfy the conditions.
* </p>
*
* <p>
* This is a dynamic finder method. See
* {@link com.scooterframework.orm.activerecord.ActiveRecord ActiveRecord}
* class for dynamic finder examples.
* </p>
*
* @param columns
* a string of column names linked by "_and_".
* @param values
* an Object[] array
* @param options
* a map of options
* @return List of ActiveRecord objects
*/
@SuppressWarnings("unchecked")
public List<ActiveRecord> findAllBy(String columns, Object[] values,
Map<String, String> options) {
List<String> names = StringUtil.splitString(columns, "_and_");
if (names == null || values.length != names.size())
throw new IllegalArgumentException(
"Number of input values does not match number of columns.");
int size = values.length;
Map<String, Object> map = new HashMap<String, Object>(size);
for (int i = 0; i < size; i++) {
map.put(names.get(i), values[i]);
}
List<ActiveRecord> list = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findAllBy")) {
cacheKey = modelCacheClient.getCacheKey("findAllBy", map, options);
list = (List<ActiveRecord>) modelCacheClient.getCache().get(cacheKey);
if (list != null) return list;
}
list = findAll(map, options);
if (modelCacheClient.useCache("findAllBy")) {
if (list != null && list.size() > 0)
modelCacheClient.getCache().put(cacheKey, list);
}
return list;
}
/**
* <p>
* Finds all the records that satisfy the conditions.
* </p>
*
* <p>
* This is a dynamic finder method. See
* {@link com.scooterframework.orm.activerecord.ActiveRecord ActiveRecord}
* class for dynamic finder examples.
* </p>
*
* @param columns
* a string of column names linked by "_and_".
* @param values
* an Object[] array
* @param options
* a string of options
* @return List of ActiveRecord objects
*/
public List<ActiveRecord> findAllBy(String columns, Object[] values, String options) {
return findAllBy(columns, values, Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds all the records of a table.
* </p>
*
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll() {
return findAll((String) null);
}
/**
* <p>
* Finds all the records that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll(Map<String, Object> conditions) {
return findAll(conditions, (Map<String, String>) null);
}
/**
* <p>
* Finds all the records that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @param options
* a map of options
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll(Map<String, Object> conditions,
Map<String, String> options) {
if (options == null) options = new HashMap<String, String>();
List<ActiveRecord> list = null;
if (options.size() > 0
&& (options.containsKey(ActiveRecordConstants.key_include) ||
options.containsKey(ActiveRecordConstants.key_strict_include))) {
list = internal_findAll_include(conditions, options);
} else {
list = internal_findAll(conditions, options);
}
return (list != null) ? list : (new ArrayList<ActiveRecord>());
}
/**
* <p>
* Finds all the records that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @param options
* a string of options
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll(Map<String, Object> conditions, String options) {
return findAll(conditions, Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds all the records that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll(String conditionsSQL) {
return findAll(conditionsSQL, (Map<String, Object>) null);
}
/**
* <p>
* Finds all the records that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param options
* a string of options.
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll(String conditionsSQL, String options) {
return findAll(conditionsSQL, (Map<String, Object>) null,
Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds all the records that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll(String conditionsSQL,
Map<String, Object> conditionsSQLData) {
return findAll(conditionsSQL, conditionsSQLData, (Map<String, String>) null);
}
/**
* <p>
* Finds all the records that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @param options
* a map of options.
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll(String conditionsSQL,
Map<String, Object> conditionsSQLData, Map<String, String> options) {
if (options == null)
options = new HashMap<String, String>();
List<ActiveRecord> list = null;
if (options.size() > 0
&& (options.containsKey(ActiveRecordConstants.key_include) ||
options.containsKey(ActiveRecordConstants.key_strict_include))) {
list = internal_findAll_include(conditionsSQL, conditionsSQLData, options);
} else {
list = internal_findAll(conditionsSQL, conditionsSQLData, options);
}
return (list != null) ? list : (new ArrayList<ActiveRecord>());
}
/**
* <p>
* Finds all the records that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @param options
* a string of options.
* @return a list of ActiveRecord objects
*/
public List<ActiveRecord> findAll(String conditionsSQL,
Map<String, Object> conditionsSQLData, String options) {
return findAll(conditionsSQL, conditionsSQLData,
Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds the first record of a table.
* </p>
*
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst() {
return findFirst((String) null);
}
/**
* <p>
* Finds the first record that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst(Map<String, Object> conditions) {
return findFirst(conditions, (Map<String, String>) null);
}
/**
* <p>
* Finds the first record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @param options
* a map of options
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst(Map<String, Object> conditions,
Map<String, String> options) {
if (options == null) options = new HashMap<String, String>();
options.put(DataProcessor.input_key_records_offset, "0");
options.put(DataProcessor.input_key_records_limit, "1");
options.put(DataProcessor.input_key_records_fixed, "true");
ActiveRecord record = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findFirst")) {
cacheKey = modelCacheClient.getCacheKey("findFirst", conditions, options);
record = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (record != null) return record;
}
List<ActiveRecord> list = findAll(conditions, options);
record = (list != null && list.size() > 0) ? (list.get(0)) : null;
if (record != null) {
if (modelCacheClient.useCache("findFirst")) {
modelCacheClient.getCache().put(cacheKey, record);
}
}
return record;
}
/**
* <p>
* Finds the first record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @param options
* a string of options
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst(Map<String, Object> conditions, String options) {
return findFirst(conditions, Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds the first record that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst(String conditionsSQL) {
return findFirst(conditionsSQL, (Map<String, Object>) null);
}
/**
* <p>
* Finds the first record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param options
* a string of options.
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst(String conditionsSQL, String options) {
return findFirst(conditionsSQL, (Map<String, Object>) null,
Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds the first record that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst(String conditionsSQL,
Map<String, Object> conditionsSQLData) {
return findFirst(conditionsSQL, conditionsSQLData, (Map<String, String>) null);
}
/**
* <p>
* Finds the first record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @param options
* a map of options.
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst(String conditionsSQL,
Map<String, Object> conditionsSQLData, Map<String, String> options) {
if (options == null) options = new HashMap<String, String>();
if (!options.containsKey(ActiveRecordConstants.key_include) &&
!options.containsKey(ActiveRecordConstants.key_strict_include)) {
options.put(DataProcessor.input_key_records_offset, "0");
options.put(DataProcessor.input_key_records_limit, "1");
options.put(DataProcessor.input_key_records_fixed, "true");
}
ActiveRecord record = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findFirst")) {
cacheKey = modelCacheClient.getCacheKey("findFirst", conditionsSQL, conditionsSQLData, options);
record = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (record != null) return record;
}
List<ActiveRecord> list = findAll(conditionsSQL, conditionsSQLData, options);
record = (list != null && list.size() > 0) ? (list.get(0)) : null;
if (record != null) {
if (modelCacheClient.useCache("findFirst")) {
modelCacheClient.getCache().put(cacheKey, record);
}
}
return record;
}
/**
* <p>
* Finds the first record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @param options
* a string of options.
* @return the first ActiveRecord found
*/
public ActiveRecord findFirst(String conditionsSQL,
Map<String, Object> conditionsSQLData, String options) {
return findFirst(conditionsSQL, conditionsSQLData,
Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds the last record of a table.
* </p>
*
* @return the last ActiveRecord found
*/
public ActiveRecord findLast() {
return findLast((String) null);
}
/**
* <p>
* Finds the last record that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @return the last ActiveRecord found
*/
public ActiveRecord findLast(Map<String, Object> conditions) {
return findLast(conditions, (Map<String, String>) null);
}
/**
* <p>
* Finds the last record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @param options
* a map of options
* @return the last ActiveRecord found
*/
public ActiveRecord findLast(Map<String, Object> conditions, Map<String, String> options) {
ActiveRecord record = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findLast")) {
cacheKey = modelCacheClient.getCacheKey("findLast", conditions, options);
record = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (record != null) return record;
}
List<ActiveRecord> list = findAll(conditions, options);
int size = list.size();
record = (size > 0) ? ((ActiveRecord) list.get(size - 1)) : null;
if (record != null) {
if (modelCacheClient.useCache("findLast")) {
modelCacheClient.getCache().put(cacheKey, record);
}
}
return record;
}
/**
* <p>
* Finds the last record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @param options
* a string of options
* @return the last ActiveRecord found
*/
public ActiveRecord findLast(Map<String, Object> conditions, String options) {
return findLast(conditions, Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds the last record that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @return the last ActiveRecord found
*/
public ActiveRecord findLast(String conditionsSQL) {
return findLast(conditionsSQL, (Map<String, Object>) null);
}
/**
* <p>
* Finds the last record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and <tt>options</tt>
* examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param options
* a string of options.
* @return the last ActiveRecord found
*/
public ActiveRecord findLast(String conditionsSQL, String options) {
return findLast(conditionsSQL, (Map<String, Object>) null,
Converters.convertSqlOptionStringToMap(options));
}
/**
* <p>
* Finds the last record that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @return the last ActiveRecord found
*/
public ActiveRecord findLast(String conditionsSQL,
Map<String, Object> conditionsSQLData) {
return findLast(conditionsSQL, conditionsSQLData, (Map<String, String>) null);
}
/**
* <p>
* Finds the last record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @param options
* a map of options.
* @return the last ActiveRecord found
*/
public ActiveRecord findLast(String conditionsSQL,
Map<String, Object> conditionsSQLData, Map<String, String> options) {
ActiveRecord record = null;
Object cacheKey = null;
if (modelCacheClient.useCache("findLast")) {
cacheKey = modelCacheClient.getCacheKey("findLast", conditionsSQL, conditionsSQLData, options);
record = (ActiveRecord) modelCacheClient.getCache().get(cacheKey);
if (record != null) return record;
}
List<ActiveRecord> list = findAll(conditionsSQL, conditionsSQLData, options);
int size = list.size();
record = (size > 0) ? ((ActiveRecord) list.get(size - 1)) : null;
if (record != null) {
if (modelCacheClient.useCache("findLast")) {
modelCacheClient.getCache().put(cacheKey, record);
}
}
return record;
}
/**
* <p>
* Finds the last record that satisfy the conditions and options.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @param options
* a string of options.
* @return the last ActiveRecord found
*/
public ActiveRecord findLast(String conditionsSQL,
Map<String, Object> conditionsSQLData, String options) {
return findLast(conditionsSQL, conditionsSQLData,
Converters.convertSqlOptionStringToMap(options));
}
/**
* Finds a list of records that satisfy the conditions and options.
*/
@SuppressWarnings("unchecked")
private List<ActiveRecord> internal_findAll(Map<String, Object> conditions,
Map<String, String> options) {
List<ActiveRecord> list = null;
try {
Map<String, Object> inputs = constructFindSQL(conditions, options);
String findSQL = (String) inputs.get(ActiveRecordConstants.key_finder_sql);
int offset = getOffset(options);
int limit = getLimit(options);
inputs = addMoreProperties(inputs, options);
Object cacheKey = null;
if (modelCacheClient.useCache("findAll")) {
cacheKey = modelCacheClient.getCacheKey("findAll", findSQL, inputs, limit, offset);
list = (List<ActiveRecord>) modelCacheClient.getCache().get(cacheKey);
if (list != null) return list;
}
TableData td = getSqlService().retrieveRows(inputs,
DataProcessorTypes.DIRECT_SQL_STATEMENT_PROCESSOR, findSQL,
limit, offset);
if (td != null) {
int records = td.getTableSize();
if (records > 0) {
list = new ArrayList<ActiveRecord>();
for (int i = 0; i < records; i++) {
ActiveRecord newRecord = (ActiveRecord) createNewInstance();
newRecord.populateDataFromDatabase(td.getRow(i));
list.add(newRecord);
}
if (modelCacheClient.useCache("findAll")) {
modelCacheClient.getCache().put(cacheKey, list);
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
throw new BaseSQLException(ex);
}
return (list != null) ? list : (new ArrayList<ActiveRecord>());
}
@SuppressWarnings("unchecked")
private List<ActiveRecord> internal_findAll(String conditionsSQL,
Map<String, Object> conditionsSQLData, Map<String, String> options) {
List<ActiveRecord> list = null;
try {
Map<String, Object> inputs =
constructFindSQL(conditionsSQL, conditionsSQLData, options);
String findSQL = (String) inputs.get(ActiveRecordConstants.key_finder_sql);
int offset = getOffset(options);
int limit = getLimit(options);
inputs = addMoreProperties(inputs, options);
Object cacheKey = null;
if (modelCacheClient.useCache("findAll")) {
cacheKey = modelCacheClient.getCacheKey("findAll", findSQL, inputs, limit, offset);
list = (List<ActiveRecord>) modelCacheClient.getCache().get(cacheKey);
if (list != null) return list;
}
TableData td = getSqlService().retrieveRows(inputs,
DataProcessorTypes.DIRECT_SQL_STATEMENT_PROCESSOR, findSQL,
limit, offset);
if (td != null) {
int records = td.getTableSize();
if (records > 0) {
list = new ArrayList<ActiveRecord>();
for (int i = 0; i < records; i++) {
ActiveRecord newRecord = (ActiveRecord) createNewInstance();
newRecord.populateDataFromDatabase(td.getRow(i));
list.add(newRecord);
}
if (modelCacheClient.useCache("findAll")) {
modelCacheClient.getCache().put(cacheKey, list);
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
throw new BaseSQLException(ex);
}
return (list != null) ? list : (new ArrayList<ActiveRecord>());
}
private int getOffset(Map<String, String> options) {
int offset = 0;
if (options.containsKey(ActiveRecordConstants.key_offset)) {
offset = Util.getIntValue(options,
ActiveRecordConstants.key_offset, 0);
} else if (options.containsKey(DataProcessor.input_key_records_offset)) {
offset = Util.getIntValue(options,
DataProcessor.input_key_records_offset, 0);
} else if (options.containsKey(ActiveRecordConstants.key_page)) {
int current_page = Util.getIntValueForKey(options,
ActiveRecordConstants.key_page);
int limit = DataProcessor.DEFAULT_PAGINATION_LIMIT;
if (options.containsKey(ActiveRecordConstants.key_limit)) {
limit = Util.getIntValueForKey(options,
ActiveRecordConstants.key_limit);
}
offset = (current_page - 1 ) * limit;
}
return offset;
}
private int getLimit(Map<String, String> options) {
int limit = DataProcessor.NO_ROW_LIMIT;
if (options.containsKey(ActiveRecordConstants.key_limit)) {
limit = Util.getIntValue(options, ActiveRecordConstants.key_limit,
DataProcessor.NO_ROW_LIMIT);
} else if (options.containsKey(DataProcessor.input_key_records_limit)) {
limit = Util.getIntValue(options,
DataProcessor.input_key_records_limit,
DataProcessor.NO_ROW_LIMIT);
} else if (options.containsKey(ActiveRecordConstants.key_page)) {
limit = Util.getIntValue(options, ActiveRecordConstants.key_limit,
DataProcessor.DEFAULT_PAGINATION_LIMIT);
}
return limit;
}
Object createNewInstance() throws Exception {
Object o = null;
try {
o = home.getClass().newInstance();
} catch (Exception ex) {
throw new ObjectCreationException(home.getClass().getName(), ex);
}
return o;
}
Map<String, Object> constructFindSQL(Map<String, Object> conditions,
Map<String, String> options) {
Map<String, Object> inputsAndSql = new HashMap<String, Object>();
String findSQL = getFinderSql(options);
boolean finderHasWhere = checkFinderSqlWhere(findSQL);
String conditionSql = null;
if (options != null && options.size() > 0) {
conditionSql = options.get(ActiveRecordConstants.key_conditions_sql);
}
// construct where clause
Map<String, Object> inputs = new HashMap<String, Object>();
String whereClause = "";
boolean useWhere = false;
if (conditions != null && conditions.size() > 0) {
whereClause = "";
int position = 1;
for (Map.Entry<String, Object> entry : conditions.entrySet()) {
String columnName = entry.getKey();
// skip system keys
if (columnName == null
|| columnName.startsWith("_")
|| columnName.toUpperCase().startsWith(
DataProcessor.framework_input_key_prefix
.toUpperCase())
|| !home.isColumnField(columnName))
continue;
Object conditionData = entry.getValue();
whereClause += columnName + " = ? AND ";
// inputs.put(columnName, conditionData);
inputs.put(position + "", conditionData);
useWhere = true;
position = position + 1;
}
if (whereClause.endsWith("AND ")) {
int lastAnd = whereClause.lastIndexOf("AND ");
whereClause = whereClause.substring(0, lastAnd);
}
inputsAndSql.putAll(conditions);
}
if (finderHasWhere) {
if (useWhere) {
findSQL += " AND " + whereClause;
}
if (conditionSql != null && !"".equals(conditionSql)) {
findSQL += " AND (" + conditionSql + ")";
}
} else {
if (useWhere) {
findSQL += " WHERE " + whereClause;
if (conditionSql != null && !"".equals(conditionSql)) {
findSQL += " AND (" + conditionSql + ")";
}
} else {
if (conditionSql != null && !"".equals(conditionSql)) {
findSQL += " WHERE " + conditionSql;
}
}
}
findSQL += QueryHelper.getAllSelectQueryClauses(options);
if (options != null)
inputsAndSql.putAll(options);
inputsAndSql.put(ActiveRecordConstants.key_finder_sql, findSQL);
inputsAndSql.putAll(inputs);
return inputsAndSql;
}
// This method is mostly used by internal and JdbcPageListSource
public Map<String, Object> constructFindSQL(String conditionsSQL,
Map<String, Object> conditionsSQLData, Map<String, String> options) {
Map<String, Object> inputsAndSql = new HashMap<String, Object>();
String findSQL = getFinderSql(options);
boolean finderHasWhere = checkFinderSqlWhere(findSQL);
if (finderHasWhere) {
if (conditionsSQL != null && !"".equals(conditionsSQL.trim())) {
findSQL += " AND (" + conditionsSQL + ")";
if (conditionsSQLData != null) {
inputsAndSql.putAll(conditionsSQLData);
}
}
} else {
if (conditionsSQL != null && !"".equals(conditionsSQL.trim())) {
findSQL += " WHERE " + conditionsSQL;
if (conditionsSQLData != null) {
inputsAndSql.putAll(conditionsSQLData);
}
}
}
findSQL += QueryHelper.getAllSelectQueryClauses(options);
if (options != null)
inputsAndSql.putAll(options);
inputsAndSql.put(ActiveRecordConstants.key_finder_sql, findSQL);
return inputsAndSql;
}
public String getFinderSql(Map<String, String> options) {
String finderSQL = "";
if (options != null
&& options.containsKey(ActiveRecordConstants.key_finder_sql)) {
finderSQL = options.get(ActiveRecordConstants.key_finder_sql);
return finderSQL;
}
// construct finger SQL query
finderSQL = "SELECT ";
if (options != null && options.size() > 0) {
String unique = options.get(ActiveRecordConstants.key_unique);
if ("true".equalsIgnoreCase(unique)) {
finderSQL = "SELECT DISTINCT ";
}
}
if (options == null) options = new HashMap<String, String>();
String table = home.getTableName();
options.put(SqlConstants.key_table, table);
boolean useColumns = false;
boolean exColumns = false;
if (options != null && options.size() > 0) {
String columns = options.get(ActiveRecordConstants.key_columns);
String excolumns = options
.get(ActiveRecordConstants.key_ex_columns);
if (columns != null) {
useColumns = true;
}
if (excolumns != null) {
exColumns = true;
}
}
if (!useColumns && !exColumns) {
finderSQL += table + ".*";
} else if (useColumns) {
String columnsStr = options.get(ActiveRecordConstants.key_columns);
List<String> columns = Converters
.convertStringToUniqueList(columnsStr.toUpperCase());
Iterator<String> it = columns.iterator();
while (it.hasNext()) {
finderSQL += table + "." + it.next() + ", ";
}
finderSQL = StringUtil.removeLastToken(finderSQL, ", ");
} else if (exColumns) {
String excolumnsStr = options
.get(ActiveRecordConstants.key_ex_columns);
List<String> excolumns = Converters
.convertStringToUniqueList(excolumnsStr.toUpperCase());
String[] columns = home.getRowInfo().getColumnNames();
int length = columns.length;
for (int i = 0; i < length; i++) {
String column = columns[i];
if (excolumns.contains(column))
continue;
finderSQL += table + "." + column + ", ";
}
finderSQL = StringUtil.removeLastToken(finderSQL, ", ");
}
finderSQL += " FROM " + table;
return finderSQL;
}
static boolean checkFinderSqlWhere(String finderSQL) {
boolean status = false;
finderSQL = finderSQL.toUpperCase();
if (finderSQL.indexOf("WHERE") != -1) {
// make sure the "where" is valid
boolean foundFrom = false;
int countP = 0;
StringTokenizer st = new StringTokenizer(finderSQL);
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (!foundFrom) {
if ("FROM".equals(token))
foundFrom = true;
} else {
if (token.startsWith("("))
countP = countP + 1;
else if (token.startsWith(")"))
countP = countP - 1;
else if ("WHERE".equals(token) && countP == 0)
status = true;
}
}
}
return status;
}
/**
* Finds a list of records that satisfy the conditions and options.
*/
List<ActiveRecord> internal_findAll_include(Map<String, Object> conditions,
Map<String, String> options) {
IncludeHelper sqlHelper = new IncludeHelper(getModelClass(),
conditions, options);
return internal_findAll_include_fetch(sqlHelper, options);
}
/**
* Finds a list of records that satisfy the conditions and options.
*/
List<ActiveRecord> internal_findAll_include(String conditionsSQL,
Map<String, Object> conditionsSQLData, Map<String, String> options) {
IncludeHelper sqlHelper = new IncludeHelper(getModelClass(),
conditionsSQL, conditionsSQLData, options);
return internal_findAll_include_fetch(sqlHelper, options);
}
/**
* Finds a list of records that satisfy the conditions and options in a
* has-many-through relation.
*/
List<ActiveRecord> internal_findAll_include_hmt(
Map<String, Object> conditions, Map<String, String> options,
String innerSQL, String midCMapping,
Map<String, Object> midCMapData, String conditionsSQL) {
IncludeHelper sqlHelper = new IncludeHelper(getModelClass(),
conditions, options, innerSQL, midCMapping, midCMapData,
conditionsSQL);
return internal_findAll_include_fetch(sqlHelper, options);
}
@SuppressWarnings("unchecked")
private List<ActiveRecord> internal_findAll_include_fetch(
IncludeHelper sqlHelper, Map<String, String> options) {
List<ActiveRecord> list = null;
try {
Map<String, Object> inputs = sqlHelper.getConstructedSqlQuery();
String findSQL = (String) inputs.get(ActiveRecordConstants.key_finder_sql);
int offset = getOffset(options);
int limit = getLimit(options);
inputs = addMoreProperties(inputs, options);
Object cacheKey = null;
if (modelCacheClient.useCache("findAll") && modelCacheClient.allowCacheAssociatedObjects()) {
cacheKey = modelCacheClient.getCacheKey("findAll", findSQL, inputs, limit, offset);
list = (List<ActiveRecord>) modelCacheClient.getCache().get(cacheKey);
if (list != null) return list;
}
TableData td = getSqlService().retrieveRows(inputs,
DataProcessorTypes.DIRECT_SQL_STATEMENT_PROCESSOR, findSQL,
limit, offset);
if (td != null) {
list = sqlHelper.organizeData(td);
if (modelCacheClient.useCache("findAll") && modelCacheClient.allowCacheAssociatedObjects()) {
modelCacheClient.getCache().put(cacheKey, list);
}
}
} catch (Exception ex) {
ex.printStackTrace();
throw new BaseSQLException(ex);
}
return (list != null) ? list : (new ArrayList<ActiveRecord>());
}
/**
*
* DELETE related
*
*/
/**
* Deletes the record with the given id, assuming ID is the primary key
* column.
*
* If there is no column name like "ID", an exception will be thrown.
*
* @param id
* key to the object with field name "ID"
* @return int number of records deleted
*/
public int deleteById(long id) {
return deleteById(Long.valueOf(id));
}
/**
* Deletes the record with the given id, assuming ID is the primary key
* column.
*
* If there is no column name like "ID", an exception will be thrown.
*
* @param id
* key to the object with field name "ID"
* @return int number of records deleted
*/
public int deleteById(Object id) {
if (!home.getRowInfo().isValidColumnName("ID")) {
throw new IllegalArgumentException("There is no column name as ID");
}
modelCacheClient.clearCache("deleteById");
Map<String, Object> inputs = new HashMap<String, Object>();
inputs = addMoreProperties(inputs, null);
String deleteSQL = "DELETE FROM " + home.getTableName() + " WHERE id = ?";
return SqlServiceClient.executeSQL(deleteSQL, inputs);
}
/**
* Deletes the record with the given <tt>pkString</tt>.
*
* If there is no primary key defined, this method returns <tt>0</tt>.
*
* @param pkString
* primary key string
* @return int number of records deleted
*/
public int deleteByPK(String pkString) {
Map<String, Object> pkMap = convertToPrimaryKeyDataMap(pkString);
if (pkMap == null) return 0;
modelCacheClient.clearCache("deleteByPK");
return deleteByPrimaryKeyMap(pkMap);
}
/**
* Deletes the record with the given data map containing primary keys.
*
* If not all primary key columns have data, an exception will be thrown.
*
* @return int number of records deleted
*/
public int deleteByPrimaryKeyMap(Map<String, Object> dataMap) {
if (dataMap == null || dataMap.size() == 0) return -1;
modelCacheClient.clearCache("deleteByPrimaryKeyMap");
// construct a map of primary keys
Map<String, Object> pkMap = new HashMap<String, Object>();
String[] pkNames = home.getPrimaryKeyNames();
int length = pkNames.length;
for (int i = 0; i < length; i++) {
String name = pkNames[i];
Object data = Util.decode(name, dataMap, null, true);
if (data == null) {
RequiredDataMissingException rdmEx = new RequiredDataMissingException();
rdmEx.setRequiredDataName(name);
throw rdmEx;
}
pkMap.put(name, data);
}
return deleteAll(pkMap);
}
/**
* Deletes all the records that satisfy the SQL statement.
*
* @param sql
* a key to a SQL string
* @return int number of records deleted
*/
public int deleteBySQL(String sql) {
return deleteBySQL(sql, null);
}
/**
* Deletes all the records that satisfy the SQL statement.
*
* The inputs is a map of name and value pairs related to the SQL statement.
*
* @param sql
* a key to a SQL string
* @param inputs
* a map of name and value pairs
* @return int number of records deleted
*/
public int deleteBySQL(String sql, Map<String, Object> inputs) {
modelCacheClient.clearCache("deleteBySQL");
return SqlServiceClient.executeSQL(sql, inputs);
}
/**
* Deletes all the records that satisfy the SQL specified by the
* <tt>sqlKey</tt>.
*
* @param sqlKey
* a key to a SQL string
* @return int number of records deleted
*/
public int deleteBySQLKey(String sqlKey) {
return deleteBySQLKey(sqlKey, null);
}
/**
* Deletes all the records that satisfy the SQL specified by the
* <tt>sqlKey</tt>.
*
* The inputs is a map of name and value pairs related to the SQL statement.
*
* @param sqlKey
* a key to a SQL string
* @param inputs
* a map of name and value pairs
* @return int number of records deleted
*/
public int deleteBySQLKey(String sqlKey, Map<String, Object> inputs) {
modelCacheClient.clearCache("deleteBySQLKey");
return SqlServiceClient.executeSQLByKey(sqlKey, inputs);
}
/**
* <p>
* Deletes all the records that satisfy the condition.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditions</tt> examples.
* </p>
*
* @param conditions
* a map of column name and value pairs
* @return int number of records deleted
*/
public int deleteAll(Map<String, Object> conditions) {
modelCacheClient.clearCache("deleteAll");
return internal_deleteAll(conditions);
}
/**
* <p>
* Deletes all the records that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @return int number of records deleted
*/
public int deleteAll(String conditionsSQL) {
return deleteAll(conditionsSQL, null);
}
/**
* <p>
* Deletes all the records that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @return int number of records deleted
*/
public int deleteAll(String conditionsSQL, Map<String, Object> conditionsSQLData) {
modelCacheClient.clearCache("deleteAll");
return internal_deleteAll(conditionsSQL, conditionsSQLData);
}
private int internal_deleteAll(Map<String, Object> conditions) {
int count = -1;
String deleteSQL = "DELETE FROM " + home.getTableName();
try {
Map<String, Object> inputs = new HashMap<String, Object>();
// construct where clause
if (conditions != null && conditions.size() > 0) {
String whereClause = " WHERE ";
int position = 1;
for (Map.Entry<String, Object> entry : conditions.entrySet()) {
whereClause += entry.getKey() + " = ? AND ";
inputs.put(position + "", entry.getValue());
position = position + 1;
}
if (whereClause.endsWith("AND ")) {
int lastAnd = whereClause.lastIndexOf("AND ");
whereClause = whereClause.substring(0, lastAnd);
}
deleteSQL += whereClause;
}
inputs = addMoreProperties(inputs, null);
count = SqlServiceClient.executeSQL(deleteSQL, inputs);
} catch (Exception ex) {
throw new BaseSQLException(ex);
}
return count;
}
private int internal_deleteAll(String conditionsSQL, Map<String, Object> conditionsSQLData) {
int count = -1;
String deleteSQL = "DELETE FROM " + home.getTableName();
try {
Map<String, Object> inputs = new HashMap<String, Object>();
// construct where clause
if (conditionsSQL != null && !"".equals(conditionsSQL.trim())) {
deleteSQL += " WHERE " + conditionsSQL;
if (conditionsSQLData != null) {
inputs.putAll(conditionsSQLData);
}
}
inputs = addMoreProperties(inputs, null);
count = SqlServiceClient.executeSQL(deleteSQL, inputs);
} catch (Exception ex) {
throw new BaseSQLException(ex);
}
return count;
}
/**
*
* UPDATE related
*
*/
/**
* <p>
* Updates all the records of a table.
* </p>
*
* <p>
* <tt>fieldData</tt> map is used to construct SET clause of the generated
* SQL. It consists of column name and its value pairs in the map. Primary
* key column and read-only columns are not updatable.
* </p>
*
* @param fieldData
* a map of field name and its data to be set on any records
* @return int number of records updated
*/
public int updateAll(Map<String, Object> fieldData) {
return updateAll(fieldData, null, null);
}
/**
* <p>
* Updates all the records that satisfy a set of conditions supplied.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> examples.
* </p>
*
* <p>
* <tt>fieldData</tt> map is used to construct SET clause of the generated
* SQL. It consists of column name and its value pairs in the map. Primary
* key column and read-only columns are not updatable.
* </p>
*
* @param fieldData
* a map of field name and its data to be set on any records that
* satisfy the conditions.
* @param conditionsSQL
* A SQL fragment string
* @return int number of records updated
*/
public int updateAll(Map<String, Object> fieldData, String conditionsSQL) {
return updateAll(fieldData, conditionsSQL, null);
}
/**
* <p>
* Updates all the records that satisfy the conditions.
* </p>
*
* <p>
* See {@link com.scooterframework.orm.activerecord.ActiveRecord
* ActiveRecord} class for <tt>conditionsSQL</tt> and
* <tt>conditionsSQLData</tt> examples.
* </p>
*
* <p>
* <tt>fieldData</tt> map is used to construct SET clause of the generated
* SQL. It consists of column name and its value pairs in the map. Primary
* key column and read-only columns are not updatable.
* </p>
*
* @param fieldData
* a map of field name and its data to be set on any records that
* satisfy the conditions.
* @param conditionsSQL
* a SQL fragment string
* @param conditionsSQLData
* a data map for dynamic attributes in <tt>conditionsSQL</tt>
* @return int number of records updated
*/
public int updateAll(Map<String, Object> fieldData, String conditionsSQL, Map<String, Object> conditionsSQLData) {
if (fieldData == null || fieldData.size() == 0)
throw new IllegalArgumentException(
"fieldData cannot be empty for updateAll()");
modelCacheClient.clearCache("updateAll");
int count = -1;
String updateSQL = "UPDATE " + home.getTableName();
try {
Map<String, Object> inputs = new HashMap<String, Object>();
StringBuilder strBuffer = new StringBuilder();
ColumnInfo ci = null;
RowInfo ri = home.getRowInfo();
for (Map.Entry<String, Object> entry : fieldData.entrySet()) {
String field = entry.getKey();
if (field == null) continue;
ci = ri.getColumnInfo(field);
if (!ri.isValidColumnName(field) || ci.isReadOnly()
|| !ci.isWritable() || ci.isPrimaryKey())
continue;
String token = getUniqueToken(field, conditionsSQLData, true);
strBuffer.append(field).append(" = ?").append(token).append(", ");
inputs.put(token, entry.getValue());
}
updateSQL += " SET " + StringUtil.removeLastToken(strBuffer, ", ");
if (conditionsSQL != null) {
updateSQL += " WHERE " + conditionsSQL;
}
if (conditionsSQLData != null) {
inputs.putAll(conditionsSQLData);
}
inputs = addMoreProperties(inputs, null);
count = SqlServiceClient.executeSQL(updateSQL, inputs);
} catch (Exception ex) {
throw new BaseSQLException(ex);
}
return count;
}
private String getUniqueToken(String field, Map<String, Object> conditionsSQLData, boolean convertToUpper) {
if (conditionsSQLData == null || conditionsSQLData.size() == 0) return field;
Map<String, Object> conditionsSQLDataCopy = conditionsSQLData;
if (convertToUpper) {
conditionsSQLDataCopy = new HashMap<String, Object>(
conditionsSQLData.size());
for (Map.Entry<String, Object> entry : conditionsSQLData.entrySet()) {
String key = entry.getKey();
if (key == null) continue;
conditionsSQLDataCopy.put(key.toUpperCase(), entry.getValue());
}
}
if (conditionsSQLDataCopy.containsKey(field.toUpperCase())) {
field = "_" + field;
return getUniqueToken(field, conditionsSQLDataCopy, false);
}
return field;
}
/**
* Updates all the records that satisfy the SQL statement.
*
* @param sql
* A valid SQL string
* @return int number of records updated
*/
public int updateBySQL(String sql) {
return updateBySQL(sql, new HashMap<String, Object>());
}
/**
* Updates all the records that satisfy the SQL statement.
*
* @param sql
* A valid SQL string
* @param inputs
* a map of name and value pairs
* @return int number of records updated
*/
public int updateBySQL(String sql, Map<String, Object> inputs) {
modelCacheClient.clearCache("updateBySQL");
return SqlServiceClient.executeSQL(sql, inputs);
}
/**
* Updates all the records that satisfy the SQL specified by <tt>sqlKey</tt>
* .
*
* @param sqlKey
* a key to a SQL string.
* @return int number of records updated
*/
public int updateBySQLKey(String sqlKey) {
return updateBySQLKey(sqlKey, new HashMap<String, Object>());
}
/**
* Updates all the records that satisfy the SQL specified by <tt>sqlKey</tt>
* .
*
* @param sqlKey
* a key to a SQL string
* @param inputs
* a map of name and value pairs
* @return int number of records updated
*/
public int updateBySQLKey(String sqlKey, Map<String, Object> inputs) {
modelCacheClient.clearCache("updateBySQLKey");
return SqlServiceClient.executeSQLByKey(sqlKey, inputs);
}
/**
* Converts a primary key RESTful id string to primary key map.
*
* @return map of primary key data
*/
public Map<String, Object> convertToPrimaryKeyDataMap(String pkString) {
String[] pkNames = home.getPrimaryKeyNames();
if (pkNames == null || pkNames.length == 0) {
return null;
}
String[] pkValues = Converters.convertStringToStringArray(pkString,
DatabaseConfig.PRIMARY_KEY_SEPARATOR);
if (pkValues.length != pkNames.length) {
throw new IllegalArgumentException(
"Failed in convertToPrimaryKeyDataMap, "
+ "the input string '" + pkString + "' has "
+ pkValues.length + " parts, while there are "
+ pkNames.length + " parts for PK.");
}
Map<String, Object> pkMap = new HashMap<String, Object>();
if (pkNames != null) {
for (int i = 0; i < pkNames.length; i++) {
String name = pkNames[i];
String value = pkValues[i];
pkMap.put(name, value);
}
}
return pkMap;
}
private Map<String, Object> addMoreProperties(Map<String, Object> inputs,
Map<String, String> options) {
return home.addMoreProperties(inputs, options);
}
private static SqlService getSqlService() {
return SqlServiceConfig.getSqlService();
}
}