1 package com.atlassian.scheduler.quartz2;
2
3 import org.junit.Before;
4 import org.junit.Rule;
5 import org.junit.Test;
6 import org.junit.runner.RunWith;
7 import org.mockito.Mock;
8 import org.mockito.invocation.InvocationOnMock;
9 import org.mockito.junit.MockitoJUnit;
10 import org.mockito.junit.MockitoRule;
11 import org.mockito.runners.MockitoJUnitRunner;
12 import org.mockito.stubbing.Answer;
13 import org.quartz.JobDetail;
14 import org.quartz.JobKey;
15 import org.quartz.JobPersistenceException;
16 import org.quartz.TriggerKey;
17 import org.quartz.impl.jdbcjobstore.DriverDelegate;
18 import org.quartz.impl.jdbcjobstore.NoSuchDelegateException;
19 import org.quartz.spi.ClassLoadHelper;
20 import org.quartz.spi.OperableTrigger;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 import javax.annotation.Nullable;
25 import java.sql.Connection;
26 import java.util.concurrent.atomic.AtomicInteger;
27
28 import static java.util.Arrays.asList;
29 import static org.hamcrest.Matchers.containsString;
30 import static org.junit.Assert.assertEquals;
31 import static org.junit.Assert.assertThat;
32 import static org.junit.Assert.fail;
33 import static org.mockito.Matchers.any;
34 import static org.mockito.Matchers.eq;
35 import static org.mockito.Mockito.when;
36 import static org.quartz.JobKey.jobKey;
37 import static org.quartz.TriggerKey.triggerKey;
38 import static org.quartz.impl.jdbcjobstore.Constants.STATE_MISFIRED;
39
40 @RunWith(MockitoJUnitRunner.class)
41 public class Quartz2HardenedJobStoreTest {
42 static final Logger LOG = LoggerFactory.getLogger(Quartz2HardenedJobStoreTest.class);
43
44 static final JobKey JOB1_KEY = jobKey("job1name", "job1group");
45 static final JobKey JOB2_KEY = jobKey("job2name", "job2group");
46 static final TriggerKey TRIGGER1_KEY = triggerKey("trigger1name", "trigger1group");
47 static final TriggerKey TRIGGER2_KEY = triggerKey("trigger2name", "trigger2group");
48
49 final AtomicInteger callCount = new AtomicInteger();
50 final AtomicInteger throwCount = new AtomicInteger();
51 final AtomicInteger escapeCount = new AtomicInteger();
52
53 @Rule
54 public MockitoRule mockitoRule = MockitoJUnit.rule();
55
56 @Mock
57 Connection conn;
58 @Mock
59 DriverDelegate delegate;
60 @Mock
61 OperableTrigger trigger1;
62 @Mock
63 OperableTrigger trigger2;
64 @Mock
65 JobDetail job1;
66 @Mock
67 JobDetail job2;
68
69 @Before
70 public void setUp() throws Exception {
71 when(trigger1.getJobKey()).thenReturn(JOB1_KEY);
72 when(trigger2.getJobKey()).thenReturn(JOB2_KEY);
73 when(trigger1.getKey()).thenReturn(TRIGGER1_KEY);
74 when(trigger2.getKey()).thenReturn(TRIGGER2_KEY);
75
76 when(delegate.selectTriggersForRecoveringJobs(conn)).thenReturn(asList(trigger1, trigger2));
77 when(delegate.selectJobDetail(eq(conn), eq(JOB1_KEY), any(ClassLoadHelper.class))).thenAnswer(new Answer<JobDetail>() {
78 @Override
79 public JobDetail answer(InvocationOnMock invocation) throws Throwable {
80 throwCount.incrementAndGet();
81 throw new ClassNotFoundException("He's DEAD, Jim!");
82 }
83 });
84 when(delegate.selectJobDetail(eq(conn), eq(JOB2_KEY), any(ClassLoadHelper.class)))
85 .thenReturn(job2);
86 }
87
88 @Test
89 public void testStoreTriggerThrowsExceptionOutsideOfRecovery() throws JobPersistenceException {
90 final Fixture fixture = new Fixture();
91 try {
92 fixture.storeTrigger(conn, trigger1, null, true, STATE_MISFIRED, true, true);
93 fail("Should have thrown JobPersistenceException");
94 } catch (JobPersistenceException jpe) {
95 assertThat(jpe.getMessage(), containsString("He's DEAD, Jim!"));
96 assertEquals("callCount", 1, callCount.get());
97 assertEquals("throwCount", 1, throwCount.get());
98 assertEquals("escapeCount", 1, escapeCount.get());
99 }
100 }
101
102 @Test
103 public void testStoreTriggerDoesNotThrowExceptionFromRecovery() throws Exception {
104 final Fixture fixture = new Fixture();
105 fixture.recoverJobs();
106 assertEquals("callCount", 2, callCount.get());
107 assertEquals("throwCount", 1, throwCount.get());
108 assertEquals("escapeCount", 0, escapeCount.get());
109 }
110
111
112 class Fixture extends Quartz2HardenedJobStore {
113
114 @Override
115 protected DriverDelegate getDelegate() throws NoSuchDelegateException {
116 return delegate;
117 }
118
119 @Override
120 protected Object executeInLock(String lockName, TransactionCallback txCallback) throws JobPersistenceException {
121 return txCallback.execute(conn);
122 }
123
124 @Override
125 protected void executeInNonManagedTXLock(
126 final String lockName,
127 final VoidTransactionCallback txCallback) throws JobPersistenceException {
128 txCallback.execute(conn);
129 }
130
131 @Override
132 protected boolean jobExists(Connection conn, JobKey jobKey) throws JobPersistenceException {
133 return true;
134 }
135
136 @Override
137 protected Logger getLog() {
138 return LOG;
139 }
140
141 @Override
142 protected void storeTrigger(Connection conn, OperableTrigger newTrigger, @Nullable JobDetail job,
143 boolean replaceExisting, String state, boolean forceState, boolean recovering)
144 throws JobPersistenceException {
145 callCount.incrementAndGet();
146 boolean ok = false;
147 try {
148 super.storeTrigger(conn, newTrigger, job, replaceExisting, state, forceState, recovering);
149 ok = true;
150 } finally {
151 if (!ok) {
152 escapeCount.incrementAndGet();
153 }
154 }
155 }
156 }
157 }