Suche

Von alten Konsolen, Affen und seltsamen Schleifen

Monkey see .

Um unser Kommando in der OSGi-Konsole verfügbar zu machen, ist es notwendig, ein bestimmtes Interface zu implementieren und die implementierende Klasse als entsprechenden OSGi Service zu registrieren. Aber dazu gibt es bereits genügend Literatur [6], [7], [8], deren Inhalt wir hier nicht wiederholen. Stattdessen werden wir selbst herausfinden, was wir im Detail zu tun haben. Dabei werden wir eine Menge Code zu Gesicht bekommen und verstehen müssen. Die gewählte Herangehensweise ist wiederverwendbar und nicht nur für den beschriebenen Fall einsetzbar.

Als Erstes suchen wir einen Ansatzpunkt, von dem aus wir uns vortasten können. Wir wissen, dass die View CONSOLE, die die OSGi-Konsole anzeigt, irgendwie an die verfügbaren Kommandos kommen muss. Also suchen wir als Erstes nach der Implementierung dieser View. Um den Plattformcode durchsuchen zu können, müssen wir ihn zunächst in die durchsuchten Quellen aufnehmen. Wir öffnen die Perspektive PLUG-IN DEVELOPMENT und bringen die Sicht PLUG-INS in den Vordergrund. In dieser Sicht gibt es einen Button, um alle Plug-ins aus der Target Platform in die Suche aufzunehmen. Vor dem Helios Release war das über das Kontextmenü möglich. Jetzt können wir mit Ctrl-H den Suchdialog und den Reiter PLUG-IN SEARCH öffnen. Wir wissen, dass alle Views an den Extension Point org.eclipse.ui.views kontributiert werden, also suchen wir nach Stellen im Code, an denen dieser Extension Point referenziert wird: SEARCH FOR stellen wir dem nach auf EXTENSION POINT, LIMIT TO auf REFERENCES und EXTERNAL SCOPE auf ALL. Als Ergebnis erhalten wir alle Bundles, die Kontributionen zu diesem Extension Point enthalten. Ein Ergebnis ist org.eclipse.ui.console. Das sieht vielversprechend aus. Wir öffnen also das entsprechende Plug-in Manifest und sehen uns die Kontribution an. Hier finden wir die Klasse, die die View implementiert, aber auch noch etwas viel interessanteres: eine Referenz zu dem Extension Point org.eclipse.ui.console.consoleFactories. Suchen wir also als Nächstes nach Referenzen zu diesem Extension Point. Anstelle des Suchdialogs selektieren wir einfach FIND REFERENCES im Kontextmenü der Extension.

Abb.2: Plug-in Manifest – Find Extension Point References

Dieser Extension Point wird nur viermal referenziert. Zwei Einträge könnten mit der gesuchten OSGi-Konsole zu tun haben: org.eclipse.ui.console oder org.eclipse.pde.ui. Da PDE als Unterstützung für die Entwicklung Plug-ins/OSGi Bundles dient, versuchen wir unser Glück als Erstes dort. Mit Erfolg: Im Plug-in Manifest von org.eclipse.pde.ui wird die Klasse org.eclipse.pde.internal.ui.util.OSGiConsoleFactory kontribuiert. Wir öffnen diese Klasse, indem wir ihren Namen aus der Kontribution kopieren und in den OPEN TYPE-Dialog einfügen, den wir mit Ctrl-Shift-T öffnen. Endlich können wir damit aufhören, uns von Deklaration zu Deklaration zu hangeln und uns die konkrete Implementierung ansehen.

Die Klasse OSGiConsoleFactory ist übersichtlich. Schnell sehen wir, dass sie eine Implementierung des Interface IOConsole als Member hält und diese über die Methode getConsole()lazy initalisiert. In dieser Methode wird eine Instanz von OSGiConsole erzeugt. Es wird wärmer. Der erste Blick auf OSGiConsole enthüllt nicht direkt, wie die Konsole an die unterstützten Befehle kommt. Wir müssen also genauer hinsehen. Und wenn uns das nicht mehr weiterbringt, fangen wir einfach an, wilde Vermutungen anzustellen. Denken wir noch einmal nach: Um einen Befehl ausführen zu können, muss die Klasse Benutzereingaben verarbeiten können. Wo könnten diese Daten herkommen? Wir sehen, dass im Konstruktor eine anonyme Instanz von ConsoleSession erzeugt wird, die die Methoden getInput()und getOutput()implementiert. Sieht so aus, als hätten wir wieder eine Spur gefunden. Jetzt sehen wir uns an, wer die Eingaben verarbeitet. Wir platzieren also den Cursor auf getInput()und drücken Ctrl-Alt-H, um die View CALL HIERARCHY zu öffnen.

Abb.3: Call Hierarchy

Der erste, direkte Aufrufer FrameworkConsole sieht hinsichtlich der gesuchten Konsolenkommandos nicht sonderlich interessant aus. Eine Ebene tiefer in der Hierarchie finden wir die Klasse ConsoleManager, die wir uns genauer ansehen. In Zeile 93 finden wir, etwas versteckt, ein Feld des Typs FrameworkCommandProvider. Wenn das mal kein Treffer ist: Schon beim Überfliegen der Kommentare stellen wir fest, dass wir fündig geworden sind [9]:

/**
 * This class provides methods to execute commands from the command line.  
 * It registers itself as a CommandProvider so it can be invoked by a 
 * CommandInterpreter.
... 
 *  There is a method for each command which is named '_'+method.  The methods are
 *  invoked by a CommandInterpreter's execute method.
 */

Um eigene Befehle für die OSGi-Konsole hinzuzufügen, müssen wir also das CommandProvider Interface implementieren und der Service Registry bekanntmachen. Jedes Kommando in einem CommandProvider wird als Methode mit dem Namen _<Kommando>() bereit gestellt. Zusätzlich sollte unser CommandProvider die Methode getHelp()implementieren, um dem Benutzer einen Hilfetext zu unserem Kommando anzeigen zu können.

Monkey do

Um mit der Implementierung starten zu können, müssen wir zunächst das Package org.eclipse.osgi.framework.console, das das Interface CommandProvider enthält, in unserem Bundle bekannt machen. Dazu öffnen wir die Datei MANIFEST.MF mit dem Plug-in Manifest Editor und fügen die Abhängigkeit im Reiter DEPENDECIES zu den IMPORTED PACKAGES hinzu. Dann verwenden wir den Wizard NEW JAVA CLASS (NEW | CLASS) unter Angabe des Interface, um eine konkrete Implementierung zu erzeugen. Wir implementieren die Methoden getHelp()und _check(CommandInterpreter intp) , da wir unsere neues Kommando mit dem Befehl check starten möchten. Als Referenz für die Implementierung der beiden Methoden verwenden wir die zuvor gefundene Klasse FrameworkCommandProvider. In der Methode getHelp()achten wir darauf, die gleiche Formatierung wie unsere Vorlage zu verwenden. In unserem Kommando _check rufen wir einfach den zuvor implementierten TestRunner auf. Das Ergebnis sehen Sie in Listing 3.

Listing 3
package net.wolfgangwerner.tutorials.osgiconsole;

import net.wolfgangwerner.tutorials.osgiconsole.test.TestRunner;

import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;

public class TestCommandProvider implements CommandProvider {

  @Override
  public String getHelp() {
    StringBuffer help = new StringBuffer();
    help.append("--- Integrity Checks ---rn");
    help.append("tcheck - Run integrity tests; display failures");
    help.append("rnrn");
    return help.toString();
  }

  public void _check(CommandInterpreter intp) throws Exception {
    intp.println(TestRunner.run());
  }
}

Im Activator des Bundles müssen wir nun unsere Klasse mit dem CommandProvider Service registrieren. Dazu legen wir den Service als Feld im Activator an. Das Feld wird in der start()-Methode initialisiert und registriert und in der stop()-Methode zurück auf null gesetzt. Damit reagiert unser Bundle dynamisch zur Laufzeit auf Lifecycle-Änderungen.

Listing 4
package net.wolfgangwerner.tutorials.osgiconsole;

import org.eclipse.osgi.framework.console.CommandProvider;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

  private static BundleContext context;
  private CommandProvider service;

  static BundleContext getContext() {
    return context;
  }

  public void start(BundleContext bundleContext) throws Exception {
    Activator.context = bundleContext;
    service = new TestCommandProvider();

    context.registerService(
      CommandProvider.class.getName(), 
      service, null);
  }

  public void stop(BundleContext bundleContext) throws Exception {
    Activator.context = null;
    service = null;
  }

}
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.