This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.
Batchjob¶
A batchjob is a job executed in regular intervals.
A minimalistic code example where just a service is called:
@BatchJob(id = "MyBatchJob", schedule = "0 0 * * ?")
@DisallowConcurrentExecution
public class MyBatchJob extends AbstractJob {
private final MyService service;
public MyBatchJob(MyService service) {
this.service = service;
}
@Override
protected void doExecute(JobExecutionContext context, JobDataMapReader jobDataMapReader) throws JobExecutionException {
service.doSomething();
}
}
The class must extend the AbstractJob
and be annoated with @BatchJob
and @DisallowConcurrentExecution
(prevents concurrent execution of a job).
If the logic is not too complex it’s normally directly implemented in the batchjob. As an example:
@BatchJob(id = "DeletionCleanupBatchjob", description = "cleanup old 'deletion' entities", schedule = "0 2 * * ?")
@DisallowConcurrentExecution
public class DeletionCleanupBatchjob extends AbstractJob {
private static final int EXPIRY_DAYS = 30;
private final Context context;
public DeletionCleanupBatchjob(Context context) {
this.context = context;
}
@Override
protected void doExecute(JobExecutionContext context, JobDataMapReader jobDataMapReader) {
DateTime oneMonthAgo = DateTime.now().minusDays(EXPIRY_DAYS);
securityManager.privileged().andThen(context.tx()).invokeRTE(() -> {
context.getEntityManager("Deletion").delete(
field("relBulk_deletion.created").lowerThan(oneMonthAgo));
context.getEntityManager("Bulk_deletion").delete(
field("created").lowerThan(oneMonthAgo));
return null;
});
}
}
Warning
If a batchjob is manual triggered via action on the batchjob entity, the batchjob is executed as the current user. However if a batchjob is triggered by the system it is executed without a login. So often it is necessary to run some code privileged.
Batchjob annotation¶
id: unique name normally just the class name is used
schedule: when to run the batchjob, format is
minutes hours day-of-month month day-of-week
description (optional): an optional description of what a batchjob does
active (optional): per default a batchjob is active. However a batchjob can be disabled here and can be manually enabled via the batchjob entity in the admin interface
Schedule format¶
The format is similar to the unix cron job syntax. The format is minutes hours day-of-month month day-of-week
.
However it is necessary to either set day-of-week
or day-of-month
to ?
(they cannot both be *
).
For example to run a job every 5 minutes the following string would be used */5 * * * ?
.
See Quartz Website for more detailed syntax explanations.
Testing¶
The EasyBatchjobTestCase
adds Batchjob specific testing utilities to the EasyTestCase.
Mocked task queue (Default case)¶
Normally not a real Quartz instance is required to execute the batchjob. Here an example test for DeletionCleanupBatchjob
:
public class DeletionCleanupBatchjobTest extends EasyBatchjobTestCase<DeletionCleanupBatchjob> {
// ...
@Override
protected DeletionCleanupBatchjob instantiateClassToTest() {
return new DeletionCleanupBatchjob(context);
}
@Test
public void testBatchjob() {
Entity bulkDeletion = createEntity("Bulk_deletion", builder -> builder
.field("created", DateTime.now().minusDays(31))
.setRelatedLookupEntity("relBulk_deletion_status", "done"));
Entity deletion = createDeletion(bulkDeletion);
runBatchjob();
assertEquals(bulkDeletion.getState(), Entity.State.PHANTOM);
assertEquals(deletion.getState(), Entity.State.PHANTOM);
}
}
First the entities should be created.
Next the batchjob should be executed using the runBatchjob()
method.
If a job is executed via TaskSchedulingService
(e.g. anonymize action starts task) you can pass the job data via runBatchjob(JobDataMapBuilder jobDataMapBuilder)
.
Verify if the entities are correctly modified.
Non-mocked task queue¶
If an actual running Quartz instance is required the TaskSchedulingModule
can be used:
@Resource
private SchedulerTestHelper schedulerTestHelper;
@Override
protected void setupTestModules() {
install(new TaskSchedulingModule(new JobFactorySupplier() {
@Override
protected AbstractJob initializeJob() {
return new EndlessJob();
}
}));
//install other modules and data model
}
@Test
public void cancelJob() throws Exception {
schedulerTestHelper.doWithScheduler(handler -> {
JobKey jobKey = taskSchedulingService.executeJobImmediately(...);
handler.waitUntilJobHasStarted(jobKey, 60 * 1000);
...
handler.waitUntilJobIsCompleted(jobKey, 60 * 1000);
}
}
The injected SchedulerTestHelper
can be used to startup/shutdown the scheduler and block until a job has started/completed.