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}