diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java index a66e247cc55..139febeb7d2 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java @@ -23,12 +23,15 @@ 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; /** * Subclass of Quartz's JobStoreCMT class that delegates to a Spring-managed @@ -131,7 +134,22 @@ public class LocalDataSourceJobStore extends JobStoreCMT { } ); + // No, if HSQL is the platform, we really don't want to use locks + try { + String productName = JdbcUtils.extractDatabaseMetaData(dataSource, + "getDatabaseProductName").toString(); + productName = JdbcUtils.commonDatabaseName(productName); + if (productName != null + && productName.toLowerCase().contains("hsql")) { + setUseDBLocks(false); + setLockHandler(new SimpleSemaphore()); + } + } catch (MetaDataAccessException e) { + logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken."); + } + super.initialize(loadHelper, signaler); + } @Override diff --git a/org.springframework.context.support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java b/org.springframework.context.support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java index 3550cd0cfdf..970687efa04 100644 --- a/org.springframework.context.support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java +++ b/org.springframework.context.support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java @@ -27,6 +27,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.sql.DataSource; + import junit.framework.TestCase; import org.easymock.MockControl; import org.junit.Test; @@ -54,6 +56,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.io.FileSystemResourceLoader; import org.springframework.core.task.TaskExecutor; +import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import org.springframework.scheduling.TestMethodInvokingTask; /** @@ -976,6 +979,25 @@ public class QuartzSupportTests { ctx.close(); } + // SPR-6038: detect HSQL and stop illegal locks being taken + @Test + public void testSchedulerWithHsqlDataSource() throws Exception { + DummyJob.param = 0; + DummyJob.count = 0; + + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( + "/org/springframework/scheduling/quartz/databasePersistence.xml"); + SimpleJdbcTemplate jdbcTemplate = new SimpleJdbcTemplate(ctx.getBean(DataSource.class)); + assertTrue("No triggers were persisted", jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").size()>0); + Thread.sleep(3000); + try { + // assertEquals(10, DummyJob.param); + assertTrue(DummyJob.count > 0); + } finally { + ctx.close(); + } + + } private static class TestSchedulerListener implements SchedulerListener { diff --git a/org.springframework.context.support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml b/org.springframework.context.support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml new file mode 100644 index 00000000000..f3fe1a38a27 --- /dev/null +++ b/org.springframework.context.support/src/test/resources/org/springframework/scheduling/quartz/databasePersistence.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.context.support/src/test/resources/org/springframework/scheduling/quartz/quartz-hsql.sql b/org.springframework.context.support/src/test/resources/org/springframework/scheduling/quartz/quartz-hsql.sql new file mode 100644 index 00000000000..33fb1e7e767 --- /dev/null +++ b/org.springframework.context.support/src/test/resources/org/springframework/scheduling/quartz/quartz-hsql.sql @@ -0,0 +1,152 @@ +DROP TABLE qrtz_locks IF EXISTS; +DROP TABLE qrtz_scheduler_state IF EXISTS; +DROP TABLE qrtz_fired_triggers IF EXISTS; +DROP TABLE qrtz_paused_trigger_grps IF EXISTS; +DROP TABLE qrtz_calendars IF EXISTS; +DROP TABLE qrtz_trigger_listeners IF EXISTS; +DROP TABLE qrtz_blob_triggers IF EXISTS; +DROP TABLE qrtz_cron_triggers IF EXISTS; +DROP TABLE qrtz_simple_triggers IF EXISTS; +DROP TABLE qrtz_triggers IF EXISTS; +DROP TABLE qrtz_job_listeners IF EXISTS; +DROP TABLE qrtz_job_details IF EXISTS; + +CREATE TABLE qrtz_job_details +( +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +JOB_CLASS_NAME VARCHAR(250) NOT NULL, +IS_DURABLE VARCHAR(1) NOT NULL, +IS_VOLATILE VARCHAR(1) NOT NULL, +IS_STATEFUL VARCHAR(1) NOT NULL, +REQUESTS_RECOVERY VARCHAR(1) NOT NULL, +JOB_DATA BINARY NULL, +PRIMARY KEY (JOB_NAME,JOB_GROUP) +); + +CREATE TABLE qrtz_job_listeners +( +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +JOB_LISTENER VARCHAR(200) NOT NULL, +PRIMARY KEY (JOB_NAME,JOB_GROUP,JOB_LISTENER), +FOREIGN KEY (JOB_NAME,JOB_GROUP) +REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP) +); + +CREATE TABLE qrtz_triggers +( +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +IS_VOLATILE VARCHAR(1) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +NEXT_FIRE_TIME NUMERIC(13) NULL, +PREV_FIRE_TIME NUMERIC(13) NULL, +PRIORITY INTEGER NULL, +TRIGGER_STATE VARCHAR(16) NOT NULL, +TRIGGER_TYPE VARCHAR(8) NOT NULL, +START_TIME NUMERIC(13) NOT NULL, +END_TIME NUMERIC(13) NULL, +CALENDAR_NAME VARCHAR(200) NULL, +MISFIRE_INSTR NUMERIC(2) NULL, +JOB_DATA BINARY NULL, +PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (JOB_NAME,JOB_GROUP) +REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP) +); + +CREATE TABLE qrtz_simple_triggers +( +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +REPEAT_COUNT NUMERIC(7) NOT NULL, +REPEAT_INTERVAL NUMERIC(12) NOT NULL, +TIMES_TRIGGERED NUMERIC(7) NOT NULL, +PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE qrtz_cron_triggers +( +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +CRON_EXPRESSION VARCHAR(120) NOT NULL, +TIME_ZONE_ID VARCHAR(80), +PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE qrtz_blob_triggers +( +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +BLOB_DATA BINARY NULL, +PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE qrtz_trigger_listeners +( +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +TRIGGER_LISTENER VARCHAR(200) NOT NULL, +PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_LISTENER), +FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP) +); + +CREATE TABLE qrtz_calendars +( +CALENDAR_NAME VARCHAR(200) NOT NULL, +CALENDAR BINARY NOT NULL, +PRIMARY KEY (CALENDAR_NAME) +); + +CREATE TABLE qrtz_paused_trigger_grps +( +TRIGGER_GROUP VARCHAR(200) NOT NULL, +PRIMARY KEY (TRIGGER_GROUP) +); + +CREATE TABLE qrtz_fired_triggers +( +ENTRY_ID VARCHAR(95) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +IS_VOLATILE VARCHAR(1) NOT NULL, +INSTANCE_NAME VARCHAR(200) NOT NULL, +FIRED_TIME NUMERIC(13) NOT NULL, +PRIORITY INTEGER NOT NULL, +STATE VARCHAR(16) NOT NULL, +JOB_NAME VARCHAR(200) NULL, +JOB_GROUP VARCHAR(200) NULL, +IS_STATEFUL VARCHAR(1) NULL, +REQUESTS_RECOVERY VARCHAR(1) NULL, +PRIMARY KEY (ENTRY_ID) +); + +CREATE TABLE qrtz_scheduler_state +( +INSTANCE_NAME VARCHAR(200) NOT NULL, +LAST_CHECKIN_TIME NUMERIC(13) NOT NULL, +CHECKIN_INTERVAL NUMERIC(13) NOT NULL, +PRIMARY KEY (INSTANCE_NAME) +); + +CREATE TABLE qrtz_locks +( +LOCK_NAME VARCHAR(40) NOT NULL, +PRIMARY KEY (LOCK_NAME) +); + +INSERT INTO qrtz_locks values('TRIGGER_ACCESS'); +INSERT INTO qrtz_locks values('JOB_ACCESS'); +INSERT INTO qrtz_locks values('CALENDAR_ACCESS'); +INSERT INTO qrtz_locks values('STATE_ACCESS'); +INSERT INTO qrtz_locks values('MISFIRE_ACCESS');