diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/JobConfigLoader.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/JobConfigLoader.java index 435349e8a..3aa0d57f5 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/JobConfigLoader.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/JobConfigLoader.java @@ -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 diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/SchedulerConfig.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/SchedulerConfig.java index 2bf668392..51bb02ed7 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/SchedulerConfig.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/SchedulerConfig.java @@ -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); diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/SchedulingProperties.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/SchedulingProperties.java index 3a98bc552..f29e6c535 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/SchedulingProperties.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/config/SchedulingProperties.java @@ -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 { diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/QuartzConfigFactory.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/QuartzConfigFactory.java index 102dc3de8..03080eed4 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/QuartzConfigFactory.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/QuartzConfigFactory.java @@ -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 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) diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/SchedulerJobConverter.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/SchedulerJobConverter.java index 6b2a832d3..7ab4e9c2a 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/SchedulerJobConverter.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/SchedulerJobConverter.java @@ -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() diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/SchedulerJobFactory.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/SchedulerJobFactory.java index 0178f962a..7d02f0365 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/SchedulerJobFactory.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/factory/SchedulerJobFactory.java @@ -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; diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/model/SchedulerJobProperties.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/model/SchedulerJobProperties.java index 684ee3816..f14afc2fa 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/model/SchedulerJobProperties.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/model/SchedulerJobProperties.java @@ -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; } \ No newline at end of file diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/repository/SchedulerJobRepository.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/repository/SchedulerJobRepository.java index 696429951..c9fbc4243 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/repository/SchedulerJobRepository.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/repository/SchedulerJobRepository.java @@ -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 { + /** + * 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); } diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/IdGenerationService.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/IdGenerationService.java index 4ee813e21..c04c87cf5 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/IdGenerationService.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/IdGenerationService.java @@ -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 diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerServiceBean.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerServiceBean.java index 314c4f9e2..2a2045e33 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerServiceBean.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerServiceBean.java @@ -57,6 +57,8 @@ public class SchedulerServiceBean implements SchedulerService { boolean recurringJob = isRecurringJob(schedulerJob); jobDetail = scheduleCreator.createJob((Class) 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 { diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerStartupService.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerStartupService.java index fd70c4d8f..63ddb74a1 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerStartupService.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerStartupService.java @@ -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) { diff --git a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/management/SchedulerJobManagementServiceBean.java b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/management/SchedulerJobManagementServiceBean.java index 9dcc21850..c19496ad5 100644 --- a/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/management/SchedulerJobManagementServiceBean.java +++ b/abstracto-application/scheduling/scheduling-impl/src/main/java/dev/sheldan/abstracto/scheduling/service/management/SchedulerJobManagementServiceBean.java @@ -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()); diff --git a/abstracto-application/scheduling/scheduling-int/src/main/java/dev/sheldan/abstracto/scheduling/model/database/SchedulerJob.java b/abstracto-application/scheduling/scheduling-int/src/main/java/dev/sheldan/abstracto/scheduling/model/database/SchedulerJob.java index 30c553f4d..9386d1d68 100644 --- a/abstracto-application/scheduling/scheduling-int/src/main/java/dev/sheldan/abstracto/scheduling/model/database/SchedulerJob.java +++ b/abstracto-application/scheduling/scheduling-int/src/main/java/dev/sheldan/abstracto/scheduling/model/database/SchedulerJob.java @@ -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 diff --git a/abstracto-application/scheduling/scheduling-int/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerService.java b/abstracto-application/scheduling/scheduling-int/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerService.java index b427fec77..ca165645f 100644 --- a/abstracto-application/scheduling/scheduling-int/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerService.java +++ b/abstracto-application/scheduling/scheduling-int/src/main/java/dev/sheldan/abstracto/scheduling/service/SchedulerService.java @@ -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(); }