18.2. Einfaches Beispiel

18.2.1. Überblick

Nachdem das Projekt im Eclipse angelegt wurde, kann die Entwicklung des Plugins beginnen. Die Implementierung eines Plugins gliedert sich prinzipiell immer in die folgenden Phasen:

  1. Bestimmung der Aufgabe des Plugins.

  2. Implementieren des com.semture.ecoube.plugin.Action-Interfaces.

  3. Implementieren des com.semture.ecoube.plugin.ToolbarButton und/oder com.semture.ecoube.plugin.MenuItem-Interfaces.

  4. Implementieren des com.semture.ecube.plugin.Plugin-Interfaces.

  5. Deployment des Codes.

  6. Funktionstest.

Abbildung 2 zeigt ein Klassenstrukturdiagramm des zu entwickelnden Plugins.

Klassenstrukturdiagramm des zu entwickelnden Plugins

Abbildung 18.2. Klassenstrukturdiagramm des zu entwickelnden Plugins

Wie aus diesen Vorüberlegungen ersichtlich wird, greift das Plugin auf Elemente der Cubetto-API zurück. Für die Implementierung eines Plugins ist zunächst das Paket com.semture.ecube.plugin interessant.

Der komplette Quellcode für das zu entwickelnde Plugin kann hier heruntergeladen werden. Gehen Sie wie folgt vor, um den Code in Ihre Entwicklungsumgebung einzubinden:

  1. Entpacken Sie das Archiv in einem Ordner in Ihrem Eclipse-workspace.

  2. Legen Sie ein Neues Projekt mit dem Namen „Plugin“ an.

  3. Das entpackte Plugin-Projekt wird von Eclipse automatisch erkannt, wenn Sie den Dialog beenden.

  4. Da der Verweis auf die ecube_classes.jar noch fehlerhaft ist, wird sich Ihr Projekt noch nicht compilieren lassen. Editieren Sie dazu den Pfad dieser .jar Datei unter Project->Properties->Java Build Path -> Libraries (vgl. Abbildung 1). Danach wird das Projekt fehlerfrei compiliert.

18.2.2. Aufgabe des Plugins

Für dieses einführende Beispiel sollen die Namen aller selektierten Modellelemente in einem Fenster ausgegeben werden. Die Aktivierung des Plugins soll durch einen Button auf der Werkzeugleiste und einen Eintrag im Menü des Modelleditors erfolgen. Beim Klicken auf den Button bzw. das Menü soll ein Dialog geöffnet werden, welcher die Namen aller im Modelleditor selektierten Modellelemente anzeigt.

18.2.3. Implementierung des Action-Interfaces

Eine Pluginaktion wird durch die Implementierung des Action-Interfaces realisiert. Dieses Interface bietet den Einstiegspunkt für die Implementierung des Plugininhalts. Das Action-Interface fordert wiederum die Implementierung einer Reihe von Methoden. Zunächst ist die Aktion selbst zu beschreiben. Dazu dienen die Methoden getName(), getToolTip() und getHelp(). Die Funktionen liefern jeweils Strings zurück, welche den Namen, das Tooltip sowie die Kurzhilfe zu dieser Aktion liefern. Der Name der Aktion wird innerhalb des Werkzeugs zur Verwaltung der Plugins verwendet. Das Tooltip wird eingeblendet, wenn der Mauscursor über dem Menüpunkt oder über dem Button auf der Werkzeugleiste steht. Die Kurzhilfe wird zukünftig in einem entsprechenden Hilfefenster angezeigt. Sie sollte daher in Form eines HTML-formatierten Textes zurückgeliefert werden.

Mit getKeyStroke() wird das Tastenkürzel definiert, welches mit dem der Aktion verknüpft werden soll. Dieses Kürzel wird nur dann aktiv, wenn zu der Aktion auch ein entsprechender Menüpunkt erzeugt wurde. In diesem Fall wird das Tastenkürzel auch in die Beschreibung des Menüpunkts aufgenommen.

VORSICHT: Wird ein Tastenkürzel doppelt belegt, so wird immer nur das zuerst definierte Kürzel weiterverwendet. Damit kann eine erste Implementierung des Action-Interfaces erfolgen:

/**
 * @return
 *   Liefert den Namen der Aktion.
 */
public String getName() {
    return "Namen ausgeben";
}

/**
 * @return
 *   Liefert eine Kurzbeschreibung der Aktion.
 */
public String getTooltip() {
    return "Gibt die Namen aller markierten Modellelemente aus.";
}

/**
 * @return
 *   Hilefetext der Aktion.
 */
public String getHelp() {
    return "<html>Gibt die Namen aller markierten Modellelemente aus. " + 
        "Die Aktion ist nur dann verfügbar, wenn mindestens ein Modellelement markiert ist.</html>";
}

/**
 * @return
 *   Liefert das Tastenkürzel der Aktion.
 */
public KeyStroke getKeyStroke() {
    return KeyStroke.getKeyStroke('N', InputEvent.ALT_MASK + InputEvent.CTRL_MASK);
}

Der Konstruktor der Aktion sollte eine Referenz auf dasjenige Objekt erhalten und speichern, dessen Klasse das Plugin-Interface implementiert (s.u.). Diese Referenz kann später zum Datenaustausch zwischen beiden Objekten verwendet werden:

/**
 * Referenz auf die Pluginimlementierung.
 */
private MyPlugin plugin;

/**
 * Defaultkonstruktor.
 * 
 * @param plugin
 *   Referenz auf das Plugin.
 */
public MyPluginAction(MyPlugin plugin) {
    this.plugin = plugin;
}

Eine weitere Beschreibung der Aktion bezieht sich auf das Transaktionskonzept des Cubetto Toolsets. Soll das Plugin innerhalb einer Transaktion ausgeführt werden, muss die Methode isTransactional() den Wert true liefern. In diesem Fall läuft das Plugin in seiner eigenen Transaktion ab. Wird die run()-Methode (siehe unten) fehlerfrei durchlaufen, wird die Transaktion mit einem commit() korrekt abgeschlossen. Wird die run()-Methode durch eine Exception unterbrochen, so wird die aktuelle Transaktion zurückgerollt und damit alle Aktionen, die durch das Plugin vorgenommen wurden, verworfen.

WICHTIG: Alle Plugins, welche die bestehenden Modelldaten verändern, sollten immer in einer eigenen Transaktion ausgeführt werden und damit in isTransactional() true liefern. Die Methode getTransactionDescription() gibt in diesem Fall denjenigen String an, der nach dem Abschluss der Transaktion diese Transaktion (genauer den Savepoint) beschreibt. Nur wenn das Plugin nicht in einer Transaktion ausgeführt werden soll, darf die Methode getTransactionDescription() null liefern.

Da das zu implementierende Plugin nur lesend auf die Daten des Cubetto Toolsets zugreift, kann auf die Verwendung des Transaktionskonzepts verzichtet werden. Mit diesem Wissen werden die Methoden isTransactional() und getTransactionDescription() wie folgt implementiert:

/**
 * @return
 *   Liefert false.
 */
public boolean isTransactional() {
    return false;
}

/**
 * @return
 *   Liefert einen Leerstring, da die Aktion nicht in einer Transaktion läuft.
 */
public String getTransactionDescription() {
    return "";
}

Die eigentliche Arbeit des Plugins wird in den Methoden canRun() und run() implementiert. Die Methode canRun() liefert true, wenn das Plugin ausgeführt werden darf. In diesem Fall werden die verknüpften Toolbar- und Menüeinträge aktiviert. Damit lässt sich die Ausführbarkeit des Plugincodes von bestimmten Bedingungen wie beispielsweise an eine bestimmte Markierung von Modellelementen im Modelleditor abhängig machen. Die Methode run() wird dann ausgeführt, wenn der Benutzer auf den Toolbar- bzw. Menüeintrag klickt.

Die Ausführbarkeit soll immer dann gewährleistet werden, wenn mindestens ein Modellelement markiert ist. Die aktuelle Markierung von Modellelementen wird im SelectionManager hinterlegt. Dieser wird bei der Initialisierung direkt an das Plugin übergeben (s.u.). Der Zugriff auf den SelectionManager wird, durch die Referenz auf das Plugin-Interface wie folgt hergestellt:

/**
 * @return
 *   Liefert true, wenn die Selektion von E³-Elementen nicht leer ist.
 */
public boolean canRun() {
    return !plugin.getSelectionManager().deriveE3Objects().isEmpty();
}

Im letzten Schritt muss noch die run()-Methode implementiert werden, welche die eigentliche Aktion des Plugins beschreibt. Gemäß der Aufgabenbeschreibung werden die selektierten Objekte als Strings in einem Dialogfeld ausgegeben. Zur Ausgabe wird der ScriptHelper verwendet, welcher Fenster für einfache Ein- und Ausgaben für diesen Zweck zur Verfügung stellt. Die Implementierung sieht wie folgt aus:

/**
 * Gibt die Daten der selektierten E³-Elemente in einem Dialogfeld aus.
 */
public void run() {
    //  Ausgabe der Daten über den ScriptHelper unter Zuhilfenahme des NameResolvers
    ScriptHelper.showInformation(
        "Markierte Objekte", 
        NameResolver.getNames(plugin.getSelectionManager().deriveE3Objects()));
}

18.2.4. Implementierung des ToolbarButton und/oder MenuItem Interfaces

Im nächsten Schritt stellt sich die Frage, wie das Plugin vom Modellierungswerkzeug aus angesprochen werden soll. Hier werden zwei Möglichkeiten bereitgestellt: Erstens, das Einblenden eines Buttons auf der Werkzeugleiste des Modelleditors und zweitens, das Einblenden eines Menüpunkts im Plugin-Menü des Modelleditors. Es können selbstverständlich auch beide Lösungen parallel implementiert werden. Beginnen wir mit dem MenuItem-Interface. Ein Menüpunkt wird in Java allgemein durch seinen Namen, das Tastenkürzel innerhalb des Menüs (Mnemonic; unterstrichener Buchstabe) sowie das Bild des Menüpunkts definiert. Analog sind die Methoden getText(), getMnemonic() und getIcon() zu implementieren:

/**
 * @return
 *   Liefert den Text des Menüpunkts.
 */
public String getText() {
    return "Namen anzeigen";
}

/**
 * @return
 *   Liefert das Tastenkürzel des Menüpunkts innerhalb des Menüs;
 */
public char getMnemonic() {
    return 'P';
}

/**
 * @return
 *   Liefert das Bild des Menüpunkts.
 */
public ImageIcon getIcon() {
    return new ImageIcon("Dateiname der Biddatei");
}

Zusätzlich gibt es im Cubetto Toolset das Konzept der Gruppe. Eine Gruppe ist im Menü des Modelleditors eine Menge zusammengehörige Menüpunkte, welche jeweils durch einen Separator (horizontale Linie) abgetrennt werden. Diese Gruppe wird für den Menüpunkt mit Hilfe der Funktion getGroup definiert. Zu beachten ist dabei, dass diese Gruppennummern möglicherweise bereits von anderen Plugins verwendet werden und folglich das Menüelement des eigenen Plugins in die gleiche Gruppe einsortiert wird, wie die Menüpunkte eines anderen Plugins. Um die Position des Menüpunkts innerhalb einer Gruppe festzulegen, wird die Funktion getIndex() verwendet:

/**
 * @return
 *   Liefert die Gruppe des Menüpunkts.
 */
public int getGroup() {
    return 1;
}

/**
 * @return
 *   Liefert den Index des Menüpunkts innerhalb dieser Gruppe.
 */
public int getIndex() {
    return 1;
}

Die Menüpunkte werden im Modelleditor dann aufsteigend anhand ihrer Gruppenposition und innerhalb der Gruppe nach ihrem Index sortiert. VORSICHT: Werden für zwei Menüeinträge die Gruppennummer und der Index jeweils doppelt vergeben, so erscheint nur ein Menüpunkt im Plugin-Menü. WICHTIG: Es ist nicht notwendig, die Index- und Gruppennummern lückenlos zu vergeben. Verwenden Sie daher für Ihre eigene Nummerierung einen festgelegten Zahlenbereich (z. B. Gruppennummern >= 1000), der nur von Ihnen benutzt wird.

Zum Schluss wird noch die mit dem Menüpunkt verknüpfte Plugin-Aktion benötigt, welche mit der Methode getAction() zurückgeliefert wird (siehe oben). Es empfiehlt sich, eine Referenz auf diese Aktion während der Initialisierung des Menüpunkts im Constructor zu übernehmen und diese Referenz in dieser Methode zurückzuliefern:

/**
 * Referenz auf die verknüpfte Aktion.
 */
private Action action;

/**
 * Initialisiert ein Objekt dieser Klasse und übernimmt die mit
 * dem Menüpunkt verknüpfte Atkion.
 *
 * @param action
 *   Verknüpfte Aktion.
 */
public MyPluginMenuItem(Action action) {
    this.action = action;
}

/**
 * @return
 *   Liefert die Aktion des Menüpunkts.
 */
public Action getAction() {
    return action;
}

Die Implementierung eines Buttons auf der Werkzeugleiste (ToolbarButton-Interface) erfolgt analog. Ein ToolbarButton ist durch sein Bild spezifiziert, welches in der Methode getIcon() zurückgeliefert wird. Zusätzlich gibt es auch für die ToolbarButtons das gleiche Gruppenkonzept wie für Menüpunkte. Das dort Gesagte gilt entsprechend, sodass die Methoden getGroup() und getIndex() implementiert werden müssen. Wiederum liefert die getAction()-Methode die mit dem ToolbarButton verknüpfte Aktion zurück, welche oben bereits beschrieben wurde. Ein minimales Codebeispiel sieht dann wie folgt aus:

/**
 * Referenz auf die zugeordnete Aktion.
 */
private Action action;

/**
 * Initialisiert ein Objekt dieser Klasse und übernimmt die mit
 * dem Button verknüpfte Atkion.
 *
 * @param action
 *   Die zum Button gehörende Aktion.
 */
public MyPluginToolbarButton(Action action) {
    this.action = action;
}

/**
 * @return
 *   Liefert das Bild des Menüpunkts.
 */
public ImageIcon getIcon() {
    return new ImageIcon("Dateiname der Biddatei");
}

/**
 * @return
 *   Liefert die Aktion zurück.
 */
public Action getAction() {
    return this.action;
}

/**
 * @return
 *   Liefert die Gruppe in der Werkzeugleiste
 */
public int getGroup() {
    return 1;
}

/**
 * @return
 *   Liefert den Index in der Werkzeugleiste
 */
public int getIndex() {
    return 1;
}

18.2.5. Implementierung des Plugin-Interfaces

Für die Einbettung des Plugins in das Cubetto Toolset muss im letzten Schritt das Plugin-Interface implementiert werden. Die Methoden getAuthor(), getName(), getVersion() und getDescription() sind selbsterklärend. Die Implementierung liefert hier die jeweiligen Zeichenketten als Ergebnis zurück.

/**
 * @return
 *   Liefert die Versionsnummer des Plugins zurück.
 */
public String getVersion() {
    return "0.1";
}

/**
 * @return
 *   Liefert den Autor des Plugins zurück.
 */
public String getAuthor() {
    return "Max Mustermann";
}

/**
 * @return
 *   Liefert den Namen des Plugins zurück.
 */
public String getName() {
    return "Testplugin";
}

/**
 * @return
 *   Liefert die Beschreibung des Plugins zurück.
 */
public String getDescription() {
    return "Gibt die markierten E³-Objekte als Strings aus.";
}

Weiterhin darf die Plugin-Implementierung einen parameterlosen Konstruktor erhalten, welcher lokale Variablen initialisiert. Im Beispiel wird der Konstruktor dazu verwendet, die Aktion, welche vom Plugin ausgeführt werden soll, zu initialisierung. Alternativ kann diese Initialisierung auch erst in der init()-Methode erfolgen:

/**
 * Die dem Plugin zugeordnete Aktion.
 */
private Action action;

/**
 * Parameterlose Konstruktoren sind für die Initialisierung
 * des Plugins erlaubt.
 */
public MyPlugin() {
    action = new MyPluginAction(this);
}

Im nächsten Schritt müssen die Menüpunkte und Toolbareinträge mit dem Plugin verknüpft werden. Dazu dienen die Methoden getMenuItems() und getToolbarButtons(). Beide Methoden liefern jeweils eine Menge von Menü- und Toolbareinträgen zurück. Die Sortierung der Einträge innerhalb dieser Menge wird anschließend über die Methoden getGroup() und getIndex() durchgeführt, welche das MenuItem-Interface und das ToolbarButton-Interface bereitstellt (siehe oben):

/**
 * @return
 *   Liefert den verknüpften Menüeintrag zurück.
 */
public Collection<? extends MenuItem> getMenuItems() {
    Collection<MenuItem> result = new ArrayList<MenuItem>();
    result.add(new MyPluginMenuItem(action));
    return result;
}

/**
 * @return
 *   Liefert den verknüpften Toolbareintrag zurück.
 */
public Collection<? extends ToolbarButton> getToolbarButtons() {
    Collection<ToolbarButton> result = new ArrayList<ToolbarButton>();
    result.add(new MyPluginToolbarButton(action));
    return result;
}

Zum Schluss müssen noch die Methoden init() und selectionHasChanged() implementiert werden. Die init()-Methode wird bei der Initialisierung des Plugins aufgerufen und übergibt wichtige Parameter an das Plugin. Zu diesen Parametern gehört die aktuelle Session, in der das Plugin läuft sowie, eine Referenz auf das Hauptfenster sowie eine Referenz auf den SelectionManager, welcher die Selektionen von Elementen im Modelleditor verwaltet.

Die Referenz auf die Session ist für transaktionale Plugins wichtig, beispielsweise um eine bestimmte Aktion in einer untergeordneten Transaktion auszuführen oder um eine Transaktion zurückzurollen. Die Referenz auf den MainFrame ist für Plugins mit einer Fenstersteuerung wichtig. Die übergebene Referenz sollte bei diesen Fenstern als parent übergeben werden. Damit lassen sich insbesondere modale Dialoge programmieren.

Für das hier vorgestellte Beispiel ist nur die Referenz auf den SelectionManager interessant, welche im Objekt der Klasse MyPlugin gespeichert und bei Bedarf mit der Methode getSelectionManager() ausgeliefert wird:

/**
 * Definition einer lokalen Variable.
 */
private SelectionManager selectionManager;

/**
 * Initialisiert das Plugin.
 *
 * @param session
 *   Wird nicht ausgewertet.
 * @param mainFrame
 *   Wird nicht ausgewertet.
 * @param selectionManager
 *   Wird zwischengespeichert.
 */
public void init(Session session, JFrame mainFrame, SelectionManager selectionManager) {
    this.selectionManager = selectionManager;
}

/**
 * @return
 *   Liefert den SelectionManager zurück.
 */
public SelectionManager getSelectionManager() {
    return selectionManager;
}

Die letzte zu implementierende Methode ist selectionHasChanged(). Diese wird immer dann aufgerufen, wenn sich die Markierung im Modelleditor verändert. Damit ist es möglich, die Ausgaben eines Plugins direkt von einer Markierung im Modelleditor abhängig zu machen bzw. auf eine Markierungsveränderung gezielt zu reagieren. Im hier vorliegenden Anwendungsfall wird diese Methode nicht benötigt. Sie kann leer bleiben:

/**
 * Wird bei einer Veränderung der Selektion im Modelleditor gerufen.
 *
 * @param oldSelection
 *   Alte Selektion (wird nicht ausgewertet)
 * @param newSelection
 *   Neue Selektion (wird nicht ausgewertet)
 */
public void selectionHasChanged(Collection oldSelection, Collection newSelection) {
    // Bleibt leer
}

18.2.6. Deployment

Um das Plugin zu aktivieren, muss es im Plugin-Verzeichnis des Cubetto Toolset abgelegt werden (Installationsverzeichnis/plugins). Soll es für alle Benutzer eines Rechners verfügbar sein, wird es im Plugin-Ordner der Cubetto-Installation abgelegt. Soll es nur für einen Benutzer freigegeben werden, so wird es in seinem Homeverzeichnis abgelegt (Homeverzeichnis/cubetto.workspace/plugins).

Mit Eclipse ist dieses Deployment über einen Export der kompilierten Klassen als .jar-File einfach möglich:

Deployment des Plugins in Eclipse

Abbildung 18.3. Deployment des Plugins in Eclipse

18.2.7. Funktionstest

Das Testen des Plugins ist denkbar einfach. Das Cubetto-Toolset wird gestartet und die Menü- und Toolbarbutton-Einträge werden sichtbar. Beim Klicken auf einen dieser Einträge wird die Pluginaktion ausgeführt.

Durch das Plugin Erzeugter Menüpunkt

Abbildung 18.4. Durch das Plugin Erzeugter Menüpunkt

Ausgabe des Plugins

Abbildung 18.5. Ausgabe des Plugins