/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.quartz;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.quartz.SchedulerConfigException;
import org.quartz.impl.jdbcjobstore.JobStoreCMT;
import org.quartz.impl.jdbcjobstore.SimpleSemaphore;
import org.quartz.spi.ClassLoadHelper;
import org.quartz.spi.SchedulerSignaler;
import org.quartz.utils.ConnectionProvider;
import org.quartz.utils.DBConnectionManager;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.lang.Nullable;
Subclass of Quartz's JobStoreCMT
class that delegates to a Spring-managed DataSource
instead of using a Quartz-managed JDBC connection pool. This JobStore will be used if SchedulerFactoryBean's "dataSource" property is set. Supports both transactional and non-transactional DataSource access.
With a non-XA DataSource and local Spring transactions, a single DataSource
argument is sufficient. In case of an XA DataSource and global JTA transactions,
SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
passing in a non-XA DataSource that will not participate in global transactions.
Operations performed by this JobStore will properly participate in any
kind of Spring-managed transaction, as it uses Spring's DataSourceUtils
connection handling methods that are aware of a current transaction.
Note that all Quartz Scheduler operations that affect the persistent
job store should usually be performed within active transactions,
as they assume to get proper locks etc.
Author: Juergen Hoeller See Also: Since: 1.1
/**
* Subclass of Quartz's {@link JobStoreCMT} class that delegates to a Spring-managed
* {@link DataSource} instead of using a Quartz-managed JDBC connection pool.
* This JobStore will be used if SchedulerFactoryBean's "dataSource" property is set.
*
* <p>Supports both transactional and non-transactional DataSource access.
* With a non-XA DataSource and local Spring transactions, a single DataSource
* argument is sufficient. In case of an XA DataSource and global JTA transactions,
* SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
* passing in a non-XA DataSource that will not participate in global transactions.
*
* <p>Operations performed by this JobStore will properly participate in any
* kind of Spring-managed transaction, as it uses Spring's DataSourceUtils
* connection handling methods that are aware of a current transaction.
*
* <p>Note that all Quartz Scheduler operations that affect the persistent
* job store should usually be performed within active transactions,
* as they assume to get proper locks etc.
*
* @author Juergen Hoeller
* @since 1.1
* @see SchedulerFactoryBean#setDataSource
* @see SchedulerFactoryBean#setNonTransactionalDataSource
* @see org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
* @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
*/
@SuppressWarnings("unchecked") // due to a warning in Quartz 2.2's JobStoreCMT
public class LocalDataSourceJobStore extends JobStoreCMT {
Name used for the transactional ConnectionProvider for Quartz.
This provider will delegate to the local Spring-managed DataSource.
See Also: - addConnectionProvider.addConnectionProvider
- SchedulerFactoryBean.setDataSource
/**
* Name used for the transactional ConnectionProvider for Quartz.
* This provider will delegate to the local Spring-managed DataSource.
* @see org.quartz.utils.DBConnectionManager#addConnectionProvider
* @see SchedulerFactoryBean#setDataSource
*/
public static final String TX_DATA_SOURCE_PREFIX = "springTxDataSource.";
Name used for the non-transactional ConnectionProvider for Quartz.
This provider will delegate to the local Spring-managed DataSource.
See Also: - addConnectionProvider.addConnectionProvider
- SchedulerFactoryBean.setDataSource
/**
* Name used for the non-transactional ConnectionProvider for Quartz.
* This provider will delegate to the local Spring-managed DataSource.
* @see org.quartz.utils.DBConnectionManager#addConnectionProvider
* @see SchedulerFactoryBean#setDataSource
*/
public static final String NON_TX_DATA_SOURCE_PREFIX = "springNonTxDataSource.";
@Nullable
private DataSource dataSource;
@Override
public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException {
// Absolutely needs thread-bound DataSource to initialize.
this.dataSource = SchedulerFactoryBean.getConfigTimeDataSource();
if (this.dataSource == null) {
throw new SchedulerConfigException("No local DataSource found for configuration - " +
"'dataSource' property must be set on SchedulerFactoryBean");
}
// Configure transactional connection settings for Quartz.
setDataSource(TX_DATA_SOURCE_PREFIX + getInstanceName());
setDontSetAutoCommitFalse(true);
// Register transactional ConnectionProvider for Quartz.
DBConnectionManager.getInstance().addConnectionProvider(
TX_DATA_SOURCE_PREFIX + getInstanceName(),
new ConnectionProvider() {
@Override
public Connection getConnection() throws SQLException {
// Return a transactional Connection, if any.
return DataSourceUtils.doGetConnection(dataSource);
}
@Override
public void shutdown() {
// Do nothing - a Spring-managed DataSource has its own lifecycle.
}
/* Quartz 2.2 initialize method */
public void initialize() {
// Do nothing - a Spring-managed DataSource has its own lifecycle.
}
}
);
// Non-transactional DataSource is optional: fall back to default
// DataSource if not explicitly specified.
DataSource nonTxDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource();
final DataSource nonTxDataSourceToUse = (nonTxDataSource != null ? nonTxDataSource : this.dataSource);
// Configure non-transactional connection settings for Quartz.
setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName());
// Register non-transactional ConnectionProvider for Quartz.
DBConnectionManager.getInstance().addConnectionProvider(
NON_TX_DATA_SOURCE_PREFIX + getInstanceName(),
new ConnectionProvider() {
@Override
public Connection getConnection() throws SQLException {
// Always return a non-transactional Connection.
return nonTxDataSourceToUse.getConnection();
}
@Override
public void shutdown() {
// Do nothing - a Spring-managed DataSource has its own lifecycle.
}
/* Quartz 2.2 initialize method */
public void initialize() {
// Do nothing - a Spring-managed DataSource has its own lifecycle.
}
}
);
// No, if HSQL is the platform, we really don't want to use locks...
try {
String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource, "getDatabaseProductName");
productName = JdbcUtils.commonDatabaseName(productName);
if (productName != null && productName.toLowerCase().contains("hsql")) {
setUseDBLocks(false);
setLockHandler(new SimpleSemaphore());
}
}
catch (MetaDataAccessException ex) {
logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken.");
}
super.initialize(loadHelper, signaler);
}
@Override
protected void closeConnection(Connection con) {
// Will work for transactional and non-transactional connections.
DataSourceUtils.releaseConnection(con, this.dataSource);
}
}