Quartz

This add-on allows you to manage Quartz jobs via user interface:

  • Create new Quartz jobs from the existing implementation of org.quartz.Job interface.

  • Pause and resume jobs execution.

  • Trigger immediate execution of inactive jobs.

  • Edit job triggers and parameters.

  • Delete jobs.

The Quartz management user interface can be accessed through the Quartz → Quartz jobs main menu item.

The add-on also provides Liquibase changelogs for automatic creation of the database schema required by Quartz.

Installation

For automatic installation through Jmix Marketplace, follow instructions in the Add-ons section.

For manual installation, add the following dependencies to your build.gradle:

implementation 'io.jmix.quartz:jmix-quartz-starter'
implementation 'io.jmix.quartz:jmix-quartz-flowui-starter'

If URL of your main database is set as variable replacing the whole URL (e.g. main.datasource.url = ${DB_URL}) you may need to explicitly set the proper driver delegate class via spring.quartz.properties.org.quartz.jobStore.driverDelegateClass application property.

Value depends on your database:

  • PostgreSQL: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate

  • Oracle: org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

  • MS SQL: org.quartz.impl.jdbcjobstore.MSSQLDelegate

  • Other database: skip this step - default value will be used

If variable within main.datasource.url property doesn’t include database prefix (e.g. main.datasource.url = jdbc:postgresql://${DB_HOST}/${DB_NAME}) or there are no variables at all – driver delegate class will be resolved automatically and this step can be skipped.

Important Note for Single Menu Mode Applications

If your application uses Single menu mode, you should manually add menu items after installing an add-on.

  1. Open the Menu Designer in Jmix Studio.

  2. Navigate to the Structure tab.

  3. Available menu items are displayed in the left panel.

    add menu items
  4. To add items:

    • Select the desired items and click the > (Add Selected) button.

    • Or, drag-and-drop them from the left panel into the current menu structure on the right.

This step is required because applications in Single menu mode do not automatically merge add-on menus with the existing structure.

Usage

To create and schedule a job for execution, do the following:

  1. Create a class implementing the org.quartz.Job interface. It’s execute() method will be invoked by the scheduler. For example:

    package com.company.demo.quartz;
    
    import org.quartz.Job;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class SampleJob implements Job {
    
        private static final Logger log = LoggerFactory.getLogger(SampleJob.class);
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            log.info("Sample job is executed");
        }
    }

    In the job class, you can inject Spring beans with @Autowired.

  2. Run the application, open Quartz → Quartz jobs view and click Create.

    • Enter an arbitrary unique name in the Name field and select your job class in the Class field.

      The Group field is optional but can be used to group jobs in UI.

    • Create at least one trigger for the job on the Triggers tab. You can select either Cron expression or Simple schedule type. In the former case, enter a Cron expression like 0/5 * * ? * * * (each 5 sec). In the latter case, enter a repeat interval in milliseconds.

    After you save the trigger and the job, it is immediately scheduled for execution according to the trigger settings.

    To edit the job settings, first pause it by selecting and clicking Pause. After saving changes, you can resume the scheduling by clicking Resume.

    You can also execute any registered job immediately by clicking Execute now, even if the job has no triggers. This is useful for testing.

Authentication in Jobs

The code executed by the scheduler is not authenticated, that is not associated with any user.

If you invoke operations that require authentication, for example working with data through DataManager, use SystemAuthenticator or @Authenticated annotation:

public class SampleAuthenticatedJob implements Job {

    private static final Logger log = LoggerFactory.getLogger(SampleAuthenticatedJob.class);

    @Autowired
    private DataManager dataManager;

    @Authenticated
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        int usersCount = dataManager.load(User.class).all().list().size();
        log.info("There are {} registered users", usersCount);
    }
}
You can also use UnconstrainedDataManager to work with data in unauthenticated contexts.

Job Parameters

The Job editor view allows you to set parameters for the job instance on the Job data parameters tab. You can use the parameters in the job class as follows:

public class SampleParameterizedJob implements Job {

    private static final Logger log = LoggerFactory.getLogger(SampleParameterizedJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        String paramStr = jobDataMap.entrySet().stream()
                .map(e -> e.getKey() + " : " + e.getValue())
                .collect(Collectors.joining(", ", "[", "]"));
        log.info("Sample job is executed with parameters: " + paramStr);
    }
}

Tracking Job Execution

If you want to track execution of jobs, create a bean implementing the JobListener interface or extending the JobListenerSupport as follows:

package com.company.demo.quartz;

import jakarta.annotation.PostConstruct;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.listeners.JobListenerSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class JobExecutionListener extends JobListenerSupport {

    private static final Logger log = LoggerFactory.getLogger(JobExecutionListener.class);

    @Autowired
    private Scheduler scheduler;

    @Override
    public String getName() {
        return "SampleJobExecutionListener";
    }

    @PostConstruct
    private void registerListener() { (1)
        try {
            scheduler.getListenerManager().addJobListener(this);
        } catch (SchedulerException e) {
            log.error("Cannot register job listener", e);
        }
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context,
                               JobExecutionException jobException) { (2)
        log.info("jobWasExecuted: name={}, context={}",
                context.getJobDetail().getKey().getName(), context);
    }
}
1 Invoked by Spring after the bean is instantiated.
2 Invoked by Quartz scheduler after a job is executed.

In the jobWasExecuted() method, you can store information about the executed job in the log or in the database.