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