Wenn ich von einer OSGi-Anwendung spreche, dann meine ich in der Regel eine Gruppe von Bundles, die gemeinsam eine Anwendung ergeben. Eine solche Aufteilung bietet sich förmlich an, wenn man OSGi verwendet. Die Kommunikation zwischen den einzelnen Teilen der Anwendung findet dann über die OSGi-Registry statt. Einer der großen Vorteile, den man dadurch gewinnt, ist die Möglichkeit Teile der eigenen Anwendung zur Laufzeit austauschen zu können, ohne dass der Rest der Anwendung davon betroffen wird.
Leider bietet OSGi keine Möglichkeit Bundles, die zu zwei unterschiedlichen Anwendungen gehören voneinander zu unterscheiden. Das Fehlen einer solchen Funktion lässt sich vielleicht historisch erklären. OSGi war ursprünglich für den Einsatz auf Kleingeräten gedacht, wo die Anzahl der Bundles klein ausfällt. Heute wird OSGi in Projekten wie zum Beispiel der Eclipse IDE eingesetzt, wo die Anzahl an Bundles dreistellig oder mehr werden kann. Wenn ich meine aktuelle Eclipse-Installation starte (./eclipse -console) und mir mit dem Befehl „ss“ die Anzahl der installierten Bundles anzeigen lasse, komme ich auf über 600 Bundles. Und dabei habe ich kaum zusätzliche Plugins installiert. Die eine oder andere Installation der Eclipse IDE wird durchaus auf 1000+ kommen. Würde ich jetzt ein bestimmtes Plugin über die Konsole deinstallieren wollen, hätte ich alle Hände voll zu tun, die Bundles, die zu diesem Plugin gehören zu finden und zu deinstallieren. Bei Eclipse nimmt man eher den Update-Manager und spart sich die Mühe. In einem anderen Projekt, wo ich einen OSGi-Container (Equinox in diesem Fall) einsetze, habe ich leider keinen Update-Manager – nur die Konsole.
Zurzeit befinden sich neben meiner Anwendung (bestehend aus 6 Bundles) ein Servlet-Container, das Spring Framework und eine Reihe von Bibliotheken im OSGi-Container. Wenn ich an meiner Anwendung arbeite, interessieren mich die restlichen Bundles eigentlich nicht. Manche davon werden nach der Installation gar nicht mehr verändert. Trotzdem kommt man nicht umhin, die Liste mit allen installierten Bundles durchzugehen, wenn man Bundles aus der eigenen Anwendung sucht. Es wäre deutlich eleganter, wenn man mit einem Befehl alle Bundles anzeigen lassen könnte, die zu einer bestimmten Anwendung gehören und direkt deren Status ändern könnte. Wie die Blog-Einträge [1][2] zeigen, bin ich nicht der einzige, der eine solche Funktion vermisst. Also habe ich mich nach einer bestehenden Lösung umgeschaut.
Dabei bin ich auf das Platform Archive von SpringSource gestoßen. Mit dem SpringSource dm Server (früher SpringSource Application Platform) hat SpringSource das Platform Archive (PAR) eingeführt. Das PAR ist eine JAR-Datei, die mehrere Bundles beinhalten kann. Auf diese Weise kann man alle Bundles, die zu einer Anwendung gehören gleichzeitig installieren, deinstallieren, etc. Das PAR bietet somit genau die gewünschten Funktionen mit einer Funktionsweise für OSGI, die man sonst von WAR und EAR Archiven gewohnt ist. Leider lassen sich PAR Dateien zurzeit nur in einem SpringSource dm Server installieren. Als Nachteil kann man zudem anführen, dass man das PAR Archiv neu bauen muss, wenn ein Bundle ausgetauscht werden soll, was das Austauschen einzelner OSGI Bundle komplexer macht.
Um mehr Flexibilität zu haben, wollte ich das Equinox Framework um die gewünschte Funktion selbst erweitern. Was ich erreichen wollte, ist die Möglichkeit Gruppen von Bundles zu definieren, ohne dass ich dabei die OSGi Spezifikation verletze. Schließlich sollen die Bundles auch in jedem anderen OSGi-Container installiert werden können. Außerdem sollen die Bundles weiterhin getrennt voneinander vorliegen.
Nach einem Blick in den Sourcecode von Equinox habe ich festgestellt, dass ich an der OSGi Implementierung nicht einmal etwas ändern musste. Es reicht aus das CommandProvider Interface aus dem org.eclipse.osgi.framework.console Package zu implementieren und ein Bundle zu bauen, was eine Instanz meiner Implementierung in die OSGi-Registry einträgt. Auf diese Weise kann man die Equinox Konsole um eigene Befehle erweitern.
Die entstandene Klasse sieht wie folg aus:
1public class GroupCommandProvider implements CommandProvider { 2 private BundleContext bctx; 3 4 public GroupCommandProvider(BundleContext bctx) { 5 this.bctx = bctx; 6 } 7 8 public String getHelp() { 9 return "\tstopgroup <group> - stops all bundles in the specified group."; 10 } 11 12 public void _stopgroup(CommandInterpreter ci) throws BundleException { 13 String groupToStop = ci.nextArgument(); 14 if (groupToStop == null) 15 return; 16 17 Bundle[] bundles = bctx.getBundles(); 18 for (Bundle bundle : bundles) { 19 String currentGroup = (String) bundle.getHeaders().get("Bundle-Group"); 20 if (currentGroup == null || !currentGroup.equals(groupToStop)) 21 continue; 22 23 if (bundle.getState() == Bundle.ACTIVE) { 24 bundle.stop(); 25 } 26 } 27 } 28}
Die interessante Methode ist _stopgroup(). Wenn sie aufgerufen wird, werden alle Bundles gestoppt, die einen bestimmten Wert für „Bundle-Group“ in der Manifest-Datei stehen haben.
Eine Manifest-Datei könnte also wie folgt aussehen:
1Manifest-Version: 1.0 2Bundle-ManifestVersion: 2 3Bundle-SymbolicName: de.codecentric.mybundle 4Bundle-Group: myGroup 5Bundle-Version: 0.1.0 6Import-Package: org.osgi.framework
Alle Bundles aus der gleichen Gruppe können dann über die Equinox Konsole mit “stopgroup myGroup” auf einen Schlag gestoppt werden.
Damit der GroupCommandProvider aktiv wird, braucht man nur noch einen Activator. Dieser fällt sehr einfach aus:
1public class Activator implements BundleActivator {
2 private CommandProvider commandProvider;
3
4 public void start(BundleContext bctx) throws Exception {
5 commandProvider = new GroupCommandProvider(bctx);
6 bctx.registerService(CommandProvider.class.getName(), commandProvider, null);
7 }
8
9 public void stop(BundleContext arg0) throws Exception {
10 commandProvider = null;
11 }
12}
Damit der GroupCommandProvider sinnvoll eingesetzt werden kann, braucht er ein paar weitere Methoden, um zum Beispiel Gruppen von Bundles starten, deinstallieren oder einfach anzeigen zu können. Bei der Methode für das Starten einer ganzen Gruppen müsste man sich noch überlegen, wie man Bundles kennzeichnet, welche gestartet werden sollen und welche nicht. Was mir da vorschwebt ist eine Erweiterung des „Bundle-Group“ Eintrags in der Manifest-Datei, zum Beispiel „Bundle-Group: myGroup;startable=true“. Aber ich denke, dass die _stopgroup() Methode die Idee deutlich macht.
Es bleibt abzuwarten, ob sich bei der OSGi Spezifikation in der nächsten Version etwas in diese Richtung tut. Für mich persönlich reicht die beschriebene Lösung aus.
Weitere Beiträge
von Yevgeniy Melnichuk
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Yevgeniy Melnichuk
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.