GeplandeTakenInit.java

/*
 * Copyright (C) 2015 B3Partners B.V.
 */
package nl.b3p.brmo.service.jobs;

import java.io.IOException;
import java.text.ParseException;
import java.util.List;
import java.util.Properties;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import nl.b3p.brmo.persistence.staging.AutomatischProces;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.stripesstuff.stripersist.Stripersist;

/**
 * Initialiseer de Quartz scheduler. Vanwege gebruik van de Stripersist entity manager kan dat pas
 * na initialisatie van de Stripes stack en dat gebeurt in een ServletFilter, dus deze servlet moet
 * een auto-start optie krijgen in web.xml waardoor dat na het Filter gebeurt.
 *
 * @author mprins
 */
public class GeplandeTakenInit implements Servlet {

  private static final Log log = LogFactory.getLog(GeplandeTakenInit.class);
  private static Scheduler scheduler;
  private static Scheduler nhrScheduler;

  public static final String JOBKEY_PREFIX = "proces_";

  public static final String TRIGGERKEY_PREFIX = "trigger_";

  public static final String SCHEDULER_NAME = "BRMOgeplandeTaken";

  public static final String QUARTZ_FACTORY_KEY = "BRMO_qtz_factory";

  public static final String NHR_SCHEDULER_NAME = "BRMOnhrTaken";

  public static final String NHR_QUARTZ_FACTORY_KEY = "BRMO_nhr_qtz_factory";

  private ServletConfig config;

  @Override
  public void init(ServletConfig config) throws ServletException {
    this.config = config;
    try {
      this.setupQuartz();
    } catch (SchedulerException ex) {
      log.error(ex);
    }
  }

  @Override
  public ServletConfig getServletConfig() {
    return config;
  }

  @Override
  public void service(ServletRequest req, ServletResponse res)
      throws ServletException, IOException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  @Override
  public String getServletInfo() {
    return "Initialiseer Quartz scheduler voor BRMO";
  }

  @Override
  public void destroy() {
    try {
      destroyQuartz();
    } catch (SchedulerException ex) {
      log.warn(ex);
    }
  }

  private void destroyQuartz() throws SchedulerException {
    if (scheduler != null) {
      scheduler.shutdown(true);
    }

    if (nhrScheduler != null) {
      nhrScheduler.shutdown(false);
    }
  }

  private void setupQuartz() throws SchedulerException {
    Properties props = new Properties();
    props.put("org.quartz.scheduler.instanceName", SCHEDULER_NAME);
    String threadCount =
        this.getServletConfig().getServletContext().getInitParameter("quartz.threadCount");
    if (threadCount != null && (Integer.parseInt(threadCount) > 1)) {
      log.warn(
          "Instellen van quartz threadcount op niet-default waarde van "
              + threadCount
              + " Gebruiker moet zorg dragen dat er geen overlappende transformatie- of GDS2 processen van eenzelfde soort zijn.");
      props.put("org.quartz.threadPool.threadCount", threadCount);
    } else {
      props.put("org.quartz.threadPool.threadCount", "1");
    }
    props.put("org.quartz.scheduler.interruptJobsOnShutdownWithWait", "true");
    props.put("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
    props.put("org.quartz.scheduler.skipUpdateCheck", "true");
    log.debug("Start quartz scheduler met de opties: " + props);

    StdSchedulerFactory factory = new StdSchedulerFactory(props);
    scheduler = factory.getScheduler();
    scheduler.start();

    Stripersist.requestInit();
    EntityManager entityManager = Stripersist.getEntityManager();

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<AutomatischProces> cq = cb.createQuery(AutomatischProces.class);
    Root<AutomatischProces> from = cq.from(AutomatischProces.class);
    cq.where(cb.isNotNull(from.get("cronExpressie")));
    List<AutomatischProces> procList = entityManager.createQuery(cq).getResultList();

    for (AutomatischProces p : procList) {
      addJobDetails(scheduler, p);
    }

    createNHRJob();

    this.getServletConfig().getServletContext().setAttribute(QUARTZ_FACTORY_KEY, factory);
  }

  private void createNHRJob() throws SchedulerException {
    Properties props = new Properties();
    props.put("org.quartz.scheduler.instanceName", NHR_SCHEDULER_NAME);
    props.put("org.quartz.threadPool.threadCount", "1");
    props.put("org.quartz.scheduler.interruptJobsOnShutdownWithWait", "true");
    props.put("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
    props.put("org.quartz.scheduler.skipUpdateCheck", "true");

    StdSchedulerFactory factory = new StdSchedulerFactory(props);
    nhrScheduler = factory.getScheduler();
    nhrScheduler.start();

    JobDetail job = JobBuilder.newJob(NHRJob.class).withIdentity("NHR").build();

    SimpleTrigger trigger =
        TriggerBuilder.newTrigger()
            .startNow()
            .forJob(job)
            .withIdentity("NHR")
            .withSchedule(SimpleScheduleBuilder.repeatMinutelyForever(10))
            .build();

    nhrScheduler.scheduleJob(job, trigger);

    JobDetail emailJob = JobBuilder.newJob(NHREmailJob.class).withIdentity("NHR-email").build();

    CronTrigger emailTrigger =
        TriggerBuilder.newTrigger()
            .startNow()
            .forJob(emailJob)
            .withIdentity("NHR-email")
            .withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(0, 0))
            .build();

    nhrScheduler.scheduleJob(emailJob, emailTrigger);

    log.info("NHR taak opgestart");

    this.getServletConfig().getServletContext().setAttribute(NHR_QUARTZ_FACTORY_KEY, factory);
  }

  /**
   * voeg een job toe op basis van het proces.
   *
   * @param scheduler taakplanner van de brmo service
   * @param p het te bewerken automatisch proces
   * @throws SchedulerException als de bewerking mislukt
   */
  public static void addJobDetails(Scheduler scheduler, AutomatischProces p)
      throws SchedulerException {
    try {
      CronExpression cron = new CronExpression(p.getCronExpressie());

      JobDetail job =
          JobBuilder.newJob(AutomatischProcesJob.class)
              .usingJobData("id", p.getId())
              .withIdentity(JOBKEY_PREFIX + p.getId())
              .build();

      CronTrigger trigger =
          TriggerBuilder.newTrigger()
              .startNow()
              .forJob(job)
              .withIdentity(TRIGGERKEY_PREFIX + p.getId())
              .withSchedule(CronScheduleBuilder.cronSchedule(cron))
              .build();
      scheduler.scheduleJob(job, trigger);

      log.info(
          String.format(
              "%s met id: %d met cron expressie %s is toegevoegd (of bijgewerkt).",
              p.getClass().getSimpleName(), p.getId(), p.getCronExpressie()));
    } catch (ParseException ex) {
      log.warn(
          String.format(
              "Ongeldige cron expressie voor %s met id %d. Het proces is niet ingepland.",
              p.getClass().getSimpleName(), p.getId()),
          ex);
    }
  }
}