Implement scheduled execution of cronjobs
This commit is contained in:
parent
89eaeaed40
commit
0de35f1a51
@ -0,0 +1,29 @@
|
|||||||
|
package org.hso.ecommerce.action.cronjob;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public interface ICronjob {
|
||||||
|
/**
|
||||||
|
* Calculate the earliest cronjob execution time that happens after the given reference time.
|
||||||
|
*
|
||||||
|
* @param reference Position in time to start searching. The implementor is allowed to modify the reference time.
|
||||||
|
* @return A new Calendar instance (or the same) containing the time for next execution.
|
||||||
|
*/
|
||||||
|
Calendar nextExecution(Calendar reference);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the latest cronjob execution time that happens before or exactly at the given refernce time.
|
||||||
|
*
|
||||||
|
* @param reference Position in time to start searching. The implementor is allowed to modify the reference time.
|
||||||
|
* @return A new Calendar instance (or the same) containing the time of the last execution.
|
||||||
|
*/
|
||||||
|
Calendar previousExecution(Calendar reference);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute this cronjob.
|
||||||
|
*
|
||||||
|
* @param time The point in time this execution was scheduled. In case of a missed cronjob, the actual time of this
|
||||||
|
* call might be much later.
|
||||||
|
*/
|
||||||
|
void executeAt(Calendar time);
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package org.hso.ecommerce.action.cronjob;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class Reorder implements ICronjob {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Reorder.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Calendar nextExecution(Calendar reference) {
|
||||||
|
if (reference.get(Calendar.HOUR_OF_DAY) >= 8) {
|
||||||
|
reference.add(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
}
|
||||||
|
reference.set(Calendar.HOUR_OF_DAY, 8);
|
||||||
|
reference.set(Calendar.MINUTE, 0);
|
||||||
|
reference.set(Calendar.SECOND, 0);
|
||||||
|
reference.set(Calendar.MILLISECOND, 0);
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Calendar previousExecution(Calendar reference) {
|
||||||
|
if (reference.get(Calendar.HOUR_OF_DAY) < 8) {
|
||||||
|
reference.add(Calendar.DAY_OF_MONTH, -1);
|
||||||
|
}
|
||||||
|
reference.set(Calendar.HOUR_OF_DAY, 8);
|
||||||
|
reference.set(Calendar.MINUTE, 0);
|
||||||
|
reference.set(Calendar.SECOND, 0);
|
||||||
|
reference.set(Calendar.MILLISECOND, 0);
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeAt(Calendar time) {
|
||||||
|
log.info("Executing Reorder Cronjob");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
package org.hso.ecommerce.controller.cronjob;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import org.hso.ecommerce.action.cronjob.ICronjob;
|
||||||
|
import org.hso.ecommerce.action.cronjob.Reorder;
|
||||||
|
import org.hso.ecommerce.entities.cron.BackgroundJob;
|
||||||
|
import org.hso.ecommerce.repos.cronjob.BackgroundJobRepository;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
class ScheduledCronjob {
|
||||||
|
public final Calendar executionTime;
|
||||||
|
public final ICronjob cronjob;
|
||||||
|
public final BackgroundJob model;
|
||||||
|
|
||||||
|
public ScheduledCronjob(Calendar executionTime, ICronjob cronjob, BackgroundJob model) {
|
||||||
|
this.executionTime = executionTime;
|
||||||
|
this.cronjob = cronjob;
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class CronjobController {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CronjobController.class);
|
||||||
|
|
||||||
|
private static final Map<String, ICronjob> cronjobs = getCronjobs();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private final BackgroundJobRepository cronjobRepository = null;
|
||||||
|
|
||||||
|
private static Map<String, ICronjob> getCronjobs() {
|
||||||
|
HashMap<String, ICronjob> map = new HashMap<>();
|
||||||
|
|
||||||
|
// Register all existing cronjobs
|
||||||
|
map.put(BackgroundJob.JOB_REORDER, new Reorder());
|
||||||
|
|
||||||
|
return Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledCronjob getNextCronjob() {
|
||||||
|
Calendar currentTime = new GregorianCalendar();
|
||||||
|
Iterable<BackgroundJob> jobs = cronjobRepository.getAllJobs();
|
||||||
|
HashMap<String, BackgroundJob> alreadyExecuted = new HashMap<>();
|
||||||
|
for (BackgroundJob job : jobs) {
|
||||||
|
alreadyExecuted.put(job.jobName, job);
|
||||||
|
}
|
||||||
|
ScheduledCronjob earliestJob = null;
|
||||||
|
for (Entry<String, ICronjob> entry : cronjobs.entrySet()) {
|
||||||
|
ScheduledCronjob resultingJob;
|
||||||
|
BackgroundJob dbEntry = alreadyExecuted.get(entry.getKey());
|
||||||
|
if (dbEntry != null) {
|
||||||
|
Calendar previousExecution = new GregorianCalendar();
|
||||||
|
previousExecution.setTimeInMillis(dbEntry.lastExecution.getTime());
|
||||||
|
Calendar followingSchedule = entry.getValue().nextExecution((Calendar) previousExecution.clone());
|
||||||
|
Calendar lastSchedule = entry.getValue().previousExecution((Calendar) currentTime.clone());
|
||||||
|
if (lastSchedule.getTimeInMillis() > followingSchedule.getTimeInMillis()) {
|
||||||
|
// This happens, if more than one execution was missed.
|
||||||
|
// In this case, run the job only once.
|
||||||
|
followingSchedule = lastSchedule;
|
||||||
|
}
|
||||||
|
resultingJob = new ScheduledCronjob(followingSchedule, entry.getValue(), dbEntry);
|
||||||
|
} else {
|
||||||
|
// This cronjob has never been executed before.
|
||||||
|
Calendar lastScheduleTime = entry.getValue().previousExecution((Calendar) currentTime.clone());
|
||||||
|
BackgroundJob model = new BackgroundJob();
|
||||||
|
model.jobName = entry.getKey();
|
||||||
|
resultingJob = new ScheduledCronjob(lastScheduleTime, entry.getValue(), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for the job with earliest executionTime - it will run next
|
||||||
|
if (earliestJob == null
|
||||||
|
|| resultingJob.executionTime.getTimeInMillis() < earliestJob.executionTime.getTimeInMillis()) {
|
||||||
|
earliestJob = resultingJob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return earliestJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runCronjobExecutionLoop() {
|
||||||
|
Thread.currentThread().setName("Cronjob");
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
ScheduledCronjob nextJob = getNextCronjob();
|
||||||
|
if (nextJob == null) {
|
||||||
|
// In case there are no cronjobs
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long waitingTime = nextJob.executionTime.getTimeInMillis() - System.currentTimeMillis();
|
||||||
|
if (waitingTime > 0) {
|
||||||
|
Thread.sleep(waitingTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextJob.cronjob.executeAt(nextJob.executionTime);
|
||||||
|
|
||||||
|
nextJob.model.lastExecution = new Timestamp(nextJob.executionTime.getTimeInMillis());
|
||||||
|
cronjobRepository.save(nextJob.model);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.error("The cronjob execution thread has been interrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void onPostConstruct() {
|
||||||
|
new Thread(this::runCronjobExecutionLoop).start();
|
||||||
|
}
|
||||||
|
}
|
@ -7,8 +7,8 @@ import javax.validation.constraints.NotNull;
|
|||||||
@Table(name = "background_jobs")
|
@Table(name = "background_jobs")
|
||||||
public class BackgroundJob {
|
public class BackgroundJob {
|
||||||
|
|
||||||
public final String JOB_DASHBOARD = "Dashboard";
|
public static final String JOB_DASHBOARD = "Dashboard";
|
||||||
public final String JOB_REORDER = "SupplierOrder";
|
public static final String JOB_REORDER = "SupplierOrder";
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package org.hso.ecommerce.repos.cronjob;
|
||||||
|
|
||||||
|
import org.hso.ecommerce.entities.cron.BackgroundJob;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface BackgroundJobRepository extends JpaRepository<BackgroundJob, Long> {
|
||||||
|
@Query(value = "SELECT * FROM background_jobs", nativeQuery = true)
|
||||||
|
Iterable<BackgroundJob> getAllJobs();
|
||||||
|
}
|
Reference in New Issue
Block a user