Java: Object Type Mappings in Elasticsearch

Verschachtelte Strukturen in Elasticsearch zu mappen, ist strukturell gesehen eigentlich gut dokumentiert, siehe Object Type (Achtung: Nicht zu verwechseln mit Nested Type!). In Java kann dabei trotzdem einiges schiefgehen – zumindest in v0.90.x, über die aktuellen Versionen kann ich nichts sagen.

Zuerst ein Klassiker, wenn man sich die zahlreichen Tipps dazu im Internet ansieht:

Merge failed with failures {[mapper [field.innerField] has different index values, mapper [field.innerField] has different tokenize values, mapper [field.innerField] has different index_analyzer]}

Das liegt gerne mal daran, dass der Index vor dem Mapping aufgebaut wurde. Was aber nicht bedeutet, dass man das selber explizit so macht! Es kann ganz simpel daran liegen, dass die früher erzeugten (und jetzt veralteten) Dateien auf der Platte nicht mehr passen. Alle Dateien löschen und neu erzeugen lassen, dann kann der Fehler schon verschwunden sein m(

Dann, auch sehr verwirrend: Der XContentBuilder hat eine string()-Funktion (nicht zu verwechseln mit toString()). Die hilft beim Debuggen insofern, als dass man das erzeugte Mapping/JSON ausgeben kann. Allerdings “tut” string() intern irgendwas, keine Ahnung, den Stream schließen oder so. Mit einem string() im Code bekommt man eine NullPointerException in UTF8JsonGenerator.writeRaw(UTF8JsonGenerator.java:670). Tollerweise (trollerweise?) liegt die Klasse zwar unter org.elasticsearch.common.jackson.core.json.UTF8JsonGenerator, die Sourcen sind aber nicht Teil der ES-Sourcen… und der UTF8JsonGenerator hat sehr viele writeRaw()-Methoden.

Bis man darauf kommt, dass es ausschließlich an string() liegt, kann schon mal einige Zeit vergehen: Mit einem string() im Code kann man zwar das erzeugte JSON ausgeben, dieses ist aber völlig irrelevant. Es kann sogar ungültig sein, etwa durch ein überzähliges endObject() – durch string() sieht man statt des eigentlichen

 JsonGenerationException: Current context not an object but ROOT

nur den NullPointer von oben.

Trotzdem ist string() nötig, denn drölf verschachtelte endObject() und field() werden schnell unübersichtlich. Meine Empfehlung: Das JSON unter Einsatz von string() gemäß der Doku aufbauen, string() dann auskommentieren, und erst dann die auftretenden Fehlermeldungen lesen.

Jackson: Map JSON Array

(von)

Gson: JSON-Serializer für bestimmte Typen überschreiben

Play nutzt (zumindest in Version 1.3?) Gson für JSON-Serialisierung. Gson macht aus Float.NaN die Zahl NaN, was für JSON nicht erlaubt ist:

play.exceptions.JavaExecutionException: NaN is not a valid double value as per JSON specification. To override this behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method.

Schöner wäre es, daraus den String “NaN” zu machen.

Ansatz 1, wie empfohlen:

Gleicher Fehler. Deshalb Ansatz 2:

mit:

und DoubleSerializer analog.

Composer: Erste Learnings

Ich versuche gerade, ein Composer-Package zu definieren und aus dem SVN zu installieren, bekomme aber immer die Meldung

Your requirements could not be resolved to an installable set of packages

Es wird nicht ganz klar, ob das an der packages.json im SVN liegt, an der lokalen composer.json, oder irgendwo dazwischen (zB an der Verbindung zum SVN). Leider liefert composer install auch im verbose Modus keine hilfreiche Informationen, aber

hilft. Es zeigt Informationen (zB die description) aus der packages.json (also scheint die Verbindung zum SVN zu klappen), als Version aber nur “dev-trunk”. Offenbar zählt die in der externen composer.json angegebene Version nicht, sondern allein die Tatsache, dass das Package im trunk liegt. Folgendes funktioniert schließlich:

externe composer.json:

lokale composer.json:

Wichtig dabei: “url” enthält nicht “trunk”, und alles hinter “trunk” wandert aus der URL nach “package-path”. So kann man mehrere Packages in einem SVN-Projekt ablegen.

Update “Versionierung”

Eine packages.json wird im externen Package ebensowenig benötigt, wie eine Versionsnummer in der externen composer.json! Die Versionsinformationen ergeben sich implizit aus Tag-/Branchnamen (oder halt “dev-trunk”), was ja auch Sinn macht, denn natürlich enthält das Repository alle Versionen.

Eine ganz bestimmte Revision kann man wohl mit

angeben (Quelle)

Update “SVN”

Mit dem oben beschriebenen Vorgehen muss man für jedes “require” Package ein eigenes Repository definieren. Das ist übrigens unabhängig davon, ob alle Packages in einem SVN-Projekt liegen, oder jedes Package sein eigenes Projekt hat – so lange man nicht jedem Package sein eigenes SVN-Repository geben möchte, kommt man da nicht drumrum. IMHO eine Designschwäche; Composer ist halt sehr auf git fixiert.

Anyway: Eine Möglichkeit, die “repositories” in der lokalen composer.json zu begrenzen, wäre der Einsatz von Satis:

[Satis] can be used to host the metadata of your company’s private packages, or your own.
[…]
You don’t need to copy all your repositories in every project anymore. Only that one unique repository that will update itself.

*Notwendig* ist das nicht, aber es macht die Sache übersichtlicher.

Update “SVN”/2.

Ohne Satis sind “verschachtelte” Abhängigkeiten nicht möglich (siehe), die unschöne “Lösung”: Alle Abhängigkeiten im Root-Projekt definieren (Quelle) 🙁

PS: Composer – und folglich auch Satis – laufen auf uberspace 🙂

Twitter API: Ziel der t.co-Short-URLs

Twitter kürzt URLs mit dem hauseigenen Dienst t.co. Die so (de facto) verschleierten URLs werden auf twitter.com zwar über den Link-Text (bis zu einer gewissen Länge) wieder aufgelöst, aber die URL dahinter bleibt die von t.co. Über die API bekommt man denn auch erst mal nur die t.co-Version. Im Netz kursieren Links zu “Unshorten”-Services (Beispiel), die aber gar nicht (mehr?) nötig sind: Der Parameter “include_entities” erweitert den Response bsplw. der Suche um alle Felder, die man benötigt (hier nur die Entities):

 

net.sf.json.xml.XMLSerializer und eckige Klammern

Es gibt einen extrem hartnäckigen Bug in net.sf.json.xml.XMLSerializer: Ein XML-Knoten

wird nicht zum String

sondern zum leeren Array

Jetzt könnte man vermuten, dass CDATA hilft, oder HTML-Sonderzeichen, oder Escapen, oder oder oder. Alles falsch. Der Serializer ist so “klug”, dass er alles trimt optimiert und ersetzt, bis er wieder den ursprünglichen String findet, und der Bug zuschlägt. Das meine ich mit “hartnäckig 🙂

Einzige Lösung, die mir eingefallen ist (Verbesserungen werden gerne gesehen!):

PS: Das ist deshalb geringfügig weniger dirty, als es aussieht, weil spitze Klammern Teil des Suchstrings sind. Spitze nicht-escapte Klammern dürfen in (validen) XMLs nur Teil der Struktur sein, deshalb werden hier tatsächlich nur Knoteninhalte, die ausschließlich “[]” lauten, ersetzt.

PPS: Ein ähnliches Problem gibt es mit

aus dem ein Object wird. Ich ersetze es deshalb einfach analog, siehe oben.