OphaalConfigActionBean.java

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

import static nl.b3p.brmo.service.jobs.GeplandeTakenInit.QUARTZ_FACTORY_KEY;
import static nl.b3p.brmo.service.jobs.GeplandeTakenInit.SCHEDULER_NAME;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.Before;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.DontValidate;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.SimpleMessage;
import net.sourceforge.stripes.action.StrictBinding;
import net.sourceforge.stripes.controller.LifecycleStage;
import net.sourceforge.stripes.validation.SimpleError;
import net.sourceforge.stripes.validation.Validate;
import net.sourceforge.stripes.validation.ValidateNestedProperties;
import net.sourceforge.stripes.validation.ValidationMethod;
import nl.b3p.brmo.persistence.staging.AfgifteNummerScannerProces;
import nl.b3p.brmo.persistence.staging.AutomatischProces;
import nl.b3p.brmo.persistence.staging.BAG2MutatieProces;
import nl.b3p.brmo.persistence.staging.BGTLoaderProces;
import nl.b3p.brmo.persistence.staging.BRK2ScannerProces;
import nl.b3p.brmo.persistence.staging.BRKScannerProces;
import nl.b3p.brmo.persistence.staging.BerichtDoorstuurProces;
import nl.b3p.brmo.persistence.staging.BerichtTransformatieProces;
import nl.b3p.brmo.persistence.staging.BerichtstatusRapportProces;
import nl.b3p.brmo.persistence.staging.ClobElement;
import nl.b3p.brmo.persistence.staging.GDS2OphaalProces;
import nl.b3p.brmo.persistence.staging.LaadprocesStatusRapportProces;
import nl.b3p.brmo.persistence.staging.LaadprocesTransformatieProces;
import nl.b3p.brmo.persistence.staging.MailRapportageProces;
import nl.b3p.brmo.persistence.staging.MaterializedViewRefresh;
import nl.b3p.brmo.persistence.staging.TopNLScannerProces;
import nl.b3p.brmo.service.jobs.GeplandeTakenInit;
import nl.b3p.brmo.service.scanner.ProcesExecutable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.CronExpression;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;
import org.stripesstuff.stripersist.EntityTypeConverter;
import org.stripesstuff.stripersist.Stripersist;

/**
 * @author mprins
 */
@StrictBinding
public class OphaalConfigActionBean implements ActionBean {

  private static final Log log = LogFactory.getLog(OphaalConfigActionBean.class);

  private static final String JSP = "/WEB-INF/jsp/beheer/processenophalen.jsp";

  private static final String DEFAULT_BAG2_MUTATIE_MIRROR =
      "https://bag.b3p.nl/dagmutaties/bestanden.json";

  private ActionBeanContext context;

  private List<AutomatischProces> processen = new ArrayList<>();

  @Validate(converter = EntityTypeConverter.class)
  @ValidateNestedProperties({@Validate(field = "cronExpressie", on = "save")})
  private AutomatischProces proces;

  @Validate private ProcesExecutable.ProcessingImple type;

  @Validate private Map<String, ClobElement> config = new HashMap<>();

  @Before(stages = LifecycleStage.BindingAndValidation)
  public void load() {
    processen =
        Stripersist.getEntityManager()
            .createQuery("from AutomatischProces p order by type(p), p.id", AutomatischProces.class)
            .getResultList();
  }

  @DefaultHandler
  public Resolution view() {
    if (proces != null) {
      config = proces.getConfig();
    }

    return new ForwardResolution(JSP);
  }

  @DontValidate
  public Resolution cancel() {
    return new RedirectResolution(OphaalConfigActionBean.class);
  }

  @DontValidate
  public Resolution delete() {
    if (proces != null) {

      Stripersist.getEntityManager()
          .createQuery(
              "update LaadProces set automatischProces = null where automatischProces = :this")
          .setParameter("this", proces)
          .executeUpdate();

      Stripersist.getEntityManager().remove(proces);
      load();
      Stripersist.getEntityManager().getTransaction().commit();
      deleteScheduledJob(proces);
      proces = null;
      getContext().getMessages().add(new SimpleMessage("Proces is verwijderd"));
    }
    return new ForwardResolution(JSP);
  }

  @DontValidate
  public Resolution add() {
    if (type == ProcesExecutable.ProcessingImple.BGTLoaderProces) {
      // default values voor BGT loader
      config.put("create-schema", new ClobElement("true"));
      config.put("feature-types", new ClobElement("all"));
    } else if (type == ProcesExecutable.ProcessingImple.BAG2MutatieProces) {
      config.put("url", new ClobElement(DEFAULT_BAG2_MUTATIE_MIRROR));
      proces = getProces(type);
      proces.setCronExpressie("0 30 2 * * ? *");
    }
    return new ForwardResolution(JSP).addParameter("type", type);
  }

  public Resolution save() {
    if (proces == null) {
      proces = getProces(type);
    }

    proces.getConfig().clear();
    proces.getConfig().putAll(config);

    if (proces instanceof BerichtDoorstuurProces) {
      String id = ClobElement.nullSafeGet(config.get("gds2_ophaalproces_id"));
      GDS2OphaalProces p =
          Stripersist.getEntityManager().find(GDS2OphaalProces.class, Long.parseLong(id));
      if (p != null) {
        String label = ClobElement.nullSafeGet(p.getConfig().get("label"));
        proces.getConfig().put("label", new ClobElement("Doorsturen " + label + " afgiftes"));
      }
    }

    Stripersist.getEntityManager().persist(proces);
    load();
    Stripersist.getEntityManager().getTransaction().commit();
    getContext().getMessages().add(new SimpleMessage("Proces is opgeslagen"));

    try {
      this.updateJobSchedule(proces);
    } catch (SchedulerException ex) {
      getContext()
          .getMessages()
          .add(
              new SimpleError(
                  "Er is een fout opgetreden tijdens inplannen van de taak. {2}", ex.getMessage()));
    }

    return new ForwardResolution(JSP).addParameter("proces", proces.getId());
  }

  /**
   * ProcesExecutable factory.
   *
   * @param type the type of {@code AutomatischProces} to create.
   * @return an instance of the specified type
   */
  private AutomatischProces getProces(ProcesExecutable.ProcessingImple type) {
    switch (type) {
      case BAG2MutatieProces:
        return new BAG2MutatieProces();
      case BRK2ScannerProces:
        return new BRK2ScannerProces();
      case BRKScannerProces:
        return new BRKScannerProces();
      case MailRapportageProces:
        return new MailRapportageProces();
      case GDS2OphaalProces:
        return new GDS2OphaalProces();
      case BerichtTransformatieProces:
        return new BerichtTransformatieProces();
      case BerichtDoorstuurProces:
        return new BerichtDoorstuurProces();
      case LaadprocesTransformatieProces:
        return new LaadprocesTransformatieProces();
      case MaterializedViewRefresh:
        return new MaterializedViewRefresh();
      case BerichtstatusRapportProces:
        return new BerichtstatusRapportProces();
      case LaadprocesStatusRapportProces:
        return new LaadprocesStatusRapportProces();
      case TopNLScannerProces:
        return new TopNLScannerProces();
      case AfgifteNummerScannerProces:
        return new AfgifteNummerScannerProces();
      case BGTLoaderProces:
        return new BGTLoaderProces();
      default:
        throw new IllegalArgumentException(type.name() + " is geen ondersteund proces type...");
    }
  }

  private ProcesExecutable.ProcessingImple getType(AutomatischProces p) {
    return ProcesExecutable.ProcessingImple.valueOf(p.getClass().getSimpleName());
  }

  /**
   * Vervang de proces job door een aangepaste job of plaats een nieuwe job mocht deze nog niet
   * bestaan. Als de cron expressie {@code null} is wordt de job verwijderd uit de scheduler.
   *
   * @param p bij te werken proces
   * @throws SchedulerException bij een quartz fout
   */
  private void updateJobSchedule(AutomatischProces p) throws SchedulerException {
    log.debug("Update scheduled job:" + p.getId());

    StdSchedulerFactory factory =
        (StdSchedulerFactory) getContext().getServletContext().getAttribute(QUARTZ_FACTORY_KEY);
    Scheduler scheduler = factory.getScheduler(SCHEDULER_NAME);
    JobKey key = new JobKey(GeplandeTakenInit.JOBKEY_PREFIX + p.getId());
    log.debug("Jobkey voor id " + p.getId() + " gevonden? " + scheduler.checkExists(key));

    scheduler.deleteJob(key);
    if (p.getCronExpressie() != null) {
      GeplandeTakenInit.addJobDetails(scheduler, p);
    }
  }

  /**
   * verwijder een proces job uit de scheduler door update met een {@code null} cron schedule.
   *
   * @param p te verwijderen proces
   */
  private void deleteScheduledJob(AutomatischProces p) {
    try {
      p.setCronExpressie(null);
      updateJobSchedule(p);
    } catch (SchedulerException se) {
      log.warn("Ingeplande taak uit de planner halen is mislukt", se);
    }
  }

  /** validatie van de cron expressie van een proces voorafgaand aan save. */
  @ValidationMethod(on = "save")
  public void validateCronExpressie() {
    if (proces != null) {
      String expr = proces.getCronExpressie();
      try {
        if (expr != null) {
          CronExpression cron = new CronExpression(expr);
        }
      } catch (ParseException ex) {
        Stripersist.getEntityManager().refresh(proces);
        load();
        getContext()
            .getValidationErrors()
            .add(
                "cronExpressie",
                new SimpleError(
                    "{0} {2} is ongeldig, (melding: {4}, mogelijk nabij positie {3})",
                    expr, ex.getErrorOffset(), ex.getLocalizedMessage()));
      }
    }
  }

  // <editor-fold defaultstate="collapsed" desc="getters en setters">
  @Override
  public ActionBeanContext getContext() {
    return context;
  }

  @Override
  public void setContext(ActionBeanContext context) {
    this.context = context;
  }

  public List<AutomatischProces> getProcessen() {
    return processen;
  }

  public void setProcessen(List<AutomatischProces> processen) {
    this.processen = processen;
  }

  public AutomatischProces getProces() {
    return proces;
  }

  public void setProces(AutomatischProces proces) {
    this.proces = proces;
    this.type = getType(proces);
  }

  public Map<String, ClobElement> getConfig() {
    return config;
  }

  public void setConfig(Map<String, ClobElement> config) {
    this.config = config;
  }

  public ProcesExecutable.ProcessingImple getType() {
    return type;
  }

  public void setType(ProcesExecutable.ProcessingImple type) {
    this.type = type;
    this.proces = getProces(type);
  }
  // </editor-fold>
}