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.