added java doc and some comments to scheduling module

This commit is contained in:
Sheldan
2020-05-26 13:50:48 +02:00
parent b554419381
commit 2aa55f6ab6
14 changed files with 197 additions and 18 deletions

View File

@@ -8,6 +8,9 @@ import org.springframework.stereotype.Component;
import java.util.HashMap;
/**
* Makes the job configuration in each of the property files accessible and usable. This causes the jobs to be automatically loaded and scheduled if they appear in a property file
*/
@Component
@Getter
@Setter

View File

@@ -11,6 +11,9 @@ import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.util.Properties;
/**
* Bean responsible to setup the scheduler factory, because we need a custom data source and the quartz support needs to be aware of the application context.
*/
@Configuration
public class SchedulerConfig {
@@ -34,6 +37,8 @@ public class SchedulerConfig {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setOverwriteExistingJobs(true);
factory.setDataSource(dataSource);
// we should not startup automatically, because some jobs rely on discord
// and they fail if the web socket connection is not yet established
factory.setAutoStartup(false);
factory.setQuartzProperties(properties);
factory.setJobFactory(jobFactory);

View File

@@ -3,6 +3,11 @@ package dev.sheldan.abstracto.scheduling.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* Loads the property file responsible to configure the scheduling application.
* This contains elements like database connection configuration, whether or not the tables should be created.
* or the amount of threads.
*/
@Configuration
@PropertySource("classpath:scheduling.properties")
public class SchedulingProperties {

View File

@@ -14,10 +14,24 @@ import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
/**
* Bean used to create the different types of jobs supported. The jobs include cron jobs and one-time jobs.
*/
@Component
@Slf4j
public class QuartzConfigFactory {
/**
* Creates a job according to the given configuration. This is the most detailed configuration. And is used when setting up the job on startup.
* @param jobClass The class object of the job to be executed. Needs to extend {@link QuartzJobBean}.
* @param isDurable Whether or not the job should be stored in the database, even though there are no triggers pointing to it. This is needed for
* one time jobs, because they might not have any immediate execution scheduled.
* @param context The spring application context for the job
* @param jobName The name of the job to be used for triggers in order to find the job
* @param jobGroup The group of the job to be used for triggers in order to find the job
* @param requestsRecovery Whether or not the job should be executed again, if the scheduling application crashes.
* @return The created description of the job according to the parameters
*/
public JobDetail createJob(Class<? extends QuartzJobBean> jobClass, boolean isDurable,
ApplicationContext context, String jobName, String jobGroup, boolean requestsRecovery) {
@@ -37,6 +51,13 @@ public class QuartzConfigFactory {
return factoryBean.getObject();
}
/**
* Creates a trigger for a cron job according to a given cron expression and schedules the job to be started at the given time.
* @param startTime The time the job should be active
* @param cronExpression The cron expression used for the job.
* @throws RuntimeException If the cron expression is not a valid expression.
* @return The {@link CronTrigger} representing the cron expression
*/
public CronTrigger createBasicCronTrigger(Date startTime, String cronExpression) {
return newTrigger()
.withSchedule(cronSchedule(cronExpression).inTimeZone(TimeZone.getTimeZone("UTC")).withMisfireHandlingInstructionDoNothing())
@@ -44,6 +65,16 @@ public class QuartzConfigFactory {
.build();
}
/**
* Creates a detailed cron trigger with the given cron expression and start date for a job specifically.
* It is also possible to directly provided parameters for the given job, which are then available within the job.
* @param jobName The name of the job to schedule
* @param jobGroup The group of the job to schedule
* @param startTime The start time at which the job should start to execute
* @param cronExpression The cron expression which represents at which times the job should execute
* @param jobDataMap The {@link JobDataMap} containing parameters available to the job
* @return The {@link CronTrigger} which can be used to directly schedule the job in the {@link Scheduler}
*/
public CronTrigger createBasicCronTrigger(String jobName, String jobGroup, Date startTime, String cronExpression, JobDataMap jobDataMap) {
return newTrigger()
.withSchedule(cronSchedule(cronExpression).inTimeZone(TimeZone.getTimeZone("UTC")).withMisfireHandlingInstructionDoNothing())
@@ -53,6 +84,11 @@ public class QuartzConfigFactory {
.build();
}
/**
* Creates a simple trigger, which executes exactly once at the given time
* @param startTime The {@link Date} object containing the time at which a job should execute at
* @return The {@link Trigger} object necessary in order to schedule a job
*/
public Trigger createSimpleOnceOnlyTrigger(Date startTime) {
return newTrigger()
.startAt(startTime)
@@ -60,6 +96,14 @@ public class QuartzConfigFactory {
.build();
}
/**
* Creates a simple one time trigger for a specific job with the provided parameters
* @param jobName The name of the job to execute
* @param jobGroup The group of the job to execute
* @param startTime The time at which the job should be executed
* @param jobDataMap The {@link JobDataMap} containing the parameters which should be available for the job
* @return The {@link Trigger} containing the given parameters, read to be scheduled with {@link Scheduler}
*/
public Trigger createOnceOnlyTriggerForJob(String jobName, String jobGroup, Date startTime, JobDataMap jobDataMap) {
return newTrigger()
.startAt(startTime)

View File

@@ -7,6 +7,11 @@ import org.springframework.stereotype.Component;
@Component
public class SchedulerJobConverter {
/**
* Converts a {@link SchedulerJobProperties} instance to a usable {@link SchedulerJob} instance
* @param properties The instance directly coming from a property file
* @return A instanc eof {@link SchedulerJob} which represents an instance from the database
*/
public SchedulerJob fromJobProperties(SchedulerJobProperties properties) {
return SchedulerJob
.builder()

View File

@@ -6,6 +6,9 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
/**
* Factory extension to make it possible to auto wire jobbeans
*/
public class SchedulerJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;

View File

@@ -3,16 +3,37 @@ package dev.sheldan.abstracto.scheduling.model;
import lombok.*;
/**
* The properties which are available to be configured via a property file
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SchedulerJobProperties {
/**
* The name of the job. Necessary to identify the job.
*/
private String name;
/**
* The group in which the job should reside. Necessary to identify the job.
*/
private String group;
/**
* If the job executes on a cron schedule, this should contain the cron expression for this. If it is a one-time job, this needs to be null.
*/
private String cronExpression;
/**
* The absolute class name of the job bean extending {@link org.springframework.scheduling.quartz.QuartzJobBean} which should be executed
*/
private String clazz;
/**
* Whether or not the job is active, and should be scheduled.
*/
private Boolean active;
/**
* Whether or not the job should be re-tried in an recovery of fail over situation.
*/
private Boolean recovery;
}

View File

@@ -3,14 +3,29 @@ package dev.sheldan.abstracto.scheduling.repository;
import dev.sheldan.abstracto.scheduling.model.database.SchedulerJob;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
/**
* Repository repsonsible to access the stored job configuration in the database
*/
@Repository
public interface SchedulerJobRepository extends JpaRepository<SchedulerJob, Long> {
/**
* Finds whether or not the job identified by the name exists in the database
* @param name The name of the job to check for existence
* @return Boolean variable representing whether or not the job identified by the name exists.
*/
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByName(String name);
/**
* Finds a job identified by the name
* @param name The name of the job to search for
* @return The found {@link SchedulerJob} instance by the name
*/
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
SchedulerJob findByName(String name);
}

View File

@@ -5,6 +5,9 @@ import org.quartz.spi.InstanceIdGenerator;
import java.util.UUID;
/**
* Generates the ID used for triggers etc. The implementation uses a {@link UUID} which is then stored and identifies the instance
*/
public class IdGenerationService implements InstanceIdGenerator {
@Override

View File

@@ -57,6 +57,8 @@ public class SchedulerServiceBean implements SchedulerService {
boolean recurringJob = isRecurringJob(schedulerJob);
jobDetail = scheduleCreator.createJob((Class<? extends QuartzJobBean>) Class.forName(schedulerJob.getClazz()),
!recurringJob, context, schedulerJob.getName(), schedulerJob.getGroupName(), schedulerJob.isRecovery());
// if its a cron job, we can schedule it directly, otherwise we just make the scheduler aware of its existance
// and trigger it later
if(recurringJob) {
Trigger trigger = scheduleCreator.createBasicCronTrigger(new Date(),
schedulerJob.getCronExpression());
@@ -95,11 +97,11 @@ public class SchedulerServiceBean implements SchedulerService {
}
@Override
public boolean unScheduleJob(String jobName) {
public boolean unScheduleJob(String triggerKey) {
try {
return schedulerFactoryBean.getScheduler().unscheduleJob(new TriggerKey(jobName));
return schedulerFactoryBean.getScheduler().unscheduleJob(new TriggerKey(triggerKey));
} catch (SchedulerException e) {
log.error("Failed to un-schedule job - {}", jobName, e);
log.error("Failed to un-schedule job - {}", triggerKey, e);
return false;
}
}
@@ -159,18 +161,6 @@ public class SchedulerServiceBean implements SchedulerService {
}
}
@Override
public String startCronJobWithParameters(String name, String group, JobDataMap dataMap, String cronExpression) {
Trigger cronTrigger = scheduleCreator.createBasicCronTrigger(name, group, new Date(), cronExpression, dataMap);
try {
schedulerFactoryBean.getScheduler().scheduleJob(cronTrigger);
return cronTrigger.getKey().getName();
} catch (SchedulerException e) {
log.error("Failed to start new job - {}", name, e);
return null;
}
}
@Override
public void stopTrigger(String triggerKey) {
try {

View File

@@ -28,6 +28,9 @@ public class SchedulerStartupService {
@Autowired
private SchedulerJobConverter schedulerJobConverter;
/**
* Loads the job definitions from the property file and schedules them, if the job does not exist yet.
*/
@EventListener
@Transactional
public void handleContextRefreshEvent(ContextRefreshedEvent ctxStartEvt) {

View File

@@ -16,6 +16,7 @@ public class SchedulerJobManagementServiceBean {
private SchedulerJobRepository repository;
public SchedulerJob createOrUpdate(SchedulerJob job) {
// TODO add group to job search
if(repository.existsByName(job.getName())) {
SchedulerJob byName = repository.findByName(job.getName());
byName.setActive(job.isActive());

View File

@@ -2,11 +2,14 @@ package dev.sheldan.abstracto.scheduling.model.database;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
import java.util.Objects;
/**
* The scheduler job instance according to the properties stored in the database. This is needed in order to have a
* reference of the jobs which *can* be scheduled.
*/
@Getter
@Setter
@Entity
@@ -20,16 +23,34 @@ public class SchedulerJob {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* The name of the job
*/
private String name;
/**
* The group of the job
*/
private String groupName;
/**
* The absolute path of a class extending {@link org.springframework.scheduling.quartz.QuartzJobBean} which should be executed by this job
*/
private String clazz;
/**
* If the job should be executed based on a cron expression, this contains this expression. If it is a one-time job this needs to be null.
*/
private String cronExpression;
/**
* Whether or not the job is active and available to be scheduled.
*/
private boolean active;
/**
* Whether or not the job should be re-tried in an recovery of fail over situation.
*/
private boolean recovery;
@Override

View File

@@ -6,16 +6,76 @@ import org.quartz.JobDataMap;
import java.util.Date;
public interface SchedulerService {
/**
* Starts all the currently active and available jobs from the database with their respective configuration
*/
void startScheduledJobs();
/**
* Schedules the given {@link SchedulerJob} instance directly
* @param job The job to schedule
*/
void scheduleJob(SchedulerJob job);
/**
* Updates an already scheduled job, with the same name and group, with the new {@link SchedulerJob} configuration
* @param job The new configuration of the job to use. The name and the group of the job to update are taken from this object as well.
* @param startDate The date at which this scheduled job should start executing
*/
void updateJob(SchedulerJob job, Date startDate);
boolean unScheduleJob(String jobName);
/**
* Removes a job from the scheduler.
* @param triggerKey The key of the trigger to unschedule
* @return if the job was found and unscheduled
*/
boolean unScheduleJob(String triggerKey);
/**
* Deletes the job from the scheduler.
* @param job The {@link SchedulerJob} instance containing the configuration of the job to remove
* @return fi the job was found and deleted
*/
boolean deleteJob(SchedulerJob job);
/**
* Pauses the given job in the scheduler
* @param job The {@link SchedulerJob} instance containing the configuration of the job to pause
* @return fi the job was found and paused
*/
boolean pauseJob(SchedulerJob job);
/**
* Continues the job in the scheduler.
* @param job The {@link SchedulerJob} instance containing the configuration of the job to continue
* @return fi the job was found and continued
*/
boolean continueJob(SchedulerJob job);
/**
* Executes the job directly in the scheduler.
* @param job The {@link SchedulerJob} instance containing the configuration of the job to execute directly
* @return fi the job was found and executed directly
*/
boolean executeJob(SchedulerJob job);
/**
* Executes the job identified by name and group with the given {@link JobDataMap} as parameters on the given {@link Date}
* @param name The name of the job to execute
* @param group The group of the job to execute
* @param dataMap The {@link JobDataMap} made available to the group
* @param date The {@link Date} at which the job should be execute at.
* @return The trigger key which triggers the job at the given date
*/
String executeJobWithParametersOnce(String name, String group, JobDataMap dataMap, Date date);
String startCronJobWithParameters(String name, String group, JobDataMap dataMap, String cronExpression);
/**
* Stops the trigger identified by the trigger key.
* @param triggerKey The key of the trigger to stop
*/
void stopTrigger(String triggerKey);
/**
* Actually starts the scheduler.
*/
void startScheduler();
}