/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.util.db;
import static com.opengamma.util.db.HibernateDbUtils.fixSQLExceptionCause;
import java.sql.Timestamp;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.threeten.bp.Clock;
import org.threeten.bp.Instant;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.Connector;
import com.opengamma.util.ReflectionUtils;
import com.opengamma.util.time.DateUtils;
/**
* Connector used to access SQL databases.
* <p>
* This class provides a simple-to-setup and simple-to-use way to access databases. It can be configured for access via JDBC, Hibernate or both. The main benefit is simpler configuration, especially
* if that configuration is in XML.
* <p>
* This class is usually configured using the associated factory bean.
*/
public class DbConnector implements Connector {
/** Logger. */
private static final Logger s_logger = LoggerFactory.getLogger(DbConnector.class);
static {
DateUtils.initTimeZone();
}
/**
* The configuration name.
*/
private final String _name;
/**
* The data source.
*/
private final DataSource _dataSource;
/**
* The dialect.
*/
private final DbDialect _dialect;
/**
* The JDBC template.
*/
private final NamedParameterJdbcTemplate _jdbcTemplate;
/**
* The Hibernate template.
*/
private final HibernateTemplate _hibernateTemplate;
/**
* The transaction template.
*/
private final TransactionTemplate _transactionTemplate;
/**
* The clock.
*/
private final DbClock _clock;
/**
* Creates an instance.
*
* @param name the configuration name, not null
* @param dialect the database dialect, not null
* @param dataSource the data source, not null
* @param jdbcTemplate the JDBC template, not null
* @param hibernateTemplate the Hibernate template, may be null
* @param transactionTemplate the transaction template, not null
*/
public DbConnector(
String name, DbDialect dialect, DataSource dataSource,
NamedParameterJdbcTemplate jdbcTemplate, HibernateTemplate hibernateTemplate, TransactionTemplate transactionTemplate) {
ArgumentChecker.notNull(name, "name");
ArgumentChecker.notNull(dialect, "dialect");
ArgumentChecker.notNull(dataSource, "dataSource");
ArgumentChecker.notNull(jdbcTemplate, "JDBC template");
ArgumentChecker.notNull(transactionTemplate, "transactionTemplate");
_name = name;
_dataSource = dataSource;
_dialect = dialect;
_jdbcTemplate = jdbcTemplate;
_hibernateTemplate = hibernateTemplate;
_transactionTemplate = transactionTemplate;
_clock = new DbClock(this); // late initialization
}
//-------------------------------------------------------------------------
/**
* Gets the display name of the connector.
*
* @return a name usable for display, not null
*/
@Override
public final String getName() {
return _name;
}
/**
* Gets the type of the connector, which is {@code DbConnector}.
*
* @return the connector type, not null
*/
@Override
public final Class<? extends Connector> getType() {
return DbConnector.class;
}
//-------------------------------------------------------------------------
/**
* Gets the data source.
*
* @return the data source, not null
*/
public DataSource getDataSource() {
return _dataSource;
}
/**
* Gets the database dialect.
*
* @return the database dialect, not null
*/
public DbDialect getDialect() {
return _dialect;
}
/**
* Gets the JDBC operations.
* <p>
* This is used for simple calls that do no use named parameters.
*
* @return the JDBC template, not null
*/
public JdbcOperations getJdbcOperations() {
return _jdbcTemplate.getJdbcOperations();
}
/**
* Gets the JDBC template.
* <p>
* This is used for named parameters.
*
* @return the JDBC template, not null
*/
public NamedParameterJdbcTemplate getJdbcTemplate() {
return _jdbcTemplate;
}
//-------------------------------------------------------------------------
/**
* Gets the Hibernate session factory.
* <p>
* This is shared between all users of this object and must not be further configured.
*
* @return the Hibernate session factory, may be null
*/
public SessionFactory getHibernateSessionFactory() {
if (_hibernateTemplate == null) {
return null;
}
return _hibernateTemplate.getSessionFactory();
}
/**
* Gets the shared Hibernate template.
* <p>
* This is shared between all users of this object and must not be further configured.
*
* @return the Hibernate template, null if the session factory is null
*/
public HibernateTemplate getHibernateTemplate() {
return _hibernateTemplate;
}
/**
* Gets the simple Hibernate transaction template.
*
* @return the Hibernate transaction template, not null
*/
public HibernateTransactionTemplate getHibernateTransactionTemplate() {
return new HibernateTransactionTemplate();
}
/**
* Gets the retrying Hibernate transaction template.
*
* @param retries how many maximum retires should be tried
* @return the retrying Hibernate transaction template, not null
*/
public HibernateTransactionTemplateRetrying getHibernateTransactionTemplateRetrying(int retries) {
return new HibernateTransactionTemplateRetrying(retries);
}
//-------------------------------------------------------------------------
/**
* Gets the transaction manager.
* <p>
* This is shared between all users of this object and must not be further configured.
*
* @return the transaction manager, may be null
*/
public PlatformTransactionManager getTransactionManager() {
return _transactionTemplate.getTransactionManager();
}
/**
* Gets the transaction template.
* <p>
* This is shared between all users of this object and must not be further configured.
*
* @return the transaction template, may be null
*/
public TransactionTemplate getTransactionTemplate() {
return _transactionTemplate;
}
/**
* Gets the retrying transaction template.
* <p>
*
* @param retries how many maximum retires should be tried
* @return the retrying transaction template
*/
public TransactionTemplateRetrying getTransactionTemplateRetrying(int retries) {
return new TransactionTemplateRetrying(retries);
}
//-------------------------------------------------------------------------
/**
* Gets the current instant based on a cached database clock.
*
* @return the current instant, may be null
*/
public Instant now() {
return _clock.instant();
}
/**
* Gets the current instant using the database clock.
*
* @return the current database instant, may be null
*/
Timestamp nowDb() {
return getJdbcOperations().queryForObject(getDialect().sqlSelectNow(), Timestamp.class);
}
/**
* Returns a time-source based on the current database clock.
* <p>
* This can be used to obtain the current instant by calling {@link Instant#now(Clock)}.
*
* @return the database time-source, may be null
*/
public Clock timeSource() {
return _clock;
}
//-------------------------------------------------------------------------
@Override
public void close() {
ReflectionUtils.close(getDataSource());
ReflectionUtils.close(getTransactionManager());
ReflectionUtils.close(getHibernateSessionFactory());
getDialect().close();
}
//-------------------------------------------------------------------------
/**
* Returns a description of this object suitable for debugging.
*
* @return the description, not null
*/
@Override
public String toString() {
return getClass().getSimpleName() + "[" + _name + "]";
}
//-------------------------------------------------------------------------
/**
* A transaction template that retries the underlying Spring-based template.
*/
public class TransactionTemplateRetrying {
private final int _retries;
private final TransactionTemplate _transactionTemplate;
TransactionTemplateRetrying(int retries) {
_retries = retries;
_transactionTemplate = getTransactionTemplate();
}
/**
* Executes the template, which will retry the code in the event of failure.
*
* @param <T> the type of the result
* @param action the underlying Spring-based template containing the action to perform, not null
* @return the result of the underlying template
* @throws TransactionException if an error occurs
*/
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
// retry to handle concurrent conflicting inserts into unique content tables
for (int retry = 0; true; retry++) {
try {
return _transactionTemplate.execute(action);
} catch (DataIntegrityViolationException ex) {
if (retry == _retries) {
throw ex;
}
s_logger.warn("Execution failure on attempt " + retry + " of " + _retries, ex);
} catch (DataAccessException ex) {
throw fixSQLExceptionCause(ex);
}
}
}
}
//-------------------------------------------------------------------------
/**
* A standard transaction template wrapping the underlying Hibernate template.
*/
public class HibernateTransactionTemplate {
private final TransactionTemplate _transactionTemplate;
private final HibernateTemplate _hibernateTemplate;
HibernateTransactionTemplate() {
_transactionTemplate = getTransactionTemplate();
_hibernateTemplate = getHibernateTemplate();
}
/**
* Executes the underlying template in a transaction.
*
* @param <T> the type of the result
* @param action the underlying Hibernate template containing the action to perform, not null
* @return the result of the underlying template
* @throws TransactionException if an error occurs
*/
public <T> T execute(final HibernateCallback<T> action) throws TransactionException {
try {
return _transactionTemplate.execute(new TransactionCallback<T>() {
@Override
public T doInTransaction(TransactionStatus status) {
return _hibernateTemplate.execute(action);
}
});
} catch (DataAccessException ex) {
throw fixSQLExceptionCause(ex);
}
}
}
//-------------------------------------------------------------------------
/**
* A transaction template that retries the underlying Hibernate template.
*/
public class HibernateTransactionTemplateRetrying {
private final int _retries;
private final TransactionTemplate _transactionTemplate;
private final HibernateTemplate _hibernateTemplate;
HibernateTransactionTemplateRetrying(int retries) {
_retries = retries;
_transactionTemplate = getTransactionTemplate();
_hibernateTemplate = getHibernateTemplate();
_hibernateTemplate.setAllowCreate(false);
}
/**
* Executes the template, which will retry the code in the event of failure.
*
* @param <T> the type of the result
* @param action the underlying Hibernate template containing the action to perform, not null
* @return the result of the underlying template
* @throws TransactionException if an error occurs
*/
public <T> T execute(final HibernateCallback<T> action) throws TransactionException {
// retry to handle concurrent conflicting inserts into unique content tables
for (int retry = 0; true; retry++) {
try {
return _transactionTemplate.execute(new TransactionCallback<T>() {
@Override
public T doInTransaction(TransactionStatus status) {
return _hibernateTemplate.execute(action);
}
});
} catch (DataIntegrityViolationException ex) {
if (retry == _retries) {
throw ex;
}
} catch (DataAccessException ex) {
throw fixSQLExceptionCause(ex);
}
}
}
}
}