/*
* Copyright(c) 2005 Center for E-Commerce Infrastructure Development, The
* University of Hong Kong (HKU). All Rights Reserved.
*
* This software is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.0 [1]
*
* [1] http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
*/
package hk.hku.cecid.piazza.commons.dao.ds;
import hk.hku.cecid.piazza.commons.dao.DAO;
import hk.hku.cecid.piazza.commons.dao.DAOException;
import hk.hku.cecid.piazza.commons.dao.DAOFactory;
import hk.hku.cecid.piazza.commons.dao.DVO;
import hk.hku.cecid.piazza.commons.dao.Transaction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* The abstract DataSourceDAO class encapsulates a DataSourceDAOFactory object.
* It basically provides methods, such as getTransaction() and createTransaction(),
* for any subclasses to get or create a transaction for accessing the underlying
* data source. It also provides some convenient methods for the
* subclasses to access, like querying and updating, the data source.
* <p>
* When using the convenient methods, some basic parameters need to be defined.
* The parameters should be stored in the Properties object which can be
* retrieved by calling getProperties().
* <p>
* The key-value pairs of the parameters must be in the following format: <table
* border="1" width="90%">
* <tr>
* <td width="33%"><b>Key </b></td>
* <td width="33%"><b>Value </b></td>
* <td width="34%"><b>Used by </b></td>
* </tr>
* <tr>
* <td>finder:key</td>
* <td>The key finder SQL statement</td>
* <td>findByKey()</td>
* </tr>
* <tr>
* <td>finder: <i>{alias name} </i></td>
* <td>The finder SQL statement</td>
* <td>find()</td>
* </tr>
* <tr>
* <td>column: <i>{name in the data source} </i></td>
* <td>The column's key in the DVO</td>
* <td>getColumnCodeName(), executeQuery()</td>
* </tr>
* <tr>
* <td>sql: <i>{alias name} </i></td>
* <td>The SQL statement</td>
* <td>getSQL(), update()</td>
* </tr>
* </table>
*
* @author Hugo Y. K. Lam
*
*/
public abstract class DataSourceDAO implements DAO {
private DataSourceDAOFactory factory;
private DataSourceTransaction transaction;
private SQLBuilder builder;
private Properties parameters = new Properties();
private Properties columnCodeNames = new Properties();
private static final String NAMESPACE_SQL = "sql";
private static final String NAMESPACE_FINDER = "finder";
private static final String NAMESPACE_COLUMN = "column";
private static final String NAMESPACE_SELECTIVE = "selective";
private static final String NAMESPACE_SEPARATOR = ":";
private static final String PARAM_TABLE = "table";
private static final String PARAM_KEY = "key";
private static final String PARAM_SEL_INSERT = "insert";
private static final String PARAM_SEL_UPDATE = "update";
private static final String NAMESPACE_FILTER = "filter";
private static final String NAMESPACE_ORDER = "order";
/**
* Creates a new instance of DataSourceDAO.
*/
protected DataSourceDAO() {
}
/**
* Initializes this DAO.
*
* @see hk.hku.cecid.piazza.commons.dao.DAO#daoCreated()
*/
public void daoCreated() {
String table = parameters.getProperty(PARAM_TABLE);
String key = parameters.getProperty(PARAM_KEY);
ArrayList columns = new ArrayList();
Enumeration paramNames = parameters.keys();
String colind = NAMESPACE_COLUMN + NAMESPACE_SEPARATOR;
while (paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement().toString();
if (paramName.startsWith(colind)) {
String colname = paramName.substring(colind.length());
columns.add(colname);
columnCodeNames.setProperty(colname.toUpperCase(), parameters
.getProperty(paramName));
}
}
builder = new SQLBuilder(table, key, (String[]) columns
.toArray(new String[]{}));
}
/**
* Gets the DAO factory of this DAO.
*
* @return the DAO factory.
*/
public DAOFactory getFactory() {
return factory;
}
/**
* Sets the DAO factory of this DAO.
*
* @param factory the DAO factory.
*/
void setFactory(DataSourceDAOFactory factory) {
this.factory = factory;
}
/**
* Gets the parameters of this DAO.
*
* @return a Properties which stored the parameters for this DAO.
*/
public Properties getParameters() {
return parameters;
}
/**
* Gets the code name of the specified column in the data source.
*
* @param name the name of the column.
* @return the code name of the specified column.
*/
protected String getColumnCodeName(String name) {
if (name != null) {
String codeName = columnCodeNames.getProperty(name.toUpperCase());
if (codeName != null) {
return codeName;
}
}
return getParameter(NAMESPACE_COLUMN, name);
}
/**
* Gets the SQL statement with the specified name.
*
* @param name the name which refers to the SQL statement.
* @return the SQL statement associated with the specified name.
*/
protected String getSQL(String name) {
return getParameter(NAMESPACE_SQL, name);
}
/**
* Gets the finder SQL statement with the specified name.
*
* @param name the name which refers to the finder SQL statement.
* @return the finder SQL statement associated with the specified name.
*/
protected String getFinder(String name) {
return getParameter(NAMESPACE_FINDER, name);
}
/**
* Gets the filter partial SQL statement with the specified name.
*
* @param name the name which refers to the filter partial SQL statement.
* @return the filter partial SQL statement associated with the specified name.
*/
protected String getFilter(String name) {
return getParameter(NAMESPACE_FILTER, name);
}
/**
* Gets the order partial SQL statement with the specified name.
*
* @param name the name which refers to the order partial SQL statement.
* @return the order partial SQL statement associated with the specified name.
*/
protected String getOrder(String name) {
return getParameter(NAMESPACE_ORDER, name);
}
/**
* Gets a parameter of this DAO.
*
* @param namespace the namespace or null.
* @param name the parameter name.
* @return the parameter value, if any.
*/
private String getParameter(String namespace, String name) {
if (name == null) {
return null;
}
else {
if (namespace != null) {
name = namespace + NAMESPACE_SEPARATOR + name;
}
return parameters.getProperty(name);
}
}
/**
* Gets the transaction of this DAO. This method returns null if this DAO
* is not under a transaction.
*
* @return the transaction of this DAO.
* @throws DAOException if unable to get the transaction.
* @see hk.hku.cecid.piazza.commons.dao.DAO#getTransaction()
*/
public Transaction getTransaction() throws DAOException {
return getTransaction(false);
}
/**
* Gets the transaction of this DAO. This method returns a new transaction
* if and only if this DAO is not under a transaction and the create
* parameter is set to true. Notice that a newly created transaction will
* not be set as the transaction of this DAO. It is only created for
* convenience of use.
*
* @param create true if a new transaction should be created if this DAO is
* not under a transaction.
* @return the transaction of this DAO or a new transaction.
* @throws DAOException if unable get or create a transaction.
*/
protected DataSourceTransaction getTransaction(boolean create) throws DAOException {
if (transaction == null && create) {
return createTransaction();
}
else {
return (DataSourceTransaction)transaction;
}
}
/**
* Creates a new DataSourceTransaction object from the underlying data
* source factory.
*
* @return a new DataSourceTransaction object.
* @throws DAOException if unable to create the transaction.
*/
protected DataSourceTransaction createTransaction() throws DAOException {
return (DataSourceTransaction)factory.createTransaction();
}
/**
* Sets the transaction of this DAO.
*
* @param tx the transaction of this DAO.
* @throws DAOException if the given transaction is not supported.
*/
public void setTransaction(Transaction tx) throws DAOException {
if (tx == null) {
transaction = null;
}
else if (tx instanceof DataSourceTransaction) {
transaction = (DataSourceTransaction)tx;
}
else {
throw new DAOException("Unsupported transaction type: " + tx.getClass());
}
}
/**
* Checks if this DAO is under a transaction.
*
* @return true if this DAO is under a transaction.
*/
public boolean isUnderTransaction() {
return transaction != null;
}
/**
* Retrieves data from the data source by searching with the specified key
* values.
*
* @param keys the key values for querying the data source.
* @return a DVO found by the specified key values. If there are more
* than one DVO found, the first one will be returned. null will
* be returned if nothing was found.
* @throws DAOException if errors found when retrieving data from the data
* source.
*/
protected DVO findByKey(Object[] keys) throws DAOException {
List data = find("key", keys);
if (data.size() > 0) {
return (DVO) data.get(0);
}
else {
return null;
}
}
/**
* Retrieves data from the data source as a List of DVO by searching
* with the specified data values.
*
* @param finder the name of the finder SQL statement.
* @param paras the parameter values used by the finder statement.
* @return a List of DVO found by the specified data values. An empty
* List will be returned if there is no matching data.
* @throws DAOException if errors found when retrieving data from the data
* source.
*/
protected List find(String finder, Object[] paras) throws DAOException {
String sql = getFinder(finder);
if (sql == null) {
throw new DAOException("Finder '" + finder + "' not defined");
}
return executeQuery(sql, paras);
}
/**
* Updates the data source with the specified data values.
*
* @param sqlname the name of the SQL statement to be used.
* @param paras the data values to be updated to the data source.
* @return an integer indicating the update result. Same as the value
* returned by java.sql.Statement.executeUpdate()
* @throws DAOException if errors found when updating data to the data
* source.
*/
protected int update(String sqlname, Object[] paras) throws DAOException {
String sql = getSQL(sqlname);
if (sql == null) {
throw new DAOException("SQL '" + sqlname + "' not defined");
}
return executeUpdate(sql, paras);
}
/**
* Executes a SQL query on the data source and returns a List of DVO
* which matches.
*
* @param sql the SQL query statement.
* @return a List of DVO resulted from the specified SQL query. An empty
* List will be returned if there is no matching data.
* @throws DAOException if errors found when retrieving data from the data
* source.
*/
protected List executeQuery(String sql) throws DAOException {
return executeQuery(null, sql);
}
/**
* Executes a SQL query on the data source and returns a List of DVO
* which matches.
*
* @param tx the DataSourceTransaction to be used in execution.
* @param sql the SQL query statement.
* @return a List of DVO resulted from the specified SQL query. An empty
* List will be returned if there is no matching data.
* @throws DAOException if errors found when retrieving data from the data
* source.
*/
protected List executeQuery(DataSourceTransaction tx, String sql)
throws DAOException {
return executeQuery(tx, sql, null);
}
/**
* Executes a SQL query on the data source and returns a List of DVO
* which matches.
*
* @param sql the SQL query statement.
* @param paras the parameter values used by the specified SQL statement.
* The number, format, and sequence of the parameter values
* should match the statement. null if there is no parameter for
* the statement.
* @return a List of DVO resulted from the specified SQL query. An empty
* List will be returned if there is no matching data.
* @throws DAOException if errors found when retrieving data from the data
* source.
*/
protected List executeQuery(String sql, Object[] paras) throws DAOException {
return executeQuery(null, sql, paras);
}
/**
* Executes a SQL query on the data source and returns a List of DVO
* which matches.
*
* @param tx the DataSourceTransaction to be used in execution.
* @param sql the SQL query statement.
* @param paras the parameter values used by the specified SQL statement.
* The number, format, and sequence of the parameter values
* should match the statement. null if there is no parameter for
* the statement.
* @return a List of DVO resulted from the specified SQL query. An empty
* List will be returned if there is no matching data.
* @throws DAOException if errors found when retrieving data from the data
* source.
*/
protected List executeQuery(DataSourceTransaction tx, String sql, Object[] paras)
throws DAOException {
DataSourceQuery query = new DataSourceQuery(this, tx, sql, paras);
query.start();
return (List) query.getResult();
}
/**
* Executes a SQL query on the data source and returns a List of raw data
* which matches.
*
* @param sql the SQL query statement.
* @param paras the parameter values used by the specified SQL statement.
* The number, format, and sequence of the parameter values
* should match the statement. null if there is no parameter for
* the statement.
* @return a List of DVO resulted from the specified SQL query. An empty
* List will be returned if there is no matching data.
* @throws DAOException if errors found when retrieving data from the data
* source.
*/
protected List executeRawQuery(String sql, Object[] paras)
throws DAOException {
return executeRawQuery(null, sql, paras);
}
/**
* Executes a SQL query on the data source and returns a List of raw data
* which matches.
*
* @param tx the DataSourceTransaction to be used in execution.
* @param sql the SQL query statement.
* @param paras the parameter values used by the specified SQL statement.
* The number, format, and sequence of the parameter values
* should match the statement. null if there is no parameter for
* the statement.
* @return a List of DVO resulted from the specified SQL query. An empty
* List will be returned if there is no matching data.
* @throws DAOException if errors found when retrieving data from the data
* source.
*/
protected List executeRawQuery(DataSourceTransaction tx, String sql, Object[] paras)
throws DAOException {
DataSourceQuery query = new DataSourceQuery(this, tx, sql, paras);
query.setRawResult(true);
query.start();
return (List) query.getResult();
}
/**
* Executes a SQL update on the data source and returns an integer
* indicating the update result.
*
* @param sql the SQL update statement.
* @return an integer indicating the update results. Same as the value
* returned by java.sql.Statement.executeUpdate().
* @throws DAOException if errors found when updating data to the data
* source.
*/
protected int executeUpdate(String sql) throws DAOException {
return executeUpdate(null, sql);
}
/**
* Executes a SQL update on the data source and returns an integer
* indicating the update result.
*
* @param tx the DataSourceTransaction to be used in execution.
* @param sql the SQL update statement.
* @return an integer indicating the update results. Same as the value
* returned by java.sql.Statement.executeUpdate().
* @throws DAOException if errors found when updating data to the data
* source.
*/
protected int executeUpdate(DataSourceTransaction tx, String sql)
throws DAOException {
return executeUpdate(tx, sql, null)[0];
}
/**
* Executes a SQL update on the data source and returns an integer
* indicating the update result.
*
* @param sql the SQL update statement.
* @param paras the parameter values used by the specified SQL statement.
* The number, format, and sequence of the parameter values
* should match the statement. null if there is no parameter for
* the statement.
* @return an integer indicating the update results. Same as the value
* returned by java.sql.Statement.executeUpdate().
* @throws DAOException if errors found when updating data to the data
* source.
*/
protected int executeUpdate(String sql, Object[] paras) throws DAOException {
return executeUpdate(null, sql, paras);
}
/**
* Executes a SQL update on the data source and returns an integer
* indicating the update result.
*
* @param tx the DataSourceTransaction to be used in execution.
* @param sql the SQL update statement.
* @param paras the parameter values used by the specified SQL statement.
* The number, format, and sequence of the parameter values
* should match the statement. null if there is no parameter for
* the statement.
* @return an integer indicating the update results. Same as the value
* returned by java.sql.Statement.executeUpdate()
* @throws DAOException if errors found when updating data to the data
* source.
*/
protected int executeUpdate(DataSourceTransaction tx, String sql, Object[] paras)
throws DAOException {
return executeUpdate(tx, sql, new Object[][]{paras})[0];
}
/**
* Executes a SQL update on the data source and returns an array of integers
* indicating the update results.
*
* @param sql the SQL update statement.
* @param paras the parameter values used by the specified SQL statement.
* The number, format, and sequence of the parameter values
* should match the statement. null if there is no parameter for
* the statement and multiple sets if it is a batch update.
* @return an array of integers indicating the update results. Same as the
* value returned by java.sql.Statement.executeUpdate().
* @throws DAOException if errors found when updating data to the data
* source.
*/
protected int[] executeUpdate(String sql, Object[][] paras)
throws DAOException {
return executeUpdate(null, sql, paras);
}
/**
* Executes a SQL update on the data source and returns an array of integers
* indicating the update results.
*
* @param tx the DataSourceTransaction to be used in execution.
* @param sql the SQL update statement.
* @param paras the parameter values used by the specified SQL statement.
* The number, format, and sequence of the parameter values
* should match the statement. null if there is no parameter for
* the statement and multiple sets if it is a batch update.
* @return an array of integers indicating the update results. Same as the
* value returned by java.sql.Statement.executeUpdate().
* @throws DAOException if errors found when updating data to the data
* source.
*/
protected int[] executeUpdate(DataSourceTransaction tx, String sql, Object[][] paras)
throws DAOException {
DataSourceUpdate update = new DataSourceUpdate(this, tx, sql, paras);
update.start();
return (int[]) update.getResult();
}
/**
* Creates the given value object in the data source.
*
* @param data the value object.
* @throws DAOException if unable to create the data.
* @throws ClassCastException if the value object is not DataSourceDVO.
* @see hk.hku.cecid.piazza.commons.dao.DAO#create(hk.hku.cecid.piazza.commons.dao.DVO)
*/
public void create(DVO data) throws DAOException {
checkSQLBuilder();
String[] cols;
if (new Boolean(getParameter(NAMESPACE_SELECTIVE, PARAM_SEL_INSERT))
.booleanValue()) {
cols = getColumnNames(((DataSourceDVO) data).getDirties());
}
else {
cols = builder.getColumns();
}
create(data, cols);
}
/**
* Creates the given value object in the data source according to the given
* columns.
*
* @param data the value object.
* @param columns the columns to be updated.
* @throws DAOException if unable to create the data.
* @throws ClassCastException if the value object is not DataSourceDVO.
* @see #create(DVO)
*/
private void create(DVO data, String[] columns) throws DAOException {
String sql = builder.getInsertStatement(columns);
Object[] values = getValues(data, columns);
int result = executeUpdate(sql, values);
if (result != 1) {
throw new DAOException("Unexpected creation error. Records updated: "+result);
}
}
/**
* Persists the given value object to the data source.
*
* @param data the value object.
* @return true if the data is found and persisted.
* @throws DAOException if unable to persist the data.
* @throws ClassCastException if the DAO data is not DataSourceDVO.
* @see hk.hku.cecid.piazza.commons.dao.DAO#persist(hk.hku.cecid.piazza.commons.dao.DVO)
*/
public boolean persist(DVO data) throws DAOException {
checkSQLBuilder();
String[] cols;
if (new Boolean(getParameter(NAMESPACE_SELECTIVE, PARAM_SEL_UPDATE))
.booleanValue()) {
cols = getColumnNames(((DataSourceDVO) data).getDirties());
}
else {
cols = builder.getColumns();
}
return persist(data, cols);
}
/**
* Persists the given value object to the data source according to the given
* columns.
*
* @param data the value object.
* @param columns the columns to be updated.
* @return true if the data is found and persisted.
* @throws DAOException if unable to persist the data.
* @throws ClassCastException if the value object is not DataSourceDVO.
* @see #persist(DVO)
*/
private boolean persist(DVO data, String[] columns) throws DAOException {
String sql = builder.getUpdateStatement(columns);
Object[] values1 = getValues(data, columns);
Object[] values2 = getValues(data, builder.getKeys());
List values = new ArrayList();
values.addAll(Arrays.asList(values1));
values.addAll(Arrays.asList(values2));
int result = executeUpdate(sql, values.toArray());
if (result > 0) {
return true;
}
else {
return false;
}
}
/**
* Retrieves the given value object from the data source.
*
* @param data the value object.
* @return true if the data is found and retrieved.
* @throws DAOException if unable to retrieve the data.
* @throws ClassCastException if the value object is not DataSourceDVO.
* @see hk.hku.cecid.piazza.commons.dao.DAO#retrieve(hk.hku.cecid.piazza.commons.dao.DVO)
*/
public boolean retrieve(DVO data) throws DAOException {
checkSQLBuilder();
String sql = builder.getSelectStatement();
Object[] values = getValues(data, builder.getKeys());
List result = executeQuery(sql, values);
if (result.size() > 0) {
DataSourceDVO d = (DataSourceDVO) result.get(0);
((DataSourceDVO) data).setData(d.getData());
return true;
}
else {
return false;
}
}
/**
* Removes the given value object from the data source.
*
* @param data the value object.
* @return true if the data is found and removed.
* @throws DAOException if unable to remove the data.
* @throws ClassCastException if the value object is not DataSourceDVO.
* @see hk.hku.cecid.piazza.commons.dao.DAO#remove(hk.hku.cecid.piazza.commons.dao.DVO)
*/
public boolean remove(DVO data) throws DAOException {
checkSQLBuilder();
String sql = builder.getDeleteStatement();
Object[] values = getValues(data, builder.getKeys());
int result = executeUpdate(sql, values);
if (result > 0) {
return true;
}
else {
return false;
}
}
/**
* Checks if the SQL builder for this DAO is valid for further operation.
*
* @throws DAOException if the SQL builder is invalid.
*/
private void checkSQLBuilder() throws DAOException {
if (!builder.isValid()) {
throw new DAOException(
"Insufficient information provided by this DAO. Operation not supported.");
}
}
/**
* Gets the values stored in the given value object according to the specified
* columns.
*
* @param data the value object.
* @param columns the columns to be retrieved.
* @throws ClassCastException if the value object is not DataSourceDVO.
* @return the retrieved values of the given value object.
*/
private Object[] getValues(DVO data, String[] columns) {
ArrayList values = new ArrayList();
for (int i = 0; i < columns.length; i++) {
String columnCodeName = getColumnCodeName(columns[i]);
Object value = columnCodeName == null ? null
: ((DataSourceDVO) data).get(columnCodeName);
values.add(value);
}
return values.toArray();
}
/**
* Gets the columns names from the column code names.
*
* @param codeNames the column code names.
* @return the column names.
*/
private String[] getColumnNames(String[] codeNames) {
String[] columnNames = new String[codeNames == null ? 0
: codeNames.length];
for (int i = 0; i < columnNames.length; i++) {
columnNames[i] = getColumnName(codeNames[i]);
}
return columnNames;
}
/**
* Gets the column name from the column code name.
*
* @param codeName the column code name.
* @return the column name.
*/
private String getColumnName(String codeName) {
if (codeName != null) {
Iterator columns = columnCodeNames.entrySet().iterator();
while (columns.hasNext()) {
Map.Entry entry = (Map.Entry) columns.next();
if (entry.getValue().equals(codeName)) {
return (String) entry.getKey();
}
}
}
return null;
}
}