Skip to content

Externes JavaScript in Pentaho Kettle laden

Kettle bietet die Möglichkeit in einer Transformation auch JavaScript aus externen Files zu laden.
Dazu braucht man lediglich einen Step "Modified Java Script Value" in dem man die Funktion "LoadScriptFile()" aufruft.
Damit die Datei nicht bei jeder Zeile erneut geladen wird, sollte optimalerweise ein neuer Tab erzeugt werden und via "Set Start Script" so markiert werden, dass er nur beim Laden der Transformation aufgerufen wird.

Das ist soweit nichts Neues, und durch ein bisschen googlen relativ schnell heraus zu finden.
Was hingegen nicht so einfach zu ergründen ist: wie kann man das benutzen, ohne den kompletten Pfad hardcoded angeben zu müssen?

Denn die ganzen Felder kann man nicht benutzen - die stehen ja nur bei der zeilenweisen Verarbeitung zur Verfügung. Aber da soll die Lib ja nicht geladen werden.
Das Beispiel das Kettle bereit stellt ist da auch alles andere als hilfreich:

// Load's a JavaScript File into your actual running Context.
// This function should called from your defined StartScript
// otherwise your JavaScript-File is loaded on each processing
// Row.
//
// Usage:
// LoadScriptFile(var);
//
// 2006-11-15
//
var xPfad = "Your Filename, with Path";
LoadScriptFile(xPfad);

Wie üblich bleibt mal wieder nichts anderes übrig als Probieren.
Das Rumspielen mit Parametern und Argumenten der Transformation war nicht von Erfolg gekrönt, aber da das JavaScript-File in meinem Fall im selben Verzeichnis wie die Transformation liegt, hat sich folgendes bewährt:
var workingDirectory = getVariable("Internal.Transformation.Filename.Directory", "/var/APP/ETL/");
var libraryPath = workingDirectory + "/etl-lib.js";
LoadScriptFile(libraryPath);
Ich habe irgendwo gesehen das jemand "getEnvironmentVar()" statt "getVariable()" benutzt hat - das hat jedoch nicht funktioniert.
Zumindest hat "getVariable()" den Vorteil, dass als zweiter Parameter ein Fallback spezifiziert werden kann, falls die Variable nicht gefunden wird.

JSON vs. xstream

Das diverse Tools oder Bibliotheken sich gerne nicht so gut verstehen ist nichts neues. Aber manchmal fällt schwer, zu sagen, welches davon jetzt Mist baut.
Aber von vorne: Für ein neues Projekt werden Metadaten in XML angeliefert, die in einer Datenbank gespeichert werden sollen. Nun ist das auseinander Pflücken von XML-Files ziemlich mühselig. Ganz zu schweigen davon, dass die Daten in einem anderen Schritt wieder zusammen gesetzt werden müssen.
Es bot sich also eine elegante Lösung an: die XML-Files mit xstream in ein POJO de-serialisieren, validieren und ebenfalls mit xstream in eine JSON-Repräsentation umwandeln - und ab damit in die CouchDB!
Das funktioniert auch ganz wunderbar. Auch der umgekehrte Weg - den JSON-String wieder in ein POJO de-serialisieren - klappt ohne Probleme.

Die Probleme fangen erst an, wenn man mit einer entsprechenden Library an die CouchDB geht, und ein org.json.JSONObject zurück bekommt. Denn in dem daraus de-serialisiertem POJO, war auf einmal in einer darin enthaltenen Map systematisch key und value vertauscht. Die CouchDB war als Problemquelle sehr schnell ausgeschlossen, denn mit dem von ihr zurück gelieferten JSON-String trat das Problem nicht auf. Der String jedoch, der von org.json.JSONObject.toString() geliefert wurde, unterschied sich merklich von dem, was CouchDB lieferte.

Nun ist es so, dass xstream eine Map, wie zB "Map" folgendermaßen als JSON serialisiert:
"mapObjectName": {
   "entry": [
      { 
         "string": "theKey",
         "CustomObject": { "someField": "fieldValue" }
      }
   ]
}


Von org.json.JSONObject.toString() wird aber folgendes ausgegeben:
"mapObjectName": {
   "entry": [
      { 
         "CustomObject": { "someField": "fieldValue" }
         "string": "theKey",
      }
   ]
}

Dafür gibt es zwei Gründe: zum einen schreibt JSON nicht vor, das die Daten in der richtigen Reihenfolge sein müssen. Zum anderen, versucht es das JSONObject auch gar nicht erst - das Problem tritt nämlich nicht erst beim ausgeben der Daten auf, sondern liegt bereits darin begründet, wie diese intern gespeichert werden.

Und das ist genau die Stelle, an der es schwer fällt einen schuldigen zu finden. Ist xstream schuld (bzw. der verwendete Driver für JSON), weil über die JSON-spec hinaus darauf vertraut, dass die Daten immer in der richtigen Reihenfolge sind? Könnte xstream ein Map überhaupt anders serialisieren, um das zu umgehen? Oder macht das "Feature" von JSON, Daten eben explizit unsortiert zu behandeln, mehr Probleme als es löst?

Da ich weder die Zeit noch die Lust hatte, diesen Fragen auf den Grund zu gehen, und es das Problem ohnehin nicht gelöst hätte, hab ich einfach bei der Implementierung angesetzt. Da die Java-library freie Software ist, war das die einfachste Lösung.
Es musste nur in zwei Konstuktoren von org.json.JSONOBject folgende Zeile geändert werden:
Von
this.map = new HashMap();

Zu
this.map = new LinkedHashMap();


...das "import java.util.LinkedHashMap;" fügt eine gute IDE automatisch ein ;-)

Urlaub in der Unterwelt


Gestresst von all den Katastrophen in der Welt der Lebenden?
Eröffne dein eigenes Hotel in Santa Daemonica, dem Lieblings-Urlaubsort der Unterwelt, dem Mallorca der Monster, Usedom der Untoten, Domrep der Dämonen!

Ein Android-handy ist dafür allerdings schon nötig. Und auch eine Internet-flat für selbiges sollte vorhanden sein, denn es handelt sich um einen "Multiplayer Hotel Manager From Hell".
Und um etwaigen Fragen zuvor zu kommen: nein, das Spiel nicht nicht allein von mir - aber ich hab einen nicht unerheblichen Teil dazu beigetragen.
Es ist vielleicht nicht mega umfangreich, aber ein netter Zeitvertreib für Zwischendurch.

Also auf nach Santa Daemonica!

Das tolle an "Standards" ist, dass es so viele davon gibt

So zum Beispiel, was die Formatierung von Datums- und Zeitangaben angeht.

In Java's DateFormat wird für das spezifizieren eines Zeitoffset der RFC 822 unterstützt, wo man einfach mit dem Zeichen "z" diesen Offset im Format "+HHmm" parsen kann.
Hätte man also einen Datums-String, der folgendermaßen aussieht:
2011-03-23T22:15:00.000+0100

kann man das mit zB SimpleDateFormat mit folgendem Format-String parsen:
yyyy-MM-dd'T'HH:mm:ss.SSSz


Soweit, so schön. Problematisch wird es allerdings, wenn man solche Angaben in XML-Dokumenten haben möchte.
Die XSD-Spezifikation stellt einen Typ "xs:dateTime" bereit, der einen String erwartet, der dem oben angegebenen "ähnlich" sieht. ähnlich, weil ein kleines Detail eben doch anders sein muss: im Offset, müssen Stunden und Minuten durch einen Doppelpunkt getrennt werden! Damit würde der String also so aussehen:
2011-03-23T22:15:00.000+01:00

...und der SimpleDateFormater würde das nicht als valide Datumsangabe ansehen, gemäß dem oben stehenden FormatString! Denn der RFC 822 sieht an dieser Stelle keinen Doppelpunkt vor. An den hält sich XSD auch gar nicht, sondern an RFC 3339 (und auch das nur partiell - RFC 3339 erlaubt einige Dinge, die xs:dateTime nicht akzeptiert. zB ein Leerzeichen statt dem "T" in der Mitte zu benutzen).

Der geneigte Entwickler möchte vermutlich bereits an dieser Stelle den Kopf auf die Tischplatte knallen. Nun das war zumindest meine erste Reaktion. Die Zweite war: "da gibts doch bestimmt was von Ratiopharm... äh, ich meine Apache (Commons)." Die haben ja sonst auch für alle gängigen Probleme "schon mal etwas vorbereitet". Bei der Suche stieß ich dann darauf, dass es sogar schon etwas in Java eingebautes gibt!
Man soll es nicht für möglich halten: "javax.xml.bind.DatatypeConverter" stellt eine Methode "parseDateTime" bereit, die einen "lexicalXSDDateTime"-String erwartet. ...natürlich, da hätte man ja drauf kommen können, dass eine Datumsangabe ein Datentyp ist

Ein Hoch auf variable Arbeitszeiten

Man stelle sich vor, man arbeitet gerade an einem neuen Feature für das hauseigene ContentManagementSystem und will das ganze testweise auf der eigenen Workstation deployen - aber das firmeneigene Maven-repository ist nicht erreichbar, weil der Server auf dem es läuft wegen Lastproblemen die Grätsche gemacht hat.
Man widmet sich anderen Dingen, nur um eine Stunde später fest zu stellen, dass das CMS nicht hoch fährt, weil ein Kollege ein paar "unnötige dependencies" raus geschmissen hat, und Hibernate auf einmal die bytecode manipulation library nicht mehr findet. Wenn man das Problem - verzeihung die "Heraussforderung" (denn es gibt nur Herausforderungen und keine Probleme) - endlich gelöst hat, einige Kollegen der Marke "kannst du mal eben schnell" abgewimmelt hat und endlich weiter arbeiten will, fällt dann plötzlich das Netz aus. Offenbar ein Kollege von Operations, der gerade ein paar "Dosen umpatcht". Es steht nur noch WLAN zur Verfügung, aber darüber kommt man nicht direkt an die Server im Office - also alle Tunnel neu aufbauen, und ggfs. laufende Applikationen neu deployen oder neu starten, weil die jetzt auf localhost und einen anderen Port zugreifen müssen. Wenn dann noch beim Speichern der neuen Import-Spezifikation der X-Server ab raucht ...dann, ja dann ist es Zeit zur Abwechslung mal früher Feierabend zu machen!


Zum Schluss noch ein Gag für alle Java-entwickler:
Das kommt dabei raus, wenn man den CTO zu wörtlich nimmt, wenn dieser postuliert "Bei uns wird Qualität groß geschrieben!"
public Format findBestContentFormat(String contentType, int QualityMin, int QualityMax) {
...

Alle die den Witz nicht verstehen, sollten einen Blick in die Java Coding Conventions werfen ;-)