/* * 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(); } }