Anwendungsbeispiele von OSGi in Liferay 7

Mit Version 7 hat Liferay nun seine lange angekündigte Umstellung auf OSGi vollzogen. Fast alle Komponenten des Portals werden jetzt als Bundles vom OSGi-Framework verwaltet. Von Entwicklern wird jetzt erwartet, dass neue Plugins ebenfalls OSGi-Fähig gebaut werden. Zwar lassen sich Plugins noch im alten Stil und losgelöst von OSGi entwickeln, doch würde man dadurch viele Vorteile einfach verwerfen.

Portlet- und Hook-Setup

Los geht es mit dem Setup von Portlets und Hooks. Portlets sind jetzt Komponenten, die den Portlet-Service konsumieren und im Quellcode die Component-Annotation verwenden. Die Annotation ist im OSGi-Kontext überaus wichtig, denn Klassen die als Komponente markiert sind, werden aktiv von OSGi verwaltet. Wird eine Komponente installiert, deren Abhängigkeiten zu anderen Komponenten nicht aufgelöst werden können, dann wird die Komponente nicht gestartet und kann von anderen Klassen nicht genutzt werden. Umgedreht wird die Komponente allerdings automatisch gestartet, sobald ihre Abhängigkeiten erfüllt sind.

@Component(
        immediate = true,
        property = {
                "com.liferay.portlet.display-category=Examples",
                "com.liferay.portlet.instanceable=true",
                "javax.portlet.display-name=Dummy Portlet",
                "javax.portlet.init-param.template-path=/",
                "javax.portlet.init-param.view-template=/view.jsp",
                "javax.portlet.resources-bundle=content.Language",
                "javax.portlet.security-role-ref=power-user,user",
                "com.liferay.portlet.css-class-wrapper=dummy-portlet-wrapper",
                "com.liferay.portlet.header-portlet-css=/css/main.css"
        },
        service = Portlet.class
)
public class DummyPortlet extends MVCPortlet {
}

Wie man am Code-Beispiel sieht, erlaubt die Annotation das Setzen von zusätzlichen Eigenschaften. Diese Eigenschaften wurden in vorherigen Version noch in der portlet.xml und liferay-portlet.xml definiert. Beide Dateien sind jetzt obsolet und werden im Projektbaum nicht mehr benötigt.
Das Selbe gilt auch für Hooks. Zum Beispiel wurden ModelListeners und ServiceWrappers zuvor noch in der liferay-hook.xml deklariert. Für beide Hook-Arten gibt es jetzt jedoch OSGi-Services die konsumiert werden können:

// Eigener ServiceWrapper
@Component(
        service = ServiceWrapper.class
)
public class CustomJournalArticleLocalServiceWrapper extends JournalArticleLocalServiceWrapper {
// Eigener ModelListener
@Component(
        service = ModelListener.class
)
public class JournalArticleModelListener extends BaseModelListener<JournalArticle> {

In den Code-Beispielen wird oben ein ServiceWrapper für den JournalArticleLocalService und darunter ein ModelListener für JournalArticle definiert.
Der Wegfall der Konfigurationsdateien sorgt insgesamt für einen saubereren Projektbaum und einen geringeren Konfigurationsaufwand.

Eine Alternative zum Ext-Plugin

Ext-Plugins sind die mächtigste Plugin-Art die Liferay zu bieten hat. Mit ihnen lassen sich alle Komponenten von Liferay anpassen. Von Anpassungen von tief im Kern liegenden Implementierungen und Konfigurationen bis zum Austausch von JAR-Dateien ist mit dem Ext-Plugin alles möglich. Dieser Grad an Anpassbarkeit hat aber ihren Preis. Ext-Plugins haben ein kompliziertes Build-Verfahren das noch auf Ant basiert. Ein Re-Deployment des Plugins erfordert gemäß Liferay ein Cleanup des Portals. Dabei müssen alle Jars und Konfigurationsdateien, die das Ext-Plugin überschreibt oder hinzufügt, manuell wieder entfernt werden. In früheren Versionen wurde sogar dazu geraten, dass Portal komplett neu aufzusetzen. Hinzu kommt, dass Anpassungen am Kern von Liferay die Update-Fähigkeit des Portals stark eingrenzen.

Liferay sind diese Probleme bewusst, weshalb es ab 7.0 das Ext-Plugin nicht mehr unterstützt und stattdessen darauf setzt, dass Nutzer die vorgegebenen OSGi-Einstiegspunkte nutzen, um Komponenten zu überschreiben. Dieser Ansatz ist um einiges Vorteilhafter als ein Ext-Plugin, wurde aber leider nicht konsequent fortgeführt. Nur bestimmte Klassen lassen sich so überschreiben und ein Ersetzen von Core-Jars oder Konfigurationsdateien (sofern sie noch existieren) ist damit nicht möglich. Entsprechend kam es auch zu einen Aufschrei in der Community, da sich viele nicht mehr in der Lage sahen ihre 6.x-Projekte vollständig auf Version 7 zu migrieren. Liferay hat darauf reagiert und die Unterstützung für Ext-Plugins ab 7.0-ga4 wieder eingeführt.

Welche OSGi-Einsteigspunkte existieren nun überhaupt? Leider gibt es keine Liste dazu. Prinzipiell lässt sich aber alles, was mit @Component oder @OSGiBeanProperties annotiert ist überschreiben. Dazu zählen unter anderen:

  • PermissionCheckerFactoryImpl
  • Alle Indexer
  • Portlet-Klassen

Abseits von Überschreiben von Klassen gibt es aber auch eine Reihe von Modulen die sich aktiv erweitern lassen. Diese warten darauf, dass sich neue Komponenten installieren, die einen bestimmten Service konsumieren. Die Komponenten werden daraufhin in die Anwendung integriert. Dies betrifft unter anderen:

  • Portlets, die von der Klasse MVCPortlet ableiten erhalten durch das Implementieren von MVCCommands neue Funktionen

    @Component(
            immediate = true,
            property = {
                    "javax.portlet.name=CustomPortlet",
                    "mvc.command.name=do-something"
            },
            service = MVCActionCommand.class
    )
    public class DoSomethingCommand extends BaseMVCActionCommand {
  • Post-Prozessoren, die Index-Dokumente Nachbearbeiten bevor sie im Index gespeichert werden, lassen sich mit per Annotation registrieren und werden automatisch aufgerufen, wenn ein passender Indexer ein neues Dokument speichert

    @Component(property = {
            "indexer.class.name=de.empulse.person.model.Person"
    }, service = IndexerPostProcessor.class)
    public class PersonSearchIndexPostProcessor extends AbstractSearchIndexerPostProcessor {
  • Die Konfiguration aller Rich-Text-Editoren lässt sich durch einen EditorConfigContributor manipulieren

    @Component(
            immediate = true,
            property = {
                    "editor.name=alloyeditor",
                    "service.ranking:Integer=1005"
            },
            service = EditorConfigContributor.class
    )
    public class CustomAlloyEditorConfigConfigurator extends BaseEditorConfigContributor {

GoGo-Shell

Wie bereits gesagt werden alle Module vom OSGi Framework verwaltet. Liferay verwendet hierfür die Implementierung von Apache Felix. Ebenfalls mit dabei ist das Kommandozeilen-Tool Apache Felix GoGo. Mit GoGo erhält man einen Einblick in den OSGi-Kontext, wodurch man einen Überblick über alle installierten Module erhält und diese nach belieben Stoppen und Starten kann.

Die Verbindung zur Shell erfolgt über Telnet über Port 11311:

root@1e614b1c50d6:/usr/local/liferay# telnet localhost 11311
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
____________________________
Welcome to Apache Felix Gogo

Nun lassen sich Befehle an den OSGi-Container übermitteln. Als überaus praktisch haben sich für mich folgende Befehle herausgestellt:

  • lb -s: Listet alle Module im Container mitsamt ihrer Id und Status auf. Als zusätzlicher Parameter kann man ein Wort eingeben, nachdem die Liste durchsucht werden soll.
  • stop / start {bundleId}: Stoppt oder Startet das Modul mit der passenden BundleId.
  • diag {bundleId}: Konnte ein Modul installiert aber nicht erfolgreich gestartet, gibt dieser Befehl eventuell den Grund dafür zurück.
  • disconnect: Beendet die Shell.