Skip to content

Commit

Permalink
feat: add UI to show jobs
Browse files Browse the repository at this point in the history
  • Loading branch information
rajadilipkolli committed Nov 5, 2023
1 parent 93f39f2 commit ca8b46c
Show file tree
Hide file tree
Showing 18 changed files with 609 additions and 31 deletions.
21 changes: 20 additions & 1 deletion scheduler/boot-scheduler-quartz/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,23 @@ services:
- POSTGRES_DB=appdb
ports:
- "5432:5432"

pgadmin:
image: dpage/pgadmin4
extra_hosts: [ 'host.docker.internal:host-gateway' ]
environment:
- [email protected]
- PGADMIN_DEFAULT_PASSWORD=admin
- PGADMIN_CONFIG_SERVER_MODE=False
- PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False
ports:
- "5050:80"
depends_on:
postgresqldb:
condition: service_started
volumes:
- ./docker_pgadmin_servers.json:/pgadmin4/servers.json
entrypoint:
- "/bin/sh"
- "-c"
- "/bin/echo 'postgresqldb:5432:*:appuser:secret' > /tmp/pgpassfile && chmod 600 /tmp/pgpassfile && /entrypoint.sh"
restart: unless-stopped
14 changes: 14 additions & 0 deletions scheduler/boot-scheduler-quartz/docker/docker_pgadmin_servers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Servers": {
"1": {
"Name": "Docker Compose",
"Group": "Servers",
"Port": 5432,
"Username": "appuser",
"Host": "postgresqldb",
"SSLMode": "prefer",
"MaintenanceDB": "appdb",
"PassFile": "/tmp/pgpassfile"
}
}
}
22 changes: 22 additions & 0 deletions scheduler/boot-scheduler-quartz/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,32 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- webJars start -->
<!-- <dependency>-->
<!-- <groupId>org.webjars</groupId>-->
<!-- <artifactId>webjars-locator-core</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.webjars</groupId>-->
<!-- <artifactId>bootstrap</artifactId>-->
<!-- <version>5.3.2</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.webjars</groupId>-->
<!-- <artifactId>jquery</artifactId>-->
<!-- <version>3.7.1</version>-->
<!-- </dependency>-->
<!-- webJars end -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.scheduler.quartz.job;

import com.scheduler.quartz.model.response.ScheduleJob;
import com.scheduler.quartz.service.OddEvenService;
import java.util.ArrayList;
import java.util.List;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;

@Component
Expand All @@ -13,13 +15,15 @@ public class SampleJob implements Job {

private final OddEvenService oddEvenService;

public static List<ScheduleJob> jobList = new ArrayList<>();

public SampleJob(OddEvenService oddEvenService) {
this.oddEvenService = oddEvenService;
}

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String jobName = jobExecutionContext.getJobDetail().getJobDataMap().getString("jobName");
public void execute(JobExecutionContext jobExecutionContext) {
String jobName = jobExecutionContext.getJobDetail().getJobDataMap().getString("scheduleJob");
oddEvenService.execute(jobName);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.scheduler.quartz.job;

import static com.scheduler.quartz.service.JobsService.groupName;
import static org.quartz.DateBuilder.futureDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

import com.scheduler.quartz.model.response.ScheduleJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -14,22 +15,28 @@ public class SchedulerRegistrationService {

@Bean
JobDetail registerOddJob() {
return newJob(SampleJob.class)
.withIdentity("sample-job", "sample-group")
.usingJobData("jobName", "odd")

JobDetail jobDetail = JobBuilder.newJob(SampleJob.class)
.withIdentity("oddEvenJob", groupName)
.withDescription("Sample OddEvenJob")
.storeDurably()
.requestRecovery()
.build();
String jobId = String.valueOf(SampleJob.jobList.size() + 1);
ScheduleJob scheduleJob = new ScheduleJob(jobId, "oddEvenJob", groupName, null, null, "Sample OddEvenJob");
jobDetail.getJobDataMap().put("scheduleJob", jobId);
SampleJob.jobList.add(scheduleJob);
return jobDetail;
}

@Bean
Trigger triggerOddJob(JobDetail registerOddJob) {
return newTrigger()
.withIdentity("trigger-sample-job", "sample-group")
.withIdentity("sample-job-trigger", groupName)
.forJob(registerOddJob.getKey())
.startAt(futureDate(10, DateBuilder.IntervalUnit.SECOND))
.withSchedule(simpleSchedule()
.withIntervalInSeconds(5) // Run every 2 seconds
.withIntervalInSeconds(60) // Run every 2 seconds
.repeatForever()
.withMisfireHandlingInstructionFireNow())
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.scheduler.quartz.model.common;

import java.io.Serial;
import java.io.Serializable;
import lombok.Getter;

@Getter
public class Message implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

public static Message failure(String msg) {
return new Message(false, msg);
}

public static Message failure(Exception e) {
return new Message(false, e.getMessage());
}

public static Message failure() {
return new Message(false);
}

public static Message success() {
return new Message(true);
}

public static Message success(String msg) {
return new Message(true, msg);
}

public Message(boolean valid, String msg) {
super();
this.valid = valid;
this.msg = msg;
}

public Message(boolean valid) {
super();
this.valid = valid;
}

public void setValid(boolean valid) {
this.valid = valid;
}

public void setMsg(String msg) {
this.msg = msg;
}

boolean valid;
String msg;
Object data;

public void setData(Object data) {
this.valid = true;
this.data = data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.scheduler.quartz.model.response;

import java.io.Serializable;
import lombok.With;

public record ScheduleJob(
@With String jobId, String jobName, String jobGroup, String jobStatus, String cronExpression, String desc)
implements Serializable {}
Original file line number Diff line number Diff line change
@@ -1,40 +1,62 @@
package com.scheduler.quartz.service;

import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;

import com.scheduler.quartz.job.SampleJob;
import com.scheduler.quartz.model.response.JobStatus;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import com.scheduler.quartz.model.response.ScheduleJob;
import java.util.*;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.utils.Key;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Service
@Slf4j
public class JobsService {

private final Scheduler scheduler;
private final String groupName = "sample-group";
public static final String groupName = "sample-group";

public JobsService(Scheduler scheduler) {
this.scheduler = scheduler;
}

public boolean deleteJob(String id) throws SchedulerException {
JobKey jobKey = new JobKey(id, groupName);
return scheduler.deleteJob(jobKey);
public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup());
scheduler.deleteJob(jobKey);
}

public List<String> getJobs() throws SchedulerException {
return scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)).stream()
.map(Key::getName)
.sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
public List<ScheduleJob> getJobs() {
List<ScheduleJob> jobList = new ArrayList<>();
try {
GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
Set<JobKey> jobKeySet = scheduler.getJobKeys(matcher);
for (JobKey jobKey : jobKeySet) {
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for (Trigger trigger : triggers) {
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
String cronExpression = null;
if (trigger instanceof CronTrigger cronTrigger) {
cronExpression = cronTrigger.getCronExpression();
}
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
String jobId = (String) jobDetail.getJobDataMap().get("scheduleJob");
ScheduleJob scheduleJob = new ScheduleJob(
jobId, jobKey.getName(), jobKey.getGroup(), triggerState.name(), cronExpression, null);
jobList.add(scheduleJob);
}
}
} catch (SchedulerException e) {
log.error("SchedulerException occurred ", e);
}
return jobList;
}

public List<JobStatus> getJobsStatuses() throws SchedulerException {
LinkedList<JobStatus> list = new LinkedList<>();
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
for (JobKey jobKey : scheduler.getJobKeys(jobGroupEquals(groupName))) {
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobDetail.getKey());
for (Trigger trigger : triggers) {
Expand All @@ -49,4 +71,63 @@ public List<JobStatus> getJobsStatuses() throws SchedulerException {
list.sort(Comparator.comparing(JobStatus::jobName));
return list;
}

public void saveOrUpdate(ScheduleJob scheduleJob) throws SchedulerException {
if (!StringUtils.hasText(scheduleJob.jobId())) {
addJob(scheduleJob);
} else {
updateJobCronExpression(scheduleJob);
}
}

private void updateJobCronExpression(ScheduleJob scheduleJob) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.jobName(), groupName);
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.cronExpression());
if (cronTrigger == null) {
cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(cronScheduleBuilder)
.build();
} else {
cronTrigger = cronTrigger
.getTriggerBuilder()
.withIdentity(triggerKey)
.withSchedule(cronScheduleBuilder)
.build();
}
scheduler.rescheduleJob(triggerKey, cronTrigger);
}

private void addJob(ScheduleJob scheduleJob) throws SchedulerException {
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.jobName(), groupName);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger != null) {
throw new SchedulerException("job already exists!");
}

// simulate job info db persist operation
ScheduleJob withJobId = scheduleJob.withJobId(String.valueOf(SampleJob.jobList.size() + 1));
SampleJob.jobList.add(withJobId);

JobDetail jobDetail = JobBuilder.newJob(SampleJob.class)
.withIdentity(withJobId.jobName(), groupName)
.storeDurably()
.requestRecovery()
.build();
jobDetail.getJobDataMap().put("scheduleJob", withJobId.jobId());

CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(withJobId.cronExpression());
trigger = TriggerBuilder.newTrigger()
.withIdentity(withJobId.jobName() + "-trigger", groupName)
.withSchedule(cronScheduleBuilder)
.build();

scheduler.scheduleJob(jobDetail, trigger);
}

public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(scheduleJob.jobName(), scheduleJob.jobGroup());
scheduler.pauseJob(jobKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.scheduler.quartz.web.controller;

import com.scheduler.quartz.model.response.ScheduleJob;
import com.scheduler.quartz.service.JobsService;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {

private final JobsService scheduleJobService;

public IndexController(JobsService scheduleJobService) {
this.scheduleJobService = scheduleJobService;
}

@RequestMapping("/index")
public String index(Model model) {
List<ScheduleJob> jobList = scheduleJobService.getJobs();
model.addAttribute("jobs", jobList);
return "index";
}
}
Loading

0 comments on commit ca8b46c

Please sign in to comment.