Communardo Software GmbH, Kleiststraße 10 a, D-01129 Dresden
+49 (0) 351/850 33-0

Confluence Persistenz mit Bandana – Fallstricke

Die Confluence API bietet über das Bandana-Framework eine einfache Möglichkeit zum Speichern beliebiger Java Objekte an. Im Prinzip muss man zu dem Objekt nur einen einen Kontext (bezieht sich auf einen bestimmten Confluence Space oder ist global) und einen bezüglich dieses Kontextes eindeutigen Key angeben und der BandanaManager kümmert sich um den Rest. Der Vorteil liegt auf der Hand: Man muss sich nicht mit dem Persistenzmodell von Confluence (basiert auf Hibernate) auseinander setzen. Davon rät Atlassian ohnehin ab, Zitat:

Unless you really understand our code, something weird will happen.

Das Speichern von Daten über Bandana geschieht im Wesentlichen in zwei Schritten

  1. Serialisierung der zu persistierenden Objekte mittels XStream
  2. Ablegen des Resultates in der Datenbank

Diese Form der Persistenz hat allerdings auch ihre Tücken. Als ich vor Kurzem bei der Entwicklung eines Plugins mal wieder Bandana verwendete bin ich über das folgende Verhalten gestolpert:

Ich hatte zwei Container-Datenstrukturen gespeichert, eine Map und eine List. Nachdem einige kurze Tests positiv verliefen, wendete ich mich einer anderen Komponente des Plugins zu. Um diese zu testen baute ich mein Plugin neu und installierte es über die Weboberfläche (ohne etwas an meiner „Persistenzschicht“ zu ändern). Allerdings musste ich nun feststellen, dass eine der Datenstrukturen (die Map) nicht mehr geladen werden konnte.  Ein weiterer Test offenbahrte noch wunderlicheres Verhalten: Nachdem ich die Map erneut gespeichert hatte, konnte ich sie wieder auslesen, allerdings nur bis zum nächsten Deploy des Plugins.

Was war passiert? An einem falschen Kontext oder Key konnte es nicht liegen, da diese bereits zuvor das Laden der Map verhindert hätten. Der Verdacht viel schnell auf irgendeinen Cache. Und tatsächlich, nach kurzer Recherche bin ich auf diese Seite bei Atlassian gestoßen. Dort wird beschrieben, dass es eigens für Bandana einen Cache gibt. Meine erste Vermutung war nun, dass die Map aus irgend einem Grund nie den Weg durch den Cache in die Datenbank geschafft hatte, der dann bei der Neuinstallation des Plugins verfiehl. Ein kurzer Blick in die Datenbank und ein wenig Debugging überzeugten mich dann aber vom Gegenteil: Die Daten waren in der Datenbank, schafften es von da aber nicht mehr in den Cache (kurzer Hinweis: die Tabelle für die Bandana-Daten heißt treffender Weise BANDANA). Nach weiterem Debugging stand fest, dass die Daten zwar noch aus der Datenbank geholt wurden, im Anschluss aber die Deserialisierung fehl schlug. Nach einem erneuten Blick in die Datenbank war schnell klar warum. Ich hatte Enums als Schlüssel für die Map verwendet. Diese waren fehlerhaft serialisiert wurden, so dass sie (und mit ihnen die Map) nicht wiederhergestellt werden konnten. Das liegt wohl daran, dass Confluence die XStream Version 1.1.1 verwendet aber Enums erst mit der Version 1.1.2 unterstützt werden. Da die Daten im Cache immer unserialisiert gespeichert werden, fiel dieses Problem erst auf, als der Cache verfallen war. Die Lösung war nun recht einfach: Nach dem Ersetzen der Enums durch Strings verhielt sich mein Plugin wie erwartet.

Enums sind nicht die einzigen Datenstrukturen, mit denen es Probleme gibt. So sollte man es auch vermeiden in einem Plugin definierte Typen mittels Bandana zu speichern, wenn diese keinen Default-Konstruktor haben. Der Grund: Diese Typen würden, wie in diesem Issue beschrieben, beim Deserialisieren mit dem ClassLoader der Web-Applikation gesucht werden. Der kann sie aber nur finden, wenn sie im Classpath der der Web-Applikation liegen, was bei über die Weboberfläche installierten Plugins nicht der Fall ist (dafür hat jedes Plugin seinen eigenen ClassLoader). Also dürften in diesem Fall ebenfalls die oben beschriebenen Probleme auftreten.

1 Kommentar

Hi,

Apologies that I am commenting in English – but my German is quite rusty. You may find the following article of interest in relation to enums and persisting such data in Bandana: http://tr.im/k5dv.

Also, and forgive me if I am misunderstanding the post (relying on Google translate!), it is possible to store plugin defined classes within Bandana. In order to achieve this, it is necessary to set the classloader of XStream to the classloader of the plugin and convert the objects to be persisted into XML (as noted here: http://confluence.atlassian.com/display/DOC/Persistence+in+Confluence?focusedCommentId=185062#comment-185062):

// Store the object
XStream xStream = new XStream();
xStream.setClassLoader(getClass().getClassLoader());

String xml = xStream.toXml(myObject);
bandanaManager.setValue(ctx, key, xml);

// Later, retrieve the object
myObject = (MyObjectClass)xStream.fromXml(bandandaManager.getValue(ctx, key));

Kommentar hinterlassen


Pin It on Pinterest