Skip to content

Commit 270867b

Browse files
committed
Overhaul MongoJobInstanceDao
1. Implement method `deleteJobInstance()` 2. Fix missing range check for `List.subList()` 3. Introduce MongoJobInstanceDaoIntegrationTests Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
1 parent 47aacca commit 270867b

File tree

2 files changed

+326
-1
lines changed

2 files changed

+326
-1
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/mongodb/MongoJobInstanceDao.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.batch.core.repository.dao.mongodb;
1717

18+
import java.util.Collections;
1819
import java.util.List;
1920

2021
import org.springframework.batch.core.job.DefaultJobKeyGenerator;
@@ -36,6 +37,7 @@
3637

3738
/**
3839
* @author Mahmoud Ben Hassine
40+
* @author Yanming Zhou
3941
* @since 5.2.0
4042
*/
4143
public class MongoJobInstanceDao implements JobInstanceDao {
@@ -117,7 +119,10 @@ public List<JobInstance> getJobInstances(String jobName, int start, int count) {
117119
org.springframework.batch.core.repository.persistence.JobInstance.class, COLLECTION_NAME)
118120
.stream()
119121
.toList();
120-
return jobInstances.subList(start, jobInstances.size())
122+
if (jobInstances.size() <= start) {
123+
return Collections.emptyList();
124+
}
125+
return jobInstances.subList(start, Math.min(jobInstances.size(), start + jobInstances.size()))
121126
.stream()
122127
.map(this.jobInstanceConverter::toJobInstance)
123128
.limit(count)
@@ -198,4 +203,9 @@ public long getJobInstanceCount(String jobName) throws NoSuchJobException {
198203
return this.mongoOperations.count(query, COLLECTION_NAME);
199204
}
200205

206+
@Override
207+
public void deleteJobInstance(JobInstance jobInstance) {
208+
this.mongoOperations.remove(query(where("jobInstanceId").is(jobInstance.getId())), COLLECTION_NAME);
209+
}
210+
201211
}
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
/*
2+
* Copyright 2008-2025 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+
* https://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+
package org.springframework.batch.core.repository.support;
17+
18+
import org.junit.jupiter.api.Disabled;
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.batch.core.job.DefaultJobKeyGenerator;
21+
import org.springframework.batch.core.job.JobExecution;
22+
import org.springframework.batch.core.job.JobInstance;
23+
import org.springframework.batch.core.job.JobKeyGenerator;
24+
import org.springframework.batch.core.job.parameters.JobParameters;
25+
import org.springframework.batch.core.job.parameters.JobParametersBuilder;
26+
import org.springframework.batch.core.repository.dao.JobExecutionDao;
27+
import org.springframework.batch.core.repository.dao.JobInstanceDao;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.test.util.ReflectionTestUtils;
30+
31+
import java.math.BigInteger;
32+
import java.nio.charset.StandardCharsets;
33+
import java.security.MessageDigest;
34+
import java.util.Date;
35+
import java.util.List;
36+
37+
import static org.junit.jupiter.api.Assertions.*;
38+
39+
/**
40+
* @author Yanming Zhou
41+
*/
42+
class MongoJobInstanceDaoIntegrationTests extends AbstractMongoDBDaoIntegrationTests {
43+
44+
@Autowired
45+
private JobInstanceDao dao;
46+
47+
private final JobParameters fooParams = new JobParametersBuilder().addString("stringKey", "stringValue")
48+
.addLong("longKey", Long.MAX_VALUE)
49+
.addDouble("doubleKey", Double.MAX_VALUE)
50+
.addDate("dateKey", new Date(DATE))
51+
.toJobParameters();
52+
53+
private static final long DATE = 777;
54+
55+
private final String fooJob = "foo";
56+
57+
@Test
58+
void testFindJobInstanceByExecution(@Autowired JobExecutionDao jobExecutionDao) {
59+
60+
JobParameters jobParameters = new JobParameters();
61+
JobInstance jobInstance = dao.createJobInstance("testInstance", jobParameters);
62+
JobExecution jobExecution = jobExecutionDao.createJobExecution(jobInstance, jobParameters);
63+
64+
JobInstance returnedInstance = dao.getJobInstance(jobExecution);
65+
assertEquals(jobInstance, returnedInstance);
66+
}
67+
68+
@Test
69+
void testHexing() throws Exception {
70+
MessageDigest digest = MessageDigest.getInstance("MD5");
71+
byte[] bytes = digest.digest("f78spx".getBytes(StandardCharsets.UTF_8));
72+
StringBuilder output = new StringBuilder();
73+
for (byte bite : bytes) {
74+
output.append(String.format("%02x", bite));
75+
}
76+
assertEquals(32, output.length(), "Wrong hash: " + output);
77+
String value = String.format("%032x", new BigInteger(1, bytes));
78+
assertEquals(32, value.length(), "Wrong hash: " + value);
79+
assertEquals(value, output.toString());
80+
}
81+
82+
@Disabled("Not supported yet")
83+
@Test
84+
void testJobInstanceWildcard() {
85+
dao.createJobInstance("anotherJob", new JobParameters());
86+
dao.createJobInstance("someJob", new JobParameters());
87+
88+
List<JobInstance> jobInstances = dao.getJobInstances("*Job", 0, 2);
89+
assertEquals(2, jobInstances.size());
90+
91+
for (JobInstance instance : jobInstances) {
92+
assertTrue(instance.getJobName().contains("Job"));
93+
}
94+
95+
jobInstances = dao.getJobInstances("Job*", 0, 2);
96+
assertTrue(jobInstances.isEmpty());
97+
}
98+
99+
@Test
100+
void testDeleteJobInstance() {
101+
// given
102+
JobInstance jobInstance = dao.createJobInstance("someTestInstance", new JobParameters());
103+
104+
// when
105+
dao.deleteJobInstance(jobInstance);
106+
107+
// then
108+
assertNull(dao.getJobInstance(jobInstance.getId()));
109+
}
110+
111+
@Test
112+
void testDefaultJobKeyGeneratorIsUsed() {
113+
JobKeyGenerator jobKeyGenerator = (JobKeyGenerator) ReflectionTestUtils.getField(dao, "jobKeyGenerator");
114+
assertNotNull(jobKeyGenerator);
115+
assertEquals(DefaultJobKeyGenerator.class, jobKeyGenerator.getClass());
116+
}
117+
118+
/*
119+
* Create and retrieve a job instance.
120+
*/
121+
122+
@Test
123+
void testCreateAndRetrieve() {
124+
125+
JobInstance fooInstance = dao.createJobInstance(fooJob, fooParams);
126+
assertEquals(fooJob, fooInstance.getJobName());
127+
128+
JobInstance retrievedInstance = dao.getJobInstance(fooJob, fooParams);
129+
assertNotNull(retrievedInstance);
130+
assertEquals(fooInstance, retrievedInstance);
131+
assertEquals(fooJob, retrievedInstance.getJobName());
132+
}
133+
134+
/*
135+
* Create and retrieve a job instance.
136+
*/
137+
138+
@Test
139+
void testCreateAndGetById() {
140+
141+
JobInstance fooInstance = dao.createJobInstance(fooJob, fooParams);
142+
assertEquals(fooJob, fooInstance.getJobName());
143+
144+
JobInstance retrievedInstance = dao.getJobInstance(fooInstance.getId());
145+
assertNotNull(retrievedInstance);
146+
assertEquals(fooInstance, retrievedInstance);
147+
assertEquals(fooJob, retrievedInstance.getJobName());
148+
}
149+
150+
/*
151+
* Create and retrieve a job instance.
152+
*/
153+
154+
@Test
155+
void testGetMissingById() {
156+
157+
JobInstance retrievedInstance = dao.getJobInstance(1111111L);
158+
assertNull(retrievedInstance);
159+
160+
}
161+
162+
/*
163+
* Create and retrieve a job instance.
164+
*/
165+
166+
@Test
167+
void testGetJobNames() {
168+
169+
testCreateAndRetrieve();
170+
List<String> jobNames = dao.getJobNames();
171+
assertFalse(jobNames.isEmpty());
172+
assertTrue(jobNames.contains(fooJob));
173+
174+
}
175+
176+
/**
177+
* Create and retrieve a job instance.
178+
*/
179+
180+
@Test
181+
void testGetLastInstances() {
182+
183+
testCreateAndRetrieve();
184+
185+
// unrelated job instance that should be ignored by the query
186+
dao.createJobInstance("anotherJob", new JobParameters());
187+
188+
// we need two instances of the same job to check ordering
189+
dao.createJobInstance(fooJob, new JobParameters());
190+
191+
List<JobInstance> jobInstances = dao.getJobInstances(fooJob, 0, 2);
192+
assertEquals(2, jobInstances.size());
193+
assertEquals(fooJob, jobInstances.get(0).getJobName());
194+
assertEquals(fooJob, jobInstances.get(1).getJobName());
195+
// assertEquals(Integer.valueOf(0), jobInstances.get(0).getVersion());
196+
// assertEquals(Integer.valueOf(0), jobInstances.get(1).getVersion());
197+
198+
assertTrue(jobInstances.get(0).getId() > jobInstances.get(1).getId(),
199+
"Last instance should be first on the list");
200+
201+
}
202+
203+
@Test
204+
void testGetLastInstance() {
205+
testCreateAndRetrieve();
206+
207+
// unrelated job instance that should be ignored by the query
208+
dao.createJobInstance("anotherJob", new JobParameters());
209+
210+
// we need two instances of the same job to check ordering
211+
dao.createJobInstance(fooJob, new JobParameters());
212+
213+
List<JobInstance> jobInstances = dao.getJobInstances(fooJob, 0, 2);
214+
assertEquals(2, jobInstances.size());
215+
JobInstance lastJobInstance = dao.getLastJobInstance(fooJob);
216+
assertNotNull(lastJobInstance);
217+
assertEquals(fooJob, lastJobInstance.getJobName());
218+
assertEquals(jobInstances.get(0), lastJobInstance, "Last instance should be first on the list");
219+
}
220+
221+
@Test
222+
void testGetLastInstanceWhenNoInstance() {
223+
JobInstance lastJobInstance = dao.getLastJobInstance("NonExistingJob");
224+
assertNull(lastJobInstance);
225+
}
226+
227+
/**
228+
* Create and retrieve a job instance.
229+
*/
230+
231+
@Test
232+
void testGetLastInstancesPaged() {
233+
234+
testCreateAndRetrieve();
235+
236+
// unrelated job instance that should be ignored by the query
237+
dao.createJobInstance("anotherJob", new JobParameters());
238+
239+
// we need multiple instances of the same job to check ordering
240+
String multiInstanceJob = "multiInstanceJob";
241+
String paramKey = "myID";
242+
int instanceCount = 6;
243+
for (int i = 1; i <= instanceCount; i++) {
244+
JobParameters params = new JobParametersBuilder().addLong(paramKey, (long) i).toJobParameters();
245+
dao.createJobInstance(multiInstanceJob, params);
246+
}
247+
248+
int startIndex = 3;
249+
int queryCount = 2;
250+
List<JobInstance> jobInstances = dao.getJobInstances(multiInstanceJob, startIndex, queryCount);
251+
252+
assertEquals(queryCount, jobInstances.size());
253+
254+
for (int i = 0; i < queryCount; i++) {
255+
JobInstance returnedInstance = jobInstances.get(i);
256+
assertEquals(multiInstanceJob, returnedInstance.getJobName());
257+
// assertEquals(Integer.valueOf(0), returnedInstance.getVersion());
258+
259+
// checks the correct instances are returned and the order is descending
260+
// assertEquals(instanceCount - startIndex - i ,
261+
// returnedInstance.getJobParameters().getLong(paramKey));
262+
}
263+
264+
}
265+
266+
/**
267+
* Create and retrieve a job instance.
268+
*/
269+
270+
@Test
271+
void testGetLastInstancesPastEnd() {
272+
273+
testCreateAndRetrieve();
274+
275+
// unrelated job instance that should be ignored by the query
276+
dao.createJobInstance("anotherJob", new JobParameters());
277+
278+
// we need two instances of the same job to check ordering
279+
dao.createJobInstance(fooJob, new JobParameters());
280+
281+
assertEquals(1, dao.getJobInstances(fooJob, 0, 1).size());
282+
assertEquals(2, dao.getJobInstances(fooJob, 0, 2).size());
283+
assertEquals(2, dao.getJobInstances(fooJob, 0, 3).size());
284+
assertEquals(1, dao.getJobInstances(fooJob, 1, 3).size());
285+
assertEquals(0, dao.getJobInstances(fooJob, 0, 0).size());
286+
assertEquals(0, dao.getJobInstances(fooJob, 4, 2).size());
287+
288+
}
289+
290+
/**
291+
* Trying to create instance twice for the same job+parameters causes error
292+
*/
293+
294+
@Test
295+
void testCreateDuplicateInstance() {
296+
297+
dao.createJobInstance(fooJob, fooParams);
298+
299+
assertThrows(IllegalStateException.class, () -> dao.createJobInstance(fooJob, fooParams));
300+
}
301+
302+
@Disabled("Version is not persisted")
303+
@Test
304+
void testCreationAddsVersion() {
305+
306+
JobInstance jobInstance = new JobInstance(1L, "testVersionAndId");
307+
308+
assertNull(jobInstance.getVersion());
309+
310+
jobInstance = dao.createJobInstance("testVersion", new JobParameters());
311+
312+
assertNotNull(jobInstance.getVersion());
313+
}
314+
315+
}

0 commit comments

Comments
 (0)