Ohne Kaffee läuft in deutschen Büros nichts. Der Beitrag beweist, dass auch Open Office mit Java viel leistet: Das Open-Office-SDK liefert ein Rezept für das belebende Gebräu.
Office-Suiten, hier ist Open Office zuerst zu nennen, sind Funktionsmonster: Der Anwender braucht zwar nur fünf Prozent aller Funktionen – aber jeder andere fünf Prozent. Bei recht speziellen Aufgaben in Firmenumgebungen – und das ist nicht selten – reichen selbst die vielen in Open Office eingebauten Funktionen nicht. Wie beim Stiefbruder von Microsoft hilft gewöhnlich ein Basic-Programm, das innerhalb des Office-Programms abläuft.
Das Verfahren setzt aber einen Benutzer voraus, der Office manuell bedient – widersinnig eingedenk der Automatisierungsfreundlichkeit und Flexibilität von Linux. Das “Open Office Software Development Kit” bringt Abhilfe, denn es schafft die Möglichkeit, sowohl interne Funktionen (beispielsweise zusätzliche Import/Export-Filter) zu erweitern als auch Office aus Fremdprogrammen heraus fernzusteuern.
Dieser Coffee-Shop erklärt am Beispiel eines Java-Programms, das ein einfaches Spreadsheet-Dokument erzeugt, die zugrunde liegende Architektur. Eine Erweiterung zu einem Servlet, das komplexere Szenarien abbildet, ist mit geringem Aufwand machbar.
Open Office muss als Objekt-Server laufen
Damit eigene Programme mit ihm kommunizieren können, muss Open Office im Servermodus laufen (siehe unten). Als Server stellt Office seine Funktionalität über Objekte bereit, deren Methoden remote aufrufbar sind. Die Objekte und Methoden bilden das API des Open-Office-SDK. In Open-Office-Sprache heißen diese Objekte Universal Network Objects, kurz UNO. Wer RMI und insbesondere CORBA kennt, bemerkt sofort die große Ähnlichkeit.
Genau genommen ist die UNO-Architektur nur eine proprietäre Version der CORBA-Architektur. Das geht so weit, dass UNO seine Objekte wie auch CORBA über eine Interface Definition Language (IDL) definiert. Programmierer haben damit die Wahl zwischen Java, C++, Open Office.org Basic, Python und OLE für die Implementation (Language Bindings).
Eigene Begriffe im API für Factories & Co.
Das Open-Office-API bedarf der Gewöhnung, weil es eine ganze Reihe eigener Begrifflichkeiten verwendet: Factories heißen darin zum Beispiel Service Manager. Ein »context« ist die logische Ablaufumgebung von Objekten. Wie bei allen verteilten Anwendungen muss das lokale Programm einen lokalen Stub für ein entferntes Objekt erzeugen, damit dies die Methodenaufrufe für den Programmierer transparent weiterleitet. Die erwähnten Service Manager haben dazu geeignete »create«-Methoden.
Erster Schritt jedes UNO-Programms ist es daher, einen Service Manager für Remote-Objekte zu erhalten. Weitere Remote-Objekte sind dann über dessen Factory-Methoden zugänglich. Auch wenn die Architektur im ersten Moment kompliziert klingt: Die Praxis ist einfach, insbesondere bei vorhandenen Codebeispielen. Den Beweis dafür will dieser Coffee-Shop antreten. Der vorgestellte Code ist hauptsächlich per Cut & Paste entstanden, die Vorlagen sind Teil des Developer-Guide des SDK.
Download und Installation
Die Open-Office-Website[1] stellt das SDK zum Download bereit (UDK-Projekt). Das Paket umfasst 26 MByte, ausgepackt sind es 105 MByte. Startpunkt für die sehr gute Dokumentation ist die Datei »index.html« im SDK-Wurzelverzeichnis. Wer eigene Programme entwickeln will, ist jetzt schon fertig, denn die eigentlichen Programmbibliotheken (für Java: JAR-Dateien) bringt Open Office mit und nicht das SDK (mit einer Ausnahme, die unten erläutert wird).
Entwickler, die allerdings ein mitgeliefertes Beispiel ausprobieren wollen, sollten den Installationsanweisungen folgen. Der wesentliche Ablauf umfasst den Aufruf des »configure«-Skripts, das ein Startskript erzeugt, das wiederum alle notwendigen Umgebungsvariablen setzt. Ausgangspunkt für die Beschäftigung mit dem SDK ist der Developers Guide. Er liegt sowohl im HTML-, als auch im gut druckbaren PDF-Format mit 898 Seiten vor.
Vorteil der HTML-Version ist, dass sie mit der restlichen API-Dokumentation sauber verlinkt ist – in beiden Richtungen. Achtung Konqueror-Benutzer: Einige Seiten der SDK-Dokumentation sind so groß, dass sie zuerst den Konqueror und dann den gesamten KDE-Desktop lahm legen (beim Autor KDE 3.1.1 und Konqueror 3.1.1 auf SuSE 8.2).
Vorarbeiten: Java aktivieren, Pfad setzen, Servermodus
Drei Voraussetzungen sind für ein erfolgreiches Java-OO-Projekt notwendig: In Open Office muss Java aktiviert sein – was meist schon während der Installation geschehen ist -, die JAR-Dateien von Open Office gehören in den »CLASSPATH« und außerdem muss Open Office im Servermodus laufen.
Java soll in einer Version 1.3.1_02 oder höher vorliegen, das gezeigte Beispiel funktioniert beim Autor ohne Probleme mit Java 1.4.1_02. Für das Java-Setup gibt es ein eigenes Programm: »/opt/ OpenOffice.org/program/jvmsetup«; je nachdem, wo Open Office installiert ist, heißt das Wurzelverzeichnis anders als »/opt/OpenOffice.org«. Die Abbildung 1 zeigt den Konfigurationsdialog.
Nachdem Java konfiguriert ist, muss es der Bediener noch freischalten. Das geschieht im laufenden Open Office über das Menü »Extras | Optionen« und dann im Baum »OpenOffice | Sicherheit« (siehe Abbildung 2).
Pfad der IDE bekannt geben
Die für Open Office erforderlichen JAR-Dateien liegen unter »/opt/OpenOffice .org/program/classes/«. Das Verzeichnis muss man dem Projekt in der IDE (etwa Eclipse) bekannt geben. Wer Ant verwendet, kann auch den Codeschnipsel aus Listing 1 verwenden, um den »CLASSPATH« zu definieren. Das komplette Build-Skript ist zusammen mit dem Java-Programm auf dem Linux-Magazin-Server[2] verfügbar.
Listing 1: »build.xml« |
042 <!-- ===== Compilation Classpath ==== -->
043
044 <path id="compile.classpath">
045 <pathelement location="${build.home}/classes"/>
046 <fileset dir="${oo.home}/program/classes">
047 <include name="*.jar"/>
048 </fileset>
049 </path>
|
Listing 2: »Setup.xml«: Start im Servermodus |
01 <ooSetupConnectionURL cfg:type="string"> 02 socket,port=8100;urp; 03 </ooSetupConnectionURL> |
Server-Start
Zum Schluss bleibt der Start von Open Office als Objekt-Server. Wichtig ist es, vorher alle Instanzen zu beenden, besonders solche von schlummernden Schnellstartprogrammen, die Open Office im Hintergrund laufen lassen. Mit
/opt/OpenOffice.org/program/soffice "-accept=socket,port=8100;urp;"
startet Open Office dann im Servermodus. Das Fenster mit einem leeren Textdokument kann man getrost in den Hintergrund schicken. Ein
netstat -an | grep 8100
zeigt, ob ein Programm (hoffentlich Open Office) am Port 8100 lauscht. Office könnte prinzipiell auch auf einem anderen Rechner im Netzwerk laufen. Der Zugriff erfolgt wie bei CORBA transparent über das Netz.
Wer Open Office immer im Servermodus starten will, kann die interne Konfiguration ändern, entweder global oder für einzelne Benutzer. Dazu muss er den Abschnitt aus Listing 2 in die Datei »/opt/OpenOffice.org/share/config/regis-try/instance/org/openoffice/Setup.xml« beziehungsweise die »~/OpenOffice.org /user/config/registry/instance/org/open- office/Setup.xml« einpflegen.
Jetzt ist alles vorbereitet und es geht endlich ans Programmieren: Das Java-Programm aus Listing 3 soll ein Spreadsheet erzeugen, in die beiden oberen Zellen von Spalte A jeweils die Zahl 42 eintragen (sie ist bekanntlich die Antwort auf alle Fragen) und von der Tabellenkalkulation addieren lassen. Anschließend speichert das Programm die Tabelle. Die nächsten Abschnitte erläutern den Programmcode.
Der Remote Service Manager
Der Konstruktor in den Zeilen 64 bis 67 ruft die »getRemoteContext()«-Methode auf. Darin (Zeilen 75 bis 99) passiert die gesamte UNO-Magie. Dieser Teil des Programms ist am schwersten zu verstehen: Zuerst erzeugt das Programm mit »initContext« einen lokalen Kontext und daraufhin einen lokalen Service Manager. Wie beschrieben sind Service Manager Factory-Objekte, damit dient der lokale Service Manager nur dem Erzeugen eines URL-Resolvers, der letztlich in Zeile 85 den Kontakt zum Open-Office-Server aufnimmt.
Damit ist ein erstes entferntes Objekt vorhanden. Über eine Reihe von Zwischenschritten erhält der Entwickler letztlich den gewünschten Remote Context. In dieser (und auch in den anderen) Methode(n) gibt es immer wieder mysteriöse Aufrufe von »UnoRuntime .queryInterface()«. Sie entsprechen den »narrow()«-Aufrufen in CORBA-Programmen und dienen dazu, den richtigen Typ des Objekts zu liefern. (Das erste Argument fungiert dabei als Klassenobjekt der gewünschten Klasse).
Spreadsheets
Nachdem mittels »RemoteContext« der »RemoteServiceManager« erzeugt ist, ist das Spreadsheet Gegenstand des Interesses. Das Objekt-Modell von Open Office ist zunächst etwas verwirrend: Es kennt eine »XComponent« samt zugeordne- tem »XModel«, ein »XSpreadsheetDocument«, die Kollektion aller Tabellen eines Spreadsheets »XSpreadsheets« sowie eine einzelne Tabelle (»XSpreadsheet«). In der »createSpreadsheet()«-Methode hangeln sich die Zeilen 107 bis 129 von Objekt zu Objekt.
Interessant wird es in der »useSpreadsheet()«-Methode. Die Zeilen 137 bis 160 füllen Zellen mit Werten und formatieren die Zellen. Die letzten vier Zeilen holen die neu erstellte Tabelle in den Vordergrund – für ein echtes Batch-Programm eher unwichtig.
Sichern und Speichern
Die letzten beiden Methoden, also »store-Spreadsheet()« und »closeDocument()« sind generell nutzbar. Natürlich muss der Filter (Zeilen 177 bis 178) dem Dokumenttyp entsprechen. Das Spreadsheet wäre durch Ändern einer einzigen Zeile (178) auch als HTML-Datei oder auch im Excel-Format abspeicherbar. Mit »closeDocument()« verschwindet das Dokument vom Bildschirm.
Das ganze Programm läuft auf einem betagten 700-MHz-Duron so schnell, dass man gerade nur das Öffnen und Schließen des Office-Dokuments mitbekommt. Deshalb darf dieses Beispielprogramm auch auf eine ausgefeilte Fehlerbehandlung verzichten. Jede Methode geht davon aus, dass die erzeugten und in privaten Feldern gespeicherten Referenzen auf die verschiedenen Objekte stets gültig sind. Im Allgemeinen ist dies aber nicht der Fall, so könnte ein Benutzer ein eben erzeugtes Dokument über Maus oder Tastatur schließen. Die Methoden des Open-Office-API werfen dann Exceptions.
Mit diesem Handwerkszeug und den auführlichen Beispielen aus dem SDK lassen sich viele Anwendungen des Typs “Automatische Dokumenterzeugung und -verarbeitung” erstellen. Stöbern lohnt sich! Open Office bietet den Java-Programmierern aber mehr.
Office Beans
In den letzten Jahren wurde immer mal wieder der Versuch unternommen, eine Office-Suite rein in Java zu implementieren. Alle derartige Experimente sind gescheitert. Den Grund darf man in der schieren Größe der Aufgabe suchen, denn eine Office-Suite, die annähernd mit den vorhandenen Alternativen konkurriert will, schreibt sich nicht von heute auf morgen. Letztlich lohnt sich der Aufwand für eine Firma, die Geld verdienen muss, nicht.
Dazu kommt, dass es einfach Aufgaben gibt, für die Java ungeeignet ist. Schon das in C++ implementierte Open Office ist manchmal träge, eine Java-Version würde die Geduld überstrapazieren. Java-Entwickler, die bislang in ihrer Desktop-Anwendung eine leistungsfähige Tabellenkalkulation oder Textverarbeitung brauchten, konnten auf keine generische Lösung zurückgreifen, die austauschbare Dokumente erzeugt (es gab nur einige auf Swing-Komponenten aufsetzende Klassen für Tabellen und Texte). Diese Lücke füllt Office Bean, eine Java-Bean-Komponente.
Office Bean kapselt die ganze Komplexität des Open-Office-API. Darüber hinaus können Office-Dokumente in normale Java-Programme eingebettet werden. Die Abbildung 3 zeigt das Resultat. Nur wer genau hinsieht (am besten auf die Buttons am unteren Bildschirmrand), erkennt, dass es sich überhaupt um ein Java-Programm handelt.
Der Screenshot zeigt das von dem Programm oben erzeugte Spreadsheet und verwendet ein Beispiel des SDK. Die Office-Bean-Komponenten, eine JAR-Datei sowie eine Reihe nativer Bibliotheken sind nicht in der normalen Open-Office-Distribution enthalten, sondern kommen mit dem SDK. Installation, Konfiguration und Gebrauch der Office Bean dokumentiert das SDK detailliert.

Abbildung 3: Ein Open-Office-Dokument, in ein Java-Programm eingebettet – was nur bei genauem Hinsehen erkennbar ist. Wieder handelt es sich um ein modifiziertes Programmierbeispiel aus dem SDK.
finally{}
Dieser Coffee-Shop hat gezeigt, dass das leistungsfähige Office-Paket der Open-Source-Community einfach in eigene Programme integrierbar ist. Wie immer wurden die Möglichkeiten nur kurz gestreift, was aber kein Problem ist, denn das SDK kommt mit sehr guter Dokumentation und vielen Beispielen.
Trotzdem bleibt auch etwas zu kritisieren: Wer Open-Office-Dokumente im Batch – etwa übers Web – erzeugen und verarbeiten will, braucht eine laufende Office-Instanz und somit auch einen X-Server. Ersteres kann kritisch werden, denn bei Versuchen des Autors verabschiedete sich Open Office gern hin und wieder. Und einen X11 will nicht jeder auf seiner Server-Maschine laufen haben – X-Server sind schließlich Programme für Client-Rechner. Eventuell schafft der virtuelle X-Server hier Abhilfe.
Die beste Alternative wäre das Erzeugen und Verarbeiten von Open Office außerhalb einer Open-Office-Umgebung. Das ist prinzipiell möglich. Die gespeicherten Daten liegen dann letztlich (komprimiert) als XML-Dateien vor und Java-Programme könnten sie mit SAX oder DOM verarbeiten, auch XSLT ist möglich. Auf[3] gibt es in dieser Richtung schon einige Ergebnisse, jedoch keine, die dem Java-API zum HSSF (Microsofts Horrible Spreadsheet Format) entsprächen. (jk)
Listing 3: »JavaSpreadsheet.java« |
023 package de.bablokb.oo;
024
025 import com.sun.star.bridge.*;
026 import com.sun.star.uno.*;
027 import com.sun.star.sheet.*;
028 import com.sun.star.table.*;
029 import com.sun.star.lang.*;
030 import com.sun.star.connection.*;
031 import com.sun.star.comp.helper.*;
032 import com.sun.star.beans.*;
033 import com.sun.star.frame.*;
034 import com.sun.star.util.*;
035
044 public class JavaSpreadsheet {
045
046 private static final String UNO_URL =
047 "uno:socket,host=localhost,port=8100;urp;StarOffice.ServiceManager";
048 private static final String TABLE_NAME = "Java";
049 private static final String DOCUMENT_FILENAME = "file:///tmp/oo-java.sxc";
050
051 private XComponentContext iRemoteContext = null;
052 private XMultiComponentFactory iRemoteServiceManager = null;
053 private XComponent iSpreadsheetComponent = null;
054 private XSpreadsheetDocument iSpreadsheetDocument = null;
055 private XModel iSpreadsheetModel = null;
056 private XSpreadsheets iSpreadsheets = null;
057
064 public JavaSpreadsheet() throws java.lang.Exception {
065 getRemoteContext();
066 iRemoteServiceManager = iRemoteContext.getServiceManager();
067 }
068
075 private void getRemoteContext() throws java.lang.Exception {
076 try {
077 XComponentContext initContext = Bootstrap.createInitialComponentContext(null);
078 XMultiComponentFactory manager = initContext.getServiceManager();
079 Object urlResolver = manager.createInstanceWithContext(
080 "com.sun.star.bridge.UnoUrlResolver",initContext);
081
082 XUnoUrlResolver xUnoUrlResolver = (XUnoUrlResolver) UnoRuntime.
083 queryInterface(XUnoUrlResolver.class,urlResolver);
084
085 Object initialObject = xUnoUrlResolver.resolve(UNO_URL);
086 XPropertySet xPropertySet = (XPropertySet)UnoRuntime.queryInterface(
087 XPropertySet.class,initialObject);
088 Object context = xPropertySet.getPropertyValue("DefaultContext");
089 iRemoteContext = (XComponentContext)UnoRuntime.queryInterface(
090 XComponentContext.class,context);
091 } catch(NoConnectException nce) {
092 System.err.println( "No process listening on the resource" );
093 nce.printStackTrace();
094 throw nce;
095 } catch(DisposedException de) {
096 iRemoteContext = null;
097 throw de;
098 }
099 }
100
107 private void createSpreadsheet() throws java.lang.Exception {
108 Object desktop =
109 iRemoteServiceManager.createInstanceWithContext(
110 "com.sun.star.frame.Desktop", iRemoteContext);
111
112 XComponentLoader componentLoader =
113 (XComponentLoader) UnoRuntime.queryInterface(
114 XComponentLoader.class, desktop);
115
116 PropertyValue[] loadProps = new PropertyValue[0];
117 iSpreadsheetComponent = componentLoader.loadComponentFromURL(
118 "private:factory/scalc", "_blank", 0, loadProps);E
119
120 iSpreadsheetDocument =
121 (XSpreadsheetDocument) UnoRuntime.queryInterface(
122 XSpreadsheetDocument.class, iSpreadsheetComponent);
123
124 iSpreadsheets = iSpreadsheetDocument.getSheets();
125 iSpreadsheets.insertNewByName(TABLE_NAME, (short)0);
126
127 iSpreadsheetModel =
128 (XModel) UnoRuntime.queryInterface(XModel.class,iSpreadsheetComponent);
129 }
130
137 private void useSpreadsheet() throws java.lang.Exception {
138 Object sheet = iSpreadsheets.getByName(TABLE_NAME);
139 XSpreadsheet spreadsheet =
140 (XSpreadsheet) UnoRuntime.queryInterface(
141 XSpreadsheet.class, sheet);
142
143 XCell cell = spreadsheet.getCellByPosition(0, 0);
144 cell.setValue(42);
145 cell = spreadsheet.getCellByPosition(0, 1);
146 cell.setValue(42);
147 cell = spreadsheet.getCellByPosition(0, 2);
148 cell.setFormula("=sum(A1:A2)");
149
150 XPropertySet cellProps =
151 (XPropertySet) UnoRuntime.queryInterface(
152 XPropertySet.class, cell);
153 cellProps.setPropertyValue("CellStyle", "Result");
154
155 XController spreadsheetController = iSpreadsheetModel.getCurrentController();
156 XSpreadsheetView spreadsheetView =
157 (XSpreadsheetView) UnoRuntime.queryInterface(
158 XSpreadsheetView.class,spreadsheetController);
159 spreadsheetView.setActiveSheet(spreadsheet);
160 }
161
168 private void storeSpreadsheet() throws java.lang.Exception {
169 XStorable store =
170 ( XStorable ) UnoRuntime.queryInterface(XStorable.class,iSpreadsheetModel);
171
172 PropertyValue[] prop = new PropertyValue[2];
173 prop[0] = new PropertyValue();
174 prop[0].Name = "Overwrite";
175 prop[0].Value = new Boolean(true);
176 prop[1] = new PropertyValue();
177 prop[1].Name = "FilterName";
178 prop[1].Value = "StarOffice XML (Calc)";
179 store.storeAsURL(DOCUMENT_FILENAME,prop);
180 System.out.println("document saved");
181 }
182
189 private void closeDocument() throws java.lang.Exception {
190 iSpreadsheetComponent.dispose();
191 System.out.println("document closed");
192 }
193
200 public static void main(String[] args) {
201 try {
202 JavaSpreadsheet js = new JavaSpreadsheet();
203 js.createSpreadsheet();
204 js.useSpreadsheet();
205 js.storeSpreadsheet();
206 js.closeDocument();
207 } catch (java.lang.Exception e){
208 e.printStackTrace();
209 }
210 System.exit(0);
211 }
213 }
|
Infos |
|
[1] Open-Office-Homepage: [http://www.openoffice.org] [2] Build-Skript und Java-Programm zum Coffee-Shop: [https://www.linux-magazin.de/Service/Listings/2003/10/Coffeeshop] [3] Das Filter-Projekt von Open Office: [http://xml.openoffice.org/filters.html] |
Der Autor |
|
Bernhard Bablok arbeitet bei der Allianz Versicherungs AG im Bereich Data-Warehouse-Systeme. Wenn er nicht Musik hört, mit dem Radl oder zu Fuß unterwegs ist, beschäftigt er sich mit Themen rund um Objektorientierung. Er ist unter [mailto:coffee-shop@bablokb.de] zu erreichen. |







