JavaScript zum Debuggen von Fusion verwenden

Manchmal ist die fertige Version nicht gut genug. Für solche Situationen bietet Fusion die Javascript-Stufe. Dieser Beitrag zeigt Ihnen, wie Sie diese entwickeln und debuggen können.

Fusion-Pipelines setzen sich aus Stufen zusammen. Fusion verfügt über mehr als zwei Dutzend Pipeline-Phasen, die vorgefertigte Komponenten zum Parsen, Transformieren, Ändern und anderweitigen Anreichern von Dokumenten und Abfragen sowie Protokollierungsphasen und Hilfsphasen für die Zusammenstellung von Pipelines bieten. Doch manchmal ist „von der Stange“ nicht gut genug. Manchmal brauchen Sie eine maßgeschneiderte Verarbeitungsstufe, die auf Ihre Daten und Ihre Bedürfnisse zugeschnitten ist. Für diese Fälle bietet Fusion die Javascript-Stufe. Dieser Beitrag zeigt Ihnen, wie Sie diese entwickeln und debuggen können.

JavaScript ist eine leichtgewichtige Skriptsprache. Das JavaScript in einer Javascript-Stufe ist standardmäßiges ECMAScript. Was ein JavaScript-Programm tun kann, hängt von dem Container ab, in dem es ausgeführt wird. In Fusion ist dieser Container eine Pipeline, so dass das JavaScript-Programm Zugriff auf die Pipeline-Objekte und die Methoden dieser Objekte hat. Das JavaScript-Programm wird vom JDK in Java kompiliert, wenn zum ersten Mal ein Dokument oder eine Abfrage an die Pipeline übermittelt wird. Zusätzlich zu den verfügbaren Pipeline-Objekten können Java-Kernklassen, einschließlich Java-Sammlungsklassen, importiert und nach Bedarf verwendet werden.

Pipelines gibt es in zwei Varianten: Index und Query. Ebenso gibt es zwei Arten von Javascript-Phasen: Javascript Index stage und Javascript Query stage. Eine Index-Pipeline wandelt einige Eingabedaten in ein Dokument um, das für die Indizierung durch Solr geeignet ist. Die Objekte, die in einer Index-Pipeline von Stufe zu Stufe gesendet werden, sind PipelineDocument-Objekte. Eine Abfrage-Pipeline wandelt eine Reihe von Eingaben in eine Solr-Abfrage um und kann auch die Solr-Antwort manipulieren. Die Objekte, die in einer Abfrage-Pipeline von Stufe zu Stufe gesendet werden, sind Request-Objekte und Response-Objekte.

Eine Javascript-Stufe kann entweder über die Fusion REST-API oder über die Fusion-Benutzeroberfläche definiert werden. Der folgende Screenshot zeigt nebeneinander das anfängliche Panel, mit dem Sie einen Javascript-Index bzw. eine Abfragestufe definieren.

erste javascript-pipeline-stufe definition panel

Wie bei allen Fusion-Pipeline-Stufen verfügt ein Javascript-Pipeline-Stufendefinitionsfeld über ein Eingabefeld für die Stufenbezeichnung, Optionsfelder zum Überspringen dieser Stufe und ein Bedingungsfeld. Das Bedingungsfeld nimmt eine optionale JavaScript-Anweisung auf, die als wahr oder falsch ausgewertet wird. Wenn Sie dieses Feld leer lassen, bedeutet dies, dass die Bedingung immer erfüllt ist und alle Dokumente in diesem Schritt verarbeitet werden. Ich habe den Titel „Script Body“ eingekreist, der erforderlich ist (wie durch das rote Sternchen angezeigt). Sowohl das Textfeld für die Bedingung als auch das für den Skripttext sind JavaScript-fähig und bieten daher eine rudimentäre Formatierung und Syntaxprüfung.

Hier sehen Sie ein Beispiel für eine einfache JavaScript-Funktion für eine Javascript-Indexstufe, die einem Dokument ein Feld hinzufügt:

function (doc) {
  doc.addField('some-new-field', 'some-value');
  return doc;
}

„Was ist diese doc Variable?“, sollten Sie sich jetzt fragen. Dies ist eine PipelineDocument Objekt, das dazu dient, jedes Datenelement, das an eine Pipeline übermittelt wird, zusammen mit Informationen über die Datenübermittlung zu speichern. Ein PipelineDocument erbt direkt von Object. Es ist kein SolrDocument Objekt. Eine PipelineDocument organisiert den Inhalt jedes an die Pipeline übermittelten Dokuments, Metadaten auf Dokumentenebene und Verarbeitungsbefehle in einer Liste von Feldern, wobei jedes Feld einen Stringnamen, einen Wert, ein zugehöriges Metadatenobjekt und eine Liste von Anmerkungen hat. Eine Javascript-Indexstufe bearbeitet die Felder des PipelineDocuments. Diese Liste von Feldern hängt sowohl von der Eingabe als auch von der Verarbeitungspipeline ab.

Kudos für alle, die es wissen wollen: „Auf welche anderen Variablen habe ich Zugriff?“ Javascript-Stufen haben auch Zugriff auf ein Logger-Objekt namens logger. Das Logger-Objekt wird von SLF4J bereitgestellt. Die Logmeldungen werden in das Fusion Services Log ($FUSION/logs/api/api.log) geschrieben. Es stehen 5 Methoden zur Verfügung, die jeweils entweder ein einziges Argument (die zu protokollierende String-Nachricht) oder zwei Argumente (die String-Nachricht und eine zu protokollierende Ausnahme) annehmen. Die fünf Methoden sind: „debug“, „info“, „warn“ und „error“. Aufrufe von logger.debug liefern Feedback während der Entwicklung und Aufrufe von logger.error liefern Feedback, sobald die Pipeline in Produktion ist.

Eine Javascript-Abfragephase hat Zugriff auf die Variablen request und response, bei denen es sich um Abfrage- bzw. Antwortobjekte handelt. Ein Request-Objekt enthält die Solr-Abfrageinformationen, und das Response-Objekt enthält die Solr-Antwort. Während die Javascript-Indexphase erwartet, dass der Skriptkörper eine Funktion ist, die ein PipelineDocument-Objekt zurückgibt, erwartet die Javascript-Abfragestufe, dass der Skriptkörper ein Javascript-Codeausschnitt ist, der die Request- und Response-Objekte direkt manipuliert. Hier ist ein (minimales) Beispiel für eine Javascript-Abfrage, die dem Request-Objekt einen neuen Abfrageparameter „foo“ mit dem Wert „bar“ hinzufügt:

request.addParam("foo", "bar");

Sie können jede Java-Klasse verwenden, die dem JDK Classloader der Pipeline zur Verfügung steht, um die Objekte in einer Javascript-Stufe zu manipulieren. Wie in Java müssen Java-Klassen importiert werden, wenn Sie auf sie über ihre einfachen Namen anstelle ihrer vollständig spezifizierten Klassennamen zugreifen möchten, z.B. um „String“ statt „java.lang.String“ schreiben zu können. Der sichere und portable Weg, dies zu tun, ist die Verwendung des JavaImporter Objekts und der with Anweisung, um den Umfang der importierten Java-Pakete und -Klassen zu begrenzen. Das Paket java.lang wird standardmäßig nicht importiert, da seine Klassen mit Object, Boolean, Math und anderen integrierten JavaScript-Objekten in Konflikt geraten würden. Außerdem kann der Import eines beliebigen Java-Pakets oder einer Klasse zu Konflikten mit dem Geltungsbereich globaler Variablen in JavaScript führen. Daher ist das folgende Beispiel der empfohlene Weg, Java-Klassen zu importieren:

var imports = new JavaImporter(java.lang.String);
...
with (imports) {
    var name = new String("foo");
    ...
}

Dieses Konstrukt hat den Vorteil, dass Sie JavaScript schreiben können, das sowohl in Java 7 als auch in Java 8 kompiliert werden kann. Bis Java 7 verwendeten die meisten JDKs, einschließlich Oracle und OpenJDK, die Mozilla Rhino Engine. In Java 8 führte Oracle seinen eigenen JavaScript-Compiler namens Nashorn ein. Wenn Sie JavaScript, das für die Mozilla Rhino-Engine geschrieben wurde, unter Nashorn ausführen, muss ein Kompatibilitätsskript geladen werden, wenn die Rhino-Funktionen importClass oder ImportPackage anstelle von JavaImporter verwendet werden:

// Load compatibility script
load("nashorn:mozilla_compat.js");
// Import the java.lang.String class
importClass(java.lang.String);
var name = new java.lang.String("foo");
...

Fusion prüft die JavaScript-Syntax nicht, wenn eine Pipeline-Stufe definiert wird. Kompilierung und Ausführung erfolgen beim ersten Senden einer Nachricht an die Pipeline. Wenn die Javascript-Phase fehlschlägt, wird ein Fehler in das Serverprotokoll eingetragen und andernfalls stillschweigend beendet. Dies macht die Fehlersuche in einer Javascript-Stufe problematisch.

Für Javascript-Indexstufen kann das Tool Pipeline-Vorschau verwendet werden, um das JavaScript zu validieren und zu testen. Das Vorschau-Tool erwartet eine Liste von PipelineDocument-Objekten im JSON-Format. Die Liste ist bereits mit zwei Skelettdokumenten gefüllt, die jeweils zwei Schlüssel-Wert-Paare enthalten. Das erste Schlüssel-Wert-Paar hat den Schlüssel „id“ und einen String-Wert für die Dokument-ID. Das zweite Schlüssel-Wert-Paar hat den Schlüssel „fields“, dessen Wert die Liste der Felder im Dokument ist, kodiert als Paare von Feldnamen und Feldwerten, d.h. jedes Feld ist ein Objekt, das aus zwei Schlüssel-Wert-Paaren besteht, Schlüssel „name“, Stringwert ist der Feldname, Schlüssel „value“, Stringwert ist der Wert dieses Feldes. String-Werte können im Eingabefeld des Vorschauwerkzeugs nicht zeilenübergreifend umbrochen werden.

Um zu sehen, wie das funktioniert, definiere ich eine einfache Pipeline mit zwei Stufen: eine Indexprotokollierungsstufe, gefolgt von einer Javascript-Indexstufe. Die Fusion Logging-Phase ist das Äquivalent zum Debugging mit printf, einer meiner Lieblings-Debugging-Techniken. (Einen Schritt zurückzutreten und über das Problem nachzudenken ist ebenfalls effektiv, aber in einem Blogbeitrag schwer zu demonstrieren).

Die Javascript-Indexstufe gibt sowohl eine Bedingung als auch einen Skriptkörper an. Die Bedingung ist doc.hasField("body");. Der Skriptkörper ist das erste Beispiel, das oben angegeben wurde. Ich bearbeite die Eingabedokumente so, dass das erste Dokument ein Feld mit dem Namen „body“ enthält. Hier sehen Sie einen Screenshot der Pipeline, der sowohl die Javascript-Stufe als auch die Eingaben des Pipeline-Vorschau-Tools zeigt:

Als Nächstes klicke ich auf die Registerkarte „Ergebnisse anzeigen“, um das Ergebnis der Übertragung dieser Eingaben durch die Pipeline zu sehen:

Die Registerkarte „Ergebnisse anzeigen“ zeigt die Pipeline-Dokumente nach Durchlaufen der einzelnen Phasen an. Indem wir eine Protokollierungsstufe als erste Stufe der Pipeline setzen, sehen wir die beiden Eingabedokumente. Die Ausgabe der Javascript-Stufe zeigt, dass für das erste Dokument die bedingte Anweisung doc.hasField("body") als wahr bewertet wird, während das zweite Dokument diesen Test nicht besteht. Daher enthält nur das erste Dokument ein zusätzliches Feld namens „some-new-field“ mit dem Wert „some-value“.

Wenn das JavaScript in der Javascript-Phase nicht kompiliert werden kann, schlägt die Javascript-Phase stillschweigend fehl und das Ergebnisfenster sieht in etwa so aus:

Die Fehlerstelle im JavaScript kann durch einen Blick in die Fusion-Protokolldateien ausfindig gemacht werden. Für das obige Beispiel enthält die Fusion API Services-Protokolldatei $FUSION/logs/api/api.log die Fehlermeldung des JavaScript-Compilers:

2015-03-25T19:31:58,251 - WARN  [qtp2032647428-15:JavascriptStage@205] - Failed to run JavascriptStage javax.script.ScriptException: sun.org.mozilla.javascript.internal.EcmaError: TypeError: Cannot find function noSuchMethodCall in object PipelineDocument [id=doc2, fields={title_s=[PipelineField [name=title_s, value=Title 2, metadata=null, annotations=null]]}, metadata=null, commands=null]. (#2) in  at line number 2
        at com.sun.script.javascript.RhinoScriptEngine.invoke(RhinoScriptEngine.java:300) ~[?:1.7.0_71]

Die Fehlersuche in einer Javascript-Stufe ist nicht so einfach, wie sie sein könnte, aber das Ergebnis sind Ihre Daten, auf Ihre Weise. Sie müssen eine Javascript-Stufe verwenden, wenn Sie eine strukturierte Eingabe haben, z.B. JSON oder XML, die eine interne Struktur hat, bei der die Zuordnung von den strukturierten Informationen zu einem einfachen Dokument mit Feldern auf einer Zwischenebene der Struktur beginnt. Bei einer hierarchischen Datenstruktur lassen sich die Wurzel- und Blattknoten leicht auf ein Dokumentenfeld abbilden, aber die Extraktion von Informationen aus Zwischenknoten und/oder die Verschachtelung von Informationen aus verschiedenen Knoten erfordert zusätzliche Arbeit.

Im folgenden Beispiel zeige ich, wie eine Javascript-Stufe verwendet werden kann, um string-kodiertes JSON zurück in ein strukturiertes JSON-Objekt zu parsen und Teilbereiche dieses Objekts zu extrahieren. In diesem Beispiel stammen die Daten aus einer Reihe von JSON-Dateien, die auf meinem lokalen Laufwerk gespeichert sind. Jede Datei enthält Informationen über einen Musiktitel, kodiert als eine Reihe von Schlüssel:Wert-Paaren, von denen die folgenden von Interesse sind:

  • key: artist, string value: der Name des Künstlers, der diesen Titel aufgenommen hat.
  • key: title, string value: der Name des Titels.
  • key: track_id, string value: ein eindeutiger Bezeichner.
  • key: Tags, eine Liste von Paarlistenwerten, die aus einer Zeichenkette und einer positiven ganzen Zahl bestehen.

Hier ist ein Beispiel für eine Eingabedatei:

{ "artist": "Bass Monkey", "tags": [["electro", "100"]], "track_id": "TRAAKBC12903CADFD4", "title": "Electrafluid" }

Es wäre schön, wenn Sie ein Dokument mit Feldern für Track_id, Interpret, Titelstrings sowie einem mehrwertigen Feld für die Tags erstellen könnten, wobei nur Tags, die einen bestimmten Schwellenwert überschreiten, dem Dokument hinzugefügt werden. Das bedeutet, dass nach dem Parsen des Objekts in eine Reihe von Schlüssel-Wert-Paaren der Wert des Schlüsselnamens „tags“ in ein Array aus 2-Element-Arrays geparst werden muss, die einen Tag-String und eine positive Ganzzahl zwischen 1 und 100 enthalten. Wenn Sie dies als ein Array von n 2-Element-Arrays darstellen, erhalten Sie für jedes Element mit dem Index i das erste Element des verschachtelten Arrays: tag-array[i][0]. Eine einfache Aufgabe, nicht wahr?

Leider gibt es keine sofort einsetzbare Lösung für dieses Problem, da die JSON-Daten mehr Struktur haben, als der aktuelle Satz von Fusion-Pipeline-Stufen verarbeiten kann. In einem ersten Versuch habe ich diesen Datensatz mit dem lucid.anda Dateisystem-Crawler und der conn_solr Pipeline verarbeitet, die in dieser Reihenfolge aus einer Apache Tika Parser-Stufe, einer Field Mapper-Stufe und einer Solr Indexer-Stufe besteht. Diese Pipeline erzeugte Dokumente, bei denen alle Namen und Werte in der Datei in ein einziges Feld mit der Bezeichnung „Inhalt“ umgewandelt worden waren. Durch das Einfügen von Protokollierungsstufen in die Pipeline conn_solr und die Durchsicht der Protokolldatei $FUSION/logs/connectors/connectors.log sehe ich, dass die Tika-Parser-Stufe mit einem PipelineDocument mit dem Feld „_raw_content_“ arbeitet, das den MIME-kodierten binären Dateiinhalt enthält:

  "name" : "_raw_content_",
  "value" : [ "[B", "eyAiYXJ0aXN0IjogIkJhc3MgTW9ua2V5IiwgInRhZ3MiOiBbWyJlbGVjdHJvIiwgIjEwMCJdXSwgInRyYWNrX2lkIjogIlRSQUFLQkMxMjkwM0NBREZENCIsICJ0aXRsZSI6ICJFbGVjdHJhZmx1aWQiIH0K" ],
  "metadata" : { },
  "annotations" : [ ]
}

Der Tika-Parser wandelt den Rohinhalt in einfachen Text um. Dieser wird dem PipelineDocument als Feld namens „body“ hinzugefügt, das den Inhalt der Datei wortwörtlich enthält. Da die Protokollierungsinformationen im JSON-Format vorliegen, werden die Dateiinhalte als JSON-escapte Zeichenketten ausgegeben.

  "name" : "body",
  "value" : "{ "artist": "Bass Monkey", "tags": [["electro", "100"]], "track_id": "TRAAKBC12903CADFD4", "title": "Electrafluid" }nn",
  "metadata" : { },
  "annotations" : [ ]
}

Die beste Lösung ist, eine eigene Javascript-Stufe zu schreiben. Hier ist der JavaScript-Code, der dies ermöglicht:

var imports = new JavaImporter(Packages.sun.org.mozilla.javascript.internal.json.JsonParser);
function(doc) {
    with (imports) {
        myData = JSON.parse(doc.getFirstFieldValue('body'));        
        logger.info("parsed object");
        for (var index in myData) {
            var entity = myData[index];
            if (index == "track_id") {
                doc.addField("trackId_s",entity);
            } else if (index == "title") {
                doc.addField("title_txt",entity);
            } else if (index == "artist") {
                doc.addField("artist_s",entity)
            } else if (index == "tags") {
                for (var i=0; i<entity.length;i++) {
                    var tag = entity[i][0];
                    doc.addField("tag_ss",tag);
                }
            }
        }
    }
    doc.removeFields("body");
    return doc;
}

Das ist echtes JavaScript-Fu! Der ECMAScript-Standard bietet eine Funktion JSON.parse, die Text in ein JSON-Objekt parst.

Die erste Zeile des Skriptkörpers importiert die Klasse, die die Funktion JSON.parse hat. Der Rest des Skripts iteriert über die Objekte der obersten Ebene und extrahiert die Informationen artist, title und track_id und fügt sie dem Dokument als separate Felder hinzu. Für das Array der Tags verwendet es Array-Indizes, um jedes Tag zu extrahieren. In diesem Beispiel besteht die letzte Operation darin, das Feld „body“ mit der Methode removeFields aus dem PipelineDocument zu entfernen. Dies könnte auch in einer Field-Mapper-Phase geschehen.

Ich habe eine Pipeline ähnlich der „conn_solr“-Pipeline erstellt, die aus einer Apache Tika Parser-Stufe, gefolgt von einer Logger-Stufe und einer Javascript-Stufe besteht. Ich habe das obige Skript ausgeschnitten und in die Javascript-Stufe „script body“ eingefügt:

Um zu sehen, wie das funktioniert, verwende ich das Tool Pipeline-Vorschau. Ich erstelle ein Dokument mit einem Feld namens „body“, dessen Wert die JSON-Songtrack-Informationen aus der Logdatei ist:

Ich klicke auf die Registerkarte „Ergebnisse anzeigen“, und voilà! es funktioniert!

Eine Javascript-Stage kombiniert die Vielseitigkeit eines Schweizer Taschenmessers mit der Leistungsfähigkeit eines Blentec Blenders und trägt daher einige Warnhinweise. Sobald Sie die Form beherrschen, könnten Sie versucht sein, überall Javascript-Stages zu verwenden. Im Allgemeinen ist es jedoch effizienter, eine spezialisierte Pipeline-Stufe zu verwenden. Anwendungsfälle, für die bereits eine spezialisierte Pipeline-Stufe existiert, sind die folgenden Stufen, die Felder aus einem Dokument filtern, hinzufügen oder löschen:

  • Kurze Feldfilter-Indexstufe. Dies entfernt Feldwerte aus einem Pipeline-Dokument entsprechend einer Reihe von Filtern, wobei jeder Filter einen Feldnamen und eine Mindestlänge angibt. Alle Feldwerte, die kürzer als die angegebene Länge sind, werden aus dem Dokument entfernt.
  • Indexphase Suchen Ersetzen. Diese Phase führt ein globales Suchen und Ersetzen über eine Reihe von Dokumenten mit Hilfe eines vorgegebenen Satzes von Regeln durch, wobei jede Regel ein Paar von Zeichenketten angibt: match string und replace string. Der Anwendungsfall für diese Stufe ist die Verarbeitung einer Sammlung von Dokumenten, deren Text einen gewissen Anteil an Standardtext enthält, z.B. Haftungsausschlüsse in E-Mails.
  • Regular Expression Extractor Indexstufe. Dieser führt einen Regex in einem Feld durch und kopiert die Übereinstimmungen in ein neues Feld.
  • Regular Expression Filter Indexstufe. Dies entfernt ein Feld oder mehrere Felder aus einem PipelineDokument entsprechend einer Reihe von Filtern, wobei jeder Filter einen Feldnamen und einen regulären Ausdruck angibt. Wenn ein Feldwert mit dem regulären Ausdruck übereinstimmt, wird das Feld aus dem Dokument gelöscht.

You Might Also Like

Vom Suchunternehmen zum praktischen KI-Pionier: Unsere Vision für 2025 und darüber hinaus

CEO Mike Sinoway gibt Einblicke in die Zukunft der KI und stellt...

Read More

Wenn KI schief geht: Fehlschläge in der realen Welt und wie man sie vermeidet

Lassen Sie nicht zu, dass Ihr KI-Chatbot einen 50.000 Dollar teuren Tahoe...

Read More

Lucidworks Kernpakete: Branchenoptimierte KI-Such- und Personalisierungslösungen

Entdecken Sie unsere umfassenden Core Packages, die Analytics Studio, Commerce Studio und...

Read More

Quick Links