StUFBGasynchroon.java

package nl.b3p.brmo.stufbg204;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.sql.DataSource;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import nl.b3p.brmo.loader.BrmoFramework;
import nl.b3p.brmo.loader.util.BrmoException;
import nl.b3p.brmo.service.util.ConfigUtil;
import nl.b3p.brmo.stufbg204.util.StUFbg204Util;
import nl.egem.stuf.sector.bg._0204.AsynchroonAntwoordBericht;
import nl.egem.stuf.sector.bg._0204.KennisgevingsBericht;
import nl.egem.stuf.sector.bg._0204.VraagBericht;
import nl.egem.stuf.stuf0204.BevestigingsBericht;
import nl.egem.stuf.stuf0204.FoutBericht;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * @author mprins
 */
@WebService(
    serviceName = "StUFBGAsynchroon",
    portName = "StUFBGAsynchronePort",
    endpointInterface = "nl.egem.stuf.sector.bg._0204.StUFBGAsynchroonPortType",
    targetNamespace = "http://www.egem.nl/StUF/sector/bg/0204",
    wsdlLocation = "WEB-INF/wsdl/bg0204.wsdl")
@HandlerChain(file = "/handler-chain.xml")
public class StUFBGasynchroon {

  private static final Log LOG = LogFactory.getLog(StUFBGasynchroon.class);

  public BevestigingsBericht ontvangAsynchroneVraag(VraagBericht vraag) {
    LOG.debug("Er is een vraag ontvangen van soort: " + vraag.getStuurgegevens().getBerichtsoort());
    BevestigingsBericht b = new BevestigingsBericht();
    b.setStuurgegevens(StUFbg204Util.maakStuurgegevens(vraag.getStuurgegevens()));

    return b;
  }

  public BevestigingsBericht ontvangAsynchroonAntwoord(
      AsynchroonAntwoordBericht asynchroonAntwoord) {
    LOG.debug(
        "Er is een antwoord ontvangen van soort: "
            + asynchroonAntwoord.getStuurgegevens().getBerichtsoort());
    BevestigingsBericht b = new BevestigingsBericht();
    b.setStuurgegevens(StUFbg204Util.maakStuurgegevens(asynchroonAntwoord.getStuurgegevens()));

    return b;
  }

  public BevestigingsBericht ontvangFout(FoutBericht fout) {
    LOG.debug("Er is een fout ontvangen van soort: " + fout.getStuurgegevens().getBerichtsoort());
    BevestigingsBericht b = new BevestigingsBericht();
    b.setStuurgegevens(StUFbg204Util.maakStuurgegevens(fout.getStuurgegevens()));

    return b;
  }

  /**
   * 4.2 Stuurgegevens voor kennisgevingberichten. In paragraaf 2.3 zijn vier varianten binnen een
   * kennisgevingbericht onderkend:
   *
   * <p><strong>Toevoeging</strong>
   *
   * <p>Bij een toevoeging is in het zendende systeem een occurrence toegevoegd, omdat is
   * vastgesteld dat in de werkelijkheid een voor het zendende systeem relevant object bestaat.
   *
   * <p><strong>Wijziging</strong>
   *
   * <p>Bij een wijziging is in het zendende systeem een occurrence gewijzigd, omdat is vastgesteld
   * dat er in de werkelijkheid eigenschappen (gegevens) zijn veranderd van het object waar naar die
   * occurrence verwijst.
   *
   * <p><strong>Verwijdering</strong>
   *
   * <p>Bij een verwijdering is in het zendende systeem een occurrence verwijderd, omdat is
   * vastgesteld dat in de werkelijkheid het object waarnaar de occurrence verwijst, niet meer
   * bestaat of niet meer relevant is voor het zendende systeem.
   *
   * <p><strong>Correctie</strong>
   *
   * <p>Bij een correctie is in het zendende systeem een occurrence gewijzigd, omdat is vastgesteld
   * dat de vastgelegde waarden niet correct waren. Bij een correctie is het object in de
   * werkelijkheid waarnaar de occurrence verwijst, zelf niet gewijzigd. Deze verschillende
   * varianten worden als volgt gecodeerd in het stuurgegeven mutatiesoort:
   *
   * <ul>
   *   <li>‘T’: Toevoeging
   *   <li>‘W’: Wijziging
   *   <li>‘V’: Verwijdering
   *   <li>‘C’: Correctie
   * </ul>
   *
   * Het is aan het ontvangende systeem om te interpreteren in hoeverre een kennisgeving relevant is
   * en of de kennisgeving het gevolg is van het ontstaan of verdwijnen van een object c.q. van het
   * relevant worden of niet meer relevant zijn van het object voor het zendende systeem. Het
   * kennisgevingbericht bevat geen aanduiding van de gebeurtenis die aanleiding gaf tot de
   * wijziging van de gegevens (zie paragraaf 2.3). <br>
   * Naast de mutatiesoort is in een kennisgevingbericht ook relevant hoe het ontvangende systeem
   * geacht wordt te reageren op het bericht. Een kennisgevingbericht kan puur informatief bedoeld
   * zijn: het ontvangende systeem mag zelf beslissen of de kennisgeving al dan niet wordt verwerkt
   * in de eigen gegevens. Daarnaast kan het verplicht zijn voor het ontvangende systeem om de
   * gegevens over te nemen. Of een kennisgevingbericht informatief is of verplicht over te nemen
   * geeft het stuurgegeven indicator overname aan met 'I' (informatief) respectievelijk 'V'
   * (verplicht). Aanvullende afspraken over de omgang met deze rubriek kunnen worden vastgelegd in
   * het sectormodel.
   *
   * @param kennisgeving te verwerken kennisgevingsbericht
   * @return een bevestiging van de kennisgeving
   */
  public BevestigingsBericht ontvangKennisgeving(KennisgevingsBericht kennisgeving) {
    LOG.debug(
        "Er is een kennisgeving ontvangen van soort: "
            + kennisgeving.getStuurgegevens().getBerichtsoort()
            + " en mutatiesoort "
            + kennisgeving.getStuurgegevens().getKennisgeving().getMutatiesoort());
    BevestigingsBericht b = new BevestigingsBericht();
    b.setStuurgegevens(StUFbg204Util.maakStuurgegevens(kennisgeving.getStuurgegevens()));

    switch (kennisgeving.getStuurgegevens().getKennisgeving().getMutatiesoort()) {
      case T:
        saveBericht(
            kennisgeving, kennisgeving.getStuurgegevens().getKennisgeving().getTijdstipMutatie());
        break;
      case W:
      case C:
        // dit werkt -nog- niet omdat dit 2 PRS nodes bevat, een was en een wordt...
        saveBericht(
            kennisgeving, kennisgeving.getStuurgegevens().getKennisgeving().getTijdstipMutatie());
        break;
      case V:
      default:
        LOG.warn("Onbekende mutatiesoort wordt niet verwerkt.");
        break;
    }
    return b;
  }

  private void saveBericht(Object body, String datum) {
    try {
      DataSource ds = ConfigUtil.getDataSourceStaging();
      BrmoFramework brmo = new BrmoFramework(ds, null, null);
      InputStream in = getXml(body);

      Date d = StUFbg204Util.sdf.parse(datum);
      String bestand_naam = "StUF-BG upload op " + StUFbg204Util.sdf.format(new Date());

      brmo.loadFromStream(BrmoFramework.BR_BRP, in, bestand_naam, d, null);
      brmo.closeBrmoFramework();
    } catch (BrmoException ex) {
      LOG.error("Fout tijdens laden van StUF-BG bericht", ex);
    } catch (JAXBException | ParseException ex) {
      LOG.error("Fout tijdens parsen van bericht", ex);
    }
  }

  private InputStream getXml(Object o) throws JAXBException {

    try {
      // maak van POJO een inputstream
      Marshaller jaxbMarshaller = StUFbg204Util.getStufJaxbContext().createMarshaller();
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      jaxbMarshaller.marshal(o, baos);
      InputStream in = new ByteArrayInputStream(baos.toByteArray());

      // maak er een document van
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      DocumentBuilder builder = factory.newDocumentBuilder();

      Document doc = builder.parse(in);
      List<SimpleEntry<String, String>> prefixes = new ArrayList<>();
      getPrefixesRecursive(doc.getDocumentElement(), prefixes);
      // haal de body eruit met xpath
      XPathFactory xPathfactory = XPathFactory.newInstance();
      XPath xpath = xPathfactory.newXPath();
      XPathExpression expr = xpath.compile("//*[local-name() = 'body']/*");
      NodeList nodelist = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);

      Document newDoc = builder.newDocument();
      Node root = newDoc.createElement("root");

      for (int i = 0; i < nodelist.getLength(); i++) {
        Node n = nodelist.item(i);
        Node newNode = newDoc.importNode(n, true);
        root.appendChild(newNode);
      }

      // Vertaal xml naar inputstream voor verwerking in brmo framework
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      Transformer t = TransformerFactory.newInstance().newTransformer();
      t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
      t.transform(new DOMSource(root), new StreamResult(outputStream));
      InputStream is = new ByteArrayInputStream(outputStream.toByteArray());

      return is;
    } catch (ParserConfigurationException
        | SAXException
        | IOException
        | XPathExpressionException
        | TransformerException ex) {
      LOG.error("Cannot parse body", ex);
    }
    return null;
  }

  private static final String XMLNAMESPACE = "xmlns";

  public static void getPrefixesRecursive(
      Element element, List<SimpleEntry<String, String>> prefixes) {
    getPrefixes(element, prefixes);
    Node parent = element.getParentNode();
    if (parent instanceof Element) {
      getPrefixesRecursive((Element) parent, prefixes);
    }
  }

  /**
   * Get all prefixes defined on this element for the specified namespace.
   *
   * @param element dom element
   * @param prefixes lijst van namespace prefixes
   */
  public static void getPrefixes(Element element, List<SimpleEntry<String, String>> prefixes) {
    NamedNodeMap atts = element.getAttributes();
    for (int i = 0; i < atts.getLength(); i++) {
      Node node = atts.item(i);
      String name = node.getNodeName();
      if (name != null && (XMLNAMESPACE.equals(name) || name.startsWith(XMLNAMESPACE + ":"))) {
        SimpleEntry s = new SimpleEntry(name, node.getNodeValue());
        prefixes.add(s);
      }
    }
  }
}