include ':av_hoheitsgrenzen'
include ':av_nachfuehrungskreise'
08 February 2017
Der Beitrag dürfte auch den Namen «INTERLIS leicht gemacht #14» erhalten, da er sehr schön die Vorzüge einer model-driven GDI mit INTERLIS zeigt.
Die Idee mit Gradle Datenflüsse zu orchestrieren resp. aus einzelnen immer wiederkehrenden Schritten verschiedene Jobs zusammenstöpseln, wurde in diesem Beitrag erläutert. Eine Herausforderung, die auftritt, ist der Umgang mit Wiederholungen. Wenn es nun häufig darum gehen soll, Daten von A nach B zu kopieren und diese in eine Datenbank zu importieren, merkt man sofort, dass auf dem fremden Server ja nicht nur eine Datei liegt, die es zu importieren gilt, sondern viele. Jetzt könnte man natürlich das build-File einfach mehrfach kopieren. Bei zwei Dateien geht das ja vielleicht noch, aber bei 109 Gemeinden mit je einem AV-Datensatz will man mit diesem Ansatz nie mehr was verändern. Oder die einzelnen Schritte sind so ausgelegt, dass sie nicht nur mit einer Datei umgehen können, sondern mit einer beliebigen Anzahl. Das ist wahrscheinlich komplizierter zu programmieren und ich finde es «schöner», wenn die einzelnen Steps möglichst atomar sind. Vor allem möchte ich aber die Ressource kennen, mit der ich mich während des Jobs beschäftigen muss. Was wenn z.B. auf dem FTP-Server eine unbekannte 10GB-Datei liegt und einfach alles vom FTP-Server herunterladen wird und versucht wird zu importieren? Uncool. Also muss was her, dass zwar tendenziell auf die einzelne Datei zielt aber es soll so etwas geben wie eine Forschleife für die Wiederverwendbarkeit von Jobs (mit anderen Parameter).
Gradle kennt Multiprojekte. Es gibt ein Root-Projekt und Subprojekte. Das Root-Projekt soll in unserem Fall dazu dienen die 1 bis n (identischen) Subprojekte zu konfigurieren und auszuführen.
Für die Probe aufs Exempel will ich Daten, die in unserer model-driven GDI, erfasst werden täglich prüfen. Dazu muss ich die Daten aus der PostgreSQL-Datenbank nach INTERLIS mit ili2pg exportieren und anschliessend mit ilivalidator auf ihre Modellkonformität prüfen. Genau genommen reicht der Export mit ili2pg, da die ilivalidator-Prüfbibliotheken bequemerweise bereits eingebaut sind. Trotz den praktischen QGIS-Editier-Widgets und -Formularen sind Fehler bei der Erfassung schnell passiert. Vor allem wenn der Benutzer Beziehungen manuell korrigiert und erfasst. Darum lohnt sich dieses tägliche Validieren. Insbesondere können so auch Geometriefehler gefunden werden.
Zum Testen stehen zwei kleinere Modelle zur Verfügung: Hoheitsgrenzen und die Nachführungskreise der amtlichen Vermessung.
Die einzelnen Subprojekte müssen in Gradle in der Datei settings.gradle
definiert werden:
include ':av_hoheitsgrenzen'
include ':av_nachfuehrungskreise'
Die Namen der Subprojekte sind frei wählbar. Weil aber diese Namen über ein Property im build-File ansprechbar sind, wählt man sinnigerweise etwas aus, das man im weiteren Verlauf noch verwenden kann. Diese Subprojekte befinden sich «eigentlich» in Unterordnern. Weil wir aber sowieso keinen Code in diesen Unterordnern resp. Subprojekten haben, müssen diese Ordner auch nicht existieren.
Nun kommt das erste Mal die Magie zum Vorschein. Im Haupt-Build-File build.gradle
können den Subprojekten Properties mitgegeben werden. Wir müssen ja z.B. wissen in welchem Datenbankschema die Daten liegen und welches INTERLIS-Datenmodell verwendet werden muss.
project(':av_hoheitsgrenzen') {
ext.dbschema = "av_hoheitsgrenzen"
ext.ilimodels = "SO_Hoheitsgrenzen_20170203"
}
project(':av_nachfuehrungskreise') {
ext.dbschema = "av_nachfuehrungskreise"
ext.ilimodels = "SO_AV_Nachfuehrungskreise_20161126"
}
Wo werden die Daten jetzt aber exportiert und geprüft? Gleich nachfolgend, hier:
subprojects {
apply from: "${rootDir}/validation.gradle"
}
Das bedeutet, dass für jedes Subprojekt das Build-Skript validation.gradle
ausgeführt wird. Und in genau diesem validation.gradle
findet der eigentliche Validierungs-Job statt:
import ch.so.agi.gdi.tasks.io.InterlisExportTask
ext {
exportDir = "/opt/tmp/validation/"
}
task validateData(type: InterlisExportTask) {
host = db_host
port = db_port
database = db_database
usr = db_usr
pwd = db_pwd
schema = dbschema
models = ilimodels
xtfFileName = exportDir + project.name + ".xtf"
logfile = exportDir + project.name + ".log"
}
task cleanUp(type: Delete) {
outputs.upToDateWhen {false}
onlyIf {
validateData.state.failure == null
}
delete fileTree(dir: exportDir, include: "**/${project.name}.*")
}
validateData.finalizedBy('cleanUp')
Zuerst wird der selber geschriebene - auf ili2pg basierende - InterlisExportTask
importiert. Anschliessend wird das Exportverzeichnis definiert. Der Task validateData
ist vom Typ InterlisExportTask
und prüft gleich während des Exportierens die Daten. Falls sie fehlerhaft sind, ist der Build nicht erfolgreich. Nach dem Validieren werden die exportierten Daten wieder gelöscht. Dies macht der cleanUp
-Task vom Typ Delete
. Dieser Typ ist bereits Bestandteil von Gradle und musste nicht selber geschrieben werden. Gelöscht werden die Daten aber nur, falls die Validierung erfolgreich war: onlyIf
in Verbindung mit validateData.state.failure == null
.
Zu guter Letzt möchten wir mit einer E-Mail benachrichtigt werden, falls etwas schief lief. Dies können wir im Root-Build-File mit dem buildFinished
-Hook erledigen. Das ist dann kein Task, denn wir aufrufen, sondern ein paar Zeilen selbst erklärendes Groovy pur.
import org.apache.commons.mail.DefaultAuthenticator
import org.apache.commons.mail.Email
import org.apache.commons.mail.SimpleEmail
gradle.buildFinished { buildResult ->
println "\nBUILD FINISHED"
if (buildResult.failure) {
logger.error("build failure - " + buildResult.failure)
}
if (buildResult.failure) {
def message = buildResult.failure.getMessage()
Email email = new SimpleEmail()
email.setHostName(email_host)
email.setSmtpPort(Integer.parseInt(email_port))
email.setAuthenticator(new DefaultAuthenticator(email_login, email_password))
email.setSSLOnConnect(true)
email.setFrom(email_from)
email.setSubject(project.name)
email.setMsg(message)
email.addTo(email_receiver)
email.send()
}
}
Will man einzelne Subprojekte ausführen, muss man den vollständigen Pfad (mit Doppelpunkt getrennt) zum Task angeben. In unserem Fall für die Hoheitsgrenzen: gradle :av_hoheitsgrenzen:validateData
. Oder aber wenn wir alle Subprojekte am Stück ausführen lassen wollen: gradle validateData
. Falls jetzt das erste Subprojekt einen Fehler wirft, wird das zweite nicht mehr ausgeführt. Dieses Verhalten können wir mit der Option --continue
ändern.
Ein weiteres kleines Goodie ist die Option --profile
. Das liefert eine nette HTML-Seite mit Informationen zum Build.
Interessanter ist so eine Auswertung aber für ein anderes Projekt: Von map.geo.admin.ch werden sämtliche 230 frei verfügbaren AV-Datensätze herunterladen und mit ilivalidator geprüft. Da gibt der Report schon ein bisschen mehr her. Mit der Performanz von ilivalidator darf man schon mal ganz zufrieden sein. 230 AV-Datensätze in 1:11h. Die grafische Aufbereitung der Resultate gibt es hier, die natürlich täglich mittels cronjob und Gradle-Build-File nachgeführt werden.