10 April 2017

Im letzten Beitrag zeigte ich wie man in ilivalidator eigene, zusätzliche Constraints definieren kann und eigene Java-Funktionen schreiben kann. Beides hat zum Ziel, dass man neben der eigentlichen Modellprüfung zusätzliche Prüfungen durchführen kann. Von der Version 0.10.0 zur Version 1.0.0 hat sich da noch einiges getan. Und eines vorweg: es ist ziemlich genial geworden.

Ich habe den fast gleichen Test-Rumspiel-Datensatz wie beim letzten Mal verwendet (ein paar Fixpunkte). Einzig den Modellnamen habe ich leicht angepasst: AVLight. Das Modell sieht jetzt so aus:

INTERLIS 2.3;

MODEL AVLight (en) AT "http://sogeo.services"
  VERSION "2017-02-11" =

  IMPORTS UNQUALIFIED INTERLIS;
  IMPORTS UNQUALIFIED GeometryCHLV95_V1;

    UNIT
      Grads = 200.0 / PI [rad];
      SquareMeters [m2] = (m * m);

    DOMAIN

      Altitude = -200.000 .. 5000.000 [m];
      Rotation = 0.0 .. 399.9 [Grads];
      Accuracy = 0.0 .. 700.0;

   TOPIC Control_points =
      OID AS INTERLIS.UUIDOID;

      CLASS Control_point =
        Category : MANDATORY 0..5;
        IdentND : MANDATORY TEXT*12;
        Number : MANDATORY TEXT*12;
        Geometry : MANDATORY Coord2;
        Plan_accuracy : Accuracy;
        Geom_alt : Altitude;
        Alt_accuracy : Accuracy;
        Mark : MANDATORY 0..8;
        State_of : XMLDateTime;
        FOSNr : 0 .. 9999;
      UNIQUE IdentND, Number;
      END Control_point;

    END Control_points;

  END AVLight.

Wenn ich jetzt einen «no-frills» ilivalidator-Aufruf mache, meldet es mir bereits einen Fehler. Eine Höhe ist viel zu gross und entspricht nicht den Vorgaben des Modelles.

Wenn ich nun weiss, dass die Nummern der Fixpunkte bei uns immer nur acht Zeichen lang sind, kann/muss ich einen zusätzlichen Contraint definieren. Beim letzten Mal habe ich diesen Contraint mit der vordefinierten INTERLIS-Funktion INTERLIS.len() direkt in das Original-Modell geschrieben. Das geht natürlich, ist aber nicht sonderlich elegant und man müsste immer so etwas wie eine Schattenkopie des wirklichen Originals vorliegen haben.

Eleganter geht es mit Views. INTERLIS-Views sind mir soweit gänzlich unbekannt und die Syntax auch nicht nicht wirklich geläuft, aber für etwas gibt es ja das Referenzhandbuch und die ilivalidator-Dokumentation. Man schreibt sich jetzt also ein AVLight_Check-Modell und definiert dort die Views mit den zusätzlichen Constraints:

INTERLIS 2.3;

CONTRACTED MODEL AVLight_Check (en) AT "http://sogeo.services"
  VERSION "2017-04-07" =
  IMPORTS AVLight;

  VIEW TOPIC Control_points_Check =
  DEPENDS ON AVLight.Control_points;

    VIEW v_Control_point
    	PROJECTION OF AVLight.Control_points.Control_point;
    =
      ALL OF Control_point;
      MANDATORY CONSTRAINT (INTERLIS.len (Number)) == 8;
    END v_Control_point;

  END Control_points_Check;

END AVLight_Check.

Um die Prüfung mit dem zusätzlichen Constraint durchzuführen, braucht es einzig noch einen Eintrag in der Konfigurationsdatei AVLight.toml:

["PARAMETER"]
additionalModels="AVLight_Check"

So wird ilivalidator mitgeteilt, dass es ein weiteres zu berücksichtigendes Modell gibt. Der Aufruf erfolgt mit dem --config-Parameter und liefert folgenden zusätzlichen Fehler:

Info: second validation pass...
Info: validate role references of AVLight.Control_points.Control_point...
Error: line 65: AVLight.Control_points.Control_point: tid a01868e1-11fd-4c7c-a109-341b778b4d44: Mandatory Constraint AVLight_Check.Control_points_Check.v_Control_point.Constraint1 is not true.
Info: ...validation failed

Schön. Nur nicht sonderlich leserlich. Mit Metattributen !!@…​ (Zeilen 14 und 15) kann man nun einerseits den Constraint mit einem Namen versehen und auch eine für den Menschen lesbare Fehlermeldung ausgeben:

INTERLIS 2.3;

CONTRACTED MODEL AVLight_Check (en) AT "http://sogeo.services"
  VERSION "2017-04-07" =
  IMPORTS AVLight;

  VIEW TOPIC Control_points_Check =
  DEPENDS ON AVLight.Control_points;

    VIEW v_Control_point
    	PROJECTION OF AVLight.Control_points.Control_point;
    =
      ALL OF Control_point;
      !!@ name = Punktnummer_Laenge
      !!@ ilivalid.msg = "Laenge der Punktenummer {Number} ist falsch. Erwartet wird 8."
      MANDATORY CONSTRAINT (INTERLIS.len (Number)) == 8;
    END v_Control_point;

  END Control_points_Check;

END AVLight_Check.

Die Metattribute müssen vor den betroffenen Constraint platziert werden. Das Ergebnis ist definitiv informativer:

Info: second validation pass...
Info: validate role references of AVLight.Control_points.Control_point...
Error: line 65: AVLight.Control_points.Control_point: tid a01868e1-11fd-4c7c-a109-341b778b4d44: Laenge der Punktenummer 1066703099 ist falsch. Erwartet wird 8.
Info: ...validation failed

Wenn ich jetzt Dinge prüfen will, die ich nicht mehr mit den INTERLIS-Standardfunktion umsetzen kann, muss ich selber in die Java-Tasten hauen. Grundsätzlich gilt weiterhin, dass man ein Java-Interface implementieren muss. Bei uns beginnen alle BfS-Nummern mit einer «2». Jetzt kann ich also eine «SubText»-Funktion schreiben, die von einem String einen Teil extrahiert und mit einer «2» vergleicht und das Ganze in einen MandatoryConstraint packen:

MANDATORY CONSTRAINT SubText(FOSNr,"0","1") == "2";

Disclamer: Die Methode habe ich gewählt, weil sie bereits aus Testzwecken im iox-ili-Code vorhanden ist.

Neu müssen die - nennen wir sie mal - Custom Functions eine Methode getQualifiedIliName() implementieren. Der Rückgabewert ist ein qualifizierter Name, wie man die Methode in Modellen ansprechen kann. Dafür fällt in der Deklaration im Modell das Metaattribut weg. Zuerst sah ich die Eleganz nicht und dachte, dass man ja jetzt gleiche Methoden für verschiedene Modelle schreiben muss. Aber weit gefehlt. Als man es mir dann erklärte, dämmerte es: Man (z.B. eine Organisation, ein Checkservice-Anbieter) erstellt für die Deklaration der Methoden ein eigenes INTERLIS-Modell, das man - analog wie das AVLight-Modell - jeweils in das Check-Modell importiert. Daher ist der qualifizierte Name meiner Implementierung der SubText-Methode SO_FunctionsExt.mySubText. SO_FunctionsExt ist das Modell in dem einzig meine Funktion deklariert wird.

Die Methode wird jetzt kompiliert und als JAR-Datei in einen Ordner kopiert. Standardmässig lädt ilivalidator seit Version 1.0.0 die Custom Functions aus dem plugins-Verzeichnis (muss erstellt werden) innerhalb der Applikation. Dies kann aber mit dem Parameter --plugins übersteuert werden. Bei mir hat das Laden meiner SubText-Funktion nicht auf Anhieb funktioniert. Ich habe das Gefühl, dass im iox-ili-Code noch ein Fehler versteckt ist. Erst eine zusätzliche Zeile und das anschliessende Kompilieren von iox-ili und kopieren der Bibliothek in das libs-Verzeichnis von ilivalidator hat die Custom Functions gefunden (resp. eben die Klasse geladen).

Anschliessend muss ich das erwähnte SO_FunctionsExt-Modell erstellen:

INTERLIS 2.3;

CONTRACTED MODEL SO_FunctionsExt (en) AT "http://sogeo.services"
  VERSION "2017-04-09" =

  FUNCTION mySubText (text: TEXT; from: NUMERIC; to: NUMERIC): TEXT;

END SO_FunctionsExt.

Ebenfalls leicht anpassen muss ich das AVLight_Check-Modell:

INTERLIS 2.3;

CONTRACTED MODEL AVLight_Check (en) AT "http://sogeo.services"
  VERSION "2017-04-07" =
  IMPORTS AVLight;
  IMPORTS SO_FunctionsExt;

  VIEW TOPIC Control_points_Check =
  DEPENDS ON AVLight.Control_points;

    VIEW v_Control_point
    	PROJECTION OF AVLight.Control_points.Control_point;
    =
      ALL OF Control_point;
      !! Constraint name will not be shown if ilivalid.msg is defined!?
      !!@ name = Punktnummer_Laenge
      !!@ ilivalid.msg = "Laenge der Punktenummer {Number} ist falsch. Erwartet wird 8."
      MANDATORY CONSTRAINT (INTERLIS.len (Number)) == 8;
      !!@ name = BfS_Nummer_erste_Ziffer
      !!@ ilivalid.msg = "Erste Ziffer der BfS-Nummer {FOSNr} ist falsch. Erwartet wird 2."
      MANDATORY CONSTRAINT SO_FunctionsExt.mySubText(FOSNr,"0","1") == "2";
    END v_Control_point;

  END Control_points_Check;

END AVLight_Check.

In Zeile 6 wird das «Funktions-Deklarations-Modell» importiert. Damit hat sich es erledigt. Natürlich muss man noch den Constraint selber ausformulieren und falls gewünscht die passende Fehlermeldung dazu (Zeilen 19 - 21).

Der ilivalidator-Aufruf liefert korrekterweise einen Fehler:

Info: second validation pass...
Info: validate role references of AVLight.Control_points.Control_point...
Error: line 65: AVLight.Control_points.Control_point: tid a01868e1-11fd-4c7c-a109-341b778b4d44: Laenge der Punktenummer 1066703099 ist falsch. Erwartet wird 8.
Error: line 82: AVLight.Control_points.Control_point: tid 2d8fef45-cc65-4305-a59a-5e046afd2fcb: Erste Ziffer der BfS-Nummer 4479 ist falsch. Erwartet wird 2.
Info: ...validation failed

Um eigene Bedingungen zu formulieren mit oder ohne eigenen Tests muss ich in erster Linie nichts Anderes verstehen als INTERLIS. Keine zusätzliche INTERLIS-Validierungssyntax oder ähnliches. Ich brauche keinen neuen Skriptsyntaxparser/-validator, denn den gibt es ja bereits: den INTERLIS-Compiler. Klar, will ich eigene Funktionen schreiben, muss man ein wenig Java beherrschen. Aber that’s it. Die Verwaltung der zusätzlichen Check-Modelle? Gelöst, nennt sich INTERLIS-Modellablage und hat sich etabliert.

Sämtliche Daten finden sich hier. Das Git-Repo mit meiner Custom Function gibt es hier.

Posted by Stefan Ziegler. | INTERLIS , Java , ilivalidator