package org.openmhealth.reference.data.sql;
import java.beans.PropertyVetoException;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openmhealth.reference.data.Dao;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.PooledDataSource;
/**
* <p>
* The {@link Dao} parent class for all SQL data access objects.
* </p>
*
* @author John Jenkins
*/
public abstract class SqlDao extends Dao {
/**
* The logger for this class.
*/
private static final Logger LOGGER =
Logger.getLogger(SqlDao.class.getName());
/**
* The default server address.
*/
public static final String DEFAULT_SERVER_ADDRESS = "localhost";
/**
* The default name for the database.
*/
public static final String DEFAULT_DATABASE_NAME = "omh";
/**
* The property key for the database driver class.
*/
public static final String KEY_PROPERTY_DATABASE_DRIVER =
"db.sql.driverClass";
/**
* The property key for the database driver class.
*/
public static final String KEY_PROPERTY_DATABASE_JDBC_URL =
"db.sql.jdbcUrl";
/**
* The database column name for the ID, which will be universal across all
* tables.
*/
public static final String KEY_DATABASE_ID = "id";
/**
* The data source used to connect to the database.
*/
private final PooledDataSource dataSource;
/**
* The JDBC template provided by SpringSource to make interacting with the
* database simpler.
*/
private final JdbcTemplate jdbcTemplate;
/**
* Initializes this DAO.
*
* @param properties The default and custom properties for this DAO.
*/
public SqlDao(final Properties properties) {
super(properties);
// Attempt to create the DataSource.
ComboPooledDataSource comboPooledDataSource =
new ComboPooledDataSource();
// Set the properties from the properties file (the ones that begin
// with "c3p0").
comboPooledDataSource.setProperties(properties);
// Attempt to load the driver to be used to connect to the database.
if(properties.containsKey(KEY_PROPERTY_DATABASE_DRIVER)) {
try {
comboPooledDataSource
.setDriverClass(
properties.getProperty(KEY_PROPERTY_DATABASE_DRIVER));
}
catch(PropertyVetoException e) {
LOGGER.log(Level.SEVERE, "The driver was rejected.", e);
throw new IllegalStateException("The driver was rejected.", e);
}
}
// Otherwise, we may error out.
else {
LOGGER
.log(
Level.SEVERE,
"For SQL database connections, a driver must be " +
"specified.");
}
// If the JDBC URL was given, use that.
if(properties.containsKey(KEY_PROPERTY_DATABASE_JDBC_URL)) {
comboPooledDataSource
.setJdbcUrl(
properties.getProperty(KEY_PROPERTY_DATABASE_JDBC_URL));
}
// Otherwise, ask the specific implementation for a default URL.
else {
comboPooledDataSource.setJdbcUrl(getJdbcUrl());
}
// Set the username and password.
comboPooledDataSource.setUser(getDatabaseUsername());
comboPooledDataSource.setPassword(getDatabasePassword());
// Save the data source.
dataSource = comboPooledDataSource;
// Create the JDBC template from the data source.
jdbcTemplate = new JdbcTemplate(dataSource);
// Initialize all of the components.
initDaos(
new SqlUserBin(),
new SqlRegistry(),
new SqlDataSet(),
new SqlThirdPartyBin(),
new SqlAuthenticationTokenBin(),
new SqlAuthorizationCodeBin(),
new SqlAuthorizationCodeResponseBin(),
new SqlAuthorizationTokenBin());
}
/**
* Returns the JDBC template used to access the database more easily.
*
* @return The JDBC template object for accessing the database.
*/
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
/**
* Returns a transaction manager to be used to create transactions.
*
* @return The transaction manager to be used to create transactions.
*/
public PlatformTransactionManager getTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}
/*
* (non-Javadoc)
* @see org.openmhealth.reference.data.Dao#shutdown()
*/
@Override
public void shutdown() {
try {
dataSource.close();
}
catch(SQLException e) {
LOGGER.log(
Level.WARNING,
"Failed to close the connection to the database",
e);
}
}
/**
* Returns the instance of this DAO as a MongoDao.
*
* @return The instance of this DAO as a MongoDao.
*
* @throws IllegalStateException
* The DAO was not built with a MongoDao.
*/
public static SqlDao getInstance() {
try {
return (SqlDao) Dao.getInstance();
}
catch(ClassCastException e) {
throw new IllegalStateException("The DAO is not a MongoDB DAO.");
}
}
/*
* (non-Javadoc)
* @see org.openmhealth.reference.data.Dao#getDefaultServerAddress()
*/
@Override
protected String getDefaultServerAddress() {
return DEFAULT_SERVER_ADDRESS;
}
/*
* (non-Javadoc)
* @see org.openmhealth.reference.data.Dao#getDefaultDatabaseName()
*/
@Override
protected String getDefaultDatabaseName() {
return DEFAULT_DATABASE_NAME;
}
/**
* Creates and returns the JDBC URL to use to connect to the database.
*
* @return The JDBC URL to use to connect to the database.
*/
protected abstract String getJdbcUrl();
/**
* Initializes the DAOs' access to the database.
*
* @param daoInterfaces
* The collection of DAOs to initialize.
*
* @throws IllegalStateException
* There was a problem initializing one of the DAOs.
*/
private final void initDaos(final SqlDaoInterface... daoInterfaces) {
// Create a transaction manager.
PlatformTransactionManager transactionManager =
new DataSourceTransactionManager(dataSource);
// Create a new transaction definition and name it.
DefaultTransactionDefinition transactionDefinition =
new DefaultTransactionDefinition();
transactionDefinition
.setName("Initializing the Open mHealth DAO database tables.");
// Create the new transaction.
TransactionStatus transactionStatus =
transactionManager.getTransaction(transactionDefinition);
// Create the table if it does not exist.
try {
for(SqlDaoInterface daoInterface : daoInterfaces) {
// Create the table if it does not exist.
jdbcTemplate.execute(daoInterface.getSqlTableDefinition());
}
}
// If creating the table fails, roll back the transaction and error
// out.
catch(DataAccessException e) {
transactionManager.rollback(transactionStatus);
throw
new IllegalStateException(
"There was an issue creating a DAO table definition.",
e);
}
// TODO: This is where the DAO interface's update scripts would
// be run.
// Commit the transaction.
try {
transactionManager.commit(transactionStatus);
}
catch(TransactionException e) {
transactionManager.rollback(transactionStatus);
throw
new IllegalStateException(
"There was an error committing the transaction.",
e);
}
}
}