OSGi:
Dynamisches Komponentensystem für Java

+ andere TechDocs
+ OSGi Alliance
+ SOA
+


OSGi bietet ein Konzept zur Modularisierung und Komponentisierung von Java-Applikationen, und zwar nicht nur zur Entwicklungszeit, sondern auch zur Laufzeit. Abhängigkeiten zwischen Komponenten werden explizit auf Package-Ebene deklariert. Versionierung wird unterstützt. Zu einer Komponente können gleichzeitig mehrere Versionen in Betrieb sein. Komponenten können während des laufenden Betriebs hinzugefügt und ausgetauscht werden ("Hot Deployment", "Hot Plugging"), auch übers Internet. Eine Service Registry bietet die Suche nach passenden Komponenten. Service-Orientierung und lose Kopplung werden unterstützt und die Testbarkeit verbessert.

Zu den bekanntesten OSGi-Implementierungen zählen Eclipse Equinox, Apache Felix, Knopflerfish und Prosyst mBedded.

Im Folgenden werden kleine OSGi-Programmierbeispiele mit Eclipse für Equinox erstellt.



Inhalt

  1. Begriffe im OSGi-Umfeld
  2. Modulabhängigkeiten und Besonderheiten
  3. Installation des Eclipse Equinox SDK und Einrichtung in der Eclipse-IDE
  4. HelloWorld-Programmierbeispiel
    Plug-in-Projekt und BundleActivator, Test in Eclipse, Test ohne Eclipse, Bundle-.jar-Datei und Manifest, Build mit Maven
  5. Tracker-Programmierbeispiel
    Dienst-Interface, Dienst-Provider, Dienst-Konsument, Verzeichnisstruktur, Manifest, Test in Eclipse, Test ohne Eclipse und mit Anlegen einer "Equinox-Configuration"
  6. Programmierbeispiel einer per Declarative Services definierten Service Component
    Dienst-Interface, Dienst-Provider, Dienst-Konsument, Verzeichnisstruktur, Test
  7. Programmierbeispiel für den Event Admin Service
    Event-Sender, Event-Empfänger, Verzeichnisstruktur, Test
  8. Http-Servlet-Programmierbeispiel
    Servlet-Bundle, Verzeichnisstruktur, Test
  9. Links auf weiterführende Informationen



Begriffe im OSGi-Umfeld

JSR 291
Dynamic Component Support for Java SE, basierend auf OSGi.
OSGi
(früher: "Open Services Gateway initiative")
"The Dynamic Module System for Java" ("Dynamisches Komponentensystem für Java") von der OSGi Alliance (gegründet 1999), welche die OSGi Specifications veröffentlicht. Einen Überblick zur OSGi-Technik finden Sie unter OSGi Technology.
OSGi Specifications
Die beiden wichtigsten Dokumente der OSGi Specifications sind:
OSGi Service Platform Core Specification: Definiert das OSGi Framework (z.B. die OSGi Layer und Framework Services).
OSGi Service Platform Service Compendium: Definiert 24 optional angebotene "OSGi Standard Services" (s.u.).
OSGi Service Platform
Basisplattform des dynamischen Komponentensystems.
Java-Komponenten ("Bundles" und "Services") können zur Laufzeit installiert, aktualisiert, gestartet, gestoppt und deinstalliert werden.
OSGi Framework
Das OSGi Framework ist die Basiskomponente der OSGi Service Platform und stellt den Container (= Laufzeitumgebung) für Bundles und Services.
Bundle
Komponente (= Modul) in Form eines .jar-Archives.
Fachlich und/oder technisch zusammenhängende Einheit von Klassen und Ressourcen, die eigenständig im OSGi Framework installiert werden kann.
Mit OSGi-Bundles können die von Komponenten/Modulen häufig geforderten Eigenschaften realisiert werden: "Self-contained" (möglichst abgeschlossene Einheit), "Cohesive" (möglichst großer innerer Zusammenhang), "Loose Coupling" (lose Kopplung zu anderen Modulen), "Public API" (öffentliche Schnittstelle als Java-Interface), "Defined Dependencies" (explizit definierte Abhängigkeiten) und "Defined Deployment" (definierter Deployment-Prozess).
Jedes im OSGi Framework installierte Bundle erhält einen eigenen ClassLoader, um die Bundles voneinander zu isolieren und um gleiche Klassennamen zu ermöglichen (z.B. für gleichzeitigen Betrieb mehrerer Versionen eines Bundles).
Innerhalb der Eclipse-IDE wird der Begriff "Plug-in" meistens synonym zu "Bundle" verwendet, obwohl es Unterschiede zwischen Eclipse-Plug-ins und OSGi-Bundles gibt.
Bitte verwechseln Sie das OSGi-Bundle nicht mit dem JavaSE-ResourceBundle, welches eine völlig andere Bedeutung hat (nämlich Lokalisierung durch sprachabhängige Properties-Dateien).
Service
Im OSGi-Sinne ist ein Service ein Java-Objekt, welches unter einem Namen (meistens dem Interface-Namen) über die Service Registry bekannt gemacht wird, damit andere Komponenten es finden und benutzen können. Die Bedeutung des Service-Begriffs ist also wesentlich eingeschränkter als beispielsweise bei SOA (Service Oriented Architecture).
Services bieten zwei wichtige Vorteile: Die Verwendung von Services ausschließlich über das Interface entkoppelt strikt von der konkreten Implementierung, so dass diese leichter ausgetauscht werden kann. Und durch die Indirektion über die Service Registry benötigt der Konsument keinerlei Wissen, wie der Dienst erzeugt wird.
Weiter unten finden Sie Programmierbeispiele für Services (z.B. per BundleContext.registerService() registrierter Service und per Declarative Services definierter Component-Service).
OSGi Framework Services
In der Core Specification definierte Basis-Services (wie z.B. Package Admin Service, Start Level Service, [Conditional] Permission Admin Service und URL Handler Service).
OSGi Standard Services
Im Service Compendium definierte von der OSGi Service Platform (optional) angebotene Services (wie z.B. Service Tracker, Declarative Services, Event Admin Service und Http Service).
Service Registry
Zentrale Registrierung von Services, damit diese dann von anderen Komponenten gefunden und genutzt werden können.
Management Agent
Tool zur Verwaltung der Bundles. Kann einfache textbasierte Konsole oder aufwändiges grafisches Tool sein.
Manifest
Die Bundles enthalten im META-INF-Verzeichnis eine MANIFEST.MF-Datei, welche Meta-Informationen zu dem Bundle enthält, zum Beispiel der Bundle-Name, die Bundle-Version und die Exporte und Importe. Die Optionen sind beschrieben in der OSGi Core Specification unter Module Layer / Bundles.
Weiter unten finden Sie Beispiele (z.B. HelloWorld, Export/Import, Declarative Services).
Bundle-SymbolicName und Bundle-Version
Die Kombination aus dem Bundle-SymbolicName und der Bundle-Version (beide eingetragen im Manifest) ist eindeutig innerhalb der OSGi Service Platform und dient zur Identifikation.
Export / Import
In Bundles enthaltene Klassen und Ressourcen sind für andere Bundles nur sichtbar, wenn deren Packages explizit exportiert werden und von dem anderen Bundle explizit importiert werden. Exporte und Importe erfolgen meistens deklarativ statisch in den Manifest-Dateien, können aber auch dynamisch erfolgen über "DynamicImport-Package: ...". Außer Package-Abhängigkeiten kann es auch Service-Abhängigkeiten geben. Beides demonstrieren Programmierbeispiele weiter unten (z.B. Tracker, Declarative Services).
Bundle-Lebenszyklus
Der Lebenszyklus der Bundles umfasst sechs verschiedene Zustände: UNINSTALLED, INSTALLED, RESOLVED, STARTING, ACTIVE und STOPPING. Der normale Betriebszustand ist ACTIVE. Zustandswechsel können programmatisch (über die Methoden des per BundleContext.getBundle(id) erhältlichen Bundle), deklarativ oder über den Management Agent erfolgen. Der aktuelle Zustand der Bundles kann zum Beispiel in der Equinox-Konsole über ss ("short status") abgefragt werden.
Activator
Wenn im Manifest über Bundle-Activator eine Activator-Java-Klasse definiert wird, die BundleActivator implementiert, kann über die start()- und stop()-Methoden auf die Aktivierung reagiert werden (siehe z.B. HelloWorld-Programmierbeispiel). Oft lohnt es sich, den übergebenen BundleContext zu speichern, da über dessen Methoden zum OSGi Framework kommuniziert werden kann.
Service Listener, Service Tracker, Declarative Services
Da Services während der Laufzeit deinstalliert oder ausgetauscht werden können, werden Möglichkeiten benötigt, um auf solche Änderungen reagieren zu können. Die OSGi Specifications sehen hierfür insbesondere folgende Verfahren vor:
Service Listener bilden die Basis. Allerdings sind sie nicht so einfach zu verwenden, da es leicht zu Problemen mit der Startreihenfolge und zu Concurrency-Problemen kommt.
Einfacher ist es, den im Service Compendium definierten Service Tracker zu verwenden (siehe Tracker-Programmierbeispiel).
Service Listener und Service Tracker werden normalerweise programatisch verwendet. Die häufig noch bessere Alternative ist die Verwendung von ebenfalls im Service Compendium definierten Declarative Services (siehe Declarative-Services-Programmierbeispiel). Declarative Services sind einfach zu verwenden und bieten implizit wichtige Vorteile: Abhängigkeiten zwischen Services werden automatisch aufgelöst, die Startreihenfolge spielt keine Rolle, die Start-up-Zeit ist kurz wegen "delayed" Aktivierung und der Speicherverbrauch ist geringer, weil nur wirklich benötigte Module aktiviert werden.
Sicherheit
In der OSGi Core Specification wird der Security Layer beschrieben, der Signierung von Bundles und die Vergabe von Permissions regelt. Im Service Compendium wird der User Admin Service beschrieben, der eine einfache Benutzerverwaltung mit Authentifizierung und Autorisierung bietet.


Modulabhängigkeiten und Besonderheiten

Abhängigkeiten zwischen Bundles

  Provider Consumer
Package-Abhängigkeiten  
Die einfachste Möglichkeit ist, in den jeweiligen MANIFEST.MF-Dateien das Package des Dienstes beim Dienstanbieter zu exportieren und beim Konsument zu importieren Export-Package: ...  Import-Package: ...
Falls Sie erst zur Laufzeit einen Klassennamen erhalten und die Klasse beispielsweise per Class.forName() laden wollen, können Sie hierfür einen dynamischen Import definieren (inkl. '*'-Wildcards), für den erst dann ein passendes Package gesucht wird, wenn die Klasse benötigt wird   DynamicImport-Package: ...
Statt des Imports einzelner Packages kann auch definiert werden, dass alle exportierten Packages eines Bundles importiert werden sollen   Require-Bundle: ...
Sie können die Exporte des importierten Bundles weiterreichen Require-Bundle: ...  
  visibility:=reexport
Fragment-Bundles  
Fragment-Bundles sind Bundles, die nicht eigenständig verwendet werden können, sondern nur der Erweiterung anderer "Host"-Bundles dienen. Fragment-Bundles haben keinen eigenen Lebenszyklus und keinen Activator. In der MANIFEST.MF des Fragment-Bundles muss der "Host" eingetragen sein. Das "Host"-Bundle weiß nichts vom Fragment-Bundle.
Ein typischer Einsatzbereich für Fragment-Bundles sind JavaSE-ResourceBundle zur Lokalisierung. Aber auch JUnit-Tests können so hinzugefügt werden (inkl. Zugriff auf private-Methoden).
Fragment-Host: ...  
Service-Abhängigkeiten  
Wenn der Dienstanbieter seinen Service bei der OSGi Service Registry registriert, können andere Bundles diesen Dienst suchen und verwenden. Hierfür muss der Konsument weder den Dienstanbieter kennen noch eine Package-Abhängigkeit deklarieren. Allerdings wird häufig ein gemeinsam vom Anbieter und Konsumenten verwendetes Schnittstellen-Interface benötigt, welches beispielsweise in einem dritten Bundle angeboten werden kann, wie es weiter unten einige Programmierbeispiele zeigen (z.B. Tracker, Declarative Services). BundleContext.
  registerService()
getServiceReferences(),

getServiceReference(),

getService()
Wenn der Service Tracker verwendet wird, können Services auch über den ServiceTrackerCustomizer ermittelt werden, wie es das Tracker-Programmierbeispiel zeigt.   ServiceTrackerCustomizer.
  addingService()
Bei Declarative Services erfolgt die Veröffentlichung und Verwendung von Services über Einträge in XML-Konfigurationsdateien (z.B. OSGI-INF/ds-component.xml), wie im Declarative-Services-Programmierbeispiel u.a. gezeigt wird <service>
  <provide
   interface=...
<reference
  interface=...
  bind=...

Besonderheiten



Installation des Eclipse Equinox SDK und Einrichtung in der Eclipse-IDE

Installation

  1. Voraussetzung ist ein aktuelles Java SE und eine aktuelle Version der Eclipse-IDE.
  2. Downloaden Sie das Eclipse Equinox SDK "equinox-SDK-...zip" (z.B. equinox-SDK-3.7.1.zip) von http://download.eclipse.org/equinox.
  3. Entpacken Sie das equinox-SDK-...zip-Archiv zum Beispiel nach C:\Tools\equinox-SDK. Überprüfen Sie, ob Ihre Equinox-Version im Equinox-plugins-Verzeichnis (z.B. C:\Tools\equinox-SDK\plugins) bereits das "Config Admin Service"-Plug-in "org.eclipse.equinox.cm_...jar" und das "Declarative Services"-Plug-in "org.eclipse.equinox.ds_...jar" enthält. Bis Equinox SDK 3.3 mussten Sie diese beiden .jar-Archive zusätzlich downloaden und in das Equinox-Plug-in-Verzeichnis kopieren. Ab Equinox SDK 3.4 ist das nicht mehr nötig.
  4. Melden Sie das Equinox SDK in der Eclipse-IDE als "Target Platform" an: Starten Sie Ihre Eclipse-IDE, wählen Sie 'Window' | 'Preferences' | 'Plug-in Development' | 'Target Platform' und geben Sie bei 'Location' Ihr Equinox-SDK-Verzeichnis ein (z.B. C:\Tools\equinox-SDK).
  5. Führen Sie für einen ersten Test folgende Schritte durch:
    Öffnen Sie im Equinox-SDK-Verzeichnis ein Kommandozeilenfenster und geben Sie ein:

    cd /D C:\Tools\equinox-SDK

    java -jar plugins\org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console -clean

    ?

    ss

    close

    Setzen Sie dabei statt org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar den org.eclipse.osgi_...jar-Dateinamen von Ihrer Equinox-Version ein.

    Das "ss"-Kommando ("short status") listet die registrierten Bundles auf: org.eclipse.osgi_... ist als "ACTIVE" gelistet.

Test inklusive Webserver mit der OSGi-Sudoku-Demo

  1. Sie können wahlweise auch einen etwas aufwändigeren Test inklusive Webserver durchführen:
    Öffnen Sie wieder im Equinox-SDK-Verzeichnis ein Kommandozeilenfenster und führen Sie folgende Kommandos aus (setzen Sie wieder bei allen .jar-Dateien die korrekten zu Ihrer Equinox-Version passenden Versionsbezeichnungen ein) (falls der Port 8080 auf Ihrem PC bereits belegt ist, ersetzen Sie ihn durch eine freie Nummer):

    cd /D C:\Tools\equinox-SDK

    java -jar plugins\org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console -clean

    install file:plugins/org.eclipse.osgi.services_3.3.0.v20110513.jar start

    install file:plugins/org.eclipse.equinox.util_1.0.300.v20110502.jar start

    install file:plugins/org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar start

    install file:plugins/javax.servlet_2.5.0.v201103041518.jar start

    setprop org.osgi.service.http.port=8080

    install file:plugins/org.eclipse.equinox.http_1.0.500.v20110413.jar start

    install http://www.aqute.biz/uploads/Code/aQute.webrpc.jar start

    install http://www.aqute.biz/uploads/Code/aQute.sudoku.jar start

    ss

    Das "ss"-Kommando listet die registrierten Bundles auf: Sie erhalten acht Bundles mit dem State "ACTIVE".

  2. Laden Sie das installierte Sudoku-Spiel im Webbrowser über: http://localhost:8080/rpc/sudoku/index.html.
    Die Demo verwendet SVG und funktioniert eventuell nicht in allen Webbrowsern, aber zum Beispiel im Mozilla Firefox und auf einigen Handys.
  3. Wenn Sie fertig sind, beenden Sie OSGi mit:

    close

  4. Bei diesem Beispiel wurden die beiden Bundles "webrpc.jar" und "sudoku.jar" über "install http:..." direkt übers Internet installiert. Falls Sie das nicht wollen, können Sie die benötigten .jar-Dateien natürlich auch zuerst von http://www.aqute.biz/uploads/Code downloaden und dann per "install file:..." installieren. Weitere Infos zu den beiden Bundles finden Sie auf der genannten Webseite und bei Peter Kriens.

  5. Bei diesem Beispiel wurden die Bundles einzeln installiert. Wie man solche Installationen mit Hilfe einer "configuration" zusammenfassen und vereinfachen kann, wird weiter unten gezeigt.



HelloWorld-Programmierbeispiel

Allgemeines zu den Bezeichnungen der Bundle-SymbolicNames, Packages und Event-Topics

Download der Sourcen

Plug-in-Projekt und BundleActivator

  1. In der Eclipse-IDE werden Bundles als Plug-in bezeichnet. Um ein OSGi-Bundle zu erzeugen, wählen Sie in Eclipse: 'File' | 'New' | 'Project...' | 'Plug-in Development' | 'Plug-in Project' | 'Next >' und tragen ein:

    Project name: OsgiHelloWorld    
    Target Platform
    This plug-in is targeted to run with:
    an OSGi framework: standard   ("standard" für Standard-OSGi
    ohne spezielle Equinox-Erweiterungen)
    'Next >'
    ID: HelloWorld    
    Version: 1.0.0    
    Name: HelloWorld-Bundle    
    Activator: osgihelloworld.Activator    
    'Finish'
  2. Öffnen Sie im Eclipse Package Explorer die Verzeichnisse 'OsgiHelloWorld' | 'src' | 'osgihelloworld' und die Datei 'Activator.java' und ersetzen Sie den Inhalt durch:

    package osgihelloworld;
    
    import org.osgi.framework.BundleActivator;
    import org.osgi.framework.BundleContext;
    
    public class Activator implements BundleActivator
    {
       @Override
       public void start( BundleContext context ) throws Exception
       {
          System.out.println( context.getBundle().getSymbolicName() +  ": Hallo OSGi-Welt." );
       }
    
       @Override
       public void stop( BundleContext context ) throws Exception
       {
          System.out.println( context.getBundle().getSymbolicName() +  ": Tschau OSGi-Welt." );
       }
    }
    

Test in Eclipse

  1. Wählen Sie in Eclipse: 'Run' | 'Run Configurations...'. Klicken Sie mit der rechten Maustaste auf 'OSGi Framework' und wählen Sie 'New'. Klicken Sie auf den Button 'Deselect All' und aktivieren Sie anschließend die beiden Bundles 'HelloWorld' und 'org.eclipse.osgi'. Klicken Sie auf 'Run'.
  2. Sie erhalten als Ergebnis im Eclipse-Konsolenfenster:

    osgi> HelloWorld: Hallo OSGi-Welt.

  3. Geben Sie im Eclipse-Konsolenfenster "ss" ein: Die beiden Bundles werden als ACTIVE angezeigt.
  4. Geben Sie im Eclipse-Konsolenfenster ein:

    uninstall HelloWorld

    Sie erhalten:

    HelloWorld: Tschau OSGi-Welt.

  5. Hiermit können Sie das Bundle wieder installieren (passen Sie den Pfad D:\MeinWorkspace\ an):

    install file:D:\MeinWorkspace\OsgiHelloWorld

    ss

    start HelloWorld

    ss

  6. Beenden Sie die OSGi-Sitzung durch Eingabe von:

    close

    (Wenn Sie keinen "ordentlichen Shutdown" wollen, können Sie auch ein "exit immediately" mit "exit" oder durch Klick auf das rote 'Terminate'-Quadrat erzwingen.)

  7. Wenn Sie einmal eine 'Run Configuration' wie oben beschrieben eingerichtet haben, können Sie die nächsten Male Equinox mit dem Bundle starten, indem Sie im Eclipse Package Explorer das Projekt 'OsgiHelloWorld' markieren und "Alt + Shift + X" und dann "O" wählen (oder "F11" betätigen).

Test ohne Eclipse

  1. Exportieren Sie das Bundle: Klicken Sie im Eclipse Package Explorer mit der rechten Maustaste auf das Projekt 'OsgiHelloWorld' und wählen Sie: 'Export...' | 'Plug-in Development' | 'Deployable plug-ins and fragments'. Tragen Sie bei Directory ein Zielverzeichnis ein, zum Beispiel: C:\Tools\equinox-SDK\MeineBundles
  2. Im Unterverzeichnis 'plugins' zum Zielverzeichnis entsteht "HelloWorld_1.0.0.jar".
  3. Öffnen Sie im Equinox-SDK-Verzeichnis ein Kommandozeilenfenster und geben Sie ein (ersetzen Sie C:\Tools\equinox-SDK\MeineBundles durch Ihr Zielverzeichnis sowie org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar durch den org.eclipse.osgi_...jar-Dateinamen von Ihrer Equinox-Version):

    cd /D C:\Tools\equinox-SDK

    java -jar plugins\org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console -clean

    install file:C:\Tools\equinox-SDK\MeineBundles\plugins\HelloWorld_1.0.0.jar start

    ss

    uninstall 1

    ss

    close

    Sie erhalten:

    HelloWorld: Hallo OSGi-Welt.

    und

    HelloWorld: Tschau OSGi-Welt.

  4. Testen Sie auch weitere Kommandos wie zum Beispiel refresh, update und stop.

Bundle-.jar-Datei und Manifest

  1. Entzippen Sie das exportierte Bundle HelloWorld_1.0.0.jar und sehen Sie sich den Inhalt an. Es enthält lediglich zwei Dateien in folgender Struktur:

    [HelloWorld_1.0.0.jar]
      |- [osgihelloworld]
      |    '- Activator.class
      '- [META-INF]
           '- MANIFEST.MF
    
  2. Die Manifest-Datei MANIFEST.MF hat ungefähr folgenden Inhalt:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: HelloWorld-Bundle
    Bundle-SymbolicName: HelloWorld
    Bundle-Version: 1.0.0
    Bundle-Activator: osgihelloworld.Activator
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: org.osgi.framework;version="1.3.0"
    

Build mit Maven (ohne Eclipse)

  1. Unter OSGi-Bundle mit dem Maven-Bundle-Plugin finden Sie eine Beschreibung, wie Sie das HelloWorld-Programmierbeispiel ohne Eclipse und mit Maven durchführen können.



Tracker-Programmierbeispiel

Im folgenden Programmierbeispiel gibt es einen Konsumenten, der per ServiceTracker dynamisch auf einen Dienst (= Service) zur Ermittlung des Datums reagiert. Konsument und Dienst-Provider sind in getrennten Bundles. Damit der Konsument auch dann bereits gestartet werden kann, wenn der Dienst-Provider noch nicht läuft, wird das Interface in einem separaten dritten Bundle angelegt.

Genaueres zum "Service Tracker" finden Sie im OSGi Service Platform Service Compendium.

Bitte beachten Sie, dass der ServiceTracker nur eine der möglichen Varianten darstellt, um auf Änderungen dynamisch zu reagieren. Oft werden stattdessen Declarative Services bevorzugt, wie es das anschließende Programmierbeispiel zeigt (siehe auch obigen Vergleich).

Dienst-Interface

  1. Erstellen Sie ein neues OSGi-Bundle-Projekt in Eclipse: 'File' | 'New' | 'Project...' | 'Plug-in Development' | 'Plug-in Project' | 'Next >':

    Project name: OsgiTrackerInterface
    Target Platform
    This plug-in is targeted to run with:
    an OSGi framework: standard
    'Next >'
    ID: Datum-Service-Interface
    Version: 1.0.0
    Name: Datum-Service-Interface-Bundle
    Plug-in Options
    Generate an activator:
    nein
    'Finish'
  2. Klicken Sie im Eclipse Package Explorer im Projekt 'OsgiTrackerInterface' mit der rechten Maustaste auf 'src' und wählen Sie: 'New' | 'Package' | 'Name = datumservice'.

    Klicken Sie mit der rechten Maustaste auf das entstandene Package 'datumservice' und wählen Sie: 'New' | 'Interface' | 'Name = DatumService'.

    Ersetzen Sie den Inhalt von 'DatumService.java' durch:

    package datumservice;
    
    public interface DatumService
    {
       String getDatum();
    }
    
  3. Öffnen Sie im Eclipse Package Explorer im Projekt 'OsgiTrackerInterface' das 'META-INF'-Verzeichnis und doppelklicken Sie auf die 'MANIFEST.MF'-Datei. Schalten Sie am unteren Rand auf den Tabulatorreiter 'Runtime'. Klicken Sie im 'Exported Packages'-Fenster auf 'Add...'. Doppelklicken Sie auf das angezeigte Package 'datumservice' und speichern Sie mit 'Strg + S'.

Dienst-Provider

  1. Erstellen Sie ein neues OSGi-Bundle-Projekt in Eclipse: 'File' | 'New' | 'Project...' | 'Plug-in Development' | 'Plug-in Project' | 'Next >':

    Project name: OsgiTrackerImpl
    Target Platform
    This plug-in is targeted to run with:
    an OSGi framework: standard
    'Next >'
    ID: Datum-Service-Impl
    Version: 1.0.0
    Name: Datum-Service-Impl-Bundle
    Activator: datumservice.impl.Activator
    'Finish'
  2. Öffnen Sie im Eclipse Package Explorer im Projekt 'OsgiTrackerImpl' das 'META-INF'-Verzeichnis und doppelklicken Sie auf die 'MANIFEST.MF'-Datei. Schalten Sie am unteren Rand auf den Tabulatorreiter 'Dependencies'. Klicken Sie im 'Imported Packages'-Fenster auf 'Add...'. Doppelklicken Sie in der Liste auf das Package 'datumservice' und speichern Sie mit 'Strg + S'.

  3. Öffnen Sie im Eclipse Package Explorer die Verzeichnisse 'OsgiTrackerImpl' | 'src' | 'datumservice.impl'. Klicken Sie mit der rechten Maustaste auf das Package 'datumservice.impl' und wählen Sie: 'New' | 'Class' | 'Name = DatumServiceImpl'. Ersetzen Sie den Inhalt von 'DatumServiceImpl.java' durch:

    package datumservice.impl;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import datumservice.DatumService;
    
    public class DatumServiceImpl implements DatumService
    {
       @Override
       public String getDatum()
       {
          return (new SimpleDateFormat( "yyyy-MM-dd HH:mm" )).format( new Date() );
       }
    }
    
  4. Öffnen Sie im Eclipse Package Explorer die Verzeichnisse 'OsgiTrackerImpl' | 'src' | 'datumservice.impl' und die Datei 'Activator.java' und ersetzen Sie den Inhalt durch:

    package datumservice.impl;
    
    import org.osgi.framework.*;
    import datumservice.DatumService;
    
    public class Activator implements BundleActivator
    {
       @Override
       public void start( BundleContext context ) throws Exception
       {
          System.out.println( context.getBundle().getSymbolicName() +  " startet ..." );
          context.registerService( DatumService.class.getName(), new DatumServiceImpl(), null );
          System.out.println( context.getBundle().getSymbolicName() +  " gestartet und Dienst registriert." );
       }
    
       @Override
       public void stop( BundleContext context ) throws Exception
       {
          System.out.println( context.getBundle().getSymbolicName() +  " gestoppt." );
       }
    }
    

Dienst-Konsument

  1. Erstellen Sie ein neues OSGi-Bundle-Projekt in Eclipse: 'File' | 'New' | 'Project...' | 'Plug-in Development' | 'Plug-in Project' | 'Next >':

    Project name: OsgiTrackerKonsument
    Target Platform
    This plug-in is targeted to run with:
    an OSGi framework: standard
    'Next >'
    ID: Datum-Service-Konsument
    Version: 1.0.0
    Name: Datum-Service-Konsument-Bundle
    Activator: datumservice.consumer.Activator
    'Finish'
  2. Öffnen Sie im Eclipse Package Explorer im Projekt 'OsgiTrackerKonsument' das 'META-INF'-Verzeichnis und doppelklicken Sie auf die 'MANIFEST.MF'-Datei. Schalten Sie am unteren Rand auf den Tabulatorreiter 'Dependencies'. Klicken Sie im 'Imported Packages'-Fenster auf 'Add...'. Doppelklicken Sie in der Liste auf das Package 'datumservice'. Klicken Sie erneut auf 'Add...' und doppelklicken Sie diesmal auf 'org.osgi.util.tracker'. Speichern Sie mit 'Strg + S'.

  3. Öffnen Sie im Eclipse Package Explorer die Verzeichnisse 'OsgiTrackerKonsument' | 'src' | 'datumservice.consumer' und die Datei 'Activator.java' und ersetzen Sie den Inhalt durch:

    package datumservice.consumer;
    
    import org.osgi.framework.*;
    import org.osgi.util.tracker.*;
    import datumservice.DatumService;
    
    public class Activator implements BundleActivator
    {
       private ServiceTracker datumServiceTracker;
    
       @Override
       public void start( final BundleContext context )
       {
          System.out.println( context.getBundle().getSymbolicName() +  " startet ..." );
          datumServiceTracker = new ServiceTracker( context, DatumService.class.getName(),
             new ServiceTrackerCustomizer()
             {
                @Override
                public Object addingService( final ServiceReference reference )
                {
                   final DatumService datumService = (DatumService) context.getService( reference );
                   if( datumService != null )
                      System.out.println( "Konsument-ServiceTracker liest Datum-Service: " + datumService.getDatum() );
                   return datumService;
                }
    
                @Override
                public void modifiedService( final ServiceReference reference, final Object service ) {/*ok*/}
                @Override
                public void removedService(  final ServiceReference reference, final Object service ) {/*ok*/}
             });
    
          datumServiceTracker.open();
          System.out.println( context.getBundle().getSymbolicName() +  " gestartet und Tracker geoeffnet." );
       }
    
       @Override
       public void stop( final BundleContext context )
       {
          if( datumServiceTracker != null )
              datumServiceTracker.close();
          System.out.println( context.getBundle().getSymbolicName() +  " gestoppt." );
       }
    }
    

    (Sehen Sie sich die API-Doku zum ServiceTracker an.)

Verzeichnisstruktur

Manifest

Sehen Sie sich die Export- und Import-Deklarationen in den MANIFEST.MF-Dateien der drei Bundles an:

  1. Ausschnitt aus der MANIFEST.MF vom OsgiTrackerInterface-Bundle:

    Bundle-SymbolicName: Datum-Service-Interface
    Export-Package: datumservice
    ...
    
  2. Ausschnitt aus der MANIFEST.MF vom OsgiTrackerImpl-Bundle:

    Bundle-SymbolicName: Datum-Service-Impl
    Import-Package: datumservice,
      org.osgi.framework;version="1.3.0"
    ...
    
  3. Ausschnitt aus der MANIFEST.MF vom OsgiTrackerKonsument-Bundle:

    Bundle-SymbolicName: Datum-Service-Konsument
    Import-Package: datumservice,
      org.osgi.framework;version="1.3.0",
      org.osgi.util.tracker;version="1.3.3"
    ...
    

Beachten Sie, dass der Konsument nichts von der Implementierung importiert, sondern lediglich das datumservice-Interface-Package.

Test in Eclipse

  1. Wählen Sie in Eclipse: 'Run' | 'Run Configurations...'. Klicken Sie mit der rechten Maustaste auf 'OSGi Framework' und wählen Sie 'New'. Tragen Sie bei 'Name' 'OSGi-Tracker' ein. Klicken Sie auf den Button 'Deselect All' und aktivieren Sie anschließend die Bundles 'Datum-Service-Impl', 'Datum-Service-Interface', 'Datum-Service-Konsument' und 'org.eclipse.osgi'. Klicken Sie auf 'Run'.
  2. Die beiden Bundles 'Datum-Service-Impl' und 'Datum-Service-Konsument' melden sich. Lassen Sie sich die IDs, SymbolicNames und Stati der Bundles anzeigen:

    ss

  3. Stoppen Sie 'Datum-Service-Impl':

    stop Datum-Service-Impl

    Sie erhalten:

    Datum-Service-Impl gestoppt.

  4. Starten Sie 'Datum-Service-Impl' erneut:

    start Datum-Service-Impl

    Sie erhalten:

    Datum-Service-Impl startet ...
    Konsument-ServiceTracker liest Datum-Service: 2009-01-02 11:22
    Datum-Service-Impl gestartet und Dienst registriert.

    Der Konsument hat also bemerkt, dass der Service neu gestartet wurde.

  5. Ein ähnliches Verhalten können Sie auch mit "refresh Datum-Service-Impl", "update Datum-Service-Impl" und "update *" beobachten.

  6. Lassen Sie sich mit bundle Informationen zu Bundles anzeigen.
    Das Datum-Service-Impl-Bundle hat einen "Registered Service", aber "No services in use":

    bundle Datum-Service-Impl
    -->
    ...
      Registered Services
        {datumservice.DatumService}={service.id=26}
      No services in use.
    ...

    Und das Datum-Service-Konsument-Bundle hat "No registered services", aber einen "Service in use":

    bundle Datum-Service-Konsument
    -->
    ...
      No registered services.
      Services in use:
        {datumservice.DatumService}={service.id=26}
    ...

  7. Lassen Sie sich mit packages anzeigen, welche anderen Bundles vom angegebenen Bundle abhängen:

    packages Datum-Service-Interface

    Sie erfahren, dass OsgiTrackerImpl und OsgiTrackerKonsument von OsgiTrackerInterface abhängen.

  8. Lassen Sie sich mit diag unerfüllte Bedingungen anzeigen:

    diag Datum-Service-Konsument

  9. Lassen Sie sich mit headers den Inhalt der Bundle-MANIFEST.MF anzeigen, zum Beispiel:

    headers Datum-Service-Konsument

  10. Suchen Sie mit services nach Services. Verwenden Sie dabei die oben erläuterten filter-Ausdrücke, bei denen auch *-Wildcards erlaubt sind. Beispiele (unter der Annahme, dass für den DatumService service.id=26 gilt):

    services (objectClass=*Datum*)

    services (objectClass=datumservice.DatumService)

    services (service.id=26)

    services (& (objectClass=datumservice.*) (service.id>=22) )

    services (& (service.id>=20) (service.id<=30) )

  11. Lassen Sie sich mit status, bundles, services, threads und props weitere Informationen anzeigen und sehen Sie sich mit ? an, welche Kommandos es sonst noch gibt.

Test ohne Eclipse und mit Anlegen einer "Equinox-Configuration"

  1. Legen Sie unter dem Equinox-SDK-Verzeichnis (z.B. C:\Tools\equinox-SDK) ein Zielverzeichnis an, zum Beispiel MeinDatumService.
  2. Bevor Sie die drei Bundles exportieren: Falls Sie außer dem bin- und META-INF-Verzeichnis weitere Dateien oder Verzeichnisse in den Bundles benötigen (z.B. das OSGI-INF-Verzeichnis, falls Sie darin eine permissions.perm- oder ds-component.xml-Datei hätten), müssten Sie dies zuerst in der build.properties-Datei eintragen, was am einfachsten über den speziellen Eclipse-Editor für diese Datei erfolgt. Für das Tracker-Programmierbeispiel ist dies jedoch nicht erforderlich.
  3. Wählen Sie in Eclipse: 'File' | 'Export...' | 'Plug-in Development' | 'Deployable plug-ins and fragments'.
    Aktivieren Sie die drei Plug-ins 'Datum-Service-Impl', 'Datum-Service-Interface' und 'Datum-Service-Konsument'.
    Tragen Sie bei Directory das angelegte Zielverzeichnis ein (z.B. C:\Tools\equinox-SDK\MeinDatumService).
  4. Im Unterverzeichnis plugins zum Zielverzeichnis entstehen die drei .jar-Bundles.
  5. Erzeugen Sie eine "Equinox-Configuration": Legen Sie im Zielverzeichnis (z.B. MeinDatumService) das Verzeichnis config und darin die Datei config.ini mit folgendem Inhalt an (passen Sie die Pfade an):

    osgi.bundles=\
      ../MeinDatumService/plugins/Datum-Service-Interface@start,\
      ../MeinDatumService/plugins/Datum-Service-Impl@start,\
      ../MeinDatumService/plugins/Datum-Service-Konsument@start
    eclipse.ignoreApp=true
    
  6. Ihre Verzeichnisstruktur sieht jetzt im Wesentlichen folgendermaßen aus (kontrollieren Sie es mit "tree /F"):

    [C:\Tools\equinox-SDK]
      '- [MeinDatumService]
           |- [config]
           |    '- config.ini
           '- [plugins]
                |- Datum-Service-Impl_1.0.0.jar
                |- Datum-Service-Interface_1.0.0.jar
                '- Datum-Service-Konsument_1.0.0.jar
    
  7. Öffnen Sie im Equinox-SDK-Verzeichnis (z.B. C:\Tools\equinox-SDK) ein Kommandozeilenfenster und geben Sie ein (setzen Sie dabei wieder statt org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar den org.eclipse.osgi_...jar-Dateinamen von Ihrer Equinox-Version ein) (in einer Zeile):

    java -jar plugins\org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar  -configuration MeinDatumService\config -console -clean

    Sie erhalten:

    osgi> Datum-Service-Impl startet ...
    Datum-Service-Impl gestartet und Dienst registriert.
    Datum-Service-Konsument startet ...
    Konsument-ServiceTracker liest Datum-Service: 2009-01-02 11:22
    Datum-Service-Konsument gestartet und Tracker geoeffnet.
    

    Falls Sie dies nicht erhalten: Dann finden Sie im C:\Tools\equinox-SDK\MeinDatumService\config-Verzeichnis eine .log-Datei mit der entsprechenden Fehlermeldung.

  8. Testen Sie die oben unter Test in Eclipse genannten Kommandos.
  9. Falls das Bauen und der Export von Bundles nicht manuell erfolgen sollen, wie hier gezeigt wurde, sondern automatisiert erfolgen soll: Sehen Sie sich folgende Tools an: "bnd", "PDE Headless Build", "Ant4Eclipse" und "maven-bundle-plugin".


Programmierbeispiel einer per Declarative Services definierten Service Component

Im letzten Programmierbeispiel wurde gezeigt, wie mit einem Service Tracker dynamisch auf Änderungen reagiert werden kann. Im folgenden Programmierbeispiel wird stattdessen eine durch "Declarative Services" ("DS") definierte "Service Component" verwendet, was häufig die bessere und einfachere Variante darstellt.

Per Declarative Services definierte Service Components bestehen aus einer XML-Beschreibung (siehe z.B. Provider / Consumer) und der Dienst-Implementierung (siehe z.B. DatumServiceImpl). Falls in der Dienst-Implementierung auf die Aktivierung reagiert werden soll, kann optional eine activate()-Methode hinzugefügt werden (siehe ComponentContext-Beschreibung). Im Konsumenten können bind...()- und unbind...()-Methoden in der XML-Beschreibung definiert und im Konsument implementiert werden, damit der Konsument dynamisch auf den Service reagieren kann (siehe z.B. DatumServiceKonsument).

Einige Vorteile der Declarative Services sind weiter oben aufgezählt. Genaueres zu "Declarative Services" finden Sie im OSGi Service Platform Service Compendium.

Die Beschreibungen erfolgen diesmal etwas kürzer, um Wiederholungen zu vermeiden.

Dienst-Interface

  1. Erstellen Sie in Eclipse das neue OSGi-Bundle-Projekt "OsgiComponentInterface":

    Project name: OsgiComponentInterface
    Plug-in ID: Datum-Component-Interface
    Plug-in Options
    Generate an activator:
    nein
  2. Legen Sie im OsgiComponentInterface-Projekt das Package datumservice und darin das Interface DatumService.java an:

    package datumservice;
    
    public interface DatumService
    {
       String getDatum();
    }
    
  3. Ersetzen Sie im OsgiComponentInterface-Projekt die META-INF/MANIFEST.MF-Datei durch:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Datum-Component-Interface-Bundle
    Bundle-SymbolicName: Datum-Component-Interface
    Bundle-Version: 1.0.0
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Export-Package: datumservice
    

Dienst-Provider

  1. Erstellen Sie das neue OSGi-Bundle-Projekt "OsgiComponentImpl":

    Project name: OsgiComponentImpl
    Plug-in ID: Datum-Component-Impl
    Plug-in Options
    Generate an activator:
    nein
  2. Ersetzen Sie im OsgiComponentImpl-Projekt die META-INF/MANIFEST.MF-Datei durch:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Datum-Component-Impl-Bundle
    Bundle-SymbolicName: Datum-Component-Impl
    Bundle-Version: 1.0.0
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: datumservice,
     org.osgi.framework;version="1.3.0",
     org.osgi.service.component;version="1.0.0"
    Service-Component: OSGI-INF/ds-component.xml
    MeinBundleMfSpezialKey: MeinBundleMfSpezialValue
    
  3. Legen Sie im OsgiComponentImpl-Projekt das Verzeichnis OSGI-INF und darin die Datei ds-component.xml an:

    <?xml version="1.0"?>
    <component name="Datum-Component-Impl">
       <implementation class="datumservice.impl.DatumServiceImpl" />
       <property name="MeinCompDescrSpezialKey" value="MeinCompDescrSpezialValue" />
       <service>
          <provide interface="datumservice.DatumService" />
       </service>
    </component>
    
  4. Legen Sie im OsgiComponentImpl-Projekt das Package datumservice.impl und darin die Datei DatumServiceImpl.java an:

    package datumservice.impl;
    
    import java.text.SimpleDateFormat;
    import java.util.*;
    import org.osgi.service.component.ComponentContext;
    import datumservice.DatumService;
    
    public class DatumServiceImpl implements DatumService
    {
       protected void activate( ComponentContext componentContext )
       {
          System.out.println( "\nDatumServiceImpl aktiviert mit folgenden Spezial-Properties:" );
          System.out.println( "   MeinCompDescrSpezialKey = " +
                componentContext.getProperties().get( "MeinCompDescrSpezialKey" ) );
          System.out.println( "   MeinPropSpezialKey      = " +
                componentContext.getBundleContext().getProperty( "MeinPropSpezialKey" ) );
          System.out.println( "   MeinBundleMfSpezialKey  = " +
                componentContext.getBundleContext().getBundle().getHeaders().get( "MeinBundleMfSpezialKey" ) );
       }
    
       protected void deactivate( ComponentContext componentContext )
       {
          System.out.println( "DatumServiceImpl deaktiviert." );
       }
    
       @Override
       public String getDatum()
       {
          return (new SimpleDateFormat( "yyyy-MM-dd HH:mm" )).format( new Date() );
       }
    }
    

Dienst-Konsument

  1. Erstellen Sie ein neues OSGi-Bundle-Projekt in Eclipse: 'File' | 'New' | 'Project...' | 'Plug-in Development' | 'Plug-in Project' | 'Next >':

    Project name: OsgiComponentKonsument
    Plug-in ID: Datum-Component-Konsument
    Plug-in Options
    Generate an activator:
    nein
  2. Ersetzen Sie im OsgiComponentKonsument-Projekt die META-INF/MANIFEST.MF-Datei durch:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Datum-Component-Konsument-Bundle
    Bundle-SymbolicName: Datum-Component-Konsument
    Bundle-Version: 1.0.0
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: datumservice,
     org.osgi.framework;version="1.3.0",
     org.osgi.service.component;version="1.0.0"
    Service-Component: OSGI-INF/ds-component.xml
    
  3. Legen Sie im OsgiComponentKonsument-Projekt das Verzeichnis OSGI-INF und darin die Datei ds-component.xml an:

    <?xml version="1.0"?>
    <component name="Datum-Component-Konsument">
       <implementation class="datumservice.consumer.DatumServiceKonsument" />
       <reference name="Datum-Component-Interface"
                  interface="datumservice.DatumService"
                  bind="bindServiceComponent"
                  unbind="unbindServiceComponent" />
    </component>
    
  4. Legen Sie im OsgiComponentKonsument-Projekt das Package datumservice.consumer und darin die Datei DatumServiceKonsument.java an:

    package datumservice.consumer;
    
    import org.osgi.service.component.ComponentContext;
    import datumservice.DatumService;
    
    public class DatumServiceKonsument
    {
       private DatumService datumService;
    
       protected void bindServiceComponent( DatumService datumService )
       {
          this.datumService = datumService;
          System.out.println( "DatumServiceKonsument: DatumService wurde uebergeben." );
       }
    
       protected void unbindServiceComponent( DatumService datumService )
       {
          this.datumService = null;
          System.out.println( "DatumServiceKonsument: DatumService wurde entfernt." );
       }
    
       protected void activate( ComponentContext componentContext )
       {
          System.out.println( "DatumServiceKonsument aktiviert." );
          System.out.println( "DatumServiceKonsument liest Datum-Service: " + datumService.getDatum() );
       }
    
       protected void deactivate( ComponentContext componentContext )
       {
          System.out.println( "DatumServiceKonsument deaktiviert." );
       }
    }
    

    Bitte beachten Sie, dass diese Methoden Multithreading-sicher gestaltet werden müssen. Eventuell müssen Sie Teile der Methoden in synchronized-Blöcken implementieren.

Verzeichnisstruktur

Test

  1. Wählen Sie in Eclipse: 'Run' | 'Run Configurations...'. Klicken Sie mit der rechten Maustaste auf 'OSGi Framework' und wählen Sie 'New'. Tragen Sie bei 'Name' 'OSGi-Component' ein. Klicken Sie auf den Button 'Deselect All' und aktivieren Sie anschließend folgende Bundles:

    (x) Datum-Component-Impl
    (x) Datum-Component-Interface
    (x) Datum-Component-Konsument
    (x) org.eclipse.equinox.ds
    (x) org.eclipse.equinox.util
    (x) org.eclipse.osgi
    (x) org.eclipse.osgi.services

    Klicken Sie auf 'Run'.

  2. 'DatumServiceImpl' und 'DatumServiceKonsument' melden ihre Aktivierungen und der 'DatumServiceKonsument' liest den Datum-Service.
    Lassen Sie sich die IDs, SymbolicNames und Stati der Bundles anzeigen:

    ss

  3. Setzen Sie eine Spezial-Property und Stoppen und Starten Sie 'Datum-Component-Impl':

    setprop MeinPropSpezialKey=MeinPropSpezialValue

    stop Datum-Component-Impl

    start Datum-Component-Impl

    Sie erhalten:

    DatumServiceImpl aktiviert mit folgenden Spezial-Properties:
       MeinCompDescrSpezialKey = MeinCompDescrSpezialValue
       MeinPropSpezialKey      = MeinPropSpezialValue
       MeinBundleMfSpezialKey  = MeinBundleMfSpezialValue
    DatumServiceKonsument: DatumService wurde uebergeben.
    DatumServiceKonsument aktiviert.
    DatumServiceKonsument liest Datum-Service: 2009-01-02 11:22

    Der Konsument hat also bemerkt, dass der Service neu gestartet wurde.

  4. Ähnliches können Sie auch mit "refresh Datum-Component-Impl", "update Datum-Component-Impl" und "update *" beobachten.

  5. Sehen Sie sich an, wie die speziellen Properties MeinBundleMfSpezialKey in der MANIFEST.MF, MeinPropSpezialKey über das setprop-Kommando und MeinCompDescrSpezialKey in ds-component.xml eingetragen und in DatumServiceImpl.java ausgelesen werden können (der einzige Sinn dieser Properties ist diese Demonstration, ansonsten können sie entfallen).

    Falls Sie weitere ComponentContext-Properties sehen wollen, rufen Sie folgende Methode auf:

       private static void printComponentContextProperties( ComponentContext componentContext )
       {
          final String[] BCP = { Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_VENDOR, "MeinPropSpezialKey" };
          System.out.println( "ComponentContext-Properties: " );
          Dictionary<?,?> props = componentContext.getProperties();
          for( Enumeration<?> keys = props.keys(); keys.hasMoreElements(); ) {
             String key = "" + keys.nextElement();
             System.out.println( "   " + key + " = " + props.get( key ) );
          }
          System.out.println( "ComponentContext-BundleContext-Properties: " );
          BundleContext bundleContext = componentContext.getBundleContext();
          for( String key : BCP )
             System.out.println( "   " + key + " = " + bundleContext.getProperty( key ) );
          System.out.println( "ComponentContext-BundleContext-Bundle-Properties: " );
          props = bundleContext.getBundle().getHeaders();
          for( Enumeration<?> keys = props.keys(); keys.hasMoreElements(); ) {
             String key = "" + keys.nextElement();
             System.out.println( "   " + key + " = " + props.get( key ) );
          }
       }
    

    Falls Sie noch mehr Properties sehen wollen, rufen Sie folgende Kommandos auf:

    getprop MeinPropSpezialKey

    getprop org.osgi

    getprop osgi

    getprop

  6. Lassen Sie sich mit packages anzeigen, welche anderen Bundles vom angegebenen Bundle abhängen:

    packages Datum-Component-Interface

    Sie erfahren, dass OsgiComponentImpl und OsgiComponentKonsument von OsgiComponentInterface abhängen.

  7. Suchen Sie mit services nach Services. Verwenden Sie dabei die oben erläuterten filter-Ausdrücke, bei denen auch *-Wildcards erlaubt sind. Beispiele (unter der Annahme, dass für den DatumService service.id=26 gilt):

    services (objectClass=*Datum*)

    services (objectClass=datumservice.DatumService)

    services (service.id=26)

    services (& (objectClass=datumservice.*) (service.id>=22) )

    services (& (service.id>=20) (service.id<=40) )

  8. Falls Sie die Bundles exportieren wollen: Vergessen Sie nicht, vorher die jeweiligen build.properties-Dateien (z.B. über den Eclipse-build.properties-Editor) so zu erweitern, dass die OSGI-INF-Verzeichnisse mit eingebunden werden.



Programmierbeispiel für den Event Admin Service

Mit dem "Event Admin Service" können Events (= Ereignisse) Bundle-übergreifend versendet werden, und zwar wahlweise synchron oder asynchron. Genaueres zum "Event Admin Service" finden Sie im OSGi Service Platform Service Compendium.

Die Beschreibungen erfolgen wieder etwas kürzer, um Wiederholungen zu vermeiden.

Event-Sender

  1. Erstellen Sie das neue OSGi-Bundle-Projekt "OsgiEventSender":

    Project name: OsgiEventSender
    Plug-in ID: Datum-Event-Sender
    Plug-in Options
    Generate an activator:
    nein
  2. Ersetzen Sie im OsgiEventSender-Projekt die META-INF/MANIFEST.MF-Datei durch:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Datum-Event-Sender-Bundle
    Bundle-SymbolicName: Datum-Event-Sender
    Bundle-Version: 1.0.0
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: org.osgi.framework;version="1.4.0",
     org.osgi.service.component;version="1.0.0",
     org.osgi.service.event;version="1.1.0"
    Service-Component: OSGI-INF/ds-component.xml
    
  3. Legen Sie im OsgiEventSender-Projekt das Verzeichnis OSGI-INF und darin die Datei ds-component.xml an:

    <?xml version="1.0"?>
    <component name="Datum-Event-Sender">
       <implementation class="datumservice.eventsender.DatumServiceSender" />
       <reference name="eventAdmin"
                  interface="org.osgi.service.event.EventAdmin"
                  bind="bindEventAdmin"
                  unbind="unbindEventAdmin" />
    </component>
    
  4. Legen Sie im OsgiEventSender-Projekt das Package datumservice.eventsender und darin die Datei DatumServiceSender.java an:

    package datumservice.eventsender;
    
    import java.text.SimpleDateFormat;
    import java.util.*;
    import org.osgi.service.component.ComponentContext;
    import org.osgi.service.event.*;
    
    public class DatumServiceSender extends TimerTask
    {
       private EventAdmin eventAdmin;
       private Timer timer;
    
       public void bindEventAdmin( EventAdmin eventAdmin )
       {
          this.eventAdmin = eventAdmin;
       }
    
       public void unbindEventAdmin( EventAdmin eventAdmin )
       {
          this.eventAdmin = null;
       }
    
       protected void activate( ComponentContext componentContext )
       {
          timer = new Timer();
          timer.scheduleAtFixedRate( this, 0, 5000 );
          System.out.println( "\nDatumServiceSender aktiviert." );
       }
    
       protected void deactivate( ComponentContext componentContext )
       {
          timer.cancel();
          System.out.println( "DatumServiceSender deaktiviert." );
       }
    
       @Override
       public void run()
       {
          String dateTime = (new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" )).format( new Date() );
          Dictionary<String,String> eventProps = new Hashtable<String,String>();
          eventProps.put( "Datum/Zeit", dateTime );
          Event event = new Event( "datumservice/eventsender/Timer", eventProps );
          // "sendEvent()" fuer synchron, "postEvent()" fuer asynchron:
          eventAdmin.sendEvent( event );
       }
    }
    

Event-Empfänger

  1. Erstellen Sie ein neues OSGi-Bundle-Projekt in Eclipse: 'File' | 'New' | 'Project...' | 'Plug-in Development' | 'Plug-in Project' | 'Next >':

    Project name: OsgiEventEmpfaenger
    Plug-in ID: Datum-Event-Empfaenger
    Plug-in Options
    Generate an activator:
    nein
  2. Ersetzen Sie im OsgiEventEmpfaenger-Projekt die META-INF/MANIFEST.MF-Datei durch:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Datum-Event-Empfaenger-Bundle
    Bundle-SymbolicName: Datum-Event-Empfaenger
    Bundle-Version: 1.0.0
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: org.osgi.framework;version="1.4.0",
     org.osgi.service.event;version="1.1.0"
    Service-Component: OSGI-INF/ds-component.xml
    
  3. Legen Sie im OsgiEventEmpfaenger-Projekt das Verzeichnis OSGI-INF und darin die Datei ds-component.xml an:

    <?xml version="1.0"?>
    <component name="Datum-Event-Empfaenger">
       <implementation class="datumservice.eventempfaenger.DatumServiceEmpfaenger" />
       <property name="event.topics" value="datumservice/eventsender/Timer" />
       <service>
          <provide interface="org.osgi.service.event.EventHandler" />
       </service>
    </component>
    
  4. Legen Sie im OsgiEventEmpfaenger-Projekt das Package datumservice.eventempfaenger und darin die Datei DatumServiceEmpfaenger.java an:

    package datumservice.eventempfaenger;
    
    import org.osgi.service.event.Event;
    import org.osgi.service.event.EventHandler;
    
    public class DatumServiceEmpfaenger implements EventHandler
    {
       @Override
       public void handleEvent( Event event )
       {
          System.out.println( event.getProperty( "Datum/Zeit" ) );
       }
    }
    

Verzeichnisstruktur

Test

  1. Wählen Sie in Eclipse: 'Run' | 'Run Configurations...'. Klicken Sie mit der rechten Maustaste auf 'OSGi Framework' und wählen Sie 'New'. Tragen Sie bei 'Name' 'OSGi-Event' ein. Klicken Sie auf den Button 'Deselect All' und aktivieren Sie anschließend folgende Bundles:

    (x) Datum-Event-Empfaenger
    (x) Datum-Event-Sender
    (x) org.eclipse.equinox.ds
    (x) org.eclipse.equinox.event
    (x) org.eclipse.equinox.util
    (x) org.eclipse.osgi
    (x) org.eclipse.osgi.services

    Klicken Sie auf 'Run'.

    Der 'DatumServiceSender' meldet seine Aktivierungen und der 'DatumServiceEmpfaenger' empfängt alle fünf Sekunden den "Datum/Zeit"-String.

  2. Führen Sie folgende Kommandos in der OSGi-Konsole aus:

    ss Anzeige der IDs, SymbolicNames und Stati der Bundles: → sieben "ACTIVE" Bundles
    stop Datum-Event-Sender Die Ausgabe der "Datum/Zeit"-Strings stoppt
    start Datum-Event-Sender Die Ausgabe der "Datum/Zeit"-Strings beginnt wieder
    bundle Datum-Event-Sender Services in use:
    {...EventAdmin}=...
    bundle Datum-Event-Empfaenger Registered Services:
    {...EventHandler}={event.topics=datumservice/eventsender/Timer ...}
    services (objectClass=*Event*) ...EventAdmin
    Bundles using service:
    initial@reference:file:.../OsgiEventSender/
    ...EventHandler
    {event.topics=datumservice/eventsender/Timer, component.name=Datum-Event-Empfaenger, ...}
    Registered by bundle: initial@reference:file:.../OsgiEventEmpfaenger/
  3. Falls Sie die Bundles exportieren wollen: Vergessen Sie nicht, vorher die jeweiligen build.properties-Dateien (z.B. über den Eclipse-build.properties-Editor) so zu erweitern, dass die OSGI-INF-Verzeichnisse mit eingebunden werden.



Http-Servlet-Programmierbeispiel

Mit dem "Http Service" können einfache Servlets als OSGi-Bundle realisiert werden. Der Http Service basiert bislang allerdings lediglich auf der Version 2.1 des Servlet API. Falls Sie weitergehende Optionen (z.B. ServletFilter) oder JSPs (JavaServer Pages) benötigen, sollten Sie sich Alternativen ansehen, zum Beispiel: Embedding an HTTP server in Equinox (z.B. "equinox.http.jetty"), Equinox in a Servlet Container ("servletbridge") oder Pax Web Extender.

Genaueres zum "Http Service" finden Sie im OSGi Service Platform Service Compendium.

Die Beschreibungen erfolgen wieder etwas kürzer, um Wiederholungen zu vermeiden.

Servlet-Bundle

  1. Erstellen Sie das neue OSGi-Bundle-Projekt "OsgiServlet":

    Project name: OsgiServlet
    Plug-in ID: OsgiServlet
    Plug-in Options
    Generate an activator:
    nein
  2. Ersetzen Sie im OsgiServlet-Projekt die META-INF/MANIFEST.MF-Datei durch:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: OsgiServlet-Bundle
    Bundle-SymbolicName: OsgiServlet
    Bundle-Version: 1.0.0
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: org.osgi.framework;version="1.4.0",
     javax.servlet;version="2.4.0",
     javax.servlet.http;version="2.4.0",
     org.osgi.service.http;version="1.2.0"
    Service-Component: OSGI-INF/ds-component.xml
    
  3. Legen Sie im OsgiServlet-Projekt das Verzeichnis OSGI-INF und darin die Datei ds-component.xml an:

    <?xml version="1.0"?>
    <component name="servletComponent">
       <implementation class="servlet.ServletComponent" />
       <reference name="httpService"
                  interface="org.osgi.service.http.HttpService"
                  bind="bindHttpService"
                  unbind="unbindHttpService"
                  cardinality="0..n"
                  policy="dynamic" />
    </component>
    
  4. Legen Sie im OsgiServlet-Projekt das Package servlet und darin die Java-Klasse MeinServlet.java an:

    package servlet;
    
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.http.*;
    
    public class MeinServlet extends HttpServlet
    {
       private static final long serialVersionUID = 1L;
    
       @Override
       protected void doGet( HttpServletRequest requ, HttpServletResponse resp ) throws ServletException, IOException
       {
          resp.setContentType( "text/html" );
          resp.getOutputStream().print( "<html><h1>Hallo " + requ.getParameter( "name" ) + "!</h1></html>" );
          resp.getOutputStream().flush();
       }
    }
    
  5. Legen Sie im Package servlet die Java-Klasse ServletComponent.java an:

    package servlet;
    
    import org.osgi.service.http.HttpService;
    
    public class ServletComponent
    {
       protected void bindHttpService( HttpService httpService )
       {
          try {
             httpService.registerServlet(   "/meinservlet", new MeinServlet(), null, null );
             httpService.registerResources( "/", "/html", null );
             httpService.registerResources( "/img", "/images", null );
          } catch( Exception ex ) {
             ex.printStackTrace();
          }
       }
    
       protected void unbindHttpService( HttpService httpService )
       {
          httpService.unregister( "/meinservlet" );
          httpService.unregister( "/" );
          httpService.unregister( "/img" );
       }
    }
    

    (Sehen Sie sich die API-Doku zu registerServlet() und registerResources() an.)

  6. Legen Sie im OsgiServlet-Projekt das Verzeichnis html und darin die Datei MeinFormular.html an:

    <html>
    <h1>Hallo</h1>
    <form name="Formular" method="get" action="meinservlet">
       Name: <input name="name" type="text" />
       <input value="Servlet anfordern" type="submit" />
    </form>
    <img src="img/MeinBild.jpg" />
    </html>
    
  7. Legen Sie im OsgiServlet-Projekt das Verzeichnis images an und und kopieren Sie dorthinein eine beliebige JPEG-Bilddatei. Umbenennen Sie die Bilddatei zu MeinBild.jpg.

Verzeichnisstruktur

Test

  1. Wählen Sie in Eclipse: 'Run' | 'Run Configurations...'. Klicken Sie mit der rechten Maustaste auf 'OSGi Framework' und wählen Sie 'New'. Tragen Sie bei 'Name' 'OSGi-Servlet' ein. Klicken Sie auf den Button 'Deselect All' und aktivieren Sie anschließend folgende Bundles:

    (x) OsgiServlet
    (x) javax.servlet
    (x) org.eclipse.equinox.ds
    (x) org.eclipse.equinox.http
    (x) org.eclipse.equinox.util
    (x) org.eclipse.osgi
    (x) org.eclipse.osgi.services

    Klicken Sie auf 'Run'.

  2. Rufen Sie das Servlet direkt auf über: http://localhost/meinservlet.
    Es meldet sich mit: "Hallo null!".

    Rufen Sie die vorgeschaltete HTML-Seite auf: http://localhost/MeinFormular.html.
    Geben Sie einen Namen ein und betätigen Sie den Button.
    Das Servlet antwortet zum Beispiel mit: "Hallo MeinName!".

  3. Falls Sie nicht den Port 80 verwenden wollen, stellen Sie einen anderen ein (z.B. 8080):
    Wählen Sie in Eclipse 'Run' | 'Run Configurations...' | 'OSGi Framework' | 'OSGi-Servlet'. Unter dem Tabulatorreiter 'Arguments' fügen Sie im Feld 'VM arguments' -Dorg.osgi.service.http.port=8080 hinzu (alternativ könnten Sie auch in der OSGi-Konsole eingeben: setprop org.osgi.service.http.port=8080).
    Anschließend rufen Sie die OSGi-Servlet-Anwendung auf über: http://localhost:8080/MeinFormular.html.

  4. Falls Sie die eingestellte Portnummer bei der Aktivierung ausgeben wollen:
    Erweitern Sie die Import-Liste in META-INF/MANIFEST.MF um:

    org.osgi.service.component;version="1.0.0"

    Und fügen Sie folgende Methode zu ServletComponent.java hinzu:

       protected void activate( ComponentContext componentContext )
       {
          String port = componentContext.getBundleContext().getProperty( "org.osgi.service.http.port" );
          if( port == null || port.trim().length() == 0 ) port = "80";
          System.out.println( "ServletComponent aktiviert: http://localhost:" + port + "/MeinFormular.html" );
       }
    
  5. Führen Sie folgende Kommandos in der OSGi-Konsole aus:

    ss Anzeige der IDs, SymbolicNames und Stati der Bundles: → sieben "ACTIVE" Bundles
    bundle OsgiServlet Services in use:
    {...HttpService}={http.port=8080, ...
    services (objectClass=*Http*) {...HttpService}={http.port=8080, ...
    Bundles using service:
    initial@reference:file:.../OsgiServlet/
  6. Falls Sie die Bundles exportieren wollen: Vergessen Sie nicht, vorher die build.properties-Datei (z.B. über den Eclipse-build.properties-Editor) so zu erweitern, dass die html-, images- und OSGI-INF-Verzeichnisse mit eingebunden werden.



Links auf weiterführende Informationen




Weitere Themen: andere TechDocs | SOA
© 2009 Torsten Horn, Aachen