Skip to content

Commit

Permalink
feat: cron scheduler support
Browse files Browse the repository at this point in the history
  • Loading branch information
arjenzhou committed Jul 23, 2023
1 parent 7c5e66b commit 832d801
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 3 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ plugins {
id("jacoco")
id("signing")
id("maven-publish")
// cannot read variable in plugins
id("org.gradlex.extra-java-module-info") version "1.4.1"
}

jacoco {
Expand Down
4 changes: 3 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ project.version=1.0.2-SNAPSHOT
project.url=https://github.com/arjenzhou/bm-kit

gradle.wrapper.version=8.2.1

jcoco.version=0.8.9
junit-bom.version=5.9.1
slf4j.version=2.0.7
slf4j.version=2.0.7
quertz.version=2.3.2
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.arjenzhou.kit.lazy;

/**
* Threw when evaluation virtual Lazy node
* Thrown when evaluation virtual Lazy node
*
* @author [email protected]
* @since 2023/7/8
Expand Down
42 changes: 42 additions & 0 deletions scheduler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store
21 changes: 21 additions & 0 deletions scheduler/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
dependencies {
implementation("org.quartz-scheduler:quartz:${project.property("quertz.version")}")
}

plugins {
id("org.gradlex.extra-java-module-info")
}

extraJavaModuleInfo {
// quartz has not supported JPMS yet
module("quartz-${project.property("quertz.version")}.jar", "org.quertz.scheduler.quartz", "${project.property("quertz.version")}") {
requires("org.slf4j")
requires("java.desktop")
requires("java.rmi")
exportAllPackages()
}
// quartz related dependencies, whose versions are depend on quartz, not global used.
module("HikariCP-java7-2.4.13.jar", "hikari", "2.4.13")
module("c3p0-0.9.5.4.jar", "c3p0", "0.9.5.4")
module("mchange-commons-java-0.2.15.jar", "mchange.commons.java", "0.2.15")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.arjenzhou.kit.scheduler;

import com.arjenzhou.kit.scheduler.job.internal.JobRunner;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

/**
* portal to run scheduler jobs
*
* @author [email protected]
* @since 2023/7/21
*/
public class SchedulerRunner {
/**
* run all scheduler jobs
*
* @throws SchedulerException when scheduler failed to start
*/
public static void run() throws SchedulerException {
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
JobDetail jobRunner = JobBuilder.newJob(JobRunner.class).build();
Trigger trigger = TriggerBuilder.newTrigger()
.startNow()
// In every second, check whether each job can run or not, till forever
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
scheduler.scheduleJob(jobRunner, trigger);
scheduler.start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.arjenzhou.kit.scheduler.job;

import java.util.Date;

/**
* Scheduler Job whose context stored in memory, thus cannot being restored from any persistence storage.
*
* @author [email protected]
* @since 2023/7/23
*/
public abstract class InMemorySchedulerJob implements ScheduledJob {
/**
* the last time this job completed.
*/
private Date lastRunTime = new Date(0L);

/**
* execute the job
*/
public abstract void execute();

@Override
public void start() {
execute();
lastRunTime = new Date();
}

@Override
public Date getLastRunTime() {
return lastRunTime;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.arjenzhou.kit.scheduler.job;

import java.lang.annotation.*;

/**
* Indicates that the Scheduled Job when to start.
*
* @author [email protected]
* @since 2023/7/21
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scheduled {
/**
* the quartz cron expression
*/
String cronExpression();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.arjenzhou.kit.scheduler.job;

import java.util.Date;

/**
* Job to run at specific condition.
* All subclass should be provided to this class, and declared in META-INF/services/com.arjenzhou.kit.scheduler.job.ScheduledJob
*
* @author [email protected]
* @since 2023/7/21
*/
public interface ScheduledJob {
/**
* how the ScheduledJob perform action
*/
void start();

/**
* @return the latest time the job completed
*/
Date getLastRunTime();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.arjenzhou.kit.scheduler.job.internal;

import com.arjenzhou.kit.scheduler.job.ScheduledJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

/**
* Container which holds all instances that implemented ScheduledJob
*
* @author [email protected]
* @see ScheduledJob
* @since 2023/7/23
*/
public class JobHolder {
private static final Logger LOG = LoggerFactory.getLogger("JOB FACTORY");
/**
* All jobs instances
*/
private static final List<ScheduledJob> SCHEDULED_JOBS;

static {
ServiceLoader<ScheduledJob> jobServiceLoader = ServiceLoader.load(ScheduledJob.class);
SCHEDULED_JOBS = jobServiceLoader.stream().map(ServiceLoader.Provider::get).toList();
String loadedJobs = SCHEDULED_JOBS.stream().map(j -> j.getClass().getTypeName()).collect(Collectors.joining("\n"));
LOG.info("load " + SCHEDULED_JOBS.size() + " jobs: \n" + loadedJobs);
LOG.info("check whether your service is declared in /META-INF/serivces/com.arjenzhou.kit.scheduler.job.ScheduledJob, " +
"or provides with com.arjenzhou.kit.scheduler.job.ScheduledJob or not.");
}

/**
* @return all scheduled jobs
*/
public static List<ScheduledJob> getAllJobs() {
return SCHEDULED_JOBS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.arjenzhou.kit.scheduler.job.internal;

import com.arjenzhou.kit.scheduler.job.Scheduled;
import org.quartz.CronExpression;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.util.Date;

/**
* runner to run all scheduled jobs
*
* @author [email protected]
* @since 2023/7/21
*/
public class JobRunner implements Job {
private static final Logger LOG = LoggerFactory.getLogger("JOB RUNNER");

/**
* for JobBuilder to invoke
*/
public JobRunner() {
}

@Override
public void execute(JobExecutionContext context) {
Date now = new Date();
JobHolder.getAllJobs().forEach(scheduledJob -> {
Scheduled scheduled = scheduledJob.getClass().getAnnotation(Scheduled.class);
String expression = scheduled.cronExpression();
try {
CronExpression cronExpression = new CronExpression(expression);
Date lastRunTime = scheduledJob.getLastRunTime();
Date nextValidTimeAfterLastRun = cronExpression.getNextValidTimeAfter(lastRunTime);
// it is not the time to start this job
if (nextValidTimeAfterLastRun.after(now)) {
return;
}
scheduledJob.start();
LOG.info("Job " + scheduledJob.getClass().getTypeName() + " started");
} catch (ParseException ignored) {
}
});
}
}
15 changes: 15 additions & 0 deletions scheduler/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @author [email protected]
* @since 2023/7/23
*/
module bm.kit.scheduler.main {
requires transitive org.quertz.scheduler.quartz;
requires org.slf4j;

exports com.arjenzhou.kit.scheduler;
exports com.arjenzhou.kit.scheduler.job;
exports com.arjenzhou.kit.scheduler.job.internal to org.quertz.scheduler.quartz;

// all jobs should be provided by this, for SPI reflection
uses com.arjenzhou.kit.scheduler.job.ScheduledJob;
}
16 changes: 16 additions & 0 deletions scheduler/src/test/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import com.arjenzhou.kit.scheduler.job.ScheduledJob;
import test.com.arjenzhou.kit.scheduler.TestJob;

/**
* @author [email protected]
* @since 2023/7/23
*/
module bm.kit.scheduler.test {
exports test.com.arjenzhou.kit.scheduler;

requires bm.kit.scheduler.main;
requires org.slf4j;
requires org.junit.jupiter.api;

provides ScheduledJob with TestJob;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package test.com.arjenzhou.kit.scheduler;

import com.arjenzhou.kit.scheduler.SchedulerRunner;
import org.junit.jupiter.api.Test;
import org.quartz.SchedulerException;

import java.util.concurrent.TimeUnit;

/**
* @author [email protected]
* @since 2023/7/21
*/
public class SchedulerTest {
@Test
public void test() throws SchedulerException, InterruptedException {
SchedulerRunner.run();
TimeUnit.SECONDS.sleep(15);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package test.com.arjenzhou.kit.scheduler;

import com.arjenzhou.kit.scheduler.job.InMemorySchedulerJob;
import com.arjenzhou.kit.scheduler.job.Scheduled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

/**
* Test Job which runs in every 5 seconds, and prints current system time.
*
* @author [email protected]
* @since 2023/7/23
*/
@Scheduled(cronExpression = "*/5 * * * * ?")
public class TestJob extends InMemorySchedulerJob {
private static final Logger LOG = LoggerFactory.getLogger(TestJob.class);

@Override
public void execute() {
LOG.info(new Date().toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test.com.arjenzhou.kit.scheduler.TestJob
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
rootProject.name = "bm-kit"
include("statemachine")
include("base")
include("lazy")
include("lazy")
include("scheduler")

0 comments on commit 832d801

Please sign in to comment.