Inhaltsverzeichnis
FAQ für Plugin-Entwickler
Diese FAQ hat Reinhold (Autor von Jollina) aus einer Reihe von ausgetauschten Mails zusammengestellt.
CVS / Eclipse
Wie richte ich mir das Beispiel-Plugin in Eclipse ein?
- Lade dir den Source des Nightly-Build von Jameica sowie den Source Beispiel-Plugins von http://www.willuhn.de/projects/jameica/?page=download_ext
- Die ZIP-Dateien enthalten „betriebsbereite“ Eclipse-Projekte. Sie müssen also nur entpackt werden und können anschliessend über den „Navigator“ oder „Project Explorer“ in Eclipse mittels „import existing project into workspace“ importiert werden.
- Falls unter Windows entwickelt wird, muß die Windows-Version von swt.jar verwendet werden. Im Java Build Path des Projekts „jameica“ also „swt.jar“ editieren und auf die Version im „win32“-Verzeichnis ändern.
- Nun sollten sich in Eclipse beide Projekte befinden. Der Classpath sollte jeweils schon richtig konfiguriert sein. „jameica_exampleplugin“ referenziert hierbei den Classpath des Projekts „jameica“. Damit sollte sich jetzt alles fehlerfrei kompilieren lassen.
- Leg eine Launch-Konfiguration mit folgenden Eigenschaften an:
Name | Wert |
---|---|
Project | jameica |
Main-Class | de.willuhn.jameica.Main |
Program-Arguments | „-f /home/<username>/.jameica.test -p test“. Der Parameter „-f“ gibt ein vom Standard abweichendes Plugin-Verzeichnis vor, „-p“ übergibt das Master-Passwort direkt, sodass es beim Starten im Eclipse-Degbugger nicht immer manuell eingegeben werden muss |
VM-Arguments | „-Djava.library.path=lib/swt/linux“ bzw. „-Djava.library.path=lib/swt/win32“ (abhängig vom verwendeten Betriebssystem) |
Classpath | Default-Classpath des Projekts „jameica“ |
- Starte nun Jameica. Hierbei wird die Anwendung ohne Plugins geladen, das Benutzerverzeichnis wurde jedoch samt Config-Dateien angelegt.
- Beende Jameica wieder.
- Öffne die Datei .jameica.test/cfg/de.willuhn.jameica.system.Config.properties in einem Text-Editor und füge mit relativen Pfaden das Plugin-Verzeichnisse für das Beispiel-Plugin hinzu:
[...] jameica.plugin.dir.0=plugins jameica.plugin.dir.1=../jameica_exampleplugin [...]
- Starte Jameica in Eclipse erneut. Der PluginLoader sucht nun im Verzeichnis „jameica_exampleplugin“ ebenfalls nach Plugins und wird im „bin“-Verzeichnis fündig (dort hat Eclipse ja hinkompiliert).
- Die Auto-Install-Routine des Plugins sollte anspringen und die Datenbank in „.jameica.test/jameica_exampleplugin/db“ anlegen.
- Sollten Exceptions des Typs „StubNotFoundException“ auftreten, dann fehlen RMI-Stubs. Wähle in Eclipse „Project→Clean“, damit das Projekt neu kompiliert wird. Hierbei wird u.a. „build/rmic.xml“ aufgerufen, welches die RMI-Stubs erzeugt.
Eclipse-Fehlermeldung: A cycle was detected in the build path of project
Dieser Fehlercode tritt auf, wenn zwei Plugin's gegenseitig auf die Daten des jeweils anderen Plugin zugreifen. Folgende Einstellungen in den Project/Properties von eclipse sind zu treffen:
- In der Navigation java Compiler/Building auswaehlen
- Im Abschnitt „Build path problems“ den Auswahlpunkt Circular dependies auf Warning umstellen
Eclipse compiliert nun alle Projekte neu - fertig .
SWT
Wenn ich aus einem separaten Thread auf die GUI zugreifen will, erhalte ich eine SWTException
SWT ist single-threaded. Greift man nicht aus dem GUI-Thread heraus auf SWT-Komponenten zu, erscheint eine „SWTException: Invalid Thread Access“. Das kannst man umgehen, indem die Aktion über die SWT-Job-Queue ausgeführt wird.
public void run() { GUI.getDisplay().syncExec(new Runnable() { public void run() { // Hier Programm-Code ausführen } }); }
Kann ich ausser den GUI-Elementen von jameica auch normale SWT-Widgets verwenden?
Ja. In einer View hast du Zugriff auf das gegehörige SWT-Composite und kannst dieses beliebig erweitern:
public class Test extends AbstractView { public void bind() throws Exception { GUI.getView().setTitle("Blubb"); Composite comp = this.getParent(); // Das Composite kannst man nun beliebig weiterverwenden // und da drauf weitere Sachen malen. Falls du z.Bsp. unter // der Tabelle Buttons haben willst, koenntest du das so machen: TablePart table = ....; table.paint(comp); ButtonArea buttons = new ButtonArea(comp,1); buttons.addButton(i18n.tr("Zurück"),new Back()); // Man kann auch direkt SWT-Widgets verwenden. Z.Bsp.: Label l = new Label(comp,SWT.BORDER); l.setText("laber"); } }
Falls Elemente nicht angezeigt werden, liegt das meist daran, dass die Angabe des Layout oder LayoutData fehlt. Beispiel für GridLayout:
Composite comp = new Composite(getParent(),SWT.NONE); GridData gridData = new GridData(GridData.FILL_BOTH); comp.setLayoutData(gridData); // Zweispaltiges Layout, wobei die Spalten unterschiedlich breit sein koennen GridLayout layout = new GridLayout(2,false); comp.setLayout(layout);
Hinweis: Das Composite, welches man von AbstractView via getParent() erhaelt, besitzt bereits ein 1-spaltiges GridLayout.
Wann muss ich SWT-Komponenten disposen
Wenn sie als Bestandteil einer View angelegt wurden, müssen sie nicht manuell disposed werden. Das übernimmt Jameica automatisch rekursiv, sobald eine View verlassen wird.
Wie kann ich Tabellen und Bäume markierter (checkable) machen?
Mit „setCheckable()“ legt man fest, ob eine Tabelle ueberhaupt Checkboxen anzeigen soll. Da hier noch nicht klar ist, ob die Checkboxen aktiviert oder deaktiviert sein sollen, sind per Default erstmal alle aktiviert. Ob nun fuer ein konkretes Objekt die Checkbox aktiv sein soll oder nicht, kann man entweder in einem Formatter machen. Oder *nachdem* die Tabelle gezeichnet wurde (also irgendwo das paint() der Tabelle direkt oder indirekt aufgerufen wurde) auch ausserhalb eines Formatters.
Mit folgendem Code z.Bsp. wird in jeder dritten Zeile die Checkbox aktiviert:
[...] c.addPart(table); [...] List<Object> objects = table.getItems(); for (int i=0;i<objects.size();++i) { table.setChecked(objects.get(i),i % 3 == 0); }
Jameica-API
Kann ich einzelne Elemente der Navigation oder des Menues zur Laufzeit aktivieren/deaktivieren?
Ja, wie folgt:
Manifest mf = Application.getPluginLoader().getManifest(<Pluginklasse>.class); // Entweder: Ermitteln des Navigationsbaumes (links in Jameica) NavigationItem item = mf.getNavigation(); // Oder: Menu (oben in Jameica) MenuItem item = mf.getMenu(); // Der erste Parameter legt fest, ob das Element aktiv oder inaktiv sein soll. // Mit dem zweiten Parameter kann die Einstellung rekursiv für alle ggf. vorhandenen Kind-Elemente übernommen werden item.setEnabled(false,true);
Ich muss in meinem Programm XML-Dateien auswerten. Hilft mir da jameica?
Ja, du kannst entweder die DOM/SAX-API von Java verwenden oder den bei Jameica ohnehin mitgelieferten minimalistischen XML-Parser NanoXML verwenden. Jameica verwendet den intern, um z.Bsp. die plugin.xml zu lesen.
Beispiel:
<?xml version="1.0" ?> <elster> <steuer typ="Lohn"> <betrag>100.00</betrag> <waehrung>EUR</waehrung> </steuer> </elster>
// Parser erzeugen XMLParser parser = XMLParserFactory.createDefaultXMLParser(); parser.setReader(new StdXMLReader(new FileInputStream("test.xml"))); // Root-Element "elster" ermitteln IXMLElement root = (IXMLElement) parser.parse(); // Element "steuer" holen IXMLElement steuer = root.getFirstChildNamed("steuer"); // Testen, ob der Typ "Lohn" ist if (!steuer.getAttribute("typ","").equals("Lohn")) return; Enumeration e = steuer.enumerateChildren(); while (e.haseMoreElements()) { IXMLElement element = (IXMLElement) e.nextElement(); System.out.println(element.getName() + ": " + element.getContent()); }
Kann ich die relevanten Verzeichnisse (Programm-, Config-, Datenverzeichnis) irgendwie ermitteln?
// Liste der Plugin-Verzeichnisse File[] dirs = Application.getConfig().getPluginDirs(); // Benutzer-Verzeichnis // Default: // Linux: /home/<username>/.jameica // Windows: C:\Dokumente und Einstellungen\<username>\.jameica String dir = Application.getConfig().getWorkDir(); // Config-Verzeichnis des Benutzers // Default: // Linux: /home/<username>/.jameica/cfg // Windows: C:\Dokumente und Einstellungen\<username>\.jameica\cfg String dir = Application.getConfig().getConfigDir(); // Work-Verzeichnis eines konkreten Plugins String dir = Application.getPluginLoader().getPlugin(<PluginClass>.class).getResources().getWorkPath();
Ich möchte in meinem Plugin Einstellungen speichern. Welche Jameica-Bordmittel sind hierfür vorhanden?
Über die Plugin-Ressourcen erhälst du ein vorkonfiguriertes Settings-Objekt mit Gettern und Settern. Ein explizites Speichern ist nicht nötig.
Settings s = Application.getPluginLoader().getPlugin(<PluginClass>.class).getResources().getSettings(); boolean test = s.getBoolean("foo.blubb",false); s.setAttribut("foo.bar","test");
Wie können Plugins miteinander kommunizieren?
Möglichkeit 1: Direkt auf die Datenservices des anderen Plugins zugreifen
Verwende hierzu die Service-Factory und schau in der plugin.xml des anderen Plugins nach den benötigten Servive-Namen.
DBService db = Application.getServiceFactory().lookup(HBCI.class,"database"); SammelUeberweisung s = (SammelUeberweisung)db.createObject(SammelUeberweisung.class,null); s.setFoo(...); [...] s.store();
Möglichkeit 2: Zugriff auf GUI-Elemente des anderen Plugins mittels Extension-System
Siehe Javadoc
Möglichkeit 3: Senden und Empfangen von Messages
Siehe Javadoc
// Definieren eines Nachrichtentyps public class MyMessage implements Message { public String getText() { return "Hallo"; } } // Abonnieren dieses Nachrichten-Typs public class MyConsumer implements MessageConsumer { /** * @see de.willuhn.jameica.messaging.MessageConsumer#handleMessage(de.willuhn.jameica.messaging.Message) */ public void handleMessage(Message message) throws Exception { MyMessage m = (MyMessage) message; System.out.println(m.getText); } /** * @see de.willuhn.jameica.messaging.MessageConsumer#autoRegister() */ public boolean autoRegister() { // Ist der MessageConsumer NICHT in einer "Inner-Class" definiert, wird // er beim Start von Jameica automatisch erkannt und kann ggf. auch automatisch // registriert werden, wenn diese Methode hier true zurueckliefert. // Andernfalls kann der Consumer auch manuell mit folgendem Code // registriert werden: // Application.getMessagingFactory().registerMessageConsumer(new MyConsumer()); return true; } /** * @see de.willuhn.jameica.messaging.MessageConsumer#getExpectedMessageTypes() */ public Class[] getExpectedMessageTypes() { // Legt fest, welche Arten von Nachrichten der Consumer erhalten will. return new Class[]{MyMessage.class}; } } // Asynchrones Senden einer Nachricht: // Sie werden dann in einem separaten Worker-Thread an alle Abonnenten verteilt. Application.getMessagingFactory().sendMessage(new MyMessage()); // Synchrones Senden Application.getMessagingFactory().sendSyncMessage(new MyMessage()); // Das Senden und Zustellen der Nachrichten erfolgt plugin-übergreifend
Kann man an eine Tabelle einen Listener anhaengen, der aufgerufen wird, wenn eine Zeile der Tabelle mit einfachem Klick markiert wird?
TablePart t = new TablePart(...); t.addSelectionListener(new Listener() { public void handleEvent(Event event) { Object o = event.data; // In event.data befindet sich dann direkt das markierte Fachobjekt. Falls mehrere markiert sind, ist o ein Array. } });
Ich möchte einen Listener an einen TextInput anhaengen. Wie geht das?
final TextInput eingabeFeld1 = new TextInput("foobar"); eingabeFeld1.addListener(new org.eclipse.swt.widgets.Listener() { public void handleEvent(Event event) { // Das Event wird immer dann ausgeloest, wenn sich der Focus // des Eingabe-Felds aendert. Also sowohl beim Aktivieren // als auch beim Deaktivieren. String value = (String) eingabeFeld1.getValue(); } });
Kann ich Formulareingaben gliedern, zum Beispiel mit Tab-Reitern?
public class MyView extends AbstractView { public void bind() { GUI.getView().setTitle("Test"); TabFolder folder = new org.eclipse.swt.widgets.TabFolder(getParent(), SWT.NONE); folder.setLayoutData(new GridData(GridData.FILL_BOTH)); folder.setBackground(Color.BACKGROUND.getSWTColor()); TabGroup tab1 = new TabGroup(folder,"Tab1"); pers.addLabelPair("Vorname",new TextInput("Max")); pers.addLabelPair("Nachname",new TextInput("Mustermann")); TabGroup tab2 = new TabGroup(folder,"Tab2"); pers.addLabelPair("Strasse",new TextInput("Musterstrasse")); pers.addLabelPair("Ort",new TextInput("Musterhausen")); [...] ButtonArea colorButtons = new ButtonArea(getParent(),2); colorButtons.addButton(i18n.tr("Zurück"), new Back()); colorButtons.addButton(i18n.tr("Speichern"), new Action() { public void handleAction(Object context) throws ApplicationException { [...] } }); } }
Wie definiere ich einzelne Menü- oder Navigationselemente in der plugin.xml?
<navigation> [...] <item name="Adressen" icon-close="page.gif" icon-open="page.gif" action="dein packagename.gui.action.AddressList" /> [...] </navigation>
public class AddressList implements Action { public void handleAction(Object context) throws ApplicationException { GUI.startView(....gui.view.AddressList.class,null); } }
Wie definiere ich in dem SQL-Create-Script für die embedded McKoi-Datenbank einen Primär-Schlüssel mit Autoincrement-Funktion?
CREATE TABLE foo ( id NUMERIC DEFAULT UNIQUEKEY('foo'), name VARCHAR(20) NOT NULL, [...] PRIMARY KEY (id) );
Wie kann ich die SQL-Datenbank beim ersten Start des Plugins automatisch anlegen?
Die Tabelle und die Datenbank muss man manuell anlegen. Im Beispiel-Plugin stehen die Statements in create.sql. Desweiteren muss eine Basis-Klasse des Plugins existieren, welche von AbstractPlugin abgeleitet und in plugin.xml registriert ist. Im Beispiel-Plugin heisst die Klasse „ExamplePlugin“. Sie besitzt 4 relevante Funktionen, welche von Jameica beim Initialisieren automatisch aufgerufen werden:
- init(): Wird bei jedem Start von Jameica aufgerufen. Dort kannst man Sachen reinschreiben, die beim jedem Start initialisiert werden sollen.
- install(): Diese Funktion wird nur beim allerersten Start aufgerufen. Wenn sie fehlerfrei durchläuft (also keine ApplicationException wirft) merkt sich Jameica, dass das Plugin erfolgreich installiert wurde und ruft die Funktion beim nächsten Start nicht mehr auf. Konkret wird das im Workverzeichnis von Jameica in cfg/de.willuhn.jameica.plugin.PluginLoader.properties vermerkt. Falls man z.Bsp. will, dass die install()-Methode nochmal aufgerufen werden soll, obwohl die Installation erfolgreich verlief, dann kann man die entsprechende Zeile einfach wieder löschen. Die Zeilen haben das Format: <Klasse des Plugins>=<Versionsnummer>
- update(double oldVersion): Diese Funktion wird nur aufgerufen, wenn das Plugin bereits installiert ist, sich aber die Versionsnummer geändert hat. Die aktuelle Versionsnummer merkt sich Jameica in o.g. Properties-Datei. Die neue Versionsnummer des Plugins wird aus plugin.xml ausgelesen.
- shutDown(): Die Funktion wird immer aufgerufen, wenn Jameica beendet wird.
public void install() throws ApplicationException { // Wenn wir im Netzwerk-Betrieb arbeiten und Jameica nicht mit // lokalen Daten sondern mit einem Jameica-Server arbeiten soll, // dann erstellen wir keine Datenbank. if (Application.inClientMode()) return; PluginResources res = Application.getPluginLoader().getPlugin(ExamplePlugin.class).getResources(); EmbeddedDatabase db = new EmbeddedDatabase(res.getWorkPath() + "/db","exampleuser","examplepassword"); // Wenn wir bis hierher gekommen sind, wurde eine leere Datenbank // erfolgreich installiert. Wir koennen nun die Tabellen erstellen // Dazu erzeugen wir ein File-Objekt, welches auf die SQL-Datei zeigt. // res.getPath() liefert hierbei das Installations-Verzeichnis, in dem // sich das Plugin befindet. java.io.File file = new File(res.getPath() + "/sql/create.sql"); // und fuehren es nun auf der Datenbank aus. db.executeSQLScript(file); }
Wenn nicht alles erfolgreich durchlief, werfen wir eine ApplicationException. Damit geben wir Jameica die Information, dass die Installation fehlschlug. Es wird daraufhin auf der Jameica-Startseite einen Hinweis anzeigen, dass die Installation nicht funktionierte. Jameica wird die install-Methode beim nächsten Start automtatisch wieder aufrufen. Und das geschieht solange bei jedem Start, bis die install-Funktion endlich fehlerfrei durchläuft.
Kann eine existierende McKoi-Tabelle, um Spalten erweitert werden oder gehen dabei Daten verloren?
Ja, hier kann das Statement „ALTER CREATE TABLE“ genutzt werden. Hierbei können sowohl Spalten hinzugefügt als auch existierende geändert werden. Existiert die Tabelle noch nicht, wird sie automatisch angelegt. Allerdings dürfen die neuen Spalten nicht „NOT NULL“ sein.
ALTER CREATE TABLE testtabelle ( id NUMERIC DEFAULT UNIQUEKEY('testtabelle'), beschreibung VARCHAR(20) NOT NULL, kommentar text, zahl DOUBLE, dasistneu charchar(40) NULL, UNIQUE (id), PRIMARY KEY (id) );
Wie sieht die Package-Gliederung des Codes im Beispiel-Plugin aus?
Package | Beschreibung |
---|---|
.rmi | Interfaces der Fachobjekte |
.server | Implementierungen der Fachobjekt-Interfaces. |
.gui | Grafische Benutzeroberfläche |
.gui.views | Die Views mit den Eingabefeldern, Tabellen usw. |
.gui.controller | Controller (Kopplung Fachobjekt-View |
.gui.dialogs | Modale Dialoge |
.gui.parts | Vorgefertigte grafische Komponenten (z.Bsp. fertig konfigurierte Umsatz-Tabelle |
.gui.menus | Context-Menus |
.gui.action | Klassen, die das Interface de.willuhn.jameica.gui.Action implementieren und die Aufgaben kapseln, die bei Benutzer-Interaktivität ausgelöst werden können |
Impressum | Datenschutz