View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.scheduling.quartz;
18  
19  import java.sql.Connection;
20  import java.sql.SQLException;
21  import javax.sql.DataSource;
22  
23  import org.quartz.SchedulerConfigException;
24  import org.quartz.impl.jdbcjobstore.JobStoreCMT;
25  import org.quartz.impl.jdbcjobstore.SimpleSemaphore;
26  import org.quartz.spi.ClassLoadHelper;
27  import org.quartz.spi.SchedulerSignaler;
28  import org.quartz.utils.ConnectionProvider;
29  import org.quartz.utils.DBConnectionManager;
30  
31  import org.springframework.jdbc.datasource.DataSourceUtils;
32  import org.springframework.jdbc.support.JdbcUtils;
33  import org.springframework.jdbc.support.MetaDataAccessException;
34  
35  /**
36   * Subclass of Quartz's JobStoreCMT class that delegates to a Spring-managed
37   * DataSource instead of using a Quartz-managed connection pool. This JobStore
38   * will be used if SchedulerFactoryBean's "dataSource" property is set.
39   *
40   * <p>Supports both transactional and non-transactional DataSource access.
41   * With a non-XA DataSource and local Spring transactions, a single DataSource
42   * argument is sufficient. In case of an XA DataSource and global JTA transactions,
43   * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
44   * passing in a non-XA DataSource that will not participate in global transactions.
45   *
46   * <p>Operations performed by this JobStore will properly participate in any
47   * kind of Spring-managed transaction, as it uses Spring's DataSourceUtils
48   * connection handling methods that are aware of a current transaction.
49   *
50   * <p>Note that all Quartz Scheduler operations that affect the persistent
51   * job store should usually be performed within active transactions,
52   * as they assume to get proper locks etc.
53   *
54   * @author Juergen Hoeller
55   * @since 1.1
56   * @see SchedulerFactoryBean#setDataSource
57   * @see SchedulerFactoryBean#setNonTransactionalDataSource
58   * @see org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
59   * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
60   */
61  @SuppressWarnings("unchecked")  // due to a warning in Quartz 2.2's JobStoreCMT
62  public class LocalDataSourceJobStore extends JobStoreCMT {
63  
64  	/**
65  	 * Name used for the transactional ConnectionProvider for Quartz.
66  	 * This provider will delegate to the local Spring-managed DataSource.
67  	 * @see org.quartz.utils.DBConnectionManager#addConnectionProvider
68  	 * @see SchedulerFactoryBean#setDataSource
69  	 */
70  	public static final String TX_DATA_SOURCE_PREFIX = "springTxDataSource.";
71  
72  	/**
73  	 * Name used for the non-transactional ConnectionProvider for Quartz.
74  	 * This provider will delegate to the local Spring-managed DataSource.
75  	 * @see org.quartz.utils.DBConnectionManager#addConnectionProvider
76  	 * @see SchedulerFactoryBean#setDataSource
77  	 */
78  	public static final String NON_TX_DATA_SOURCE_PREFIX = "springNonTxDataSource.";
79  
80  
81  	private DataSource dataSource;
82  
83  
84  	@Override
85  	public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler)
86  			throws SchedulerConfigException {
87  
88  		// Absolutely needs thread-bound DataSource to initialize.
89  		this.dataSource = SchedulerFactoryBean.getConfigTimeDataSource();
90  		if (this.dataSource == null) {
91  			throw new SchedulerConfigException(
92  				"No local DataSource found for configuration - " +
93  				"'dataSource' property must be set on SchedulerFactoryBean");
94  		}
95  
96  		// Configure transactional connection settings for Quartz.
97  		setDataSource(TX_DATA_SOURCE_PREFIX + getInstanceName());
98  		setDontSetAutoCommitFalse(true);
99  
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 }