Howto OSGI Declarative Services

Nachdem ich auf der Arbeit ständig damit hantiere, gibts jetzt ne kleine Anleitung bzw. Checkliste für Declarative Services (DS) unter OSGI. Was sind DS? Ganz einfach: Normalerweise müsste man per Hand codieren, welche Services ein OSGI-Bundle anbietet und welche es verwendet. Dadurch entstehen einige Probleme, vor allem muss man sich selbst darum kümmern, was passieren soll, wenn ein Service mal nicht zur Verfügung steht oder sich im Betrieb verabschiedet. Außerdem ist es für einen Außenstehenden oft schwer einzusehen, welche Services das Bundle verwendet bzw. anbietet. Die Ressourcenbelegung ist auch ein Punkt, da die Services sich sofort bei Bundle-Start registrieren und nicht erst, wenn sie benötigt werden. Hier kommen die Declarativen Services ins Spiel. Mit ihnen kann man Services per XML-Datei konfigurieren.

Die folgende Checkliste wurde unter der Verwendung von Eclipse Equinox erstellt.

Checkliste: Was wird für Declarative Services benötigt?

  • Die Implementierung der Deklarative Services erfolgt durch das Bundle org.eclipse.equinox.ds. Dieses Bundle muss immer mit geladen werden, bevor die DS verwendet werden können.
  • Zusätzlich wird die Klasse ComponentContext aus dem Bundle org.osgi.service.component benötigt.
  • Die eigentliche Implementierung des DS erfolgt als sog. Service Component, also einer Klasse, welche den Service (besser gesagt: das Service Interface) implementiert. Im Gegensatz zu einem Activator wird diese nicht abgeleitet oder implementiert eine Interface. Sie muss trotzdem folgende zwei Funktionen enthalten, damit die ServiceComponent funktioniert. Diese Funktionieren ähnlich wie die start und stop Methoden des Bundle-Activators.
    • protected void activate(Component context){…}
    • protected void deactivate(Component context){…}
  • Eine Konfigurationsdatei in XML: Diese Datei wird üblicherweise in einem Verzeichnis namens OSGI-INF abgelegt, der Dateiname ist beliebig. Die XML-Datei selbst folgt dem hier zu findenden Schema. Wichtig ist hier, dass alle Inhalte der XML-Datei, also Tags, Attribute aber Paket- und Klassennamen richtig geschrieben sind, ansonsten kann die Service Component nicht aktiviert werden.
  • Änderung im Manifest-File des Bundles: Füge eine neue Zeile hinzu, der Inhalt ist Service-Component: OSGI-INF/service.xml (wenn die XML-Datei service.xml heißt)

Beispiel:

Hierzu ein kleines Beispiel: Eine ServiceComponent soll erstellt werden, die einen Service mit dem Interface MyServiceInterface bereitstellt. Sie soll außerdem eine Liste von Services des Interface OtherService beinhalten. Dazu bindet sie diesen Service (der auch über eine Service Component realisiert werden muss) mit der cardinality=”1..n”, so dass mindestens ein OtherService vorhanden sein muss, um die Service Component zu aktivieren. Die einzige Funktion in MyServiceInterface gibt die Anzahl der angebundenen OtherService zurück.

Service Interface:

package org.example.ds
import ...
public interface MyServiceInterface {
    int getCount();
}

Implementierungs-Klasse:

package org.example.ds.internal
import ...
public class MyServiceImplementation implements MyServiceInterface {

    private ComponentContext context;
    private List<OtherService> servicelist = new Vector<OtherService>(0);

    protected void activate(ComponentContext context){
        this.context = context;
    }

    protected void deactivate(ComponentContext context){
        this.context = null;
    }

    protected void bindOtherService(OtherService otherservice){
        this.servicelist.add(otherservice)
    }

    protected void unbindOtherService(OtherService otherservice){
        this.servicelist.remove(otherservice)
    }

    public int getCount(){
        return this.servicelist.size();
    }
}

Die XML-Datei im OSGI-INF Ordner:

<?xml version="1.0" encoding="UTF-8"?>
<component name="myServiceComponent">
    <implementation class="org.example.ds.MyServiceImplementation"/>
    <service>
        <provide interface="org.example.ds.MyServiceInterface"/>
    </service>
    <reference
        name="otherservice"
        interface="org.example.ds.OtherService"
        bind="bindOtherService"
        unbind="unbindOtherService"
        cardinality="1..n"
        policy="static"
    />
</component>

Fehlersuche:

  • Das Bundle startet, aber die Service Component nicht. Dies kann viele Ursachen haben:
    • Schreibfehler in der Manifest- oder XML-File. Am besten nochmal nachschauen, ob alle Paket- und Klassennamen stimmen.
    • Eine geforderte Referenz ist nicht verfügbar. Untersuche alle Referenzen, ob deren Service Components vollständig gestartet wurden.
    • Eintrag in der Manifest-File fehlt oder ist ungültig. Mein absoluter Lieblings-Fehler ;)
    • Funktionen wie activate/deactivate bzw. bind/unbind fehlen oder sind ungültig.
  • NullPointerException oder andere krude Exceptions werden geworfen: Dies kann auftreten, wenn Klassenmember in der activate-Funtion belegt werden. Diese wird nämlich erst dann aufgerufen, wenn alle Referenzen erfüllt sind, also erst nach den bind-Funktionen. Wenn in bind schon auf das Member zugegriffen wird, ist es dann noch null und die NullPointerException wird geworfen.
  • Hilfe, ich sehe nicht ob sich irgendwas tut!!!
    • Der Status einer Service Component kann leider nicht einfach über den status-Befehl abgefragt werden, da Service Components ein relativ neues Konzept sind.
    • Eine Service Component meldet ihre Services sofort an der Service Registry an, der Befehl services gibt also keinerlei Aufschluss über den Status ser Service Component.
    • Am besten kann der Lebenszyklus über den Log-Service verfolgt werden, den Equinox mitliefert. Er kann über das Bundle org.eclipse.equinox.log angebunden werden

15 Antworten zu Howto OSGI Declarative Services

  1. stefan sagt:

    Interessanter Beitrag! Scheint, als seien damit die herkömmliche Services deprecated. Schade eigentlich, daß der Status der Declarative Services nicht implizit über die osgi-console verfügbar ist. Die Unterstützung in Eclipse ist wohl auch noch etwas dürftig. Alles in allem macht das jedenfalls den Eindruck als sei OSGi noch stark im Fluss. Bleibt also spannend was da noch kommt. Hast Du noch paar Quellen/Links zu diesen Themen? Eine der besten Quellen bzgl. OSGi i.A. die ich bisher fand war der Vortrag auf der eclipsecon’06: http://www.eclipsecon.org/2006/Sub.do?id=176

  2. hexor2k sagt:

    Die herkömmlichen Services haben schon noch ihre Berechtigung, vor allem wenn man ein zur Laufzeit ausgewähltes Interface als Service anbieten möchte, stößt man mit den deklarativen Services (DS) an eine Grenze, da hier nur vorher bekannte Interfaces möglich sind. Allerdings finde ich für alle anderen Fälle die DS klar besser, da man sofort sieht, welche Services ein Bundle bzw. eine ServiceComponent anbietet und referenziert.
    Ein frei verfügbares Tool zur Anzeige und zum Managment der DS kenne ich leider noch nicht, evtl. können da Managment Agents wie Knopflerfish oder andere proprietäre Lösungen schon mehr. Da ich diese Funktionalität allerdings schon lange auf der Arbeit bräuchte, stehe ich schon kurz davor, mir selber was zu stricken.
    Informationen über OSGI und besonders über DS sind sehr rar gesäht, als große Nachschlagewerke würde ich die OSGI-Spezifikation und das OSGI-Buch empfehlen. Besonders das Buch ist Gold wert, es widmet den DS ein eigenes, umfassendes Kapitel.

  3. [...] den Artikel-Aufrufen liegt das Howto zu den OSGI-Declarative Services mit 156 vorne, dicht gefolgt von dem Howto zum Debuggen von ANT-Tasks mit 107. Platz 3 belegt [...]

  4. paweloque sagt:

    Ich versuche das org.eclipse.equinox.ds bundle zu finden, leider erfolgslos. Wo kann ich es zum download finden? Eclipse 3.5 M3 enthält ds support für ds im editor, aber das ds bundle ist trotzdem nicht mit eclipse ausgeliefert? Sehe ich das richtig?

  5. hexor2k sagt:

    Du hast recht, was DS in 3.5 M3 anbelangt. Dieser Post sagt das selbe aus. Es gab auch schon Unterschiede in DS zwischen der 3.3 und 3.4 Version. Auf der OSGi-Buch-Homepage steht eine Anleitung, wie man DS unter 3.4 nachrüstet, vielleicht geht das auch unter 3.5. Ich kann leider nicht mehr dazu sagen, weil ich nur 3.4 verwende. Es könnte auch sein, dass du das Bundle org.eclipse.equinox.cm noch benötigst, damit DS funktioniert.

  6. Sam sagt:

    Danke für den Beitrag.

    Ich musste zusätzlich noch den Eintrag “Bundle-ActivationPolicy: lazy” in die MANIFEST.MF Datei hinzufügen. Ohne ging es nicht.

  7. hexor2k sagt:

    Das ist mir jetzt neu. Eigentlich hat das Eine mit dem Anderen nichts zu tun. Bundle-ActivationPolicy ist eine eclipse-spezifische Funktion, um Bundles verzögert zu laden, wenn sie gebraucht werden. DS funktionieren auch in anderen OSGI-Implementierungen, die diese Option nicht kennen. Aber vielleicht bin ich da auch nicht mehr auf dem neuesten Stand.

  8. Ich glaube das liegt einfach daran, dass das Bundle nicht gestartet wird. Dies ist nämlich nötig, damit DS einen Service initialisiert. Ich hab meistens auch Lazy aktiviert.

  9. Ich versuche gerade, Declarative Services aus einer eclipse rcp GUI zu nutzen. In meinem Projekt habe ich ein bundle, das den Service anbietet und ein GUI bundle, das diesen nutzen soll. Wenn ich nun eine component auf Basis des Activators oder einer View definiere, die einen Service referenziert dann habe ich folgendes Problem: Eclipse startet eine Instanz der Klasse und Declarative Services startet eine zweite Instanz. So wird zwar der Service schön gesetzt aber eben nicht in die gewünschte Instanz der Klasse. Gibt es dafür eine Lösung?
    P.S. Ich habe genau die gleiche Problematik wenn ich spring osgi verwende.

  10. hexor2k sagt:

    Dieses Problem hatte ich bisher nicht. Wenn eine Klasse sowohl der DS Runtime als auch der RCP GUI bekannt gemacht wird, so erscheint es mir aber schlüssig, dass auch zwei Instanzen angelegt werden. Für statische Servicestrukturen hat sich folgendes Pattern aber bei mir bewährt:

    1. Eine Klasse wird entweder per Service (DS) bekannt gemacht oder in der RCP genutzt, enthält also UI-Code
    2. Der Activator von jedem UI-Bundle enthält eine statische Methode getInstance(), die nach dem Singleton-Pattern genau die Instanz des Activator-Objektes zurückliefert
    3. Diesen statischen Activator kann man nun von überall aus dem UI-Code aufrufen und über diesen wiederum mit den normalen OSGi-Service-Methoden die gewünschten Services anbinden.

    Dieses Pattern funktioniert aber nur gut, wenn ein Service einmalig gestartet wird und dann aktiv bleibt. Können Services dynamisch starten und herunterfahren, klappt es leider nicht, da dann die Referenz nur null zurückliefert. Auch erfordert es eine strikte Trennung der Services und der UI.

  11. Ich habe inzwischen auch eine Lösung für die Views gefunden. Die Servicereferenz lasse ich über die bind und unbind Operationen setzen, aber speichere diese dann in einer statischen Variable der View. So erhält auch die von Eclipse kontrollierte Instanz die Servicereferenz. Ich denke, das ist sauberer als der Weg über den Activator, da man so leichter sieht, welche Views welche Services nutzen.

  12. hexor2k sagt:

    Das ist auch eine Möglichkeit, allerdings werden dann immer noch zwei Instanzen der Klasse angelegt, was evtl. zu Problemen führen kann, da jede Service-Referenz erst einmal statisch gemacht werden muss. Bei der Activator-Methode fungiert jeder Activator als Schnittstelle der UI zu den Services. Zum Beispiel kannst du auch einem Activator eines Bundles noch Methoden wie getServiceX() mitgeben, die einen bestimmten Service zurückgeben.

  13. [...] more alternatives: Peaberry (Google Guice), Blueprint Service (Spring DM in OSGi standard) Howto OSGi Declarative Services (German) Mail List discussion about the differences between Declarative Services in Felix and [...]

  14. Manuel sagt:

    @Christian Schneider (8. Mai 2010 um 09:45):
    Man kann auf die via DS definierten Services auch über den bekannten OSGI ServiceTracker zugreifen:

    BundleContext context = Activator.getContext();

    serviceProviderTracker = new ServiceTracker(context, MyServiceInterface.class.getName(), null);
    serviceProviderTracker.open();
    MyServiceInterface serviceProvider = (MyServiceInterface) serviceProviderTracker.waitForService(5000);
    if(serviceProvider==null){
    throw new RuntimeException(“MyServiceInterface component missing”);
    }

    So ist es möglich Services via DS zu definieren. Services, die sich untereinander brauchen können diese Abhängigkeit auch über DS definieren. Soll ein Service einfach nur genutzt werden, kann das mit obigem Code aus beliebigen Code-Stellen heraus erfolgen.
    Zu beachten ist eben nur, dass der Service so nicht automatisch gestartet wird, weil die Abhängigkeit nicht via DS definiert ist.

  15. hexor2k sagt:

    Man kann auch einfach via ServiceReference auf den DS zugreifen. Voraussetzung ist aber, dass der DS mit dem Flag immediate=”true” gestartet wird. Grundsätzlich würde ich nicht empfehlen, den Zugriff auf DS mit ServiceReference oder ServiceTracker zu realisieren, da nur bei rein auf DS basierenden Architekturen die Abhängigkeiten der Services gemanged werden. Greift man mittels ServiceReference oder ServiceTracker auf einen Service zu, so stellt das einen Bruch der Architektur dar. Als mögliche Ausnahme würde ich den Zugriff von oberster Ebene (z.B. Gui, Frontend) gelten lassen, da die vorhandene Architektur von außen benutzt wird.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Log Out / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Log Out / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Log Out / Ändern )

Verbinde mit %s

Follow

Bekomme jeden neuen Artikel in deinen Posteingang.