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