package org.hso.ecommerce.controller.cronjob; import org.hso.ecommerce.entities.cron.BackgroundJob; import org.hso.ecommerce.repos.booking.BookingAccountEntryRepository; import org.hso.ecommerce.repos.booking.BookingRepository; import org.hso.ecommerce.repos.cronjob.BackgroundJobRepository; import org.hso.ecommerce.repos.dashboard.DashboardSummaryRepository; import org.hso.ecommerce.repos.shop.ArticleRepository; import org.hso.ecommerce.repos.shop.CustomerOrderRepository; import org.hso.ecommerce.repos.supplier.ArticleOfferRepository; import org.hso.ecommerce.repos.supplier.SupplierOrderRepository; import org.hso.ecommerce.repos.supplier.SupplierRepository; import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.PostConstruct; import java.sql.Timestamp; import java.util.*; import java.util.Map.Entry; @Component @RequestMapping("intern/cronjobs") class CronjobController { private static final Logger log = LoggerFactory.getLogger(CronjobController.class); private Map cronjobs; @Autowired private final BackgroundJobRepository cronjobRepository = null; @Autowired final ArticleRepository articleRepository = null; @Autowired final ArticleOfferRepository articleOfferRepository = null; @Autowired final CustomerOrderRepository customerOrderRepository = null; @Autowired final BookingRepository bookingRepository = null; @Autowired final BookingAccountEntryRepository bookingAccountEntryRepository = null; @Autowired final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null; @Autowired final DashboardSummaryRepository dashboardSummaryRepository = null; @Autowired final SupplierRepository supplierRepository = null; @Autowired final SupplierOrderRepository supplierOrderRepository = null; @Autowired final Reorder reorderJob = null; @Autowired final DashboardCronjob dashboardCronjob = null; @Autowired final AutoSupplierPayment autoSupplierPaymentJob = null; @PostConstruct public void init() { cronjobs = getCronjobs(); } private Map getCronjobs() { HashMap map = new HashMap<>(); // Register all existing cronjobs map.put(BackgroundJob.JOB_REORDER, reorderJob); map.put(BackgroundJob.JOB_DASHBOARD, dashboardCronjob); map.put(BackgroundJob.JOB_SUPPLIER_AUTO_PAYMENT, autoSupplierPaymentJob); return Collections.unmodifiableMap(map); } private ScheduledCronjob getNextCronjob() { Calendar currentTime = new GregorianCalendar(); Iterable jobs = cronjobRepository.findAll(); HashMap alreadyExecuted = new HashMap<>(); for (BackgroundJob job : jobs) { alreadyExecuted.put(job.jobName, job); } ScheduledCronjob earliestJob = null; for (Entry 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); } try { nextJob.cronjob.executeAt(nextJob.executionTime, this); } catch (Throwable t) { log.error("Failed to execute cronjob " + nextJob.cronjob.getClass() + ":"); t.printStackTrace(); } 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(); } class ManualCronjob { public final String identifier; public final String visibleName; public ManualCronjob(String identifier, String visibleName) { this.identifier = identifier; this.visibleName = visibleName; } } private List listAllCronjobs() { ArrayList entries = new ArrayList<>(); for (Entry job : cronjobs.entrySet()) { entries.add(new ManualCronjob(job.getKey(), job.getValue().getDisplayName())); } return entries; } @GetMapping("/") public String cronjobOverview(Model model) { model.addAttribute("jobs", listAllCronjobs()); return "intern/cronjobs/index"; } @PostMapping("/run/{identifier}") public String runCronjob(Model model, @PathVariable("identifier") String identifier) { ICronjob jobToExecute = cronjobs.get(identifier); if (jobToExecute != null) { jobToExecute.executeAt(new GregorianCalendar(), this); model.addAttribute("info", String.format("Der Cronjob \"%s\" wurde ausgeführt.", jobToExecute.getDisplayName())); } else { model.addAttribute("error", "Der Cronjob konnte nicht gefunden werden."); } model.addAttribute("jobs", listAllCronjobs()); return "intern/cronjobs/index"; } }