MutatiesActionBean.java
/*
* Copyright (C) 2018 B3Partners B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nl.b3p.brmo.verschil.stripes;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.validation.*;
import nl.b3p.brmo.verschil.util.ConfigUtil;
import nl.b3p.brmo.verschil.util.ResultSetJSONSerializer;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Mutaties actionbean. Haalt mutaties uit de BRMO RSGB database voor de gegeven
* periode.
* <br> Voorbeeld url: {@code /rest/mutaties?van=2018-08-01} of
* {@code /rest/mutaties?van=2018-08-01&tot=2018-09-01} .
*
* @author mark
* @since 1.0
*/
@RestActionBean
@UrlBinding("/rest/{location}")
public class MutatiesActionBean implements ActionBean, ValidationErrorHandler {
private static final Log LOG = LogFactory.getLog(MutatiesActionBean.class);
private final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
/**
* verplichte datum begin periode. Datum in yyyy-mm-dd formaat, de
* begindatum is deel van de periode.
*/
@Validate(required = true, mask = "\\d{4}-\\d{2}-\\d{2}")
private Date van;
/**
* optionele datum einde periode, default is datum van aanroepen. Datum in
* yyyy-mm-dd formaat, de einddatum is deel van de periode, dwz. tot-en-met.
*/
@Validate(mask = "\\d{4}-\\d{2}-\\d{2}")
private Date tot = new Date();
/**
* optionele format parameter, default is {@code json}.
*/
@Validate
private String f = "json";
private ActionBeanContext context;
private long copied;
private boolean errorCondition = false;
// als gekoppeld wordt met een table
private static final String TAX_JOIN_CLAUSE_TBL = new StringBuilder()
.append("tax.belastingplichtige tax ON ( ")
.append(" q.ka_kad_gemeentecode = trim(LEADING '0' from tax.gemeentecode) ")
.append(" AND q.ka_sectie = trim(tax.sectie) ")
.append(" AND q.ka_perceelnummer = trim(LEADING '0' from tax.perceelnummer) ")
// deelperceel nummer wordt niet gevuld vanuit BRK want dat bestaat niet meer, dus ook niet in rsgb
//.append(" AND coalesce(q.ka_deelperceelnummer,'')=coalesce(trim(LEADING '0' from tax.deelperceelnummer),'') ")
.append(" AND coalesce(q.ka_appartementsindex,'') = coalesce(trim(LEADING '0' from tax.appartementsindex),'') )").toString();
// als gekoppeld wordt met een view
private static final String TAX_JOIN_CLAUSE_VW = new StringBuilder()
.append("tax.belastingplichtige tax ON ( ")
.append(" q.gemeentecode = trim(LEADING '0' from tax.gemeentecode) ")
.append(" AND q.sectie = trim(tax.sectie) ")
.append(" AND q.perceelnummer = trim(LEADING '0' from tax.perceelnummer) ")
// deelperceel nummer wordt niet gevuld vanuit BRK want dat bestaat niet meer, dus ook niet in rsgb
//.append(" AND coalesce(q.deelperceelnummer,'')=coalesce(trim(LEADING '0' from tax.deelperceelnummer),'') ")
.append(" AND coalesce(q.appartementsindex,'') = coalesce(trim(LEADING '0' from tax.appartementsindex),'') )").toString();
/**
* context param voor view vb_koz_rechth.
*
* @see #initParams()
*/
private String VIEW_KOZ_RECHTHEBBENDE = "vb_koz_rechth";
/**
* context param voor view vb_kad_onrrnd_zk_adres.
*
* @see #initParams()
*/
private String VIEW_KAD_ONRRND_ZK_ADRES = "vb_kad_onrrnd_zk_adres";
/**
* context param voor view vb_kad_onrrnd_zk_archief.
*
* @see #initParams()
*/
private String VIEW_KAD_ONRRND_ZK_ARCHIEF = "vb_kad_onrrnd_zk_archief";
/**
* context param voor sql JDBC_FETCH_SIZE.
*
* @see #initParams()
*/
private int JDBC_FETCH_SIZE = 0;
/**
* context param voor sql timeout.
*
* @see #initParams()
*/
private int QRY_TIMEOUT = 600;
/**
* CSV separator character.
*
* @see #initParams()
*/
private String SEP = ";";
/**
* CSV quote character.
*
* @see #initParams()
*/
private String QUOTE = "";
/**
* newline voor CSV output.
*/
private final String NL = System.getProperty("line.separator");
@ValidationMethod(when = ValidationState.NO_ERRORS)
public void validateVanBeforeTot(ValidationErrors errors) {
if (tot.before(van)) {
errors.addGlobalError(new SimpleError("`van` datum is voor `tot` datum"));
}
}
@Override
public Resolution handleValidationErrors(ValidationErrors errors) throws Exception {
StringBuilder msg = new StringBuilder("Validatiefout(en): \n");
if (errors.hasFieldErrors()) {
errors.entrySet().stream().forEach((entry) -> {
entry.getValue().stream().map((e) -> {
if (LOG.isDebugEnabled()) {
msg.append("veld: ").append(entry.getKey()).append(", waarde: ");
msg.append(e.getFieldValue()).append(", melding: ");
}
return e;
}).forEach((e) -> {
msg.append(e.getMessage(Locale.ROOT)).append(" \n");
});
});
}
if (errors.get(ValidationErrors.GLOBAL_ERROR) != null) {
errors.get(ValidationErrors.GLOBAL_ERROR).stream().forEach((e) -> {
msg.append(e.getMessage(Locale.ROOT));
});
}
return new ErrorResolution(HttpServletResponse.SC_BAD_REQUEST, msg.toString());
}
@GET
@DefaultHandler
public Resolution get() throws IOException {
errorCondition = false;
LOG.trace("`get` met params: van=" + van + " tot=" + tot + ", format: " + f);
LOG.info(String.format("Uitvoeren opdracht met params: van=%s tot=%s", df.format(van), df.format(tot)));
this.initParams();
// maak werkdirectory en werkbestand
Path workPath = Files.createTempDirectory(
Paths.get(System.getProperty("java.io.tmpdir")),
"brkmutsvc"
);
File workDir = workPath.toFile();
workDir.deleteOnExit();
File workZip = Files.createTempFile("brkmutsvc", ".zip").toFile();
workZip.deleteOnExit();
// uitvoeren queries
// 2.3
LOG.debug("Ophalen nieuwe onroerende zaken");
long nwOnrrgd = this.getNieuweOnroerendGoed(workDir);
LOG.info("Aantal nieuwe onroerende zaken is: " + nwOnrrgd);
// 2.4
LOG.debug("Ophalen gekoppelde objecten");
long gekoppeld = this.getGekoppeldeObjecten(workDir);
LOG.info("Aantal gekoppeld objecten: " + gekoppeld);
// 2.5
LOG.debug("Ophalen vervallen objecten");
long vervallen = this.getVervallenOnroerendGoed(workDir);
LOG.info("Aantal vervallen objecten: " + vervallen);
// 2.6
LOG.debug("Ophalen object verkopen");
long verkopen = this.getVerkopen(workDir);
LOG.info("Aantal object verkopen: " + verkopen);
// 2.7
LOG.debug("Ophalen oppervlakte veranderd objecten");
long oppVeranderd = this.getGewijzigdeOpp(workDir);
LOG.info("Aantal oppervlakte veranderd objecten: " + oppVeranderd);
// 2.8
LOG.debug("Ophalen nieuwe subjecten");
long nwSubject = this.getNieuweSubjecten(workDir);
LOG.info("Aantal nieuwe subjecten: " + nwSubject);
// 2.9
LOG.debug("Ophalen BSN aangepast");
long bsn = this.getBSNAangevuld(workDir);
LOG.info("Aantal aangepast bsn: " + bsn);
if (nwOnrrgd < 0 || gekoppeld < 0 || vervallen < 0 || verkopen < 0 || oppVeranderd < 0 || nwSubject < 0 || bsn < 0) {
errorCondition = true;
LOG.trace("Een van de queries heeft een onverwacht resultaat gegeven, errorCondition=" + errorCondition);
}
// zippen resultaat in workZip
try (ZipOutputStream zs = new ZipOutputStream(Files.newOutputStream(workZip.toPath()))) {
LOG.debug("Aanmaken van zip bestand: " + workZip);
Files.walk(workPath)
.filter(path -> !Files.isDirectory(path))
.forEach(path -> {
ZipEntry zipEntry = new ZipEntry(workPath.relativize(path).toString());
try {
LOG.debug("Toevoegen van bestand: " + zipEntry);
zs.putNextEntry(zipEntry);
Files.copy(path, zs);
zs.closeEntry();
} catch (IOException e) {
LOG.error("Probleem tijdens aanmaken van zipfile", e);
}
});
}
return new StreamingResolution("application/zip") {
@Override
public void stream(HttpServletResponse response) throws Exception {
copied = FileUtils.copyFile(workZip, response.getOutputStream());
LOG.debug("bytes copied: " + copied);
if (errorCondition) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
FileUtils.deleteQuietly(workDir);
FileUtils.deleteQuietly(workZip);
}
}.setFilename("mutaties_" + df.format(van) + "_" + df.format(tot) + ".zip")
// 0! .setLength(copied)
.setAttachment(true)
.setLastModified(tot.getTime());
}
/**
* ophalen nieuwe percelen en appartementsrechten. [2.3].
*
* @param workDir directory waar resultaat wordt neergezet
* @return aantal nieuw
*/
private long getNieuweOnroerendGoed(File workDir) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT ")
// de ON (o.kad_identif) zorgt ervoor dat we niet voor ieder bron
// object van een samenvoeging een record krijgen, maar dat is een uitdrukkelijke wens (mail dd.18-12-18)
// .append("ON (o.kad_identif) ")
.append("o.kad_identif, ")
.append("o.dat_beg_geldh, ")
.append("q.ka_kad_gemeentecode AS gemeentecode, ")
.append("q.ka_perceelnummer AS perceelnummer, ")
.append("q.ka_deelperceelnummer AS deelperceelnummer, ")
.append("q.ka_sectie AS sectie, ")
.append("q.ka_appartementsindex AS appartementsindex, ")
.append("tax.kpr_nummer, ")
.append("bel.bpl_identif, ")
.append("bel.naam_belastingplichtige, ")
.append("q.grootte_perceel, ")
.append("q.x, ")
.append("q.y, ")
.append("z.ar_teller AS aandeel_teller, ")
.append("z.ar_noemer AS aandeel_noemer, ")
.append("z.fk_3avr_aand AS rechtcode, ")
.append("avr.omschr_aard_verkregenr_recht AS rechtomschrijving, ")
.append("h.fk_sc_rh_koz_kad_identif AS ontstaan_uit, ")
.append("h.aard, ")
.append("arch.gemeentecode AS ontstaan_uit_gemeentecode, ")
.append("arch.perceelnummer AS ontstaan_uit_perceelnummer, ")
.append("arch.deelperceelnummer AS ontstaan_uit_deelperceelnummer, ")
.append("arch.sectie AS ontstaan_uit_sectie, ")
.append("arch.appartementsindex AS ontstaan_uit_appartementsindex ")
.append("FROM kad_onrrnd_zk o ")
// samengestelde app_re en kad_perceel als q
.append("LEFT JOIN (SELECT ")
.append(" ar.sc_kad_identif, ")
.append(" ar.ka_kad_gemeentecode, ")
.append(" ar.ka_perceelnummer, ")
.append(" null AS ka_deelperceelnummer, ")
.append(" ar.ka_sectie, ")
.append(" ar.ka_appartementsindex, ")
.append(" null AS grootte_perceel, ")
.append(" null AS x, ")
.append(" null AS y ")
.append("FROM app_re ar ")
.append("UNION ALL SELECT ")
.append(" p.sc_kad_identif, ")
.append(" p.ka_kad_gemeentecode, ")
.append(" p.ka_perceelnummer, ")
.append(" p.ka_deelperceelnummer, ")
.append(" p.ka_sectie, ")
.append(" null AS ka_appartementsindex, ")
.append(" p.grootte_perceel, ")
.append(" ST_X(p.plaatscoordinaten_perceel) AS x, ")
.append(" ST_Y(p.plaatscoordinaten_perceel) AS y ")
.append("FROM kad_perceel p) q ")
// einde samenstelling app_re en kad_perceel als q
.append("ON o.kad_identif = q.sc_kad_identif ")
// zakelijk recht erbij
.append("LEFT JOIN zak_recht z ON o.kad_identif = z.fk_7koz_kad_identif ")
// soort recht omschrijving
.append("LEFT JOIN aard_verkregen_recht avr ON z.fk_3avr_aand = avr.aand ")
// ontstaan uit (NB. alleen eerste ontstaan uit, object kan uit meer dan 2 ontstaan)
.append("LEFT JOIN kad_onrrnd_zk_his_rel h ON o.kad_identif = h.fk_sc_lh_koz_kad_identif ")
// ophalen aanduiding van 'onstaan uit'
.append("LEFT JOIN mb_kad_onrrnd_zk_archief arch ON h.fk_sc_rh_koz_kad_identif = arch.koz_identif ")
// belastingplichtige
.append("LEFT JOIN wdd.belastingplichtige bel ON o.kad_identif = bel.sc_kad_identif ")
// BKP erbij
.append("LEFT JOIN ")
.append(TAX_JOIN_CLAUSE_TBL)
// Eigendom (recht van) (2), Erfpacht (recht van) (3), Gebruik en bewoning (recht van) (4), Vruchtgebruik (recht van) (12)
.append(" WHERE z.fk_3avr_aand IN ( '2', '4', '3', '12') AND '[")
// objecten met datum begin geldigheid in de periode "van"/"tot" inclusief,
.append(df.format(van)).append(",").append(df.format(tot))
.append("]'::DATERANGE @> o.dat_beg_geldh::date ")
// maar niet in de archief tabel
.append(" AND o.kad_identif NOT IN (")
.append(" SELECT kad_identif FROM kad_onrrnd_zk_archief ")
// met een datum voor "van" == geen archief record voorafgaand aan gevraagde periode
//.append(" WHERE 'dat_beg_geldh::date' < '")
//.append(df.format(van)).append("'::date ")
//
// gerechtigde is niet null
.append(") AND z.fk_8pes_sc_identif IS NOT NULL ")
// kpr nummer/gibs onbekend
.append(" AND tax.kpr_nummer IS NULL");
switch (f) {
case "csv":
return queryToCSV(workDir, "NieuweOnroerendGoed.csv", sql.toString());
case "json":
default:
return queryToJson(workDir, "NieuweOnroerendGoed.json", "nieuw", sql.toString());
}
}
/**
* ophalen gekoppelde objecten [2.4]. Nieuwe objecten met bijbehoren adres
* en/of adresbeschrijving.
*
* @param workDir directory waar resultaat wordt neergezet
* @return aantal gekoppelde objecten
*/
private long getGekoppeldeObjecten(File workDir) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT ")
.append("adr.koz_identif, ")
.append("adr.gemeentecode, ")
.append("adr.sectie, ")
.append("adr.perceelnummer, ")
.append("adr.appartementsindex, ")
.append("adr.loc_omschr, ")
.append("adr.benoemdobj_identif, ")
.append("adr.straatnaam, ")
.append("adr.huisnummer, ")
.append("adr.huisletter, ")
.append("adr.huisnummer_toev, ")
.append("adr.woonplaats, ")
.append("adr.postcode ")
.append("FROM ").append(VIEW_KAD_ONRRND_ZK_ADRES).append(" adr ")
.append(" WHERE '[")
.append(df.format(van)).append(",").append(df.format(tot)).append("]'::DATERANGE @> adr.begin_geldigheid::date ")
.append("AND adr.koz_identif NOT IN (SELECT kad_identif FROM kad_onrrnd_zk_archief WHERE '")
.append(df.format(van))
.append("'::date < dat_beg_geldh::date) ORDER BY adr.koz_identif");
switch (f) {
case "csv":
return queryToCSV(workDir, "GekoppeldeObjecten.csv", sql.toString());
case "json":
default:
return queryToJson(workDir, "GekoppeldeObjecten.json", "koppeling", sql.toString());
}
}
/**
* ophalen vervallen percelen en appartementsrechten. [2.5] Het jongste
* archief record van een object dat niet meer in de actuele tabel voorkomt.
*
* @param workDir directory waar resultaat wordt neergezet
* @return aantal vervallen
*/
private long getVervallenOnroerendGoed(File workDir) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT ON (arch.koz_identif) ")
// TODO data uit RSGB heeft geen 0-padding
.append("arch.koz_identif, ")
.append("arch.eind_geldigheid, ")
.append("arch.gemeentecode, ")
.append("arch.sectie, ")
.append("arch.perceelnummer, ")
.append("arch.deelperceelnummer, ")
.append("arch.appartementsindex ")
// 0-padding
.append("FROM ")
.append(VIEW_KAD_ONRRND_ZK_ARCHIEF)
.append(" arch ")
// object heeft archief record in gevraagde periode
.append("WHERE '[")
.append(df.format(van)).append(",").append(df.format(tot))
.append("]'::DATERANGE @> arch.eind_geldigheid::date ")
// object niet meer in actuele tabel
.append("AND arch.koz_identif NOT IN (SELECT kad_identif FROM kad_onrrnd_zk) ")
// alleen de jongste archief record
.append("ORDER BY arch.koz_identif, arch.eind_geldigheid::date DESC");
switch (f) {
case "csv":
return queryToCSV(workDir, "VervallenOnroerendGoed.csv", sql.toString());
case "json":
default:
return queryToJson(workDir, "VervallenOnroerendGoed.json", "vervallen", sql.toString());
}
}
/**
* Ophalen gewijzigde oppervlakte [2.7]. Jongste archief perceel die in de
* periode waarvan de oppervlakte anders is dan het actuele perceel.
*/
private long getGewijzigdeOpp(File workDir) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT ON (za.kad_identif) ")
.append("za.kad_identif, ")
// TODO data uit RSGB heeft geen 0-padding
.append("k.ka_kad_gemeentecode AS gemeentecode, ")
.append("k.ka_sectie AS sectie, ")
.append("k.ka_perceelnummer AS perceelnummer, ")
.append("k.ka_deelperceelnummer AS deelperceelnummer, ")
// 0-padding
.append("za.dat_beg_geldh, ")
.append("pa.grootte_perceel AS opp_oud, ")
.append("k.grootte_perceel AS opp_actueel ")
.append("FROM kad_onrrnd_zk_archief za, kad_perceel_archief pa, kad_perceel k ")
// perceel moet in de archief zitten in gevraagde periode
.append("WHERE '[")
.append(df.format(van)).append(",").append(df.format(tot))
.append("]'::DATERANGE @> za.dat_beg_geldh::DATE ")
.append("AND za.dat_beg_geldh = pa.sc_dat_beg_geldh ")
.append("AND za.kad_identif = pa.sc_kad_identif ")
.append("AND za.kad_identif = k.sc_kad_identif ")
.append("AND pa.grootte_perceel != k.grootte_perceel ")
// dubbelop vanwege grootte_perceel check
// .append("AND za.clazz = 'KADASTRAAL PERCEEL' ")
// perceel moet in de actueel zitten in de periode, anders vervallen of datafout
.append("AND za.kad_identif IN ( SELECT kad_identif FROM kad_onrrnd_zk ")
.append("WHERE '[")
.append(df.format(van)).append(",").append(df.format(tot))
.append("]'::DATERANGE @> dat_beg_geldh::DATE ) ")
.append("ORDER BY za.kad_identif, za.dat_beg_geldh DESC");
switch (f) {
case "csv":
return queryToCSV(workDir, "GewijzigdeOpp.csv", sql.toString());
case "json":
default:
return queryToJson(workDir, "GewijzigdeOpp.json", "gewijzigdeopp", sql.toString());
}
}
/**
* ophalen verkopen [2.6].
*
* @param workDir directory waar resultaat wordt neergezet
* @return aantal verkopen
*/
private long getVerkopen(File workDir) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT ")
.append("bron.ref_id, ")
.append("bron.datum::text as verkoopdatum, ")
.append("tax.gemeentecode, ")
.append("tax.sectie, ")
.append("tax.perceelnummer, ")
.append("tax.deelperceelnummer, ")
.append("tax.appartementsindex, ")
// altijd leeg want alleen als belastingplichtige onbekend is is verkoop relevant
.append("kpr_nummer, ")
.append("z.ar_teller AS aandeel_teller, ")
.append("z.ar_noemer AS aandeel_noemer, ")
.append("z.fk_3avr_aand AS rechtcode, ")
.append("avr.omschr_aard_verkregenr_recht AS rechtomschrijving ")
// verkoop + datum
.append("FROM ( ")
.append(" SELECT brondocument.ref_id, max(brondocument.datum) AS datum FROM brondocument WHERE brondocument.omschrijving = 'Akte van Koop en Verkoop' GROUP BY brondocument.ref_id) bron ")
// samengestelde app_re en kad_perceel als q
.append("LEFT JOIN (SELECT ")
.append(" ar.sc_kad_identif, ")
.append(" ar.ka_kad_gemeentecode, ")
.append(" ar.ka_perceelnummer, ")
.append(" null AS ka_deelperceelnummer, ")
.append(" ar.ka_sectie, ")
.append(" ar.ka_appartementsindex ")
.append("FROM app_re ar ")
.append("UNION ALL SELECT ")
.append(" p.sc_kad_identif, ")
.append(" p.ka_kad_gemeentecode, ")
.append(" p.ka_perceelnummer, ")
.append(" p.ka_deelperceelnummer, ")
.append(" p.ka_sectie, ")
.append(" null AS ka_appartementsindex ")
.append("FROM kad_perceel p) q ")
// einde samenstelling app_re en kad_perceel als q
.append("ON bron.ref_id=q.sc_kad_identif::text ")
.append("LEFT JOIN zak_recht z ON bron.ref_id = z.fk_7koz_kad_identif::text ")
.append("LEFT JOIN aard_verkregen_recht avr ON z.fk_3avr_aand = avr.aand ")
// gebruik left join ipv join; mail Dimitri dd.23 april 2019
.append("LEFT JOIN ")
// levert b
.append(TAX_JOIN_CLAUSE_TBL)
.append("WHERE '[")
.append(df.format(van)).append(",").append(df.format(tot))
.append("]'::DATERANGE @> bron.datum ")
.append("AND z.fk_8pes_sc_identif IS NOT null ")
// alleen verkochte percelen waar belastingplichtige onbekend is
.append(" AND tax.kpr_nummer IS null ");
switch (f) {
case "csv":
return queryToCSV(workDir, "Verkopen.csv", sql.toString());
case "json":
default:
return queryToJson(workDir, "Verkopen.json", "verkopen", sql.toString());
}
}
/**
* Nieuwe subjecten [2.8]. De voor het systeem nieuwe subjecten zijn de
* subjecten van nieuwe kadastrale objecten die niet aan de
* belastingplichtige kunnen worden gekoppeld.
*
* @param workDir directory waar resultaat wordt neergezet
* @return aantal nieuwe subjecten
*/
private long getNieuweSubjecten(File workDir) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT ON (q.naam) ")
.append("q.begin_geldigheid, ")
.append("q.soort, ")
.append("q.geslachtsnaam, ")
.append("q.voorvoegsel, ")
.append("q.voornamen, ")
.append("q.naam, ")
.append("q.subject_identif, ")
.append("q.woonadres, ")
.append("q.geboortedatum, ")
.append("q.overlijdensdatum, ")
.append("q.bsn, ")
.append("q.rsin, ")
.append("q.kvk_nummer, ")
.append("q.straatnaam, ")
.append("q.huisnummer, ")
.append("q.huisletter, ")
.append("q.huisnummer_toev, ")
.append("q.postcode, ")
.append("q.woonplaats ")
// altijd null: tax.kpr_nummer
.append("FROM ").append(VIEW_KOZ_RECHTHEBBENDE).append(" q ")
.append("LEFT JOIN ")
.append(TAX_JOIN_CLAUSE_VW)
// objecten met datum begin geldigheid in de periode "van"/"tot" inclusief,
// maar niet in de archief tabel met een datum voor "van".
.append("WHERE '[")
.append(df.format(van)).append(",").append(df.format(tot))
.append("]'::DATERANGE @> q.begin_geldigheid::date ")
.append("AND q.koz_identif NOT IN (SELECT kad_identif FROM kad_onrrnd_zk_archief WHERE '")
.append(df.format(van)).append("'::date < dat_beg_geldh::date) ")
// die niet gekoppeld kunnen worden
.append("AND tax.kpr_nummer IS NULL ")
// alleen de eerste naam met de oudste datum
.append("ORDER BY q.naam, q.begin_geldigheid ASC");
switch (f) {
case "csv":
return queryToCSV(workDir, "NieuweSubjecten.csv", sql.toString());
case "json":
default:
return queryToJson(workDir, "NieuweSubjecten.json", "nieuwe_subjecten", sql.toString());
}
}
/**
* Nieuwe subjecten in de gevraagde periode [2.9]. Nieuwe subjecten hebben
* een record in de herkomst_metadata tabel met verwijzing naar subject en
* met een datum binnen de gevraagde periode en zitten ook in de
* ander_nat_prs tabel (want die zijn niet ingeschreven/geen bsn).
*
* @param workDir directory waar resultaat wordt neergezet
* @return aantal bsn bijgewerkt
*/
private long getBSNAangevuld(File workDir) {
StringBuilder sql = new StringBuilder("SELECT ")
.append("inp.bsn, ")
.append("inp.sc_identif, ")
.append("hm.datum::TEXT ")
// (?)KPR nummer kan niet want er was toch geen bsn bekend dus waar komt dat dan vandaan?
.append("FROM ingeschr_nat_prs inp ")
.append("LEFT JOIN herkomst_metadata hm ON ")
.append("inp.sc_identif = hm.waarde ")
.append("WHERE inp.sc_identif IN (SELECT sc_identif FROM ander_nat_prs) ")
.append("AND hm.tabel='subject' ")
.append("AND '[")
.append(df.format(van)).append(",").append(df.format(tot))
.append("]'::DATERANGE @> datum::DATE ");
switch (f) {
case "csv":
return queryToCSV(workDir, "BsnAangevuld.csv", sql.toString());
case "json":
default:
return queryToJson(workDir, "BsnAangevuld.json", "bsnaangevuld", sql.toString());
}
}
/**
* Voert de sql query uit en schrijft het resultaat in het bestand in json
* formaat.
*
* @param workDir directory waar json resultaat wordt neergezet
* @param bestandsNaam naam van resultaat bestand
* @param resultName naam van de json node met resultaten, default is
* {@code results}
* @param sql uit te voeren query
* @param params optionele prepared statement params
* @return aantal verwerkte records of -1 in geval van een fout
*/
private long queryToJson(File workDir, String bestandsNaam, String resultName, String sql, String... params) {
long count = -1;
if (resultName == null) {
resultName = "results";
}
try (Connection c = ConfigUtil.getDataSourceRsgb().getConnection()) {
c.setReadOnly(true);
try (PreparedStatement stm = c.prepareStatement(sql)) {
int index = 0;
for (String p : params) {
stm.setString(index++, p);
}
SimpleModule module = new SimpleModule();
ResultSetJSONSerializer serializer = new ResultSetJSONSerializer();
ObjectMapper mapper = new ObjectMapper();
stm.setQueryTimeout(QRY_TIMEOUT);
stm.setFetchDirection(ResultSet.FETCH_FORWARD);
stm.setFetchSize(JDBC_FETCH_SIZE);
LOG.debug(stm);
try (ResultSet r = stm.executeQuery()) {
module.addSerializer(serializer);
mapper.registerModule(module);
ObjectNode objectNode = mapper.createObjectNode();
objectNode.putPOJO(resultName, r);
mapper.writeValue(new FileOutputStream(workDir + File.separator + bestandsNaam), objectNode);
}
count = serializer.getCount();
}
} catch (SQLException | IOException e) {
LOG.error(
String.format("Fout tijdens ophalen en uitschrijven gegevens (sql: %s, bestand: %s %s",
sql,
workDir,
bestandsNaam), e);
}
return count;
}
private long queryToCSV(File workDir, String bestandsNaam, String sql, String... params) {
long count = -1;
try (Connection c = ConfigUtil.getDataSourceRsgb().getConnection()) {
c.setReadOnly(true);
try (PreparedStatement stm = c.prepareStatement(sql)) {
int index = 0;
for (String p : params) {
stm.setString(index++, p);
}
stm.setQueryTimeout(QRY_TIMEOUT);
stm.setFetchDirection(ResultSet.FETCH_FORWARD);
stm.setFetchSize(JDBC_FETCH_SIZE);
LOG.debug(stm);
try (ResultSet r = stm.executeQuery();
FileOutputStream fos = new FileOutputStream(workDir + File.separator + bestandsNaam);
Writer out = new OutputStreamWriter(new BufferedOutputStream(fos), "UTF-8")) {
ResultSetMetaData metaData = r.getMetaData();
int numCols = metaData.getColumnCount();
LOG.trace("uitlezen query resultaat metadata");
// schrijf kolommen
for (int j = 1; j < (numCols + 1); j++) {
out.append(QUOTE);
out.append(metaData.getColumnName(j));
if (j < numCols) {
out.append(QUOTE);
out.append(SEP);
} else {
out.append(QUOTE);
out.append(NL);
}
}
count = 0;
LOG.trace("uitlezen en uitschrijven query resultaat");
while (r.next()) {
for (int k = 1; k < (numCols + 1); k++) {
// het zou mooier zijn om de type specifieke getters van de resultset te gebruiken,
// maar uiteindelijk doen we toch een toString() dus resultaat is gelijk.
String o = r.getString(k);
out.append(QUOTE);
out.append(o != null ? o : "");
if (k < numCols) {
out.append(QUOTE);
out.append(SEP);
} else {
out.append(QUOTE);
out.append(NL);
}
}
count++;
}
}
}
} catch (SQLException | IOException e) {
LOG.error(
String.format("Fout tijdens ophalen en uitschrijven gegevens (sql: %s, bestand: %s %s",
sql,
workDir,
bestandsNaam), e);
}
return count;
}
private void initParams() {
boolean use_mv = Boolean.parseBoolean(getContext().getServletContext().getInitParameter("use_mv"));
if (use_mv) {
LOG.info("Gebruik materialized views in de queries.");
VIEW_KOZ_RECHTHEBBENDE = VIEW_KOZ_RECHTHEBBENDE.replaceFirst("vb_", "mb_");
VIEW_KAD_ONRRND_ZK_ADRES = VIEW_KAD_ONRRND_ZK_ADRES.replaceFirst("vb_", "mb_");
VIEW_KAD_ONRRND_ZK_ARCHIEF = VIEW_KAD_ONRRND_ZK_ARCHIEF.replaceFirst("vb_", "mb_");
} else {
LOG.warn("Gebruik reguliere views in de queries (zeer langzaam).");
}
try {
JDBC_FETCH_SIZE = Integer.parseInt(getContext().getServletContext().getInitParameter("jdbc_fetch_size"));
LOG.info(String.format("Gebruik fetch size van: %s records", JDBC_FETCH_SIZE));
} catch (Exception e) {
// ignore
}
final String sep = getContext().getServletContext().getInitParameter("csv_separator_char");
if (sep != null && !sep.isEmpty()) {
SEP = sep;
LOG.info(String.format("Gebruik '%s' als scheidingsteken in CSV", SEP));
}
final String quote = getContext().getServletContext().getInitParameter("csv_quote_char");
if (quote != null && !quote.isEmpty()) {
QUOTE = quote;
LOG.info(String.format("Gebruik '%s' als aanhalingsteken in CSV", QUOTE));
}
try {
QRY_TIMEOUT = Integer.parseInt(getContext().getServletContext().getInitParameter("jdbc_query_timeout"));
LOG.info(String.format("Gebruik query timout van: %s seconden", QRY_TIMEOUT));
} catch (Exception e) {
// ignore
}
}
@Override
public ActionBeanContext getContext() {
return context;
}
@Override
public void setContext(ActionBeanContext context) {
this.context = context;
}
public Date getVan() {
return van;
}
public void setVan(Date van) {
this.van = van;
}
public Date getTot() {
return tot;
}
public void setTot(Date tot) {
this.tot = tot;
}
public String getF() {
return f;
}
public void setF(String f) {
this.f = f;
}
}