How do you write a Job-Config for Jira?

I’m trying to write a plugin containing a scheduled job for Jira according to this page:
https://developer.atlassian.com/server/confluence/job-config-module/

Plugin XML is:

	<!-- Scheduled Job -->
	<job-config name="Test Job Config" key="TestJobConfig">
		<job key="testJob" perClusterJob="false" clusteredOnly="false" />
		<schedule cron-expression="0 * * * * ?" jitterSecs="10" />
		<managed editable="true" keepingHistory="true" canRunAdhoc="true" canDisable="true" />
	</job-config>

With the JobRunner class like this:

import javax.inject.Named;
@Named
public class TestJob implements JobRunner {

	@Override
	public JobRunnerResponse runJob(JobRunnerRequest request) {
		try {
			return JobRunnerResponse.success("Done!");
		} catch (Exception ex) {
			return JobRunnerResponse.failed(ex);
		}
	}

}

But the result is the module is not enabled:


I cannot find any information about why it is disabled in the logs.

An old thread from 2019 said you need to implement a LifeCycleAware class:

But that’s not included in the job-config documentation. I tried to implement it anyway, but found only LifeCycleAware interface is available, the other one NotificationScheduler isn’t. I implemented LifecycleAware and it has no effect. The old post did not mention any configuration to go with it.

How do you implement a job-config for Jira?

Also, the job-config documentation only mentions Confluence. What about Jira? Once the module is enabled, will the job shows up in Gear | System | Scheduler details | Jobs?

How do I implement a JobRunner in plugin so that it shows up here?

Can I get a confirmation that job-config is for Confluence only? Jira cannot use it so that’s why the module is disabled?

Is there a way to add scheduled job for Jira?

Hello! The best documentation I can find is here: https://developer.atlassian.com/server/jira/platform/developing-for-high-availability-and-clustering/

I think this is up to date and correct. I copied an example from a plugin we use inside my company:

package com.mycompany;

import com.atlassian.scheduler.*;
import com.atlassian.scheduler.config.JobConfig;
import com.atlassian.scheduler.config.JobId;
import com.atlassian.scheduler.config.JobRunnerKey;
import com.atlassian.scheduler.config.Schedule;
import org.joda.time.DateTime;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Date;

import static com.atlassian.scheduler.config.JobConfig.forJobRunnerKey;
import static com.atlassian.scheduler.config.RunMode.RUN_ONCE_PER_CLUSTER;
import static java.util.concurrent.TimeUnit.DAYS;

/**
 * This is a class I deleted a bunch of stuff from to show as an example. It registers a 
 * particular type of scheduled task in Jira.
 */
@Named
public class MyClassChangeMe implements JobRunner, InitializingBean, DisposableBean {
    private final static String IDENTIFIER = MyClassChangeMe.class.getName();
    private final static JobRunnerKey JOB_RUNNER_KEY = JobRunnerKey.of(IDENTIFIER);
    private final static JobId JOB_ID = JobId.of(IDENTIFIER);

	@ComponentImport
	private final SchedulerService schedulerService;

    @Inject
    public MyClassChangeMe(SchedulerService schedulerService) {
        this.schedulerService = schedulerService;
    }

    @Nullable
    @Override
    public JobRunnerResponse runJob(@Nonnull JobRunnerRequest jobRunnerRequest) {
        return JobRunnerResponse.success("Success");
    }

    @Override
    public void destroy() throws Exception {
        schedulerService.unscheduleJob(JOB_ID);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        schedulerService.registerJobRunner(JOB_RUNNER_KEY, this);
        final JobConfig configuration = forJobRunnerKey(JOB_RUNNER_KEY)
                .withRunMode(RUN_ONCE_PER_CLUSTER)
                .withSchedule(Schedule.forInterval(DAYS.toMillis(1), new DateTime()
                	.withTimeAtStartOfDay()
                	.plusDays(1)
                	.plusMinutes(30)
                	.toDate()));

        try {
            schedulerService.scheduleJob(JOB_ID, configuration);
        } catch (SchedulerServiceException e) {
            e.printStackTrace();
        }
    }
}
1 Like

Thank you very much @steve.behnke!

I have a class implementing LifecycleAware and annotated:

@ExportAsService({ LifecycleAware.class })
@Named
@Component
public class TestJobSchedule implements LifecycleAware {

It was fired but it failed to register a scheduled job, because I wasn’t doing the right thing.

In its onStart() method, I was doing this:

this.schedulerService.registerJobRunner(JobRunnerKey.of("test"), new TestJob());

And then I tried to list the registered items but could not see my new addition.

With your example, now I can see the job listed in Jira’s system page.

I managed to schedule and run my JobRunner class successfully now, but I’ve ran into a new problem.

When the JobRunner class is executed, it does not have access to the other classes in the plugin:

Caused by: java.lang.ClassNotFoundException: Unable to load class 'com.igsl.ldapuserattributes.LDAPUserAttributes' because the bundle wiring for com.igsl.LDAPUserAttributes is no longer valid.

Edit: It might actually be this problem… not my plugin class LDAPUserAttributes, but rather the LDAP context factory class.

How do I add the classes in my plugin to the JobRunner’s classpath?

Edit:
It is related to OSGI:
https://jira.atlassian.com/browse/JRASERVER-29896

So the workaround is to temporarily switch context class loader for the current thread to the system class loader, where the Sun class is available.

1 Like

Is it possible to make the job editable in Jira?

e.g. Allow admin to run it manually, or to edit its schedule.

I know Confluence has a mechanism to manually execute jobs or change them, but I don’t believe Jira has similar capabilities. You could perhaps extract your work to a separate service and invoke it separately from your own page, as well as run it in the scheduled job.

Edit: I could be wrong, but I am not aware of a way to do it and searching didn’t show anything obvious to me.

1 Like

Thanks. I’ll add a configuration page to my plugin and do the job registration there then.

1 Like