001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.scheduling.quartz; 018 019import java.sql.Connection; 020import java.sql.DatabaseMetaData; 021import java.sql.SQLException; 022 023import javax.sql.DataSource; 024 025import org.quartz.SchedulerConfigException; 026import org.quartz.impl.jdbcjobstore.JobStoreCMT; 027import org.quartz.impl.jdbcjobstore.SimpleSemaphore; 028import org.quartz.spi.ClassLoadHelper; 029import org.quartz.spi.SchedulerSignaler; 030import org.quartz.utils.ConnectionProvider; 031import org.quartz.utils.DBConnectionManager; 032 033import org.springframework.jdbc.datasource.DataSourceUtils; 034import org.springframework.jdbc.support.JdbcUtils; 035import org.springframework.jdbc.support.MetaDataAccessException; 036import org.springframework.lang.Nullable; 037 038/** 039 * Subclass of Quartz's {@link JobStoreCMT} class that delegates to a Spring-managed 040 * {@link DataSource} instead of using a Quartz-managed JDBC connection pool. 041 * This JobStore will be used if SchedulerFactoryBean's "dataSource" property is set. 042 * 043 * <p>Supports both transactional and non-transactional DataSource access. 044 * With a non-XA DataSource and local Spring transactions, a single DataSource 045 * argument is sufficient. In case of an XA DataSource and global JTA transactions, 046 * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set, 047 * passing in a non-XA DataSource that will not participate in global transactions. 048 * 049 * <p>Operations performed by this JobStore will properly participate in any 050 * kind of Spring-managed transaction, as it uses Spring's DataSourceUtils 051 * connection handling methods that are aware of a current transaction. 052 * 053 * <p>Note that all Quartz Scheduler operations that affect the persistent 054 * job store should usually be performed within active transactions, 055 * as they assume to get proper locks etc. 056 * 057 * @author Juergen Hoeller 058 * @since 1.1 059 * @see SchedulerFactoryBean#setDataSource 060 * @see SchedulerFactoryBean#setNonTransactionalDataSource 061 * @see org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection 062 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection 063 */ 064@SuppressWarnings("unchecked") // due to a warning in Quartz 2.2's JobStoreCMT 065public class LocalDataSourceJobStore extends JobStoreCMT { 066 067 /** 068 * Name used for the transactional ConnectionProvider for Quartz. 069 * This provider will delegate to the local Spring-managed DataSource. 070 * @see org.quartz.utils.DBConnectionManager#addConnectionProvider 071 * @see SchedulerFactoryBean#setDataSource 072 */ 073 public static final String TX_DATA_SOURCE_PREFIX = "springTxDataSource."; 074 075 /** 076 * Name used for the non-transactional ConnectionProvider for Quartz. 077 * This provider will delegate to the local Spring-managed DataSource. 078 * @see org.quartz.utils.DBConnectionManager#addConnectionProvider 079 * @see SchedulerFactoryBean#setDataSource 080 */ 081 public static final String NON_TX_DATA_SOURCE_PREFIX = "springNonTxDataSource."; 082 083 084 @Nullable 085 private DataSource dataSource; 086 087 088 @Override 089 public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException { 090 // Absolutely needs thread-bound DataSource to initialize. 091 this.dataSource = SchedulerFactoryBean.getConfigTimeDataSource(); 092 if (this.dataSource == null) { 093 throw new SchedulerConfigException("No local DataSource found for configuration - " + 094 "'dataSource' property must be set on SchedulerFactoryBean"); 095 } 096 097 // Configure transactional connection settings for Quartz. 098 setDataSource(TX_DATA_SOURCE_PREFIX + getInstanceName()); 099 setDontSetAutoCommitFalse(true); 100 101 // Register transactional ConnectionProvider for Quartz. 102 DBConnectionManager.getInstance().addConnectionProvider( 103 TX_DATA_SOURCE_PREFIX + getInstanceName(), 104 new ConnectionProvider() { 105 @Override 106 public Connection getConnection() throws SQLException { 107 // Return a transactional Connection, if any. 108 return DataSourceUtils.doGetConnection(dataSource); 109 } 110 @Override 111 public void shutdown() { 112 // Do nothing - a Spring-managed DataSource has its own lifecycle. 113 } 114 @Override 115 public void initialize() { 116 // Do nothing - a Spring-managed DataSource has its own lifecycle. 117 } 118 } 119 ); 120 121 // Non-transactional DataSource is optional: fall back to default 122 // DataSource if not explicitly specified. 123 DataSource nonTxDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource(); 124 final DataSource nonTxDataSourceToUse = (nonTxDataSource != null ? nonTxDataSource : this.dataSource); 125 126 // Configure non-transactional connection settings for Quartz. 127 setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName()); 128 129 // Register non-transactional ConnectionProvider for Quartz. 130 DBConnectionManager.getInstance().addConnectionProvider( 131 NON_TX_DATA_SOURCE_PREFIX + getInstanceName(), 132 new ConnectionProvider() { 133 @Override 134 public Connection getConnection() throws SQLException { 135 // Always return a non-transactional Connection. 136 return nonTxDataSourceToUse.getConnection(); 137 } 138 @Override 139 public void shutdown() { 140 // Do nothing - a Spring-managed DataSource has its own lifecycle. 141 } 142 @Override 143 public void initialize() { 144 // Do nothing - a Spring-managed DataSource has its own lifecycle. 145 } 146 } 147 ); 148 149 // No, if HSQL is the platform, we really don't want to use locks... 150 try { 151 String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource, 152 DatabaseMetaData::getDatabaseProductName); 153 productName = JdbcUtils.commonDatabaseName(productName); 154 if (productName != null && productName.toLowerCase().contains("hsql")) { 155 setUseDBLocks(false); 156 setLockHandler(new SimpleSemaphore()); 157 } 158 } 159 catch (MetaDataAccessException ex) { 160 logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken."); 161 } 162 163 super.initialize(loadHelper, signaler); 164 165 } 166 167 @Override 168 protected void closeConnection(Connection con) { 169 // Will work for transactional and non-transactional connections. 170 DataSourceUtils.releaseConnection(con, this.dataSource); 171 } 172 173}