From 0be69e9e04574ee36d9cfe09cc556a9beff7a86c Mon Sep 17 00:00:00 2001
From: Faiz Akram <156657523+faizorg@users.noreply.github.com>
Date: Mon, 1 Apr 2024 23:30:23 +0530
Subject: [PATCH 1/8] added jar file
---
pom.xml | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/pom.xml b/pom.xml
index 2adc8ca..841d170 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,6 +21,15 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
org.springframework.boot
spring-boot-starter-test
From 8734e3aa5a8adc1ffdb0a78d1286a1bf50c70397 Mon Sep 17 00:00:00 2001
From: Faiz Akram <156657523+faizorg@users.noreply.github.com>
Date: Tue, 2 Apr 2024 07:48:48 +0530
Subject: [PATCH 2/8] Schedule Tasks Dynamically
---
pom.xml | 8 ++
src/main/java/com/app/config/RedisConfig.java | 41 ++++++++++
.../java/com/app/config/SchedulerConfig.java | 21 +++++
.../app/controller/ScheduleController.java | 30 +++++++
src/main/java/com/app/dto/Schedule.java | 14 ++++
src/main/java/com/app/model/ScheduleTask.java | 45 +++++++++++
src/main/java/com/app/model/ScheduleType.java | 5 ++
.../repository/ScheduleTaskRepository.java | 10 +++
.../com/app/service/RedisLockService.java | 15 ++++
.../com/app/service/RedisLockServiceImpl.java | 55 +++++++++++++
.../java/com/app/service/ScheduleService.java | 11 +++
.../com/app/service/ScheduleServiceImpl.java | 58 ++++++++++++++
.../app/service/TaskSchedulingService.java | 12 +++
.../service/TaskSchedulingServiceImpl.java | 72 +++++++++++++++++
.../com/app/utils/CreateCronExpression.java | 79 +++++++++++++++++++
src/main/resources/application.yml | 24 +++++-
16 files changed, 497 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/com/app/config/RedisConfig.java
create mode 100644 src/main/java/com/app/config/SchedulerConfig.java
create mode 100644 src/main/java/com/app/controller/ScheduleController.java
create mode 100644 src/main/java/com/app/dto/Schedule.java
create mode 100644 src/main/java/com/app/model/ScheduleTask.java
create mode 100644 src/main/java/com/app/model/ScheduleType.java
create mode 100644 src/main/java/com/app/repository/ScheduleTaskRepository.java
create mode 100644 src/main/java/com/app/service/RedisLockService.java
create mode 100644 src/main/java/com/app/service/RedisLockServiceImpl.java
create mode 100644 src/main/java/com/app/service/ScheduleService.java
create mode 100644 src/main/java/com/app/service/ScheduleServiceImpl.java
create mode 100644 src/main/java/com/app/service/TaskSchedulingService.java
create mode 100644 src/main/java/com/app/service/TaskSchedulingServiceImpl.java
create mode 100644 src/main/java/com/app/utils/CreateCronExpression.java
diff --git a/pom.xml b/pom.xml
index 0346b33..b61d0dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,14 @@
mysql-connector-j
runtime
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ redis.clients
+ jedis
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/com/app/config/RedisConfig.java b/src/main/java/com/app/config/RedisConfig.java
new file mode 100644
index 0000000..a6f1af3
--- /dev/null
+++ b/src/main/java/com/app/config/RedisConfig.java
@@ -0,0 +1,41 @@
+package com.app.config;
+
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisPassword;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.util.StringUtils;
+
+@Configuration
+public class RedisConfig {
+ private String hostName;
+ private Integer port;
+ private String password;
+
+ public RedisConfig(@Value("${spring.data.redis.host}") String hostName, @Value("${spring.data.redis.port}")
+ Integer port, @Value("${spring.data.redis.password}") String password) {
+ this.hostName = hostName;
+ this.port = port;
+ this.password = password;
+ }
+
+ @Bean
+ public RedisConnectionFactory redisConnectionFactory() {
+ RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(hostName, port);
+ if (!StringUtils.isEmpty(password))
+ redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
+ return new JedisConnectionFactory(redisStandaloneConfiguration);
+ }
+
+ @Bean
+ public RedisTemplate redisTemplate() {
+ RedisTemplate template = new RedisTemplate<>();
+ template.setConnectionFactory(redisConnectionFactory());
+ return template;
+ }
+}
diff --git a/src/main/java/com/app/config/SchedulerConfig.java b/src/main/java/com/app/config/SchedulerConfig.java
new file mode 100644
index 0000000..9a89c1d
--- /dev/null
+++ b/src/main/java/com/app/config/SchedulerConfig.java
@@ -0,0 +1,21 @@
+package com.app.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@Configuration
+@EnableScheduling
+public class SchedulerConfig {
+
+ @Bean
+ public TaskScheduler taskScheduler() {
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.setPoolSize(10); // Define the pool size
+ scheduler.setThreadNamePrefix("scheduled-task-");
+ scheduler.initialize();
+ return scheduler;
+ }
+}
diff --git a/src/main/java/com/app/controller/ScheduleController.java b/src/main/java/com/app/controller/ScheduleController.java
new file mode 100644
index 0000000..e391a00
--- /dev/null
+++ b/src/main/java/com/app/controller/ScheduleController.java
@@ -0,0 +1,30 @@
+package com.app.controller;
+
+import com.app.dto.Schedule;
+import com.app.service.ScheduleService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("schedule")
+public class ScheduleController {
+
+ private final ScheduleService ScheduleService;
+
+ @PostMapping("add")
+ public ResponseEntity addSchedule(@RequestBody Schedule schedule) {
+ return new ResponseEntity<>(ScheduleService.addSchedule(schedule), HttpStatus.OK);
+ }
+
+ @PutMapping("update")
+ public ResponseEntity updateSchedule(@RequestBody Schedule schedule) {
+ return new ResponseEntity<>(ScheduleService.updateSchedule(schedule), HttpStatus.OK);
+ }
+ @DeleteMapping("delete")
+ public ResponseEntity deleteSchedule(@RequestParam Long id) {
+ return new ResponseEntity<>(ScheduleService.deleteSchedule(id), HttpStatus.OK);
+ }
+}
diff --git a/src/main/java/com/app/dto/Schedule.java b/src/main/java/com/app/dto/Schedule.java
new file mode 100644
index 0000000..d5fa7ca
--- /dev/null
+++ b/src/main/java/com/app/dto/Schedule.java
@@ -0,0 +1,14 @@
+package com.app.dto;
+
+import com.app.model.ScheduleType;
+import lombok.Data;
+
+@Data
+public class Schedule {
+ private Long id;
+ private String name;
+ private String parameters;
+ private Boolean isActive;
+ private Integer day;
+ private ScheduleType scheduleType;
+}
diff --git a/src/main/java/com/app/model/ScheduleTask.java b/src/main/java/com/app/model/ScheduleTask.java
new file mode 100644
index 0000000..995b32a
--- /dev/null
+++ b/src/main/java/com/app/model/ScheduleTask.java
@@ -0,0 +1,45 @@
+package com.app.model;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "schedule")
+@Data
+@DynamicInsert
+@DynamicUpdate
+public class ScheduleTask {
+ @Id
+ @Column(name = "id", updatable = false, nullable = false)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(length = 255)
+ private String name;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false)
+ private ScheduleType scheduleType;
+
+ @Column
+ private LocalDateTime scheduleTime;
+
+ @Column(columnDefinition = "TEXT", nullable = false)
+ private String customScheduleDetails;
+
+ private LocalDateTime lastRun;
+
+ @Column
+ private LocalDateTime nextRun;
+
+ @Column
+ private Boolean isActive;
+
+ @Column(columnDefinition = "TEXT")
+ private String parameters;
+
+}
diff --git a/src/main/java/com/app/model/ScheduleType.java b/src/main/java/com/app/model/ScheduleType.java
new file mode 100644
index 0000000..cd495f3
--- /dev/null
+++ b/src/main/java/com/app/model/ScheduleType.java
@@ -0,0 +1,5 @@
+package com.app.model;
+
+public enum ScheduleType {
+ MINUTE, HOUR, DAILY, WEEKLY, MONTHLY;
+}
diff --git a/src/main/java/com/app/repository/ScheduleTaskRepository.java b/src/main/java/com/app/repository/ScheduleTaskRepository.java
new file mode 100644
index 0000000..d500b37
--- /dev/null
+++ b/src/main/java/com/app/repository/ScheduleTaskRepository.java
@@ -0,0 +1,10 @@
+package com.app.repository;
+
+import com.app.model.ScheduleTask;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface ScheduleTaskRepository extends JpaRepository {
+ List findAllByIsActiveTrue();
+}
diff --git a/src/main/java/com/app/service/RedisLockService.java b/src/main/java/com/app/service/RedisLockService.java
new file mode 100644
index 0000000..3ba88bf
--- /dev/null
+++ b/src/main/java/com/app/service/RedisLockService.java
@@ -0,0 +1,15 @@
+package com.app.service;
+
+import java.util.concurrent.TimeUnit;
+
+public interface RedisLockService {
+
+ boolean shouldDeferExecution(String taskKey);
+
+ boolean extendLock(String lockKey, String lockValue, long extensionTime);
+
+ void updateLastExecutionTime(String taskKey);
+
+ boolean acquireLock(String lockKey, String lockValue, long expireTime);
+ void releaseLock(String lockKey, String lockValue);
+}
diff --git a/src/main/java/com/app/service/RedisLockServiceImpl.java b/src/main/java/com/app/service/RedisLockServiceImpl.java
new file mode 100644
index 0000000..4b3d76c
--- /dev/null
+++ b/src/main/java/com/app/service/RedisLockServiceImpl.java
@@ -0,0 +1,55 @@
+package com.app.service;
+
+import lombok.AllArgsConstructor;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+
+@Service
+@AllArgsConstructor
+public class RedisLockServiceImpl implements RedisLockService {
+ private final StringRedisTemplate stringRedisTemplate;
+ private final long defermentPeriod = 5000;
+ @Override
+ public boolean shouldDeferExecution(String taskKey) {
+ ValueOperations ops = stringRedisTemplate.opsForValue();
+ String lastExecutionTimeStr = ops.get(taskKey + ":lastExecution");
+ if (lastExecutionTimeStr != null) {
+ long lastExecutionTime = Long.parseLong(lastExecutionTimeStr);
+ long currentTime = Instant.now().toEpochMilli();
+ return (currentTime - lastExecutionTime) < defermentPeriod;
+ }
+ return false;
+ }
+ @Override
+ public boolean extendLock(String lockKey, String lockValue, long extensionTime) {
+ ValueOperations ops = stringRedisTemplate.opsForValue();
+ String currentValue = ops.get(lockKey);
+ if (lockValue.equals(currentValue)) {
+ stringRedisTemplate.expire(lockKey, extensionTime, TimeUnit.MILLISECONDS);
+ return true;
+ }
+ return false;
+ }
+ @Override
+ public void updateLastExecutionTime(String taskKey) {
+ ValueOperations ops = stringRedisTemplate.opsForValue();
+ ops.set(taskKey + ":lastExecution", String.valueOf(Instant.now().toEpochMilli()));
+ }
+ @Override
+ public boolean acquireLock(String lockKey, String lockValue, long timeout) {
+ ValueOperations ops = stringRedisTemplate.opsForValue();
+ Boolean success = ops.setIfAbsent(lockKey, lockValue, timeout, TimeUnit.MILLISECONDS);
+ return Boolean.TRUE.equals(success);
+ }
+ @Override
+ public void releaseLock(String lockKey, String lockValue) {
+ String currentValue = stringRedisTemplate.opsForValue().get(lockKey);
+ if (lockValue.equals(currentValue)) {
+ stringRedisTemplate.delete(lockKey);
+ }
+ }
+}
diff --git a/src/main/java/com/app/service/ScheduleService.java b/src/main/java/com/app/service/ScheduleService.java
new file mode 100644
index 0000000..2ab9218
--- /dev/null
+++ b/src/main/java/com/app/service/ScheduleService.java
@@ -0,0 +1,11 @@
+package com.app.service;
+
+import com.app.dto.Schedule;
+
+public interface ScheduleService {
+ Schedule addSchedule(Schedule schedule);
+
+ Schedule deleteSchedule(Long id);
+
+ Schedule updateSchedule(Schedule schedule);
+}
diff --git a/src/main/java/com/app/service/ScheduleServiceImpl.java b/src/main/java/com/app/service/ScheduleServiceImpl.java
new file mode 100644
index 0000000..3f826ee
--- /dev/null
+++ b/src/main/java/com/app/service/ScheduleServiceImpl.java
@@ -0,0 +1,58 @@
+package com.app.service;
+
+import com.app.dto.Schedule;
+import com.app.model.ScheduleTask;
+import com.app.repository.ScheduleTaskRepository;
+import com.app.utils.CreateCronExpression;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+@RequiredArgsConstructor
+@Log4j2
+public class ScheduleServiceImpl implements ScheduleService {
+ private final CreateCronExpression createCronExpression;
+ private final TaskSchedulingService taskSchedulingService;
+ private final ScheduleTaskRepository scheduleTaskRepository;
+
+ @Override
+ public Schedule addSchedule(Schedule schedule) {
+ String cron = switch (schedule.getScheduleType()) {
+ case MINUTE -> createCronExpression.generateEveryMinuteCronExpression();
+ case HOUR -> createCronExpression.generateHourlyCronExpression();
+ case DAILY -> createCronExpression.generateDailyCronExpression();
+ case WEEKLY -> createCronExpression.generateWeeklyCronExpression(schedule.getDay());
+ case MONTHLY -> createCronExpression.generateMonthlyCronExpression(schedule.getDay());
+ };
+ ScheduleTask scheduleTask = new ScheduleTask();
+ scheduleTask.setCustomScheduleDetails(cron);
+ scheduleTask.setName(schedule.getName());
+ scheduleTask.setScheduleType(schedule.getScheduleType());
+ scheduleTask.setIsActive(schedule.getIsActive());
+ scheduleTask = scheduleTaskRepository.save(scheduleTask);
+ log.info(cron);
+ schedule.setId(scheduleTask.getId());
+ taskSchedulingService.scheduleTask(scheduleTask);
+ return schedule;
+ }
+
+ @Override
+ public Schedule deleteSchedule(Long id) {
+ Optional scheduleTaskOptional = scheduleTaskRepository.findById(id);
+ if(scheduleTaskOptional.isPresent()){
+ ScheduleTask scheduleTask = scheduleTaskOptional.get();
+ scheduleTask.setIsActive(false);
+ scheduleTask = scheduleTaskRepository.save(scheduleTask);
+ taskSchedulingService.cancelScheduledTask(scheduleTask.getId());
+ }
+ return null;
+ }
+
+ @Override
+ public Schedule updateSchedule(Schedule schedule) {
+ return null;
+ }
+}
diff --git a/src/main/java/com/app/service/TaskSchedulingService.java b/src/main/java/com/app/service/TaskSchedulingService.java
new file mode 100644
index 0000000..a0d92e7
--- /dev/null
+++ b/src/main/java/com/app/service/TaskSchedulingService.java
@@ -0,0 +1,12 @@
+package com.app.service;
+
+import com.app.model.ScheduleTask;
+
+public interface TaskSchedulingService {
+
+ void scheduleTask(ScheduleTask scheduleTask);
+
+ void cancelScheduledTask(Long taskId);
+
+ void addOrUpdateTask(ScheduleTask scheduleTask);
+}
diff --git a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
new file mode 100644
index 0000000..81e3392
--- /dev/null
+++ b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
@@ -0,0 +1,72 @@
+package com.app.service;
+
+
+import com.app.model.ScheduleTask;
+import com.app.repository.ScheduleTaskRepository;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.support.CronTrigger;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+
+@Service
+@RequiredArgsConstructor
+@Log4j2
+public class TaskSchedulingServiceImpl implements TaskSchedulingService {
+ private final RedisLockService redisLockService;
+ private final TaskScheduler taskScheduler;
+ private final ScheduleTaskRepository scheduleTaskRepository;
+ private Map> tasks = new ConcurrentHashMap<>();
+
+ @PostConstruct
+ public void initializeScheduledTasks() {
+ scheduleTaskRepository.findAllByIsActiveTrue().forEach(this::scheduleTask);
+ }
+
+
+ @Override
+ public void scheduleTask(ScheduleTask scheduleTask) {
+ String lockKey = "lock:" + scheduleTask.getId();
+ Runnable taskWrapper = () -> {
+ String lockValue = UUID.randomUUID().toString();
+ if (redisLockService.shouldDeferExecution(lockKey)) {
+ log.info("Execution deferred due to recent execution.");
+ return;
+ } else if (!redisLockService.acquireLock(lockKey, lockValue, 30000)) {
+ log.info("Task " + scheduleTask.getId() + " is already running. Skipping execution.");
+ return;
+ }
+ try {
+ // Task execution logic here
+ log.info("Executing Task " + scheduleTask.getId());
+ redisLockService.updateLastExecutionTime(lockKey);
+ } finally {
+ redisLockService.releaseLock(lockKey, lockValue);
+ }
+ };
+ ScheduledFuture> future = taskScheduler.schedule(taskWrapper, new CronTrigger(scheduleTask.getCustomScheduleDetails()));
+ tasks.put(scheduleTask.getId(), future);
+ }
+
+ @Override
+ public void cancelScheduledTask(Long taskId) {
+ ScheduledFuture> future = tasks.get(taskId);
+ if (future != null) {
+ future.cancel(false);
+ tasks.remove(taskId);
+ }
+ }
+
+ @Override
+ public void addOrUpdateTask(ScheduleTask scheduleTask) {
+ cancelScheduledTask(scheduleTask.getId()); // Cancel the current task if it's already scheduled
+ scheduleTask(scheduleTask); // Reschedule it
+ }
+
+}
diff --git a/src/main/java/com/app/utils/CreateCronExpression.java b/src/main/java/com/app/utils/CreateCronExpression.java
new file mode 100644
index 0000000..ea8c980
--- /dev/null
+++ b/src/main/java/com/app/utils/CreateCronExpression.java
@@ -0,0 +1,79 @@
+package com.app.utils;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.Locale;
+
+@Component
+public class CreateCronExpression {
+
+ private Integer hour;
+ private Integer minute;
+
+ public CreateCronExpression(@Value("${app.default.hour}") Integer hour, @Value("${app.default.minute}") Integer minute) {
+ this.hour = hour;
+ this.minute = minute;
+ }
+
+ public String generateEveryMinuteCronExpression() {
+ // This will run at the start of every minute
+ return "0 * * * * ?";
+ }
+
+
+ public String generateHourlyCronExpression() {
+ // Validate the input
+ if (minute < 0 || minute > 59) {
+ throw new IllegalArgumentException("Minute must be between 0 and 59");
+ }
+
+ // Construct the cron expression
+ // This will run at the start of the specified minute past every hour
+ return String.format(Locale.US, "0 %d * * * ?", minute);
+ }
+
+ public String generateDailyCronExpression() {
+ // Validate the input
+ if (hour < 0 || hour > 23) {
+ throw new IllegalArgumentException("Hour must be between 0 and 23");
+ }
+ if (minute < 0 || minute > 59) {
+ throw new IllegalArgumentException("Minute must be between 0 and 59");
+ }
+ // Construct the cron expression
+ return String.format(Locale.US, "%d %d * * *", minute, hour);
+ }
+
+ public String generateWeeklyCronExpression(int dayOfWeek) {
+ // Validate the input
+ if (hour < 0 || hour > 23) {
+ throw new IllegalArgumentException("Hour must be between 0 and 23");
+ }
+ if (minute < 0 || minute > 59) {
+ throw new IllegalArgumentException("Minute must be between 0 and 59");
+ }
+ if (dayOfWeek < 1 || dayOfWeek > 7) {
+ throw new IllegalArgumentException("Day of week must be between 1 (Sunday) and 7 (Saturday)");
+ }
+ // Construct the cron expression with the second field included
+ return String.format(Locale.US, "0 %d %d * * %d", minute, hour, dayOfWeek - 1);
+ }
+
+
+ public String generateMonthlyCronExpression(int dayOfMonth) {
+ // Validate the input
+ if (hour < 0 || hour > 23) {
+ throw new IllegalArgumentException("Hour must be between 0 and 23");
+ }
+ if (minute < 0 || minute > 59) {
+ throw new IllegalArgumentException("Minute must be between 0 and 59");
+ }
+ if (dayOfMonth < 1 || dayOfMonth > 31) {
+ throw new IllegalArgumentException("Day of month must be between 1 and 31");
+ }
+
+ // Construct the cron expression
+ return String.format(Locale.US, "%d %d %d * *", minute, hour, dayOfMonth);
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 56fbd1d..7d44b54 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,6 +1,24 @@
spring:
- application:
- name: QuickStartSpringBoot
+ data:
+ redis:
+ host: localhost
+ port: 6379
+ password:
+ application:
+ name: QuickStartSpringBoot
+ datasource:
+ url: jdbc:mysql://coofun.local:3306/test
+ username: root
+ password: root
+ jpa:
+ hibernate:
+ ddl-auto: update
+ show-sql: true
server:
- port: 8080
+ port: 8080
+
+app:
+ default:
+ minute: 00
+ hour: 03
From 329f7654a3dc9550822aabcc0028bd6677ffff5d Mon Sep 17 00:00:00 2001
From: Faiz Akram <156657523+faizorg@users.noreply.github.com>
Date: Tue, 2 Apr 2024 09:00:28 +0530
Subject: [PATCH 3/8] Schedule Tasks Dynamically
---
pom.xml | 11 +++
src/main/java/com/app/config/RedisConfig.java | 6 ++
.../com/app/service/RedisLockService.java | 15 ----
.../com/app/service/RedisLockServiceImpl.java | 55 --------------
.../service/TaskSchedulingServiceImpl.java | 71 +++++++++++++------
5 files changed, 67 insertions(+), 91 deletions(-)
delete mode 100644 src/main/java/com/app/service/RedisLockService.java
delete mode 100644 src/main/java/com/app/service/RedisLockServiceImpl.java
diff --git a/pom.xml b/pom.xml
index b61d0dd..22e11bf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,17 @@
redis.clients
jedis
+
+ net.javacrumbs.shedlock
+ shedlock-spring
+ 4.0.0
+
+
+ net.javacrumbs.shedlock
+ shedlock-provider-redis-spring
+ 4.0.0
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/com/app/config/RedisConfig.java b/src/main/java/com/app/config/RedisConfig.java
index a6f1af3..fc67648 100644
--- a/src/main/java/com/app/config/RedisConfig.java
+++ b/src/main/java/com/app/config/RedisConfig.java
@@ -1,6 +1,8 @@
package com.app.config;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -38,4 +40,8 @@ public RedisTemplate redisTemplate() {
template.setConnectionFactory(redisConnectionFactory());
return template;
}
+ @Bean
+ public LockProvider lockProvider() {
+ return new RedisLockProvider(redisConnectionFactory());
+ }
}
diff --git a/src/main/java/com/app/service/RedisLockService.java b/src/main/java/com/app/service/RedisLockService.java
deleted file mode 100644
index 3ba88bf..0000000
--- a/src/main/java/com/app/service/RedisLockService.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.app.service;
-
-import java.util.concurrent.TimeUnit;
-
-public interface RedisLockService {
-
- boolean shouldDeferExecution(String taskKey);
-
- boolean extendLock(String lockKey, String lockValue, long extensionTime);
-
- void updateLastExecutionTime(String taskKey);
-
- boolean acquireLock(String lockKey, String lockValue, long expireTime);
- void releaseLock(String lockKey, String lockValue);
-}
diff --git a/src/main/java/com/app/service/RedisLockServiceImpl.java b/src/main/java/com/app/service/RedisLockServiceImpl.java
deleted file mode 100644
index 4b3d76c..0000000
--- a/src/main/java/com/app/service/RedisLockServiceImpl.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.app.service;
-
-import lombok.AllArgsConstructor;
-import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
-import org.springframework.stereotype.Service;
-
-import java.time.Instant;
-import java.util.concurrent.TimeUnit;
-
-@Service
-@AllArgsConstructor
-public class RedisLockServiceImpl implements RedisLockService {
- private final StringRedisTemplate stringRedisTemplate;
- private final long defermentPeriod = 5000;
- @Override
- public boolean shouldDeferExecution(String taskKey) {
- ValueOperations ops = stringRedisTemplate.opsForValue();
- String lastExecutionTimeStr = ops.get(taskKey + ":lastExecution");
- if (lastExecutionTimeStr != null) {
- long lastExecutionTime = Long.parseLong(lastExecutionTimeStr);
- long currentTime = Instant.now().toEpochMilli();
- return (currentTime - lastExecutionTime) < defermentPeriod;
- }
- return false;
- }
- @Override
- public boolean extendLock(String lockKey, String lockValue, long extensionTime) {
- ValueOperations ops = stringRedisTemplate.opsForValue();
- String currentValue = ops.get(lockKey);
- if (lockValue.equals(currentValue)) {
- stringRedisTemplate.expire(lockKey, extensionTime, TimeUnit.MILLISECONDS);
- return true;
- }
- return false;
- }
- @Override
- public void updateLastExecutionTime(String taskKey) {
- ValueOperations ops = stringRedisTemplate.opsForValue();
- ops.set(taskKey + ":lastExecution", String.valueOf(Instant.now().toEpochMilli()));
- }
- @Override
- public boolean acquireLock(String lockKey, String lockValue, long timeout) {
- ValueOperations ops = stringRedisTemplate.opsForValue();
- Boolean success = ops.setIfAbsent(lockKey, lockValue, timeout, TimeUnit.MILLISECONDS);
- return Boolean.TRUE.equals(success);
- }
- @Override
- public void releaseLock(String lockKey, String lockValue) {
- String currentValue = stringRedisTemplate.opsForValue().get(lockKey);
- if (lockValue.equals(currentValue)) {
- stringRedisTemplate.delete(lockKey);
- }
- }
-}
diff --git a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
index 81e3392..ae3e7c2 100644
--- a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
+++ b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
@@ -6,12 +6,17 @@
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
+import net.javacrumbs.shedlock.core.LockAssert;
+import net.javacrumbs.shedlock.core.LockConfiguration;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.core.SimpleLock;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
+import java.time.Instant;
import java.util.Map;
-import java.util.UUID;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
@@ -19,8 +24,8 @@
@RequiredArgsConstructor
@Log4j2
public class TaskSchedulingServiceImpl implements TaskSchedulingService {
- private final RedisLockService redisLockService;
private final TaskScheduler taskScheduler;
+ private final LockProvider lockProvider;
private final ScheduleTaskRepository scheduleTaskRepository;
private Map> tasks = new ConcurrentHashMap<>();
@@ -32,28 +37,52 @@ public void initializeScheduledTasks() {
@Override
public void scheduleTask(ScheduleTask scheduleTask) {
- String lockKey = "lock:" + scheduleTask.getId();
- Runnable taskWrapper = () -> {
- String lockValue = UUID.randomUUID().toString();
- if (redisLockService.shouldDeferExecution(lockKey)) {
- log.info("Execution deferred due to recent execution.");
- return;
- } else if (!redisLockService.acquireLock(lockKey, lockValue, 30000)) {
- log.info("Task " + scheduleTask.getId() + " is already running. Skipping execution.");
- return;
- }
- try {
- // Task execution logic here
- log.info("Executing Task " + scheduleTask.getId());
- redisLockService.updateLastExecutionTime(lockKey);
- } finally {
- redisLockService.releaseLock(lockKey, lockValue);
- }
- };
- ScheduledFuture> future = taskScheduler.schedule(taskWrapper, new CronTrigger(scheduleTask.getCustomScheduleDetails()));
+// Runnable taskWrapper = () -> {
+// String lockName = scheduleTask.getName()+" - "+scheduleTask.getId();
+// Instant lockAtMostUntil = Instant.now().plusSeconds(600); // Lock for 10 minutes
+// Instant lockAtLeastUntil = Instant.now().plusSeconds(300);
+// LockConfiguration config = new LockConfiguration(lockName, lockAtMostUntil, lockAtLeastUntil);
+// Optional lock = lockProvider.lock(config);
+// try {
+// lock.ifPresentOrElse(simpleLock -> {
+// try {
+// LockAssert.assertLocked();
+// // Execute the actual task logic here
+// log.info("Executing Task " + scheduleTask.getId());
+// } catch (Exception e) {
+// log.error("Error executing task: " + scheduleTask.getId(), e);
+// }
+// }, () -> {
+// log.info("Could not acquire lock for task " + scheduleTask.getId());
+// });
+// } finally {
+// lock.ifPresent(SimpleLock::unlock);
+// }
+// };
+ ScheduledFuture> future = taskScheduler.schedule(() -> this.executeTask(scheduleTask), new CronTrigger(scheduleTask.getCustomScheduleDetails()));
tasks.put(scheduleTask.getId(), future);
}
+ private void executeTask(ScheduleTask scheduleTask) {
+ String lockName = "myDynamicTask";
+ Instant lockAtMostUntil = Instant.now().plusSeconds(60); // Lock for 10 minutes
+ Instant lockAtLeastUntil = Instant.now().plusSeconds(30); // Minimum lock time
+
+ LockConfiguration config = new LockConfiguration(lockName, lockAtMostUntil, lockAtLeastUntil);
+ Optional lock = lockProvider.lock(config);
+ try {
+ lock.ifPresentOrElse(simpleLock -> {
+ LockAssert.assertLocked();
+ // Task logic here
+ log.info("Executing Task " + scheduleTask.getId());
+ }, () -> {
+ System.out.println("Could not acquire lock for task");
+ });
+ } finally {
+ lock.ifPresent(SimpleLock::unlock);
+ }
+ }
+
@Override
public void cancelScheduledTask(Long taskId) {
ScheduledFuture> future = tasks.get(taskId);
From a598ea62f1ca68807af46a71deb58cc44d9384e9 Mon Sep 17 00:00:00 2001
From: Faiz Akram <156657523+faizorg@users.noreply.github.com>
Date: Tue, 2 Apr 2024 09:00:44 +0530
Subject: [PATCH 4/8] Schedule Tasks Dynamically
---
pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index 22e11bf..659e264 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,12 +41,12 @@
net.javacrumbs.shedlock
shedlock-spring
- 4.0.0
+ 5.12.0
net.javacrumbs.shedlock
shedlock-provider-redis-spring
- 4.0.0
+ 5.12.0
From ab878e81e893b2ea879a340a856126559608b9de Mon Sep 17 00:00:00 2001
From: Faiz Akram <156657523+faizorg@users.noreply.github.com>
Date: Tue, 2 Apr 2024 09:13:52 +0530
Subject: [PATCH 5/8] Schedule Tasks Dynamically
---
.../service/TaskSchedulingServiceImpl.java | 65 +++++++------------
1 file changed, 22 insertions(+), 43 deletions(-)
diff --git a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
index ae3e7c2..0bbebb5 100644
--- a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
+++ b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
@@ -14,6 +14,7 @@
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
+import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
@@ -37,50 +38,28 @@ public void initializeScheduledTasks() {
@Override
public void scheduleTask(ScheduleTask scheduleTask) {
-// Runnable taskWrapper = () -> {
-// String lockName = scheduleTask.getName()+" - "+scheduleTask.getId();
-// Instant lockAtMostUntil = Instant.now().plusSeconds(600); // Lock for 10 minutes
-// Instant lockAtLeastUntil = Instant.now().plusSeconds(300);
-// LockConfiguration config = new LockConfiguration(lockName, lockAtMostUntil, lockAtLeastUntil);
-// Optional lock = lockProvider.lock(config);
-// try {
-// lock.ifPresentOrElse(simpleLock -> {
-// try {
-// LockAssert.assertLocked();
-// // Execute the actual task logic here
-// log.info("Executing Task " + scheduleTask.getId());
-// } catch (Exception e) {
-// log.error("Error executing task: " + scheduleTask.getId(), e);
-// }
-// }, () -> {
-// log.info("Could not acquire lock for task " + scheduleTask.getId());
-// });
-// } finally {
-// lock.ifPresent(SimpleLock::unlock);
-// }
-// };
- ScheduledFuture> future = taskScheduler.schedule(() -> this.executeTask(scheduleTask), new CronTrigger(scheduleTask.getCustomScheduleDetails()));
- tasks.put(scheduleTask.getId(), future);
- }
-
- private void executeTask(ScheduleTask scheduleTask) {
- String lockName = "myDynamicTask";
- Instant lockAtMostUntil = Instant.now().plusSeconds(60); // Lock for 10 minutes
- Instant lockAtLeastUntil = Instant.now().plusSeconds(30); // Minimum lock time
+ Runnable taskWrapper = () -> {
+ String lockName = scheduleTask.getName()+" - "+scheduleTask.getId();
+ Instant createdAt = Instant.now();
+ LockConfiguration config = new LockConfiguration(createdAt, lockName, Duration.ofMinutes(1), Duration.ofSeconds(10));
+ Optional lock = lockProvider.lock(config);
+ try {
+ lock.ifPresent(simpleLock -> {
+ try {
+ LockAssert.assertLocked();
+ // Execute the actual task logic here
+ log.info("Executing Task " + scheduleTask.getId());
+ } catch (Exception e) {
+ log.error("Error executing task: " + scheduleTask.getId(), e);
+ }
+ });
+ } finally {
+ lock.ifPresent(SimpleLock::unlock);
+ }
+ };
- LockConfiguration config = new LockConfiguration(lockName, lockAtMostUntil, lockAtLeastUntil);
- Optional lock = lockProvider.lock(config);
- try {
- lock.ifPresentOrElse(simpleLock -> {
- LockAssert.assertLocked();
- // Task logic here
- log.info("Executing Task " + scheduleTask.getId());
- }, () -> {
- System.out.println("Could not acquire lock for task");
- });
- } finally {
- lock.ifPresent(SimpleLock::unlock);
- }
+ ScheduledFuture> future = taskScheduler.schedule(taskWrapper, new CronTrigger(scheduleTask.getCustomScheduleDetails()));
+ tasks.put(scheduleTask.getId(), future);
}
@Override
From 047be00630b0e17ce4ac6282d734c687bb27a462 Mon Sep 17 00:00:00 2001
From: "IIPL\\14261"
Date: Wed, 4 Sep 2024 22:48:01 +0530
Subject: [PATCH 6/8] Correct task locking logic
---
pom.xml | 29 ++++--------
src/main/java/com/app/config/RedisConfig.java | 47 -------------------
.../com/app/config/ShedLockConfiguration.java | 16 +++++++
.../service/TaskSchedulingServiceImpl.java | 6 ++-
src/main/resources/application.yml | 7 +--
.../QuickStartSpringBootApplicationTests.java | 13 -----
6 files changed, 29 insertions(+), 89 deletions(-)
delete mode 100644 src/main/java/com/app/config/RedisConfig.java
create mode 100644 src/main/java/com/app/config/ShedLockConfiguration.java
delete mode 100644 src/test/java/com/app/QuickStartSpringBootApplicationTests.java
diff --git a/pom.xml b/pom.xml
index 659e264..df73e86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,35 +30,22 @@
mysql-connector-j
runtime
-
- org.springframework.boot
- spring-boot-starter-data-redis
-
-
- redis.clients
- jedis
-
-
- net.javacrumbs.shedlock
- shedlock-spring
- 5.12.0
-
net.javacrumbs.shedlock
- shedlock-provider-redis-spring
- 5.12.0
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
+ shedlock-provider-jdbc
+ 5.15.1
org.projectlombok
lombok
true
+
+ org.testng
+ testng
+ RELEASE
+ compile
+
diff --git a/src/main/java/com/app/config/RedisConfig.java b/src/main/java/com/app/config/RedisConfig.java
deleted file mode 100644
index fc67648..0000000
--- a/src/main/java/com/app/config/RedisConfig.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.app.config;
-
-
-import net.javacrumbs.shedlock.core.LockProvider;
-import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.connection.RedisPassword;
-import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
-import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.util.StringUtils;
-
-@Configuration
-public class RedisConfig {
- private String hostName;
- private Integer port;
- private String password;
-
- public RedisConfig(@Value("${spring.data.redis.host}") String hostName, @Value("${spring.data.redis.port}")
- Integer port, @Value("${spring.data.redis.password}") String password) {
- this.hostName = hostName;
- this.port = port;
- this.password = password;
- }
-
- @Bean
- public RedisConnectionFactory redisConnectionFactory() {
- RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(hostName, port);
- if (!StringUtils.isEmpty(password))
- redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
- return new JedisConnectionFactory(redisStandaloneConfiguration);
- }
-
- @Bean
- public RedisTemplate redisTemplate() {
- RedisTemplate template = new RedisTemplate<>();
- template.setConnectionFactory(redisConnectionFactory());
- return template;
- }
- @Bean
- public LockProvider lockProvider() {
- return new RedisLockProvider(redisConnectionFactory());
- }
-}
diff --git a/src/main/java/com/app/config/ShedLockConfiguration.java b/src/main/java/com/app/config/ShedLockConfiguration.java
new file mode 100644
index 0000000..c80e269
--- /dev/null
+++ b/src/main/java/com/app/config/ShedLockConfiguration.java
@@ -0,0 +1,16 @@
+package com.app.config;
+
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.jdbc.JdbcLockProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+@Configuration
+public class ShedLockConfiguration {
+
+ @Bean
+ public LockProvider lockProvider(DataSource dataSource) {
+ return new JdbcLockProvider(dataSource);
+ }
+}
diff --git a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
index 0bbebb5..96da133 100644
--- a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
+++ b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
@@ -43,18 +43,20 @@ public void scheduleTask(ScheduleTask scheduleTask) {
Instant createdAt = Instant.now();
LockConfiguration config = new LockConfiguration(createdAt, lockName, Duration.ofMinutes(1), Duration.ofSeconds(10));
Optional lock = lockProvider.lock(config);
+ LockAssert.TestHelper.makeAllAssertsPass(true);
try {
lock.ifPresent(simpleLock -> {
try {
LockAssert.assertLocked();
// Execute the actual task logic here
- log.info("Executing Task " + scheduleTask.getId());
+ log.info("Executing Task {}", lockName);
} catch (Exception e) {
- log.error("Error executing task: " + scheduleTask.getId(), e);
+ log.error("Error executing task: {}", scheduleTask.getId(), e);
}
});
} finally {
lock.ifPresent(SimpleLock::unlock);
+ log.info("Release Task {}", lockName);
}
};
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 7d44b54..505c4c1 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,13 +1,8 @@
spring:
- data:
- redis:
- host: localhost
- port: 6379
- password:
application:
name: QuickStartSpringBoot
datasource:
- url: jdbc:mysql://coofun.local:3306/test
+ url: jdbc:mysql://coofun.local:3306/schedular
username: root
password: root
jpa:
diff --git a/src/test/java/com/app/QuickStartSpringBootApplicationTests.java b/src/test/java/com/app/QuickStartSpringBootApplicationTests.java
deleted file mode 100644
index 0137031..0000000
--- a/src/test/java/com/app/QuickStartSpringBootApplicationTests.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.app;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class QuickStartSpringBootApplicationTests {
-
- @Test
- void contextLoads() {
- }
-
-}
From ab30306d60d3baad87ec7fe9a6fb15e1cffb2be6 Mon Sep 17 00:00:00 2001
From: "IIPL\\14261"
Date: Wed, 4 Sep 2024 22:49:34 +0530
Subject: [PATCH 7/8] Update message
---
src/main/java/com/app/service/TaskSchedulingServiceImpl.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
index 96da133..cfdcec0 100644
--- a/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
+++ b/src/main/java/com/app/service/TaskSchedulingServiceImpl.java
@@ -28,7 +28,7 @@ public class TaskSchedulingServiceImpl implements TaskSchedulingService {
private final TaskScheduler taskScheduler;
private final LockProvider lockProvider;
private final ScheduleTaskRepository scheduleTaskRepository;
- private Map> tasks = new ConcurrentHashMap<>();
+ private final Map> tasks = new ConcurrentHashMap<>();
@PostConstruct
public void initializeScheduledTasks() {
From 0edf2d5e0d6f97e19e6f3e1fdcb7423b27619325 Mon Sep 17 00:00:00 2001
From: "IIPL\\14261"
Date: Wed, 4 Sep 2024 23:01:29 +0530
Subject: [PATCH 8/8] Update message
---
src/main/resources/application.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 505c4c1..9653425 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -11,7 +11,6 @@ spring:
show-sql: true
server:
port: 8080
-
app:
default:
minute: 00