Xpert.press
Die Reihe Xpert.press vermittelt Professionals in den Bereichen Softwareentwicklung, Internettechnologie und IT-Management aktuell und kompetent relevantes Fachwissen über Technologien und Produkte zur Entwicklung und Anwendung moderner Informationstechnologien.
Thomas Ekert
Java unter Lotus Domino Know-how für die Anwendungsentwicklung Mit 121 Abbildungen, 80 Listings und CD-ROM
123
Thomas Ekert Kommunikationsdesign Hamburg
[email protected] www.tom-quadrat.de
Bibliografische Information der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.
ISSN 1439-5428 ISBN-10 3-540-22176-X Springer Berlin Heidelberg New York ISBN-13 978-3-540-22176-0 Springer Berlin Heidelberg New York Dieses Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere die der Übersetzung, des Nachdrucks, des Vortrags, der Entnahme von Abbildungen und Tabellen, der Funksendung, der Mikroverfilmung oder der Vervielfältigung auf anderen Wegen und der Speicherung in Datenverarbeitungsanlagen, bleiben, auch bei nur auszugsweiser Verwertung, vorbehalten. Eine Vervielfältigung dieses Werkes oder von Teilen dieses Werkes ist auch im Einzelfall nur in den Grenzen der gesetzlichen Bestimmungen des Urheberrechtsgesetzes der Bundesrepublik Deutschland vom 9. September 1965 in der jeweils geltenden Fassung zulässig. Sie ist grundsätzlich vergütungspflichtig. Zuwiderhandlungen unterliegen den Strafbestimmungen des Urheberrechtsgesetzes. Springer ist nicht Urheber der Daten und Programme. Weder Springer noch der Autor übernehmen die Haftung für die CD-ROM und das Buch, einschließlich ihrer Qualität, Handelsund Anwendungseignung. In keinem Fall übernehmen Springer oder der Autor Haftung für direkte, indirekte, zufällige oder Folgeschäden, die sich aus der Nutzung der CD-ROM oder des Buches ergeben. Springer ist ein Unternehmen von Springer Science+Business Media springer.de © Springer-Verlag Berlin Heidelberg 2006 Printed in Germany Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Text und Abbildungen wurden mit größter Sorgfalt erarbeitet. Verlag und Autor können jedoch für eventuell verbliebene fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Satz: Druckfertige Daten des Autors Herstellung: LE-TEX, Jelonek, Schmidt & Vöckler GbR, Leipzig Umschlaggestaltung: KünkelLopka Werbeagentur, Heidelberg Gedruckt auf säurefreiem Papier 33/3142 YL – 5 4 3 2 1 0
V
Vorwort Mit einer explosionsartigen Verbreitung zog Java vor einigen Jahren auch in die Domino-Welt ein. Groß geworden mit Pascal, entsprach Java meinen Ansprüchen an die Ästhetik einer Programmiersprache, die gleichzeitig den aktuellen Anforderungen an die Leistungsfähigkeit moderner Anwendungen gerecht wird, und so machte ich Java schnell zum wichtigsten Standbein der Anwendungsentwicklung in vielen Projekten. Vor allem in der Webentwicklung versprach – und verspricht – die Kombination mit Java große Vorteile für die Domino-Anwendungsentwicklung und so lag es nahe, meine seit Mitte der 90er Jahre in der Entwicklung verschiedenster kleiner und großer Domino-Anwendungen gesammelte Erfahrung nun um den Einsatz von Java zu erweitern. Während für einen Domino-Programmierer – gewohnt an ein geschlossenes Entwicklungsframework bestehend aus dem Domino Designer, in dem alle Aspekte der Anwendungsentwicklung vom Debugging bis zur GUI Entwicklung abgedeckt werden – der Einsatz externer Komponenten aus Java neu ist und die Einarbeitung in neue Entwicklungsabläufe notwendig macht, steht der Java-Profi der komplexen und hoch integrierten Infrastruktur Dominos mit seinen ausgefeilten Sicherheitsmechanismen und der schier endlosen Zahl an Diensten und Anwendungen gegenüber. Im Rahmen der Entwicklungsarbeit an einem großen Projekt bei der Erstellung eines geschlossenen Anwenderportals einer Versicherung wurde im Team aus Domino- und Java-Entwicklern schnell klar, dass der Austausch von Know-how an erster Stelle stehen musste, um eine gemeinsame Sprache zu finden, die es den jeweiligen Spezialisten ermöglichte, die Bedürfnisse und Notwendigkeiten der Domino-Entwicklung auf der einen und der Java-Entwicklung auf der anderen Seite zu verstehen und zu berücksichtigen. Hierbei entstand auch die Idee zu diesem Buch, da sich herausstellte, dass es zwar an Beschreibungen des Domino-Java-APIs nicht mangelt, aber Praxiserfahrungen, Best-Practice-Beispiele und vor allem Know-how in Domino-spezifischen Design Pattern und Lösungsansätzen rar sind, was nicht zuletzt in der noch recht jungen Historie der Domino-Java-Entwicklung begründet liegt. So haben Domino- und Java-Entwickler jeweils einen spezifischen Blick auf die Aufgaben und Lösungsansätze für ihre eigenen Anforderungen und Bedürfnisse. Dieses Buch ist weder ausschließlich ein Handbuch für die Domino-Anwendungsentwicklung noch ein Java-Lehrbuch, sondern beleuchtet die speziellen Anforderungen der Domino-Anwendungsentwicklung in Java. Alle Fragestellungen, Lösungsansätze und Best-Practice-Beispiele sind aus der täglichen Praxis entnommen und weiterentwickelt und Bestandteil vieler tatsächlich im Einsatz befindlicher Anwendungen. Als begeisterter und bekennender XP-Programmierer sind automatisierte Tests und Qualitätssicherungsverfahren wichtige Bestandteile meiner Arbeit und so enthält dieses Buch – last but not least – ein umfangreiches Kapitel zum Debugging oder besser gesagt zu dessen Vermeidung, ergänzt durch Verfahren, die automatisierte Tests von Domino-Java-Anwendungen ermöglichen. Die Java-Programmierung für Domino ist aktueller denn je. Mit der derzeitigen Version gehören durch die DB2-Integration Themen wie SQL und J2EE nun zu den wichtigen Fragestellungen im Domino-Umfeld. Große Begeisterung und Erwartungen ruft der Blick auf die nächsten Domino-Generation hervor, deren Client auf Java
VI und dem Eclipse-Framework basieren wird. Damit wird die Basis für Domino-Plugins und Erweiterungen gelegt, die sich mittels des Domino-Java-API nahtlos integrieren lassen. Dieses Buch war nur durch die tägliche Arbeit und im Austausch mit vielen Domino- und Java-Spezialisten in meinem Umfeld möglich. Ich möchte daher meinen MitarbeiterInnen, aber auch den KollegInnen bei unseren Kunden danken, die es durch Fragen, Gespräche, Anregung und Kritik möglich gemacht haben, Antworten und Lösungen zu finden und Design Pattern zu entwickeln. Eine große Stütze war der Springer-Verlag, der schnell von Idee und Thema überzeugt war und mich mit Geduld begleitet hat. Thorsten Dietsche gilt mein Respekt und Dank. Er hat mit Ausdauer das Manuskript konstruktiv und kritisch hinterfragt und viel zur Geschlossenheit des Inhalts beigetragen. Ihm verdanke ich unter anderem die Anregung für das Kapitel über die Kapselung von Schleifen. Danken möchte ich meinen beiden Familien und meinen Freunden, die mich während der fast zwei Jahre der Entstehung dieses Buches nicht vergessen haben. Mein besonderer Dank gilt Nico, die nicht nur auf viel gemeinsame Zeit verzichtet, sondern die mich antrieb, dieses Buch zu beginnen und dessen Abschluss erst ermöglicht hat.
Thomas Ekert Hamburg im April 2006
VII
Über dieses Buch Dieses Buch richtet sich vor allem an Java-Programmierer, die neu in der DominoWelt sind. Dementsprechend bietet der erste Teil eine Einführung in die für die JavaEntwicklung wichtigen Domino-Komponenten, stellt die Inhalte aber bereits für die Java-Sicht optimiert dar. So eignet sich dieses Buch auch für Domino-Anwendungsentwickler, die in die Domino-Java-Programmierung einsteigen, wobei allerdings solide Java-Grundkenntnisse vorausgesetzt werden. Um die praktische Arbeit zu erleichtern, werden an vielen Stellen administrative Techniken beschrieben, die notwendig sind, um bestimmte Verfahren anwenden oder zugehörige Services und Servertasks aktivieren zu können. Auch wird auf viele Aspekte der Sicherheitsfunktionen eingegangen, auch wenn diese über die reine Anwendungsentwicklung hinausgehen oder administrativer Natur sind. Teil I Domino ist zugleich Datenbank und Anwendungsserver und dazu eine Plattform für Kollaborationsanwendungen wie E-Mail, Kalender, Aufgaben oder Ressourcenverwaltung. Datentypen, Gestaltungselemente und der Domino Designer liefern die Grundlage, auf der das Buch aufbaut. Masken, Ansichten, Agenten und natürlich der XMLähnlichen, offenen Schemastruktur Dominos gilt das Hauptaugenmerk, stellen diese doch die Grundlagen für sämtliche Anwendungen dar. Im ersten Teil dieses Buches werden die wichtigsten Komponenten erläutert und dienen als Basiswissen für die weiteren Kapitel. Teil II Der zweite Teil dieses Buches legt die Grundlagen für jede Domino-Java-Anwendung und erweitert dieses Wissen durch die Erläuterung von Best-Practice-Beispielen, Design Pattern und Spezialfällen. Die Domino-Session, als lokale oder remote Anwendung, bildet den Einstieg, ist sie doch das Herz einer jeden Domino-Java-Anwendung, erzeugt durch die NotesFactory, der zwei Kapitel gewidmet sind. Die Handhabung der Domino-Objekte erfordert viel Spezialwisssen, so dass sich jeweils ein Kapitel mit den Objekten Session, Document, Database, View und RichText beschäftigt. Hierbei ist der Überblick über die gesamte Objektstruktur ebenso berücksichtigt wie die Aufgabenstellungen der täglichen Arbeit, von der Performanceoptimierung über die Handhabung von Threads bis hin zur Garbage-Collection und dem in Domino sehr wichtigen Recycling, dem ebenfalls ein eigenes Kapitel gewidmet ist. Werden die wichtigsten Objekte beherrscht, geht es nun an den praktischen Einsatz. Dieser wird vor allem in den Kapiteln 13 bis 15 über die Objekt- und Speichertechniken und die verschiedenen Suchalgorithmen berücksichtigt, so dass ein kompletter Wissensschatz entsteht, der den Aufbau solider, alltagstauglicher Java-Anwen-
VIII dungen für Domino ermöglicht. Dort findet sich eine Anleitung für den Einsatz von Domino mit DB2 ebenso wie ausführliche Modelle zur Erweiterung und Implementierung von Domino-Java-Objekten. Teil III Wie eingangs erwähnt, bietet vor allem der Einsatz im Web-Umfeld, aber auch in großen Enterprice-Infrastrukturen eine besondere Motivation für den Einsatz von Java für Domino. Daher liefert Teil III das Spezialwissen für dieses Einsatzgebiet von der Entwicklung von JSP-Seiten mit den domtags über das Wissen für die Einbindung von Domino in eine J2EE-Infrastruktur. Abgerundet wird das Buch durch das letzte Kapitel über Debugging, JUnit Tests, Logging und Qualitätssicherung. Sourcecode Alle Sourcecode-Listings der Beispiele dieses Buches liegen als vollständige Code Sammlung auf einer CD bei. Eine begleitende Web-Site befindet sich unter http:// www.tom-quadrat.de/dominojava. Darüber hinaus wurden der CD ergänzende Beispiele und Klassen beigefügt, die als Listing nicht abgedruckt wurden. Dies ist jeweils im Text erwähnt. Notationelle Konventionen Werden im Text Java-Klassen oder Notes-Formeln erwähnt, so sind diese in Courier gesetzt, es sei denn es ist allgemeinsprachig vom gleichnamigen Domino-Objekt die Rede (z.B. „Die Domino-Session“ oder „Die Klasse Session“). Den Beispiel-Listings im Buch wurden in der Regel grafische Nummernsymbole hinzugefügt, über die die jeweiligen Codestellen im Text referenziert werden. Methoden sind nicht immer in ihrer vollen Signatur genannt, insbesondere dann, wenn es mehrere Signaturen gibt und eine Methode im Allgemeinen erwähnt wird. In Tabelle 6-1 befindet sich eine vollständige Übersicht über alle Klassen des Domino-Java-API. Ein vollständiges Klassen- und Methodenverzeichnis ist in den Index im Anhang eingearbeitet.
Über den Autor Thomas Ekert ist seit 10 Jahren freier IT-Berater und führt seit 7 Jahren als CTO und geschäftsführender Gesellschafter die IT-Agentur BITSDONTBYTE. Als zertifizierter Domino- Java- und DB2-Entwickler ist er Spezialist für Domino- und Web-Application-Server-Entwicklung.
IX Als Hauptverantwortlicher für große Projekte für öffentliche und private Auftraggeber ist er erfahren in Controlling und Qualitätssicherung von umfangreichen TÜVgeprüften Java-basierten Domino-Projekten, in denen er neben Projektierung, Konzept und Leitung auch Schulungen in Unternehmen konzipiert und durchführt.
Inhalt
Teil 1 1
2
Notes und Domino ..................................................................1
Das Domino-Prinzip.................................................................................................3 1.1 Replikation ......................................................................................................5 1.2 Schemabasiertes Objekt- und Speichermodell..............................................6 1.3 Erweiterbarkeit................................................................................................7 1.4 Zugriffskontrolle und Verschlüsselung ..........................................................9 1.5 Geschichte .....................................................................................................11 1.6 Lotus Domino und Java................................................................................14 1.7 Lotus Domino als Datenbank ......................................................................16 1.8 Die Client-Server-Umgebung Lotus und Domino .....................................18 1.9 Zusammenfassung ........................................................................................20 Notes @ Domino .....................................................................................................23 2.1 „Hello WWWorld“ – URL-Loader-Anwendung.........................................24 2.1.1 Datenbank anlegen ..........................................................................25 2.1.2 Maske anlegen ..................................................................................26 2.1.3 Ansicht anlegen................................................................................32 2.1.4 Agent anlegen...................................................................................34 2.1.5 Die Anwendung testen.....................................................................38 2.1.6 Erweiterungen und Ausblick ...........................................................39 2.2 Das Domino-Speichermodell ......................................................................41 2.2.1 Die Domino-„Note“ ........................................................................41 2.2.2 Notes Document und Notes Form..................................................43 2.2.3 Item und Feld ...................................................................................49 2.2.4 Datentypen.......................................................................................55 2.2.4.1 Text und Textliste ...............................................................55 2.2.4.2 Namen, Autoren und Leser ...............................................56 2.2.4.3 Datum / Uhrzeit.................................................................57 2.2.4.4 Zahl und Zahlliste..............................................................59 2.2.4.5 Antwortreferenzliste ..........................................................60 2.2.4.6 DokLink-Referenzliste.......................................................60 2.2.4.7 Mime Part ..........................................................................60 2.2.4.8 RichText, RichText Light und Embedded Object.............61 2.3 Replikation ....................................................................................................62 2.3.1 Replik ID und Universal ID.............................................................62
XII
3
Inhaltsverzeichnis 2.3.2 Replikations- und Speicherkonflikte ..............................................65 2.4 Das Domino-Objektmodell (Einführung)..................................................67 2.5 Suchkonzepte (Einführung).........................................................................68 2.6 Sicherheitskonzepte ......................................................................................74 2.7 Zusammenfassung ........................................................................................76 Notes IDE ..........................................................................................................79 3.1 Der Domino Designer ..................................................................................80 3.2 Masken ..........................................................................................................85 3.2.1 Feld ...................................................................................................86 3.2.2 Ebene ................................................................................................88 3.2.3 Layout-Ebene ...................................................................................88 3.2.4 Aktionen...........................................................................................88 3.2.5 Ressourcen........................................................................................88 3.2.6 Seitengestaltungselemente...............................................................91 3.2.7 Abschnitte.........................................................................................91 3.2.8 Berechneter Text...............................................................................92 3.2.9 Hotspots ...........................................................................................92 3.2.10 Eingebettetes Element......................................................................92 3.2.11 Verwendung von Masken im Notes Client und im Browser .........92 3.2.12 Verwendung von Java in Masken ....................................................97 3.3 Ansichten und Ordner..................................................................................97 3.3.1 Spaltenberechnungen ......................................................................99 3.3.2 Sortieren und Kategorisieren ..........................................................99 3.3.3 SELECT-Formeln...........................................................................102 3.3.4 Größenbeschränkungen in Ansichten ..........................................104 3.4 Agenten........................................................................................................105 3.4.1 Agenten-Trigger .............................................................................105 3.4.2 Agenten-Targets .............................................................................108 3.5 Bibliotheken ................................................................................................111 3.6 Zusammenfassung ......................................................................................112
Teil 2 Java @Domino..........................................................................113 4
Java-Anwendungen @ Domino ............................................................................115 4.1 Domino- und J2SE- und J2EE-Anwendungen .........................................116 4.2 Vorbereitungen ...........................................................................................117 4.3 Lokale Notes Session...................................................................................119 4.4 Java und Domino........................................................................................121 4.5 Statische Notes Threads..............................................................................123 4.6 Session im statischen Thread .....................................................................126 4.7 NotesThread erweitern ...............................................................................127 4.8 Runnable implementieren..........................................................................128
Inhaltsverzeichnis 4.9 4.10 4.11 4.12 4.13 4.14 4.15 4.16 4.17 4.18 4.19
5
6
XIII
Domino-Java-Applets.................................................................................128 Notes Agent im Domino Designer erstellen..............................................137 Agent in Domino Designer importieren ...................................................141 Agent in eine Java-Anwendung umwandeln .............................................143 Agenten in Notes-Masken ..........................................................................144 WebQueryOpen und WebQuerySave ........................................................147 Parameterübergabe zwischen Agenten und runOnServer........................148 Sicherheit bei der Agentenausführung ......................................................154 Debugging von Domino-Java-Agenten .....................................................159 Der Domino Agent Manager......................................................................160 Domino-Java-Agent und Domino-Java-Anwendung – Konzepte im Vergleich ...............................................................................161 4.20 Zusammenfassung ......................................................................................162 Java-Web-Anwendungen @ Domino ...................................................................165 5.1 Domino-Web-Agenten ...............................................................................166 5.2 Servlets.........................................................................................................171 5.2.1 Servlets im Domino Servlet Manager ...........................................174 5.2.2 Domino-Web-Agenten versus Domino-Servlets .........................177 5.2.3 Login und Internet-Session für Web-Agenten und Servlets........179 5.3 Remote Computing via DIIOP / CORBA .................................................184 5.3.1 Server und Client Setup von DIIOP / HTTP ...............................187 5.3.2 Domino-Remote-Session ..............................................................193 5.3.3 Remote-DIIOP-Aufrufe via IOR / getIOR....................................195 5.3.4 SSL ..................................................................................................197 5.3.5 Single Sign On................................................................................202 5.3.5.1 Setup von WebSphere und Domino für SSO via LtpaToken.........................................................................207 5.3.6 Connection Pooling – Object Request Broker .............................220 5.3.7 Die NotesFactory – Überblick.......................................................229 5.4 Troubleshooting .........................................................................................232 5.5 Zusammenfassung ......................................................................................236 Domino-Objekt- und Speichermodell.................................................................239 6.1 Objekt- und Datenstruktur ........................................................................240 6.1.1 Objekte, die über Session bezogen werden können .....................242 6.1.2 Objekte, die über Database bezogen werden können ..................243 6.1.3 Objekte, die über View bezogen werden können .........................243 6.1.4 Objekte in Document ....................................................................244 6.1.5 Domino-Objektklassen .................................................................244 6.2 Basis- und Sonderklassen ...........................................................................248 6.2.1 AgentBase .......................................................................................249 6.2.2 AppletBase und JAppletBase .........................................................250 6.2.3 NotesException und NotesError ...................................................252
XIV
7
8
9
Inhaltsverzeichnis 6.3 Zusammenfassung ......................................................................................253 Document......... .....................................................................................................255 7.1 Document und Item ...................................................................................256 7.1.1 Mit Items arbeiten .........................................................................257 7.1.2 Datentypen handhaben .................................................................259 7.1.3 Item Properties und weitere Methoden in Item...........................268 7.1.4 Mit Document arbeiten – Lifecycle eines Dokuments ................271 7.1.4.1 Dokumente selektieren über Methoden in Database ....271 7.1.4.2 Dokumente selektieren über Methoden in Document..273 7.1.4.3 Dokumente selektieren über vordefinierte DocumentCollections .....................................................275 7.1.4.4 Neue Dokumente erstellen..............................................276 7.1.4.5 Speichern von Dokumenten ...........................................278 7.1.4.6 Dokumente löschen.........................................................280 7.2 Profildokumente .........................................................................................282 7.3 Antwortdokumente ....................................................................................284 7.3.1 Antwortreferenzen über Self ID und Parent ID ...........................286 7.4 Attachments ................................................................................................288 7.4.1 Übersicht über die Methoden zur Attachmentbearbeitung ........298 7.5 Encryption und Signing .............................................................................302 7.5.1 Items mit geheimen Verschlüsselungsschlüsseln verschlüsseln ...304 7.5.2 Verwendung der Verschlüsselungsschlüssel..................................306 7.5.3 Fehlerquellen bei der Verwendung von Verschlüsselungsschlüsseln............................................................308 7.5.4 Verwendung von Verschlüsselungsschlüsseln über DIIOP..........310 7.5.5 Verschlüsselung und Signatur von Dokumenten.........................311 7.5.6 Verwendung öffentlicher Verschlüsselungsschlüssel....................311 7.5.7 Signieren von Daten.......................................................................311 7.5.8 Versenden von signierten und verschlüsselten Daten ..................314 7.6 Document Properties und weitere Methoden...........................................315 7.7 Document Locking .....................................................................................319 7.8 Zusammenfassung ......................................................................................325 Session....................................................................................................................327 8.1 Bezug von Datenbanken.............................................................................328 8.2 Einfache unabhängige Stil- und Eigenschafts-Objekte.............................329 8.3 Service-Anwendungen................................................................................331 8.4 Ausführen verschiedener Befehle ...............................................................334 8.5 Verschiedene Eigenschaften........................................................................336 8.6 Zusammenfassung ......................................................................................338 Database.................................................................................................................339 9.1 Datenbanken öffnen, schliessen, replizieren und löschen ........................340 9.1.1 Vergleich – Methoden zum Öffnen einer Datenbank ..................344
Inhaltsverzeichnis 9.2 9.3
XV
Datenbanken, Dokumente und Document Collection ............................345 Allgemeine Datenbankeigenschaften .........................................................346 9.3.1 Weitere Datenbankeigenschaften ..................................................350 9.4 Suche und Catalog ......................................................................................352 9.5 Sicherheit.....................................................................................................355 9.6 Designelemente und Ordner......................................................................361 9.6.1 Designspezifische Methoden in Database ....................................362 9.7 Locking und Signatur .................................................................................365 9.8 Service Tasks................................................................................................365 9.9 Zusammenfassung ......................................................................................366 10 View........................................................................................................................367 10.1 Updates, Indizes und Performance ............................................................369 10.2 Programmatische Manipulation des Designs von Views..........................370 10.3 Allgemeine Properties und Methoden.......................................................377 10.4 View und Document ..................................................................................380 10.5 ViewNavigator, View und ViewEntry.........................................................384 10.6 Locking ........................................................................................................401 10.7 DocumentCollection und ViewEntryCollection.......................................401 10.7.1 Methoden in DocumentCollection...............................................406 10.7.2 Methoden in ViewEntryCollection ...............................................410 10.8 Schleifen – View, DocumentCollection und ViewEntryCollection..........413 10.9 Kapselung von Schleifen.............................................................................420 10.10 Zusammenfassung ......................................................................................423 11 RichText............ .....................................................................................................425 11.1 Arbeiten mit RichText.................................................................................426 11.2 RichTextItem ...............................................................................................429 11.2.1 Methoden in RichTextItem ...........................................................429 11.3 RichTextNavigator ......................................................................................435 11.3.1 Methoden in RichTextNavigator...................................................437 11.4 RichTextRange ............................................................................................439 11.4.1 Methoden in RichTextRange .........................................................440 11.5 RichTextSection ..........................................................................................442 11.5.1 Methoden in RichTextSection .......................................................442 11.6 RichTextTable ..............................................................................................442 11.6.1 Methoden in RichTextTable ..........................................................443 11.6.2 Im- und Export von RichText – Erzeugen von RichText .............445 11.7 RichTextTab.................................................................................................452 11.7.1 Methoden in RichTextTab .............................................................453 11.8 RichTextDoclink .........................................................................................453 11.8.1 Methoden in RichTextDoclink......................................................453 11.9 RichTextParagraphStyle..............................................................................455 11.9.1 Methoden in RichTextParagraphStyle ..........................................456
XVI
Inhaltsverzeichnis
11.10 RichTextStyle...............................................................................................459 11.10.1 Methoden in RichTextStyle ...........................................................459 11.11 Zusammenfassung ......................................................................................461 12 Weitere Objekte .....................................................................................................463 12.1 Das Name-Objekt .......................................................................................464 12.1.1 Methoden in Name für RFC 821 und 822 Namen.......................466 12.1.2 Methoden in Name für Notesnamen (X.400) ..............................467 12.2 Arbeiten mit Zeit-Objekten........................................................................469 12.2.1 Methoden in DateTime .................................................................470 12.2.2 Methoden in DateRange................................................................473 12.3 Arbeiten mit internationalen Einstellungen..............................................474 12.3.1 Methoden in International............................................................474 12.4 Methoden in Agent .....................................................................................475 12.5 Zusammenfassung ......................................................................................478 13 Objekt- und Speichertechniken............................................................................479 13.1 Erweitern der Objekt-Funktionalitäten.....................................................480 13.1.1 Erweiterung der Document-Funktionalitäten .............................481 13.1.2 Pseudoimplementierung von lotus.domino.Document..............485 13.1.3 Implementieren von Domino-Java-Objekten ..............................490 13.2 Cashing von Domino-Objekten.................................................................498 13.2.1 Das DJCacheDocument.................................................................499 13.2.2 Performancevergleich ....................................................................512 13.2.3 Verwendung des DJCacheDocument ...........................................513 13.3 Domino-Objekte in Multithreading-Umgebungen..................................514 13.3.1 Gemeinsame oder geteilte Session ................................................515 13.3.2 DbDirectory darf nicht über Threads geteilt werden ..................516 13.3.3 Dokumente und Multithreading...................................................516 13.3.4 Profildokument und Multithreading............................................518 13.3.5 Dokumente löschen im Multithreading .......................................518 13.3.6 Parent- und Child-Objekte im Multithreading............................518 13.3.7 Multithreading mit gemeinsamer Session ....................................519 13.3.8 Multithreading ohne gemeinsame Session ...................................522 13.4 Domino und DB2 .......................................................................................523 13.4.1 Einführung .....................................................................................523 13.4.2 Struktur ..........................................................................................525 13.4.3 Sicherheit, Benutzer und Gruppen ...............................................529 13.4.4 Setup ...............................................................................................535 13.4.5 DB2 Access View ............................................................................545 13.4.5.1 DB2 Access View – Ein Beispiel.......................................549 13.4.6 Query View.....................................................................................558 13.4.6.1 Query View – Ein Beispiel ...............................................563 13.4.7 Federated Data – Entfernte SQL-Daten und Domino in DB2 ....566
Inhaltsverzeichnis
XVII
13.5 Zusammenfassung ......................................................................................566 14 Recyling von Domino-Objekten...........................................................................569 14.1 Notwendigkeit des Recyclings ....................................................................570 14.2 Grundregeln des Recyclings .......................................................................571 14.3 Besonderheiten in verschiedenen Umgebungen .......................................582 14.3.1 Recycle (Vector) .............................................................................582 14.3.2 Unterschiede zwischen R5 und R6/7 ............................................583 14.3.3 Temporäre Dateien ........................................................................584 14.4 Vereinfachung – Die DJBuch-GC-Klasse ..................................................585 14.5 Recycling im MultiThreading.....................................................................587 14.6 Zusammenfassung ......................................................................................587 15 Wer suchet, der findet............................................................................................589 15.1 Überblick über die Suchkonzepte ..............................................................590 15.2 Zugriff über UniversalID und NoteID.......................................................593 15.3 Suche über Views ........................................................................................594 15.4 Suche über Datenbanken mit db.search ....................................................600 15.5 Volltextindizes .............................................................................................601 15.6 Weitere Suchkonzepte (Domainsearch / browsergestützt) .......................605 15.7 Performanceoptimierung ...........................................................................608 15.8 Zusammenfassung ......................................................................................610
Teil 3
Domino-Enterprise-Anwendungen ...................................611
16 J2EE Infrastruktur .................................................................................................613 16.1 Application Server ......................................................................................616 16.2 Integrationsszenarien .................................................................................616 16.2.1 Domino als primärer Server..........................................................616 16.2.2 Application Server als primärer Server .........................................619 16.2.3 Domino und Application Server...................................................622 16.3 Single Sign On.............................................................................................624 16.4 Zusammenfassung ......................................................................................626 17 J2EE @ Domino.....................................................................................................627 17.1 Servlets.........................................................................................................628 17.2 JSP für Domino...........................................................................................628 17.3 Domtags – Installation und Verwendung..................................................630 17.4 Domtags ......................................................................................................634 17.4.1 domino:session ..............................................................................635 17.4.2 domino:db......................................................................................637 17.4.3 domino:view ..................................................................................637 17.4.4 domino:document .........................................................................641 17.4.5 domino:form..................................................................................644 17.4.6 domino:ftsearch .............................................................................647
XVIII
Inhaltsverzeichnis
17.4.7 domino:runagent ...........................................................................648 17.4.8 Weitere Domtags............................................................................648 17.5 Domutils......................................................................................................650 17.6 MVC Pattern für Domino-Anwendungen ................................................652 17.7 Domino-XML .............................................................................................654 17.7.1 NoteCollection ...............................................................................657 17.7.2 XML-Methoden in Domino-Objekten.........................................661 17.7.3 DxlExporter und DxlImporter......................................................663 17.8 Zusammenfassung ......................................................................................667 18 Domino-Projektentwicklung................................................................................669 18.1 Infrastruktur, Domino-Core-Applikationen.............................................670 18.1.1 Notes Client....................................................................................672 18.1.2 Domino Designer ..........................................................................673 18.1.3 Domino Administrator..................................................................673 18.2 Anwendungen, E-Mail, Replikation...........................................................676 18.3 Entwicklungsumgebungen .........................................................................679 18.4 Team ............................................................................................................684 18.5 Zusammenfassung ......................................................................................685 19 Debugging und Qualitätssicherung......................................................................687 19.1 Notwendigkeit und verschiedene Typen von Tests ...................................688 19.2 Agenten debuggen und testen ....................................................................690 19.3 Domino-Java-Anwendungen testen ..........................................................698 19.4 Remote Debugging von Java-Agenten .......................................................700 19.5 JUnit Tests....................................................................................................703 19.6 Logging........................................................................................................707 19.6.1 Notes-Log-Klasse ...........................................................................707 19.6.2 Logging ohne Domino-Session, Die DJLog-Klasse .....................711 19.6.3 Apache log4J und Domino............................................................715 19.7 Multithreading und Lasttests .....................................................................718 19.8 Notes-INI-Variablen ...................................................................................719 19.9 Zusammenfassung ......................................................................................729 20 Anhang...................................................................................................................731 20.1 Bekannte Speicherlimits in Domino-Datenbanken..................................732 20.2 Übersicht über die Domtags Tag Library ..................................................733 20.3 Literatur und Links.....................................................................................745 20.4 Disclaimer ...................................................................................................748 20.5 Index............................................................................................................749 20.6 Abbildungsverzeichnis................................................................................799
Teil 1
Notes und Domino Teil 1 von Java unter Lotus Domino wird Sie mit Domino infizieren. Grundlagen der Programmierung unter Domino und das besondere Konzept der Domino-Datenbanken und deren Datenstrukturen bieten eine besondere Chance im Rapid Development, insbesondere beim Einsatz von Java für Domino-Anwendungen. Das grundlegende Verständnis der besonderen Domino-Programmiertechniken wird zur Basis für die Verwendung der Domino-Java-Objekte, abgerundet durch einen kurzen historischen Exkurs der Entwicklung der Domino-Plattform. Neben der Einführung in die Datenstrukturen gehört ein Überblick über die Domino-Entwicklungswerkzeuge ebenso dazu, wie das Verständnis des Domino-Servers als Plattform, der Domino-Designelemente und der speziellen Techniken der Domino-Anwendungsentwicklung.
1
1 Das Domino-Prinzip
In diesem Kapitel: Die Vision von Lotus Notes und Domino Besonderheiten von Domino als Datenbank Geschichte
4
1 Das Domino-Prinzip
Lotus Domino ist als meistverbreiteter Application- und Collaborationserver für viele mittlere und große Unternehmen der Quasi-Standard für Groupware und Anwendungsplattform. Durch die gleichzeitig explosionsartige Verbreitung von Java als Basis vieler Anwendungen, die Einbindung von Java in Domino und die konsequente Bindung an Standards durch Lotus/IBM findet Java zunehmend Verbreitung als Basis für Domino-Anwendungen und führt gleichzeitig zur weiteren stetig steigenden Verbreitung von Domino. Einer der Erfolgsfaktoren von Lotus Domino ist die Einbindung von einigen wesentlichen Funktionalitäten schon von der ersten Version im Jahr 1989 an. Dies sind Prinzipien, die heute zur Basis der meisten Anwendungsarchitekturen im Bereich Collaboration geworden sind.
• •
•
•
• • •
• •
Hierzu gehören: Konsequente Umsetzung der Client-Server-Architektur und das Konzept des Remotezugriffs mit der Bereitstellung von Mechanismen für die Remoteeinwahl. Verschlüsselung und Signatur von Daten und Authentifizierung durch die Verwendung einer RSA-Public-Key-Infrastruktur, um Dokumente und Daten nicht nur sicher zu transportieren, sondern auch gegen unerlaubte Veränderung zu schützen. Namens- und Adressbücher. Bereitstellung von zentralen Namens-Diensten zur einfachen Administration und Verwaltung von Benutzern und Gruppen und deren Berechtigungen. Replikation von Daten. Die Replikation ist eins der Alleinstellungsmerkmale von Lotus Domino. Sie ermöglicht nicht nur den einfachen Austausch und sicheren Abgleich von Daten zwischen mehreren Servern, sondern auch zwischen Server und Client. So wurde von der ersten Stunde an die Ressourcen sparende Offlinearbeit am Remoteclient möglich. Schutz von Daten auf Datensatz- und Feldebene durch ACL-Zugriffskontrolllisten. Organisation von Daten auf Basis von Schemata. Portierbarkeit der Datenbanken und Anwendungen auf eine Vielzahl von Serverund Client-Plattformen und eine Vielzahl von Betriebssystemen und Prozessoren. Erreicht wird dies durch eine Architektur in Layern, die die Betriebssysteme von der eigentlichen Domino-Anwendung trennen, so dass der Code für die verschiedenen Plattformen parallel entwickelt werden kann. Dies gilt nicht nur für die Entwicklung der Kern-Anwendung Lotus Domino, sondern auch für die Anwendungen, die auf Basis von Lotus Domino entwickelt werden. Die Erstellung von Designelementen, wie Masken und Ansichten, aber auch von Code auf Basis von LotusScript, einem Visual-Basic-Derivat, oder auf Basis von Java erfolgt Plattform-unabhängig und kann auf allen Plattformen, die Lotus Domino unterstützt, eingesetzt werden. Remoteadministration. Customization der Datenbanken.
1.1 Replikation •
•
• • •
5
Rapid Development auf Basis von integrierten Entwickler-Tools. Lotus Domino war schon in der ersten Version eine Plattform, die neben einer Vielzahl von Basisanwendungen, wie E-Mail, Dokumentenmanagement, Diskussionsdatenbanken, eine Plattform für die Entwicklung neuer Anwendungen darstellte, die sich der durch die Plattform zur Verfügung gestellten Tools und Infrastruktur bedienen konnten. Verwendung internationaler Standards für alle Protokolle und Programmiersprachen. Kaum ein Produkt integriert so konsequent wie Lotus Domino internationale Standards, bei strikter Einhaltung der RFC. Konzept des RichText zur Eingabe von gestalteten Texten. Kontextuelle Hilfe. Verlinkung von Inhalten.
Neben der einerseits strikten Einhaltung von Standards und der gleichzeitig umfangreichen Implementierung vieler dieser Standards – so kann ein Anwendungsentwickler bei der Implementierung direkt auf vorgefertigte Server und Tools, von LDAP über POP und SMTP, von Newslettersystemen bis zum HTTP und nicht zuletzt auf eine robuste RSA-Public-Key-Infrastruktur zurückgreifen, ohne hier selbst neu entwickeln zu müssen – basiert Lotus Domino auf Prinzipien, die die große Verbreitung und gleichzeitig die Alleinstellung und den Erfolg von Lotus Domino begründen.
1.1
Replikation
Immer wieder wird die Replikation als Argument für Lotus Domino ins Feld geführt. Zu Recht. Kein anderer Application Server vereinfacht die Replikation von Daten derart radikal. Datenbanken werden über die gesamte Infrastruktur einfach als so genannte Repliken, man könnte auch von Instanzen reden, angelegt. Serververbindungen machen die Server miteinander bekannt und regeln den Zeitplan, nach dem der Datenabgleich erfolgen soll. Zugriffskontrolllisten sorgen für die Sicherheit. Der Vorgang der Replikation als solcher ist völlig transparent für die Anwender. Es werden alle Repliken im Serververbund – sofern die Replikation aktiviert ist – miteinander abgeglichen. Änderungen auf allen Instanzen werden auf alle anderen beteiligten Instanzen übertragen. Der Vorgang lässt sich so fein und granulär kontrollieren, dass selbst Änderungen einzelner Felder zwischen Datensätzen gemischt werden können. Konflikte, die durch Änderungen gleicher Datensätze auf verschiedenen Servern entstehen können, werden erkannt und in Konflikt-Datensätzen gespeichert. Bei der Java-Programmierung der Backendverarbeitung ist es wichtig das Konzept der Replikation zu verstehen und zu berücksichtigen. Das Verständnis verteilter Umgebungen ist daher Bestandteil dieses Buches (s. Kap. 2.3).
6
1 Das Domino-Prinzip
1.2
Schemabasiertes Objekt- und Speichermodell
Das Herz jeder Domino-Anwendung ist die Domino-Datenbank oder auch die „Note“-Datenbank – vereinfacht gesprochen ein Container für „Datensätze“. Das Konzept von Lotus Notes – an dieser Stelle wird zur Verdeutlichung der ursprüngliche Name des Lotus-Domino-Servers verwendet, der seinen Namen erst im Jahr 1996 erhielt – sieht vor, dass alle Bestandteile einer Anwendung in der Notes-Datenbank, dem NSF (Notes Storage Facility), File gespeichert werden. Gemeint ist, dass wirklich alles, vom Datensatz bis zur Zugriffskontrollliste im NSF-File gespeichert wird. Umgekehrt ist jeder Bestandteil einer Notes-Anwendung eine „Note“. Jedes Designelement – so heißen neben WYSIWYG Gestaltungselementen wie Masken, die Programm-Bibliotheken von Lotus Notes – wird in einer „Note“ gespeichert. Auch z.B. die ACL oder Datenbank-Eigenschaften werden in speziellen „Notes“ gespeichert. Dieses Konzept, nur eine einzige Datei für eine gesamte Anwendung als zentrales Repository anzulegen, ist unerlässlich für eine leicht zu administrierende Replikation und natürlich ein großer Vorteil für die Administration insgesamt. Selbstverständlich werden insbesondere die eigentlichen Daten in Notes gespeichert. Diese Notes werden auch als Dokumente bezeichnet. Jedes Dokument und jede „Note“ besteht nun wieder aus mehreren so genannten Items, die in einem ersten Ansatz als Felder bezeichnet werden können, vergleichbar mit den Spalten einer SQL-Tabelle, wobei später noch näher auf die Eigenschaft von Items eingegangen wird. Items können einerseits mehrere Werte und andererseits in bester objektorientierter Manier wiederum Items aufnehmen. Das Speicherkonzept von Lotus Domino unterscheidet sich jedoch wesentlich von SQL-Datenbanken. In diesen Datenbanken wird exakt festgelegt, welche Regeln den Daten zugrunde liegen und in welchen Verhältnissen diese zueinander stehen. Dies erfordert eine strikte Administration und umfangreiche Planung, auch in der späteren Handhabung der Daten. Das Konzept von Notes geht hier einen anderen Weg und speichert die Daten in losen Schemata, d.h. ein einzelnes Dokument kann beliebige Items oder auch Felder enthalten. Lediglich über Masken, die eine unverbindliche Vorgabe bei der Erstellung von Dokumenten geben, werden Dokumente kategorisiert und mit einem – zunächst – stringenten Schema versehen, das jedoch nicht bindend ist oder erzwungen wird. Am ehesten lässt sich die Datenstruktur von Domino mit der von XML vergleichen. Auch dort werden die Daten in Namens-Wert-Paaren gespeichert und sind nicht zwingend, wenn auch in der Regel üblich, einem Schema unterworfen. Die Tatsache, dass Lotus Domino nicht relational aufgebaut ist und insbesondere, dass die Schemata nicht erzwungen werden, wird oft als Kritik an Lotus Domino genannt und ist ungewohnt für Programmierer, die aus der SQL-Entwicklung kommen. Die Einhaltung eines Schemas und der daraus entstehende Verwaltungs- und Programmieraufwand ist jedoch nicht unerheblich und erfordert ein hohes Niveau an Kenntnissen und Erfahrungen. Dies hat die Begründer der Lotus-Notes-Datenstruktur dazu bewogen sich zugunsten eines leicht zu handhabenden und zu erweiternden Datenkonzeptes zu entscheiden.
1.3 Erweiterbarkeit
7
Die offene Schemastruktur und Objektorientierung der Lotus Notes NSF-Datei ist eine Stärke von Lotus Domino und ermöglicht die Erstellung von robusten Datenbankanwendungen im Rapid Development. Hieraus folgt nicht nur, dass in Lotus Domino Datenbanken Die offene Schemastruktur und Objektorientierung bevorzugt nicht strukturierte Daten, wie z.B. in Diskussionen geder Lotus Notes NSF-Datei ist eine Stärke von Losammelte Beiträge, gespeichert tus Domino und ermöglicht die Erstellung von rowerden können, sondern auch, busten Datenbankanwendungen im Rapid Devedass entsprechende Anwendunlopment. gen, wie Workflow- oder Kollaboration-Anwendungen bevorzugt realisiert werden. SQL-Datenbanken dagegen speichern bevorzugt strukturierte Daten, wie z.B. Produkt-Kataloge, die stark auf relationale Verknüpfungen zwischen Einzeldaten angewiesen sind, um Redundanzen zu vermeiden. Mit der Einführung von Version 7 von Domino hat dieses Bild eine grundlegende Erweiterung erfahren. Mit Lotus Domino R7 wird die Möglichkeit eingeführt, die Domino-Daten in einer DB2-Datenbank zu speichern, also in einer relationalen SQLDatenbank, wobei diese Art der Speicherung für den Notes-Programmierer oder Anwender zunächst transparent bleibt. Zusätzlich wird es die Möglichkeit geben, Domino Views anzulegen, die SQL-Daten aus DB2-Datenbanken direkt über SQL Queries laden können. Durch die Speicherung von Domino-Daten in DB2 einerseits und die Selektion von SQL-Daten andrerseits werden neue Möglichkeiten eröffnet. So wird es hierdurch zum Beispiel möglich sein, Daten aus mehreren Notes Views oder auch Notes-Datenbanken in einem neuen View anzuzeigen, der diese Daten entsprechend mischt. Dies war bisher in Domino nicht ohne weiteres möglich. Zum einen folgt hiermit IBM den Anforderungen des Marktes, SQL-Daten mit Hilfe der leicht zu erlernenden RAD Tools des Domino Designers anzeigen zu können und andrerseits die durch Domino-Anwendungen erzeugten Daten in einer relationalen Struktur speichern zu können. Gleichzeitig eröffnet IBM hiermit den Entwicklern die Möglichkeit das beste aus beiden Konzepten zu vereinen. Diese Weiterentwicklung bietet einen weiteren Schritt der Integrationsmöglichkeiten. Java-Anwendungen auf Basis von J2EE können noch nahtloser in Domino-Anwendungen integriert werden.
1.3
Erweiterbarkeit
Eines der Schlüsselkonzepte von Lotus Domino war und ist die Erweiterbarkeit. Zwar bietet Domino seit Beginn an Basismodule aus dem Bereich der Kollaboration, aber diese waren immer nur die Basis und ein Angebot an die Programmierer, sie zu verwenden und zu erweitern.
8
1 Das Domino-Prinzip
Bei der Entwicklung von Domino-Anwendungen kann zwischen zwei Elementen unterschieden werden, einerseits den so genannten Designelementen, mit denen die GUIs entwickelt werden und zum anderen programmatischen Code, der im Wesentlichen zur Programmierung von Backend-Prozessen verwendet wird. Die wichtigsten Designelemente, mit denen Notes-Datenbanken programmiert werden sind Masken und Ansichten. Masken sind Formular-Seiten, die zur BenutzerInteraktion, insbesondere zur Eingabe und Anzeige von Daten entwickelt werden. Ansichten dienen der Selektion von Daten und der Darstellung als Listen oder Tabellen. Sowohl in Masken als auch in Ansichten können so genannte @-Formeln eingesetzt werden, die im Wesentlichen der Validierung von Eingaben oder der berechneten Anzeige von Inhalten dienen. Durch die DesigneleJava war und ist der Booster für die Erweiterbarkeit mente können Anwendungen efvon Domino-Anwendungen, insbesondere durch fizient und schnell erstellt werden weiten Markt an Open-Source-Bibliotheken. den. Um im Backend DominoDaten verarbeiten zu können, führte Lotus im Januar 1996 für Version 4.0 die Programmiersprache LotusScript und im Dezember 1996 für Version 4.5 die Möglichkeit, per Java auf Domino-Objekte zuzugreifen, ein. Im Gegensatz zu LotusScript wurden für Java nur die Backend-Objekte zugänglich gemacht, so dass zunächst LotusScript wesentlich stärker verbreitet war. Inzwischen ist Java die Core-Sprache bei der Neuentwicklung von Domino-Anwendungen. Java bietet Möglichkeiten der Programmierung, die bisher mit LotusScript nicht möglich waren.
• • • • •
Hierzu gehören: Multithreading-Anwendungen Verarbeitung von URL-Verbindungen, wie z.B. HTTP, FTP, SSH etc. Einbinden von vorgefertigten Open-Source-Bibliotheken Verarbeitung von Streams (diese Möglichkeit gibt es seit Version 6 auch für LotusScript) SQL-Datenbankbindung durch native JDBC-Treiber
Durch den Einsatz von Java ist Domino zu einem vollwertigen Application Server erwachsen, der viele der J2EE-Standards unterstützt. Mit der Version 6 bietet Lotus die Möglichkeit, zusätzlich zu Domino einen WebSphere Application Server einzusetzen, der über Connector Plugins mit Domino verbunden wird. Hierdurch eröffnet sich die Möglichkeit vollwertige J2EE-Anwendungen zu schreiben, die gleichzeitig Zugriff auf die Domino-Daten haben. Hierdurch ergibt sich ein typisches Szenario, wie z.B. für ein Domino-basiertes Web Content Management. Die Redaktion bedient sich des Notes Client, um Inhalte zu pflegen. Gleichzeitig wird durch WebSphere ein Darstellungslayer realisiert, um die Daten in einer leistungsfähigen und robusten Web-Anwendung darzustellen. Für die Verwendung von Java bei der Programmierung und Erweiterung von Domino-Anwendungen ergeben sich drei wesentliche Einsatzgebiete:
1.4 Zugriffskontrolle und Verschlüsselung • • •
9
Backendverarbeitung von Domino-Daten Web-Anwendungen mit Zugriff auf Domino-Daten Enterprise-Datenbankanbindungen für Domino-Applikationen Auf alle drei Anwendungsgebiete wird in diesem Buch eingegangen werden.
1.4
Zugriffskontrolle und Verschlüsselung
Domino ist die erste wichtige kommerzielle Software, die Verschlüsselung, Signatur und Authentifizierung auf Basis von öffentlichen RSA-Schlüsseln anbot [Lotus, History]. Gleichzeitig ist dies eines der Hauptargumente, das am häufigsten für Lotus Domino ins Feld geführt wird. Zu Recht: Lotus Domino, aber auch seine Komponenten-Server wie LDAP oder HTTP werden zu den sichersten am Markt gezählt. Durch das einfache Management der Benutzer in den öffentlichen Notes-Adressbüchern, die entweder nativ oder per LDAP angesprochen werden können, bietet sich dem Java-Programmierer ein robustes und ausgereiftes Framework für die Verwaltung von Rechten. Das Konzept dieses Frameworks ist ausgereift und sehr granulär steuerbar. In einer Abstufung der Rechte, vom Server bis hin zu einzelnen Feldern in Dokumenten, kann der Programmierer genau festlegen, welche Daten eingesehen werden können. Durch die RSA-Infrastruktur stehen reichhaltige Werkzeuge zur Verschlüsselung von Daten und Datenströmen und Identifizierung von Benutzern und deren Rechten zur Verfügung. Darüber hinaus können Schlüsselpaare definiert werden, mit denen Benutzer, die Zugriff auf diese Schlüssel haben, ihre Daten oder Teile davon schützen können. Dies wird im Wesentlichen durch das Speicherkonzept der losen Schemata ermöglicht, da hierdurch objektorientiert zu schützende Items definiert werden können. Der Zugriff auf Daten wird in mehreren Schritten abgesichert. Zunächst wird der Zugriff auf den Server überprüft. Der Zugriff erfolgt immer gegen das so genannte Notes-Adressbuch. Allerdings kann das Adressbuch über die so genannte Directory Assistance entweder durch sekundäre Adressbücher oder durch LDAP-Verzeichnisse erweitert werden. So ist es zum Beispiel einfach möglich, ein Active Directory in ein Notes-Adressbuch einzubinden. Grundsätzlich wird beim Zugriff nach zwei Arten unterschieden. Zum einen der Zugriff über den Notes Client und zum anderen der Zugriff als so genannter Web-Access. Der Zugriff über den Notes Client erfolgt immer mittels einer ID-Datei, in der die privaten RSA-Schlüssel gespeichert sind. Beim Web-Zugriff können verschiedene Authentifizierungsmethoden gewählt werden. Es stehen Login-Verfahren mit X.509Client-Zertifikaten, mit Name und Passwort oder das anonyme Login zur Verfügung. Nach der Überprüfung auf den Serverzugriff erfolgt eine Überprüfung der ACL, der so genannten Zugriffskontrollliste (Access Control List). In dieser werden die einzelnen Rechte nach Benutzern, Servern und Gruppen eingetragen, die unterschiedliche Rechte als Leser, Autor, Datenbankdesigner oder Manager (s. auch Kap. 9.5) erhalten können.
10
1 Das Domino-Prinzip
Internet
Notes-IDDatei
Internet User
Notes Client
Stufe 1 - Server
Stufe 1 - Server Prüfung des Serverzugriffs Prüfung des Internet-Passworts
Prüfung des Serverzugriffs Prüfung der Notes ID
Domino Stufe 2 - Datenbank Prüfung der ACL Prüfung der Rollen
Stufe 3 - Dokument Prüfung von Leserund Autorenfeldern
Stufe 4 - Feld Feldverschlüsselungen
Abb. 1-1 Domino-Sicherheit
Zusätzlich können in der ACL Rollen definiert werden, so dass einzelne Bereiche oder Daten von Anwendungen abhängig von dem Erhalt dieser Rollen gesteuert werden können. Mit so genannten Leser- und Autorenfeldern können dann auf Dokumentenebene für jedes einzelne Dokument explizit Rechte vergeben werden. Mit Leserfeldern wird festgelegt, wer ein Dokument lesen darf. Mit Autorenfeldern wird festgelegt, wer ein Dokument verändern darf. Zusätzlich kann dann noch auf Feldebene eine Verschlüsselung erfolgen, so dass derart verschlüsselte Felder nur von Inhabern dieses Schlüssels gelesen werden können. Da diese Schlüssel in der ID-Datei gespeichert werden, kann diese Art des Schutzes nur beim Zugriff über den Notes Client, nicht jedoch bei der Benutzung über Browser erfolgen (Näheres hierzu auch in Kapitel 7.5.1ff.)
1.5 Geschichte
11
Im Einzelnen ist die Sicherheitsstruktur wie folgt angelegt: 1 – Serversicherheit – Überprüfung der Notes ID, bzw. des Internet-Benutzernamens und Passwortes. 2 – Datenbanksicherheit – Überprüfung der ACL. 3 – Dokumentensicherheit – Leser- und Autorenfelder. 4 – Sicherheit auf Feldebene – verschlüsselte Items (s. Abb. 1-1). Insbesondere das Konzept der Leser- und Autorenfelder und der Feldverschlüsselung wird erst durch das für Lotus Domino einzigartige Konzept der offenen Schemastruktur ermöglicht. Jeder Datensatz wird in einem Dokument zusammengefasst. Dieses Dokument kann Felder (Items) enthalten, die von dem einen Benutzer gelesen werden können, von einem anderen nicht. Ob das Dokument diese Felder tatsächlich enthält ist nicht relevant, da es durch das Schema nicht erzwungen wird. Dadurch hat ein Benutzer, der einen Schlüssel nicht zur Verfügung hat, dennoch die Möglichkeit ein entsprechend reduziertes Dokument mit den ihm zur Verfügung stehenden Feldern zu erzeugen. Die Java-Programmierung greift, wie bereits erwähnt, nur auf die Backend-Objekte von Domino zu. Daher ist an dieser Stelle erwähnenswert, dass auch die JavaZugriffe, egal ob diese über DIIOP1 (s. Kap. 5.3), über einen Web-Browser, oder über einen Lotus-Domino-Agenten erfolgen, Zugriff auf Schlüssel haben können. Diese müssen in diesen Fällen in der so genannten Server-ID gespeichert sein. Die ServerID ist der RSA-Schlüssel mit den Private Keys, unter denen die Server-Prozesse laufen.
1.5
Geschichte
Die ursprüngliche Idee von Lotus Notes reicht zurück ins Jahr 1973, als am Computerbased Education Research Laboratory (CERL) an der Universität Illinois PLATO Notes auf der Basis von Host-Systemen entwickelt wurde. Diese Software diente dazu, Bug-Reports an Projekt-Administratoren zu senden, die diese Bug-Reports wiederum mit Antworten versehen konnten. Dieses frühe Diskussionsinstrument sah bereits vor, Dokumente mit den User IDs der Benutzer zu versehen, um nachträgliches Löschen oder Verändern von Inhalten auszuschließen. Unter anderem ist hier auch schon ein für Notes sehr typisches Szenario sichtbar, nämlich dass Dokumente in Dokument- und AntwortdokumentHierarchien dargestellt werden. Drei Jahre später wurde PLATO zu PLATO Group Notes erweitert und konnte in dieser Version bereits zwischen privaten und öffentlichen Inhalten unterscheiden, Zugriffskontrolllisten verwalten und Links zwischen Dokumenten und anderen PLATOSystemen herstellen. Auf Basis von PLATO Group Notes entwickelte ein Team um 1
DIIOP = Domino IIOP = Internet Inter-ORB Protocol; ORB = Object Request Broker, als Teil der CORBA-Architektur. CORBA = Common Object Request Broker. CORBA wird von der Object Management Group OMG spezifiziert. [CORBA], [OMG]
12
1 Das Domino-Prinzip
Ray Ozzie, Tim Halvorsen und Len Kawell gegen Ende des Jahres 1984 mit der Unterstützung von Lotus die Basis von Lotus Notes, das aufgrund der Entwicklung des PCs nun auf DOS oder OS/2 laufen sollte, unter dem Dach von Iris Associates Inc. Für die Ur-Version von Notes waren die Funktionen E-Mail, Diskussionen, Telefonbücher und Dokumenten-Datenbanken geplant. Aufgrund der damals langsamen Netzwerke und der noch nicht weit entwickelten PC-Systeme wurde es zu einem der großen Ziele von Notes, diese Hürden zu überwinden. Auf der Basis von zentralen PC-basierten Gruppen-Servern, die bereits mit anderen solchen Servern Daten replizieren konnten, entstand so die erste wichtige Groupware-Software. Ein weiterer wesentlicher Punkt war zu dieser Zeit die Entscheidung, kein OutOf-The-Box-Produkt zu erstellen, sondern eine Plattform zu bieten, die es den Benutzern ermöglicht die Anwendungen an ihre Bedürfnisse anzupassen. Diese Entscheidung spiegelt sich auch in der heutigen Version wider. Vor-Versionen von Lotus Notes wurden bereits 1986 eingesetzt, wobei die Rechte daran 1987 von Lotus gekauft wurden. Die Version 1.0 von Lotus Notes wurde dann 1989 ausgeliefert und hatte zu dieser Zeit wesentliche Funktionen des heutigen Lotus Notes implementiert. Hierzu gehören Encryption und Signaturen, Dial-Up und Import/Export-Funktionalität, Zugriffskontrolllisten, Online Help, die @-Formelsprache und Funktionen für die Remoteadministration. Die Versionen 1.1 und 2.0 und 3.0 brachten zum einen neue Fähigkeiten für mittlere und große Unternehmen, so dass auch 10.000 und mehr Benutzer unterstützt wurden, zum anderen weitere Funktionen, wie zum Beispiel die C API, die Unterstützung von RichText und Funktionen für die Volltextsuche, wobei insbesondere mit der Version 3.0 die Cross-Plattform-Fähigkeit weiter ausgebaut wurde. 1994 kaufte Lotus Iris Associates und 1995 kaufte IBM Lotus, insbesondere wegen Lotus Notes. Die Versionen 4.0 und 4.5 spiegeln die Entwicklung dieser Zeit wider, so dass etliche der Internet-Protokolle Einzug in die Notes-Technologie fanden. Neben Socks und HTTP-Proxy wurden SMTP/MIME, POP3 und HTTP unterstützt, wobei Lotus Notes mit einem eigenen HTTP-Server aufwartete. Der Notes-Server wurde mit Version 4.5 in Domino umbenannt, wobei der Client weiter Lotus Notes Client hieß. Version 4.0 erhielt mit LotusScript, basierend auf Visual Basic, eine erste Programmiersprache, mit der komplette Anwendungsentwicklung betrieben werden konnte. Bereits in Version 4.5 wurde Java unterstützt, so dass Agenten komplett in Java programmiert werden konnten. Lotus Domino konnte nun die meisten der Notes-Funktionalitäten und Anwendungen auch über den Web-Browser darstellen. Hierzu gehört auch die Fähigkeit des Notes Client, Internetdienste abzurufen und umgekehrt Notes-Dienste wie E-Mail über den Browser abzurufen. Mit Version 5.0 erhielt Lotus Domino 1999 ein komplettes Face-Lifting. Die etwas aus der Mode gekommene Oberfläche wurde vollständig überarbeitet. Technisch wurde die Integration von Internet-Funktionalitäten vorangetrieben. Standard-Protokolle wurden integriert, wie LDAP, JavaScript und CORBA, bereits vorhandene Technologien, wie MIME und SMTP wurden überarbeitet und insbeson-
1.5 Geschichte
13
dere streng an den Industriestandards ausgerichtet. Gleichzeitig wurden die Entwickler- und Administrationswerkzeuge (Notes Designer, Notes Administrator) verbessert. Alle wichtigen Internet-Protokolle und -Standards werden mit der Version 5.0 unterstützt. Mit einem eigenen Servlet Manager geht Domino seine ersten Schritte in Richtung Web Application Server. Zusätzlich bietet diese Version Plugins und Schnittstellen zum Apache Webserver, Tomcat Application Server und dem IBM-eigenen WebSphere Application Server, die so die Brücke zur J2EE-Welt schlagen. Java hat in alle Teile des Domino-Servers Einzug erhalten, wird aber nur in der Version 1.1.8 unterstützt, so dass hier einer der wichtigen Kritikpunkte an dieser Version ansetzt. Der Lotus Notes Client in Version 6.0 und 6.5 wurde erneut optisch überarbeitet und ähnelt nun immer stärker Portal-orientierten Collaboration-Anwendungen. Viele der langjährigen Basis-Funktionalitäten wie Kalender, Aufgaben und EMail erhalten in diesen Versionen eine Überarbeitung, um sie an die modernen Anforderungen einer Groupware-Software anzupassen. So wurden Spam-Filter und Messaging-Funktionen eingebaut bzw. verbessert. Gleichzeitig wurde der Web-Zugriff auf die Notes-Funktionalitäten, die ihren Niederschlag im iNotes Web Access Client fanden, erheblich verbessert, insbesondere die Offlineservices sind eine Besonderheit in diesem Bereich und ermöglichen es Anwendern über den Browser zu arbeiten, ohne dauerhaft online sein zu müssen. Der Server wurde in vielen Bereichen überarbeitet und verbessert. In vielen Bereichen konnten erhebliche Geschwindigkeitssteigerungen erreicht werden. Native Internet-Protokolle sind nun zur Basis vieler Prozesse im Domino-Server geworden, hierzu gehört z.B. nun die nahtlose Integration einer R.509-Zertifikat- und S/MIMEInfrastruktur, die Möglichkeit, LDAP als Protokoll für Namens-Anfragen zu verwenden und die Implementierung vieler XML-Funktionen in die Domino-Core-Klassen von Java und LotusScript. Java wartet nun mit J2SE 1.3.1 auf. Um die fehlenden J2EE-Funktionalitäten auszugleichen hat sich IBM für ein neues Lizenzierungsmodell entschieden, das es erlaubt mit einer Domino-Lizenz einen WebSphere Application Server neben einem Domino-Server zu betreiben, sofern beide auf der gleichen Maschine betrieben werden und WebSphere nur auf Domino-Objekte zugreift. Diese beiden Server können nahtlos miteinander integriert werden. Single Sign On gehört ebenso zu den Standards wie ausführliche Custom-Tag-Bibliotheken (Domtags Tag Library) für den Einsatz von JSP. Die Entwickler-Umgebung von WebSphere, der WebSphere Application Developer (WSAD), wird mit dem Lotus Domino Toolkit for WebSphere Studio ausgeliefert, einem Plugin, das die Erstellung von JSP auf Basis der Domtags Tag Library erleichtert. Dieses Toolkit kann auch mit Eclipse, einem der inzwischen beliebtesten Java IDEs, das als Open-Source-Projekt von IBM gefördert wird, betrieben werden. Durch diese Strategie wird eine Entwicklung deutlich, die einen erheblichen Einfluss auf den Einsatz von Lotus Domino hat: Es bilden sich verschiedene Layer heraus, die von den verschiedenen Servern bedient werden. Lotus Domino konzentriert sich wieder stärker auf seine ureigenen Fähigkeiten, der Collaboration und Dokumenten-Management, das sich zum Beispiel im Redaktion-Layer eines Content Ma-
14
1 Das Domino-Prinzip
nagements ausprägen kann. Backend-Batch-Verarbeitung, aber auch typische WebApplikationen, also z.B. der Display Layer eines typischen Web-Content-Management-Systems und MVC-Pattern-basierte Anwendungen (Modell-View-Controller, s. auch Kap. 17.6), die eine robuste J2EE-Application-Server-Umgebung benötigen, finden im WebSphere Application Server eine Plattform. Die Fähigkeiten von Domino, Java und Servlets zu unterstützen, erleichtern diese Entwicklung erheblich, können doch Anwendungen zunächst ausschließlich auf Domino betrieben und später für einen Application Server erweitert werden. Version 7, die Ende 2005 ausgeliefert wurde, stellt einen weiteren Meilenstein für Domino dar. Diese Version bricht alte Datenstrukturen auf und ermöglicht die Speicherung der Domino-Daten im RDBMS/SQL-Umfeld. Ab Version 7 wird es möglich sein, Domino-Daten direkt in DB2-Datenbanken abzulegen und diese aber im gewohnten Domino-Umfeld, also in Masken und Views darzustellen. Java-Entwicklern steht ab Version 7 das JDK 1.4.1 zur Verfügung. Linux findet seit Domino Version 5 Einzug in die Multiplattform-Fähigkeit von Domino und ist mit Version 7 zum Standard herangereift. Die Performance unter Linux soll in dieser Version erheblich verbessert werden. Ein weiterer Schwerpunkt sind viele neue Monitoring-Funktionen für das Server Management und eine neue Multi-Domain-Fähigkeit, so dass mehrere Notes Domains auf einem Server betrieben werden können. Die Entwicklung der Internet-Standards findet erneut Einzug in die DominoEntwickler-Tools. Unter anderem können nun WebServices direkt im Domino Designer (dem Domino IDE) entwickelt werden.
1.6
Lotus Domino und Java
Java fand bereits Ende 1996 mit Version 4.5 Einzug in Domino. Es gehört zu den Grundsatzentscheidungen von IBM, international anerkannte und verbreitete Standards für Protokolle, Frameworks, Konzepte, Plattformen und Sprachen zu unterstützen. Hierzu gehört z.B. die Entscheidung, Linux als Plattform in die Entwicklung aufzunehmen und konsequent zu unterstützen. Java ist ein zentraler Punkt in dieser Strategie. Java ist die Core-Programmiersprache im Application-Server-Bereich. Durch diese frühzeitige Unterstützung ist das Java-Framework zu einem robusten Entwickler-Werkzeug für Domino erwachsen, obwohl die Gemeinde der Domino-Entwickler erst in letzter Zeit nach und nach von LotusScript auf Java umschwenkt, oder zumindest ihr Wissen in dieser Richtung erweitert. Wie bereits zuvor aufgezeigt hat der rasant wachsende Application-Server-Markt einen großen Einfluss auf die Weiterentwicklung von Lotus Domino, so dass Java eine zentrale Bedeutung zukommt. Im Objektmodell von Lotus Domino wird unterschieden nach den so genannten UI-Klassen und den Backend-Klassen. Die UI-Klassen bilden alle Objekte ab, die in direkter Interaktion mit dem Benutzer stehen. Öffnet z.B. ein Benutzer ein Dokument, so werden die Daten dieses Dokuments mit einer so genannten Maske dargestellt.
1.6 Lotus Domino und Java
15
Die UI-Klassen bilden die meisten Funktionen und Objekte ab, die im Lotus Notes Client zur Verfügung stehen. Das derart geöffnete Dokument wird in den UI-Klassen abgebildet. Mit den Methoden dieser Klassen können dann z.B. Cursor bewegt oder Fenster geschlossen werden. Gleichzeitig hat ein Dokument eine Repräsentation in einer Backendklasse, in Java ist dies die Klasse Document. Eine solche Instanz eines Dokuments kann gleichzeitig mit der UI-Instanz existieren. Die Instanz des Backend-Objekts hat jedoch keinen Zugriff auf Objekte oder Funktionen, mit denen der Benutzer interagiert, sondern lediglich auf den Datensatz als solchen. Bei der Klasse Document sind dies alle Items (Felder) eines Dokuments und Methoden zur Manipulation, die unabhängig vom Benutzer stattfinden können, wie z.B. das Verschlüsseln (document. encrypt()) oder Speichern (document.save ()) von Daten, oder das Versenden von Daten als E-Mail (document.send()). Java hat, im Gegensatz zu LotusScript, ausschließlich Zugriff auf die Backend Repräsentation eines Notes-Objektes.
• • •
Dies prädestiniert Java für folgende Anwendungsbereiche: Backend- und Batch-Verarbeitung von Domino-Daten Web-Anwendungen mit Zugriff auf Domino-Daten, auch über Remotezugriffe und Application Server wie WebSphere Enterprise-Datenbankanbindungen für Domino-Applikationen
Die fehlende Möglichkeit, Frontend-Objekte durch Java zu steuern, bedeutet nicht, dass Java nicht für Frontend-Anwendungen (Lotus Notes Client-Anwendungen) eingesetzt oder aus diesen angesteuert werden könnte. Hierzu mehr in Kapitel 4. Ohnehin haben sich vorwiegend die Backend-Klassen in den letzten Versionen weiterentwickelt. In ihnen liegt die eigentliche Macht des Domino API. Oft wird LotusScript mit Java verglichen und versucht herauszukristallisieren, wo die Vor- und Nachteile der beiden Programmiersprachen im Zusammenhang mit Lotus Domino liegen. Java ist die modernere der beiden Programmiersprachen und blickt auf ein weites Umfeld an Open-Source-Projekten und -Bibliotheken, die es dem Programmierer ermöglichen, auf vorgefertigte (Teil-)Ergebnisse zurückzugreifen und so die Entwicklungszyklen erheblich zu beschleunigen. Auch wenn LotusScript im Ansatz auch Objektorientierung und Klassen-Strukturen bietet, fällt bei der Bevorzugung einer objektorientierten Sprache eindeutig die Wahl auf Java. Interessant ist, versucht man beide Sprachen zu vergleichen, dass beide auf die gleichen C++ Objekte im Hintergrund zugreifen, denn die Domino-Java-Objekte sind im Wesentlichen nicht als originärer Java-Code implementiert, sondern basieren auf dem Java Native Interface JNI. Folglich ist sichergestellt, dass äquivalente Methoden absolut äquivalent arbeiten. Durch die besseren Möglichkeiten in Java, das Speichermanagement zu beeinflussen, lässt sich, wie später gezeigt werden wird, mit Java eine nicht unerhebliche Performance-Steigerung erreichen.
16
1 Das Domino-Prinzip
Durch die bereits in der Core-Sprache verankerten IOStream-Klassen kann Java erheblich besser mit Daten- oder Character-Strömen umgehen, wobei erwähnt werden soll, dass IBM mit der Version 6.0 auch Stream-Klassen für LotusScript ausliefert. Sollen Multithreading-Anwendungen programmiert oder URL-Verbindungen realisiert werden, muss auf Java zurückgegriffen werden, da diese Möglichkeiten nicht für LotusScript zur Verfügung stehen. Neben den nativen XML-Methoden, die das Domino API für Java (und LotusScript) zur Verfügung stellt, bietet es sich an, bei Projekten, die XML verarbeiten, auch die XML-Klassen des Apache Projektes (Xerxes, Xalan, FOP und andere) anzusehen [Apache, XML]. Durch die recht alte JVM 1.1.8, die mit Domino R5 ausgeliefert wird, empfiehlt es sich Java-Anwendungen für Domino R6 zu entwickeln, wo dies möglich ist, da diese Version mit JDK 1.3.1 ausgeliefert wird. Sollen JSP- oder Servlet-2.0-Funktionalitäten zum Einsatz kommen, können die Plugins für Domino oder WebSphere (oder auch Tomcat und sogar JBoss – hierzu mehr in Kapitel 16.1ff.) verwendet werden. Mit diesen modernen J2EE-Containern lassen sich Domino-Daten optimal verarbeiten, z.B. auch über eine Remoteverarbeitung über IIOP. Müssen Domino-Daten regelmäßig periodisch oder eventgesteuert verarbeitet werden, bietet es sich an, diese als Domino-Agenten (so heißen die Batch-Prozesse unter Domino) umzusetzen. Durch diese vielfältigen Schnittstellen ist Java zur der wichtigsten Programmiersprache herangewachsen, mit der Domino-Prozesse und -Daten verarbeitet werden.
1.7
Lotus Domino als Datenbank
Der Lotus Domino Server ist in seinem Selbstverständnis weit mehr als eine Datenbank. Lotus Domino ist vielmehr eine Entwickler-Plattform für Groupware-Anwendungen in verteilten Umgebungen. Daher stellt Domino zusätzlich zu den Datenbanken viele Dienste zur Verfügung, die für die Programmierung von Unternehmensanwendungen herangezogen werden können. Einer der wichtigsten Dienste in der Domino-Infrastruktur ist das Namensund Adressbuch. Hier werden alle Benutzer- und Berechtigungsinformationen, aber auch alle Konfigurationen für den Server gespeichert. Ganz im Sinne der Erweiterbarkeit steht seit Version 5 dieses Adressbuch als LDAP-Dienst zur Verfügung. Weiterhin stehen folgende Basisdienste zur Verfügung: • • • • • • • •
POP3, IMAP, SMTP, jeweils auch über einen SSL-verschlüsselten Kanal Domino Router LDAP, auch über SSL News IIOP, auch über SSL HTTP und HTTPS Indexer (erstellt Volltext-Indizes über einzelne Datenbanken) Domänen übergreifender Volltext-Index über Datenbanken und File-Systeme
1.7 Lotus Domino als Datenbank • •
17
Agent Manager (zur periodischen und eventgesteuerten Abwicklung von Prozessen – auch Java-Anwendungen können hierdurch gesteuert werden). diverse administrative Domino-spezifische Dienste, wie z.B. Replikation, Schedule Manager, Statistik und Monitoring
Alle diese Dienste basieren auf den RFC-Standards und können über die üblichen Java-Klassen angesprochen werden. Domino basiert auf einem Namens- und Adressbuch, dem Domino Directory oder auch „names.nsf“, das auch als LDAP-Verzeichnis betrieben werden kann und so nicht nur in der Java-Programmierung vielfältig genutzt werden kann, sondern auch Drittanwendungen zur VerfüBeispiel für eine Select-Formel einer Ansicht: gung steht. Die Domino-Datenbanken SELECT selbst basieren (vgl. aber auch die Verwendung von Domino-Da((@LowerCase(Form) = "logfile") | tenbanken in DB2, s. Kap. 13.4), (@LowerCase(Form) = "fo_logfile")) wie bereits erwähnt, auf einer vereinfacht gesprochen losen Sammlung von Datensätzen oder auch „Notes“. Organisiert werden die Daten durch den Aufbau von Indizes über so genannte Ansichten (Views). Ansichten werden in der Regel im IDE, dem Domino Designer, angelegt. Die in den Ansichten selektierten Dokumente werden über Select-Formeln ausgewählt, die den SELECT-Formeln des SQL recht ähnlich sind. Ansichten können auch über das Backend aus Java heraus generiert werden, sollten aber jeweils nur einmal angelegt werden. Wichtig für die spätere SeÜber sortierte Spalten legt Domino automatisch Inlektion von Dokumenten ist, dass dizes an. mindestens die erste Spalte einer Ansicht sortiert ist. Diese Eigenschaft kann entweder über den Domino Designer oder programmatisch erstellt werden. Über sortierte Spalten legt Domino automatisch Indizes an. Daten werden entweder über das UI Frontend von Lotus Domino in Masken (Form) erfasst und in Dokumenten gespeichert. Dokumente besitzen neben einigen Standard-Feldern, für Berechtigungen, Revisionen und Zeitstempel, eine beliebige (nur durch einige Speicherlimits begrenzte) Anzahl von Items, in denen die Daten gespeichert werden. Der große Vorteil der Dokumente ist, dass weder die Größe einzelner Felder, noch die Anzahl der Felder in einem Dokument durch ein Schema erzwungen wird. Daher sind Dokumente nur in einer starken Vereinfachung mit einer Zeile einer SQLTabelle zu vergleichen. Bei der Backend-Programmierung in Java werden die Dokumente als Instanz der Klasse Document abgebildet. Felder werden über spezielle getter- und setter-Methoden hinzugefügt, verändert oder gelöscht.
18
1 Das Domino-Prinzip
Es gibt verschiedene Möglichkeiten, Dokumente in Domino-Datenbanken zu selektieren. Hierzu mehr in Kapitel 7.1.4ff. Der wichtigste und effizienteste Weg erfolgt über Ansichten. Diese Ansichten können mit verschiedenen Methoden durchsucht werden. getAllDocumentsByKey () durchsucht z.B. die erste(n) Spalte(n) einer Ansicht anhand eines Schlüsselwortes. Eine Java-basierte Domino-Datenbank, oder besser gesagt Domino-Anwendung besteht in der Regel aus einigen Masken und Ansichten (und vielen anderen Gestaltungselementen, s. Kap. 3 und 6), die das graphische Interface für den Benutzer darstellen und Java-Programmen, die im Hintergrund Funktionen ausführen, bzw. Funktionen, die über Aktionsbuttons durch den Anwender ausgelöst werden. Alternativ kann Domino natürlich auch aus stand-alone Java-Programmen oder auch mit Servlets und JSP angesprochen werden. Nicht zuletzt können alle DominoAnwendungen oft nahtlos, ohne zusätzliche Programmierung, über einen Browser angezeigt werden. Hier kommen dann, neben den nativen Rendering-Möglichkeiten von Domino, die es erlauben, Notes-Masken „on the fly“ im Browser anzuzeigen, die J2EE-Techniken zum Einsatz. Insbesondere das Lotus Domino Toolkit for WebSphere Studio ist ein sehr hilfreiches Plugin für den Einsatz der Domtags Tag Library, mit der Domino-Objekte durch die Verwendung von JSP dargestellt werden können.
1.8
Die Client-Server-Umgebung Lotus und Domino
Lotus Domino war schon immer mit Remotefähigkeiten ausgestattet, eines der wichtigen Features von Domino war für viele Jahre die Remoteeinwahl in einen Domino Server über Telefon. Anwender konnten Domino-Applikationen lokal auf ihren Rechnern betreiben und bedienen. Sollten Daten zwischen dem Client-System und dem Server aktualisiert werden, wählte der Benutzer sich ein und über die Replikation erfolgte ein Datenabgleich. Der gesamte Datenabgleichsvorgang ist für den Benutzer transparent. Inzwischen sind die Leitungskapazitäten im LAN und WAN erheblich größer, so dass sich die Infrastruktur von Domino ebenfalls verändert hat. Immer mehr Remoteanwender wählen über WAN direkt die Notes-Anwendungen an oder verbinden sich mit ihrem Home-Server über so genannte Durchgangsserver, die zur Verbindung von Netzwerken aus Sicherheitsgründen zwischengeschaltet werden. Die Replikation ist weiterhin ein wichtiges Instrument, um in verteilten Umgebungen die Anwendungsdaten verschiedener Server aktuell zu halten und um verschiedene Netzwerke und Sicherheitszonen miteinander abgleichen zu können. So genannte Replikationsregeln ermöglichen es, in vorgelagerten Servern, die dem Internet „ausgesetzt“ sind, Daten entgegenzunehmen und „nach innen“ zu replizieren, so dass auf Servern, in weniger sicheren Umgebungen, Daten immer nur kurz vorliegen und dann per Replikation in das sichere Intranet transportiert werden können. Die Netzwerkfähigkeit von Domino in seinen verschiedenen Ausprägungen hat einen großen Einfluss darauf, wie in der Java-Programmierung auf Domino zugegriffen wird.
1.8 Die Client-Server-Umgebung Lotus und Domino • • • •
19
Zugriff lokal auf Domino-Datenbanken des Clients (Kapitel 4.3) Zugriff lokal auf einen Domino-Server (Kapitel 4.3) Zugriff per DIIOP (Domino IIOP) auf einen entfernten Server (Kapitel 5.3.2) Zugriff auf eine entfernte Domino-Datenbank auf einem als vertrauenswürdig eingestuften Server über die aktuelle Notes Session (seit Domino Version 6)(Kapitel 5.3)
Um eine Verbindung zu Domino-Datenbanken, egal ob lokal oder remote, herzustellen, bedarf es immer einer so genannten Notes Session. Eine Notes Session wird bei der Java-Programmierung innerhalb einer Domino-Datenbank, also bei der Erstellung eines Java-Agenten, über AgentBase.getSession() zur Verfügung gestellt. Für alle anderen Arten der Verbindungsherstellung muss über die Klassen NotesThread oder NotesFactory eine Session aufgebaut werden. Diese verschiedenen Zugriffe können auf unterschiedlichen Wegen ausgelöst werden: • • • •
Stand-alone Java-Programm (Kapitel 4.4) Domino-Agent (Kapitel 4.10) Servlet im Domino Servlet Manager (läuft im HTTP-Task des Domino-Servers)(Kapitel 5.2.1) Servlet oder JSP in einem Servlet Manager außerhalb von Domino (z.B. WebSphere Application Server) (Kapitel 16 und 17)
Diese unterschiedlichen Methoden erfordern verschiedene Authentifizierungen. Ein Servlet, das z.B. im Domino Servlet Manager aufgerufen wird, erhält bei der Initialisierung einer Notes Session per Default die Rechte des Domino-Servers, genauer gesagt der so genannten Server-ID, unter der der Domino-Server läuft. Standardmäßig werden dann die Aktionen dieser Notes Session als lokale Zugriffe behandelt, d.h. aus Sicht des Servlets ist der Server lokal. Alternativ wird die Notes Session des Servlets mit den Rechten des über den Browser angemeldeten Benutzers aufgebaut. Hierbei übernimmt Domino die Authentifizierung gegen sein Adressbuch oder ein entsprechend nachgeordnetes LDAP-Verzeichnis. Wird ein Application Server auf der gleichen Hardware wie der Domino-Server betrieben, dann sind ebenfalls lokale Zugriffe möglich. Alle anderen Fälle erfolgen dann remote. Die Authentifizierung kann im so genannten SSO (Single Sign On) erfolgen. Alternativ kann ein spezieller Domino-Benutzer definiert werden, unter dem dann authentifiziert wird. Hieraus ergeben sich grundsätzlich zwei Konzepte für die Authentifizierung: A
Authentifizierung über einen dedizierten Benutzer oder über die Server-ID. Alle Domino-Daten sind für diesen einen Benutzer/Server zugänglich. Die Unterscheidung der Rechte für die einzelnen Endanwender erfolgt innerhalb der Anwendung. – –
Vorteil: Kein administrativer Aufwand für die Benutzerverwaltung. Nachteil: Die Möglichkeiten der granulären Rechtevergabe in Domino werden nicht ausgeschöpft.
20 B
1 Das Domino-Prinzip Authentifizierung über registrierte Domino-Benutzer. Alle Lese- oder Schreibzugriffe auf Daten werden mit den Rechten des angemeldeten und im Domino-Adressbuch registrierten Benutzers abgeglichen. –
–
Vorteil: Jeder authentifizierte Benutzer kann nur die Daten sehen / ändern, die im Domino-Rechte-System für ihn vorgesehen sind. Rollen, Leser- und Autorenfelder. Nachteil: Mehraufwand für Administration der Benutzerverwaltung.
Die Architektur einer Java-Anwendung wird letztendlich festlegen, wie die Verbindung und die Authentifizierung mit dem Domino-Server hergestellt werden muss.
1.9
Zusammenfassung
Domino ist ein robuster Application Server mit einer langjährigen Geschichte. Durch die Vielzahl integrierter Services auf der Basis von RFC-Standards bietet sich dem Java-Entwickler eine Plattform, die auf viele vorgefertigte Tools und Dienste zurückgreifen kann. Insbesondere für Groupware-Anwendungen in verteilten Umgebungen zeigt Domino seine Stärken. Durch das Konzept der offenen Schemata für die Speicherung von Daten, Objekten, Gestaltungselementen und Zugriffskontrolllisten ist Domino eine Plattform, die durch den Anwender selbst einfach erweitert werden kann. Das wichtigste Grundprinzip der Organisation von Daten in einer Domino-Datenbank ist: • • • •
Speicherung der Daten in Dokumenten Aufteilen der Daten in Items Erstellung von Ansichten zur Indizierung von Dokumenten Selektion von Dokumenten über die Suche in Ansichten
Interessant ist für den Java-Programmierer das Konzept der Dokumente und Items, durch das Daten und Felder in Datensätzen beliebig erweitert werden können. Ansichten sind ein wesentliches Selektionswerkzeug für die Auswahl von Dokumenten. Java ist die wichtige Programmiersprache für die Anwendungsentwicklung in Lotus Domino und kann auf vielfältige Weise eingesetzt werden, insbesondere Servlets können direkt in den Domino-Server integriert werden oder über externe Application Server wie WebSphere auf Domino-Daten zurückgreifen. Mit der Version 7 wird Domino mit vielen neuen Möglichkeiten aufwarten. Insbesondere die Integration von DB2-Daten stellt eine interessante Erweiterung dar und lässt die J2EE-Application-Server-Welt und die Domino-Welt näher zusammenwachsen.
1.9 Zusammenfassung
21
Da die Stärken von Domino im Groupware- und Collaboration-Bereich liegen, eröffnet sich durch die Verwendung von Java die Möglichkeit, solche Anwendungen im Web mit einem robusten J2EE Layer auszustatten ohne auf die Stärken von Domino verzichten zu müssen.
2
2 Notes @ Domino
In diesem Kapitel: Einführung in die grundlegenden DominoProgrammierkonzepte Die Domino-Note und das Domino-Speicher- und Objektmodell Replikation in Domino Suchen und Finden Sicherheit in Domino
24
2 Notes @ Domino
Zur Java-Programmierung einer Lotus-Domino-Anwendung gehört ein Grundverständnis der Domino-Objekte, das in diesem Kapitel mit den Grundprinzipien der Objektspeicherung und -struktur vermittelt wird. Eine Java-Anwendung kann auf verschiedene Arten in eine Domino-Infrastruktur eingebunden werden bzw. Zugriff auf die Domino-Objekte erhalten. Der von Domino hierfür zunächst vorgesehene Weg ist die Erstellung eines so genannten Agenten. Domino-Agenten sind Code-Container, die eventgesteuert, periodisch oder durch manuelle Aktionen gestartet werden können. Dieses Verfahren wird in dem folgenden Beispiel, anhand der Erstellung einer kleinen Anwendung, mit einem solchen Agenten aufgezeigt und die Grundprinzipien der Domino-Objekte „Document“, „Item“, „Form“, „View“ und „Note“ erläutert. Ganz nebenbei ist diese Anwendung ein schönes Beispiel für einen Anwendungsfall, der in Domino nur mit Java gelöst werden kann, da es auf Java-Klassen zurückgreift, die z.B. in LotusScript generisch nicht vorliegen.
2.1
„Hello WWWorld“ – URL-Loader-Anwendung
Nach der bisher noch recht theoretischen Einführung soll mit diesem Beispiel die praktische Arbeit der Java-Programmierung für Domino eröffnet werden und selbstverständlich wird die Anwendung zunächst den Text „Hallo Welt“ ausgeben, um aber einen guten Überblick über die Programmierung in Lotus Domino zu geben, geht dieses Beispiel noch ein wenig darüber hinaus. Zunächst wird natürlich ein IDE benötigt, mit dem die einzelnen Bestandteile erstellt und programmiert werden können. Es wird für Domino als Teil des Notes Clients mitgeliefert und als Domino Designer (auch Lotus Notes Designer Client genannt) bezeichnet, über den im Wesentlichen die Programmierung von Designelementen, die der grafischen Benutzer-Interaktion dienen, erfolgt. Auch wenn der wesentliche Schwerpunkt dieses Buches auf der Programmierung von Java für Domino liegt, die – wie noch zu zeigen sein wird – besser außerhalb des Domino Designers erfolgt, ist es für den Java-Programmierer wichtig, ein Grundverständnis der Domino-Objekte zu haben. Die Erstellung von grafischen Oberflächen für Anwender des Lotus Notes Clients liegt außerhalb des Ziels dieses Buches und wird hier nur kurz angeschnitten. Für die Programmierung von Oberflächen für den Notes Client ist als weiterführende Literatur die Lotus Notes and Domino 6 Programming Bible [Programming Bible] empfehlenswert. Die Beispielanwendung wird Folgendes leisten: • • • •
Die Worte „Hello World“ als Ansprache an den aktuellen Benutzer richten. Die Eingabe einer URL ermöglichen. Diese URL abrufen und den HTML-Code in einem Notes-Dokument ablegen. Dem Benutzer eine Liste aller bisher abgerufenen URL anzeigen.
Anhand dieser Funktionen werden folgende Programmierprinzipien erläutert:
2.1.1 Datenbank anlegen
• •
• •
25
Die Notes-Maske als grafisches und programmatisches Element für die Anzeige von Daten im Notes Client. Der Notes View als grafisches Element zur Anzeige von vorselektierten Dokumenten und als programmatisches Element zur Suche oder Auswahl von Dokumenten. Die Aktion zur Ansteuerung eines Notes-Agenten. Der Notes-Agent zur Durchführung von Backend-Programmierung in Java.
Zunächst wird der Lotus Notes Client und der Lotus Domino Designer Client benötigt. Trial-Versionen sind bei IBM [developerWorks] erhältlich. Beachten Sie, dass Sie den Client einschließlich des Designers downloaden. Der Client ist einfach mit dem Installer zu installieren. Die Frage während des Setup, nach dem ersten Start des Clients, ob eine Verbindung mit einem Domino-Server hergestellt werden soll, kann zunächst mit „Nein“ beantwortet werden, sofern Sie noch keinen Server installiert haben, andernfalls geben Sie hier Ihren Domino-Server an.
2.1.1
Datenbank anlegen
Wie bereits eingangs erläutert, werden alle Daten und Programm-Elemente einer Domino-Anwendung in der so genannten NSF-Datei gespeichert. Diese Datei oder auch Datenbank genannt, kann entweder lokal auf der Arbeitsstation des Anwenders (oder Programmierers) erstellt werden oder auf einem Server. Um eine neue Datenbank anzulegen, wird zunächst der Domino Designer Client gestartet und im Menü2 „File“ der Eintrag „Database“ -> „New“ gewählt. Für die Beispielanwendung kann die Datenbank lokal angelegt werden, d.h. im Feld „Server“ wird der voreingestellte Wert „local“ belassen (Abb. 2-1). Lokale Datenbanken werden im Datenverzeichnis (data directory) des Notes Clients angelegt. Bei einer Standard-Installation auf einem Windows-System ist dies „C:\Lotus\Notes\ data“ oder „C:\Programme\Lotus\Notes\data“. Der Titel der Datenbank kann frei gewählt werden. Wählen Sie bitte „K2 URL Loader“ als Titel. Auch der Dateiname der Datenbank kann zwar frei gewählt werden, aber es empfiehlt sich hier einige Regeln zu beachten. Auch wenn Domino vorbildlich mit internationalen Zeichen und Groß-Kleinschreibung umgeht, hat es sich bewährt, für Dateinamen nur die üblichen Zeichen a bis z, A bis Z, den Unterstrich _ und 0..9 zu verwenden. Insbesondere auf Unix-Systemen, beim Aufruf von Datenbanken über die Domino URL (s. Kap. 7.1.4.2) macht man sich hier das Leben leichter. 2
Die Beschreibung der Domino-Anwendungen bezieht sich auf die englischen Versionen des Notes Clients, des Domino Designers, Administrators und Servers. Da der überwiegende Teil der Literatur und insbesondere die Foren zu diesem Thema im englischsprachigen Raum zu finden sind, hat dies den großen Vorteil, dass Begriffe und Bezeichnungen dort leichter wiedergefunden werden können und Bezüge leichter herzustellen sind.
26
2 Notes @ Domino
Wählen Sie als Dateiname „djbuch\ urlloader.nsf“. Durch den Backslash „\“ wird ein Ordner namens „djbuch“ angelegt. In diesem Ordner wird die Datenbank mit Namen „urlloader.nsf“ angelegt. Im Dialogfenster zeigt sich eine weitere Spezialität von Domino. Datenbanken können auf Basis von so genannten „Templates“ oder auch Schablonen erstellt werden. Eine so erstellte Datenbank wird als Kopie dieser Schablone erstellt und erbt die Funktionen und Eigenschaften der Schablone. Zum einen wird dieser Mechanismus zur Auslieferung von StandardAnwendungen an Kunden verwendet. Außerdem liefert Lotus eine große Aus- Abb. 2-1 Neue Datenbank erstellen wahl von Schablonen mit, die für Systemfunktionen, aber auch für viele Standard-Anwendungsfälle verwendet werden können (z.B. Diskussionsforen, Dokumenten-Datenbanken, Gruppen-E-Mail und andere). Die Datenbank für die Beispielanwendung wird natürlich aus der Schablone „-Blank-“ erstellt.
2.1.2
Maske anlegen
Um einem Benutzer Daten im Notes Client anzeigen zu können, oder um ihn Daten eingeben lassen zu können, muss in Notes eine so genannte Maske (engl. form) angelegt werden. Die Notes-Maske ist ein sehr mächtiges Gestaltungselement. Zunächst ist es ein Filter, mit dem bestimmt wird, welche Daten eines Datensatzes (also eines Dokuments) anzuzeigen sind. Daten werden in einem Dokument in so genannten Items gespeichert. Korrespondierend hierzu gibt es in der Maske Felder (engl. field), mit denen festgelegt wird, welche Items (auf welche Art) anzuzeigen sind. Darüber hinaus können noch viele programmatische Aufgaben mit der Maske erfüllt werden (s. Kap. 3.2). Damit der Benutzer der URL-Loader-Anwendung eine URL eingeben kann, wird eine Maske mit mindestens einem Feld zur Eingabe der URL benötigt. In der neu angelegten Datenbank wird eine neue Maske angelegt, indem Sie wie folgt vorgehen: A
B
Im Menü „Create“ findet sich der Eintrag „Design“ und dort der Eintrag „Form“. Alternativ kann mit dem Klick auf den Button „New Form“ eine neue Maske erstellt werden. Die neue Maske stellt sich im Designer zunächst als leere weiße Fläche dar. Ähnlich wie in einem HTML-Editor im WYSIWYG-Modus kann nun zeilenorientiert das Layout der Maske erstellt werden.
2.1.2 Maske anlegen C
27
Tippen Sie die Headline Klicken Sie im Lotus Domino Designer auf Objekte „URL Loader“ für die Maske und Einträge mit der rechten Maustaste. ein. Markieren Sie den Text. Klicken Sie mit der rechten So erreichen Sie die Eigenschaften der Objekte und Maustaste auf den Text und viele andere Funktionen. wählen Sie „Text Properties“. Hier können Schrift-Eigenschaften für die Überschrift eingestellt werden. D Fügen Sie nun nach einer Leerzeile die entscheidenden Worte „Hello World!“ in die Maske ein. E Aktivieren Sie in den Eigenschaften der Maske im zweiten Tabellenreiter die Eigenschaft „Automatically enable Edit Mode“ (Abb. 2-2). F Ein wichtiges Layout-Element in Notes ist die Tabelle. Wählen Sie „Create“ -> „Table“. Übernehmen Sie die Voreinstellungen, geben Sie aber 5 für die Zeilenzahl an. Nach dem Erstellen der Tabelle klicken Sie mit der rechten Maustaste in die linke Spalte und wählen Sie „Table Properties“. Stellen Sie die Breite der Spalte auf 4 cm ein. Auf die gleiche Art geben Sie der rechten Spalte eine Breite von 10 cm. G Geben Sie in die linke Spalte die Beschriftungen für die Felder ein: „Titel“, „URL“, „URL laden“, „Beschreibung“ und „HTML-Code der Seite“. H In der rechten Spalte werden nun Felder erstellt. Ein Feld wird über das Menü „Create“ -> „Field“ erstellt. Ein Klick mit der rechten Maustaste öffnet die Feld-Eigenschaften. Erstellen Sie auf diese Weise in der ersten, zweiten und vierten Zeile die Felder „F_Titel“, „F_URL“ und „Body“. Ausnahmen bestätigen bekanntlich die Regel. In den meisten DominoApplikationen hat es sich eingebürgert, dass in einer Maske, die nur ein Feld mit RichText enthält, dieses Feld den Namen „Body“ erhält. Gleiches gilt für das Feld „Title“ oder „Subject“. Unsere Maske wird diese Felder berechnen. Abb. 2-2 Maskeneigenschaften
28 I J
2 Notes @ Domino Das Feld Body soll ein RichText-Feld werden. In den Feld-Eigenschaften können Sie die Eigenschaft „Typ“ auf „RichText“ ändern. In der fünften Zeile, neben der Beschriftung „HTML-Code der Seite“ soll der geladene HTML-Code angezeigt werden. Dieser Code wird im Backend durch den Agenten erzeugt und im Item F_Code gespeichert. Um dieses Item anzuzeigen, soll kein Feld erstellt werden, damit der Benutzer keine Änderungen vornehmen kann. Stattdessen wählen Sie im Menü „Create“ -> „Computed Text“. Im rechten unteren Rahmen des Designers erscheint ein leeres weißes Feld mit der Überschrift „Computed Text : Value“. Tragen Sie hier F_Code ein. Dies ist der Name des Items, in dem der Java-Agent seine Daten speichern wird. Das Eintragen des Namens eines Items stellt bereits eine Berechnung dar und evaluiert in den Wert des Items.
Best Practice: Verwenden Sie Aliasnamen für alle Objekte. Aliasnamen für Masken (und andere Objekte) werden einfach durch Anhängen des PipeSymbols „|“ und des Aliasnamens definiert. Verwenden Sie eine Nomenklatura, die an Ihre Bedürfnisse angepasst ist, an die sich aber alle Entwickler ihres Teams halten müssen. Sinnvolle Aliasnamen beginnen mit einem (oder zwei) Buchstaben für den Typ des Objekts und einem Unterstrich, also z.B.
F_ für ein Feld FO_ für eine Maske V_ für eine Ansicht, usw. Aliasnamen sind wichtig, damit der Darstellungsname unabhängig vom programmatischen Namen bleibt. Die führenden Kennzeichner sind sehr wichtig für Übersetzungen mit der Domino Global Workbench [Lotus, DGW] oder beim globalen Suchen-Ersetzen. Durch den führenden Identifizierer können so objektspezifische Operationen auf eine bestimmte Art von Objekten global angewandt werden. Zusätzlich entsteht so leicht lesbarer Code.
2.1.2 Maske anlegen K
29
Unterhalb der Tabelle erstellen Sie zwei neue Felder mit Namen „Subject“ und „Title“ (englische Schreibweise!), um den allgemeinen Gepflogenheiten zu genügen. Diese Felder erhalten die Eigenschaft „computed“ – Abb. 2-3. Wenn das Feld Subject ausgewählt ist, erscheint im unteren rechten Frame des Designers ein leeres Feld mit der Überschrift „Subject (Field) : Value“. Hier wird die Berechnung für dieses Feld eingegeben. Für unseren Fall genügt es, hier den Namen des Feldes einzutragen, dessen Wert übernommen werden soll: F_Titel. Gibt der Benutzer nun in das Feld F_Titel ei- Abb. 2-3 Berechnetes Textfeld nen Text ein, wird das Feld Subject den gleichen Wert annehmen. L Da die Felder Subject und Title nur internen Zwecken dienen, sollen sie nicht für den Benutzer sichtbar sein. Durch so genannte „Hide-When“-Formeln können Felder vor der Anzeige verborgen werden. Hierbei handelt es sich um KEINE Sicherheitsfunktion. Die Felder werden weder durch Zugriffsrechte noch etwa durch eine Encryption geschützt. Hide-When-Formeln dienen lediglich der bequemen und flexiblen Anzeige (oder Nicht-Anzeige) von Inhalten. Markieren Sie die gesamte Zeile mit den beiden Feldern, öffnen Sie mit der rechten Maustaste die Text-Eigenschaften und wählen Sie das Symbol, das entfernt an den zu flach geratenen Buchstaben „T“ erinnert. Dieses Symbol steht für eine Jalousie. Wählen Sie alle drei Checkboxen für Notes R 4.6, Web und Mobil aus (s. Abb. 2-5). M Nun soll noch ein Aktions-Button für das Laden einer HTML-Seite (natürlich durch Starten eines Java-Agenten) erstellt werden. Setzen Sie den Cursor in die dritte Zeile der rechten Spalte und wählen Sie im Menü „Create“ -> „Hotspot“ -> „Button“. In den Eigenschaften des Buttons (rechte Maustaste) können Sie die Beschriftung des Buttons in „URL laden“ ändern.
30
2 Notes @ Domino
N Die eigentliche Berechnung für das Feld erfolgt mit so genannten @-Formeln (sprich: „ät-Formel“). Geben Sie im rechten unteren Rahmen mit der Überschrift „URL Laden (Button) : Click“ folgende Formel ein: @Command ([FileSave]); @Command ([ToolsRunMacro];"AG_UrlLaden"); @PostedCommand ([ViewRefreshFields]);
O Im Einzelnen bedeuten diese Formeln: P Durch @Command ([FileSave]); wird das Dokument gespeichert, damit der Agent auf aktuelle Daten trifft. Q Durch @Command ([ToolsRunMacro];"AG_UrlLaden"); wird der Agent mit (Alias-) Namen „AG_UrlLaden“ gestartet (Abb. 2-4).
Abb. 2-4 @Formel „@command ([ToolsRunMacro])“ startet einen Java-Agenten
R
S
T
Durch @PostedCommand( [ViewRefreshFields] ); werden die Felder der Maske neu berechnet. Hierdurch wird erreicht, dass der oben erstellte berechnete Text für die Anzeige des neu geladenen HTML-Codes aktualisiert wird. @PostedCommand statt @Command muss verwendet werden, damit diese Befehle NACH den vorangegangenen ausgeführt werden. Dies ist ein etwas unerwartetes Verhalten und im Wesentlichen historisch bedingt. Die @-Formelsprache kommt in Lotus Domino in der Regel dann zum Einsatz, wenn innerhalb von Masken (oder Ansichten oder einigen anderen Gestaltungselementen) einfache Berechnungen oder Validierungen durchgeführt werden sollen. Bei der Java-Programmierung spielen diese nur eine Rolle am Rande. Da die @Formeln aber für einen Laien leicht verständlich sind und für Masken eine wichtige Rolle spielen, sollte der Java-Programmierer sich der @-Formeln bewusst sein. Nähere Informationen zu den @-Formeln finden Sie in der Lotus Domino Designer-Hilfe in der Lotus Developer Domain [Lotus, LDD], die auch online [Lotus, Designer] und [Lotus, R6Formula] zur Verfügung steht. Der zugehörige zu startende Agent fehlt noch, wird aber weiter unten erstellt. Wählen Sie „Datei“ -> „Speichern“ im Menü, um die Maske zu speichern. In den nun abgefragten Namen für die Maske geben Sie bitte ein:
2.1.2 Maske anlegen
31
Kapitel 2\1. URL Loader Dokument | FO_URL_Loader_k2 Links des Pipe-Symbols wird der Klar-Name der Maske vergeben. Dieser Name wird dem Anwender im Notes Client angezeigt. Rechts des Pipe-Symbols wird ein programmatischer Aliasname vergeben, unter dem die Maske in der Programmierung angesprochen werden kann. Das Backslash-Zeichen „\“ kategorisiert die Maskeneinträge im Menü „Create“. Die Vergabe von Aliasnamen ist zwar nicht vorgeschrieben, hat sich aber als Best Practice durchgesetzt.
Abb. 2-5 Fertiggestellte Maske und Hide-When-Formeln
Die Maske ist jetzt fertig (Abb. 2-5), aber noch fehlen einige Bestandteile, um die Anwendung richtig testen zu können. Dennoch kann die Maske auch schon allein stehend gestartet werden. Wenn die Maske geöffnet ist, wählen Sie im Menü „Design“ den Eintrag „Preview in Notes“. Sie können auch links oben das kleine runde orangerote Notes-Icon wählen. Nun startet der Notes Client und zeigt eine neue Maske mit leeren Feldern an. Die Felder Titel, URL und HTML-Code werden als bearbeitbar angezeigt und können verändert werden. Bei der Vorschau fallen zwei kleine Unzulänglichkeiten auf.
32 A
2 Notes @ Domino Der Fenstertitel lautet „untitled“. Hier handelt es sich um eine der häufigsten Unaufmerksamkeiten beim Erstellen einer Maske. Sie können dies beheben, indem Sie im unteren mittleren Frame des Designers bei geöffneter Maske unter „Objects“ auf „Window Title“ klicken und rechts folgende Formel eingeben: @if (@isNewDoc; "Neues Dokument";F_Titel);
B
Diese Formel sorgt dafür, dass im Fenstertitel der Text „Neues Dokument“ erscheint, solange das Dokument noch nicht gespeichert wurde, andernfalls wird der vom Benutzer eingegebene Titel angezeigt. Der Maske fehlt die Möglichkeit, gespeichert zu werden. Sie können bis auf weiteres hierfür die Tastenkombination STRG-S verwenden oder im Menü „File“ den Eintrag „save“. Wenn Sie ein in A neu erstelltes Dokument so speichern, können Sie beobachten, wie sich der Fenstertitel entsprechend Ihren Eingaben ändert.
Um Ihre so gespeicherten Dokumente wiederzufinden, benötigen Sie die Ansicht, die Sie in Kapitel 2.1.3 erstellen.
2.1.3
Ansicht anlegen
Mit der zuvor erstellen Maske erreichen Sie zweierlei. Zum einen kann der Benutzer Datensätze anlegen, die die gewünschten URL enthalten. Gleichzeitig ist die Maske das GUI, mit dem der Benutzer einen Agenten ansteuert, der die HTML-Daten aus dem Internet herunterladen wird. Bevor nun der Agent erstellt werden soll, wird noch eine Ansicht benötigt, damit alle erstellten Datensätze nicht in der Datenbank „verschwinden“, sondern jederzeit vom Benutzer (und wie noch zu zeigen sein wird, vom Java-Backend) wiedergefunden, selektiert, geöffnet, geändert und gelöscht werden kann. Also erfüllt auch die Ansicht mehrere Aufgaben. Neben der beschriebenen Aufgabe, Daten übersichtlich, optional sortiert und tabellarisch darzustellen, ermöglicht sie es dem Java-Programmierer, effizient Dokumente nachzuschlagen. Um eine neue Ansicht zu erstellen, gehen Sie wie folgt vor: A
B C
Im linken Frame der Arbeitsoberfläche des Designers findet sich unter anderem der Eintrag „Views“. Wählen Sie diesen Eintrag. In der sich öffnenden Übersicht finden Sie bereits eine Ansicht „untitled“. Löschen Sie diese Ansicht. Über der nun im rechten Frame erscheinenden Übersicht findet sich der Button „New View“. Klicken Sie diesen Button. Wählen Sie folgende Einstellungen für die neue Ansicht (Abb. 2-6): View name: Kapitel 2\URL Loader View type: „Shared“ Selection conditions: SELECT Form = „FO_URL_Loader_k2“
2.1.3 Ansicht anlegen
33
Beachten Sie: Rechts von dem Schlüsselwort „SELECT“ handelt es sich erneut um eine @-Formel. In diesem Fall wird lediglich ein Feld (Form) mit einer Konstante ("FO_URL_Loader_k2") verglichen. An dieser Stelle kommt zum ersten Mal der eingeführte Alias zum Tragen. Soll nun – aus welchem Grund auch immer (z.B. Übersetzung) – der Name der Maske (in der Darstellung für den Benutzer) geändert werden, dann kann der Klarname Abb. 2-6 Neue Ansicht erstellen der Maske getrost von „Kapitel 2\1. URL Loader Dokument“ (s. Abb. 26) in einen beliebigen neuen Wert geändert werden, ohne dass der Bezug zu dieser Selektionsformel verloren ginge. Durch diese Selektionsformel wird festgelegt, dass in dem View nur solche Dokumente angezeigt werden, deren Item mit dem Namen „Form“ den Inhalt „FO_URL_Loader_k2“ hat (in dem Item „Form“ wird automatisch der Name, bzw. Aliasname der Maske gespeichert). D Ein Klick auf „Save and Customize“ öffnet die neue Ansicht. E Die neue Ansicht hat bereits eine Spalte mit dem Titel „#“. Diese wird so nicht benötigt und muss über die Eigenschaften der Spalte (rechte Maustaste auf den Spaltentitel) verändert werden. In den Eigenschaften wählen Sie bitte „URL“ als Spaltentitel. F Erstellen Sie zwei zusätzliche Spalten durch einen Doppel-Klick auf die Spaltentitel rechts neben der vorhandenen Spalte. Bezeichnen Sie die Spalten mit „zuletzt geladen am“ und „Autor“. Die Breite der Spalte „URL“ setzen Sie bitte auf 20, die Breite der Spalte „zuletzt geladen am“ auf 14 und die Autoren-Spalte auf 20. G Um diese drei neuen Spalten mit Leben zu füllen, müssen Sie eine Berechnung oder einen Feldnamen in das dafür vorgesehene Feld eingeben. Wählen Sie die Spalte durch einen Klick auf die Spaltenüberschrift an. Im rechten unteren Frame des Designer-Arbeitsplatzes erscheint (befindet sich) ein leeres weißes Fenster (Rahmen) mit der Überschrift „URL (Column) : Column Value“ (Abb. 2-7). In dem Rahmen wählen Sie nun „field“ statt „simple function“ und selektieren den Feldnamen „F_URL“.
34
2 Notes @ Domino
H In der zweiten Spalte soll angezeigt werden, wann die HTML-Seite der URL aus dem Feld F_URL zuletzt geladen wurde. Dieser Wert wird natürlich nicht durch den Benutzer eingegeben, sondern durch den Agenten nach erfolgreicher Arbeit in einem Item gespeichert. Da dieses Item nicht in der Maske zu finden ist, kann hier nicht direkt ein Feldname ausgewählt werden, sondern er wird über eine @Formel Abb. 2-7 Spaltenwert ändern angesprochen. Wählen Sie „Formel“ im Rahmen für den Spaltenwert aus und geben Sie F_Ladezeit ein. Dies wird der Name des Items sein, das durch den Agenten gefüllt werden wird. I In der dritten Spalte soll der Name des Autors des Dokuments angezeigt werden. Geben Sie im Spaltenwert für die dritte Spalte die Formel @author ein. Diese @Formel zeigt den Autor des Dokuments an. J Durch Klick mit der rechten Maustaste auf den oberen rechten Rahmen können Sie die Ansicht-Eigenschaften öffnen. Hier gibt es ein eigenes Feld für den Alias. Das Pipe-Symbol muss daher an dieser Stelle nicht verwendet werden. Bitte geben Sie „V_URL_Loader_k2“ als Alias ein. K In den Eigenschaften der Ansicht findet sich ein „Reiter“ mit einem „i“ als Symbol. Hier aktivieren Sie die oberste Schaltfläche für „Default when database is first opened“. L Speichern Sie die Ansicht über „File“ -> „Save“. Auch eine Ansicht können Sie über die Preview-Funktion anzeigen. Wählen Sie „Design“ -> „Preview in Notes“. Die Ansicht öffnet sich und Sie können die in Kapitel 2.1.2 erstellten Dokumente sehen. Um neue Dokumente erstellen zu können, müssen Sie nicht erneut in den Designer wechseln. Notes platziert die Klarnamen von Masken automatisch im Menü „Create“ des Clients. Hier finden Sie den Eintrag „Kapitel 2\1. URL Loader Dokument“, mit dem Sie ein neues Dokument erstellen können.
2.1.4
Agent anlegen
Die beiden wichtigsten Elemente der Domino-Anwendung sind bereits fertig. Eine Maske wurde angelegt, um Daten durch den Benutzer in Empfang zu nehmen, bzw. um vorhandene Dokumente anzuzeigen. Eine Ansicht ermöglicht dem Benutzer, den Überblick über die bereits angelegten Datensätze zu gewinnen. Um den Agenten zu erstellen, der die gewünschten HTMLSeiten in das Dokument lädt, gehen Sie wie folgt vor:
2.1.4 Agent anlegen
35
Abb. 2-8 Basisgerüst eines Java-Agenten
A B
C
Im linken Frame des Designers wählen Sie „Shared Code“ -> „Agents“. Wählen Sie im rechten Frame „New Agent“. Ein Agent kann auf verschiedene Weise programmiert werden. Wählen Sie im rechten Frame im Aufklappmenü mit dem Eintrag „Simple action(s)“ den Eintrag Java. Die Darstellung im rechten Frame wird sich verändern und das Basisgerüst eines Java-Agenten erscheinen (Abb. 2-8). Geben Sie in den Agenteneigenschaften den Namen „Kapitel 2\1. URL Laden| AG_UrlLaden“ ein. Beachten Sie auch hier das Pipe-Symbol „|“ für die Eingabe eines Alias-Namens. Die Bedeutung des Namensbestandteils „Kapitel 2\1.“ wird in Kapitel 2.1.6 erläutert.
Der eigentliche Java-Agent hat folgenden Code (Listing 2-1): Jeder Java-Agent, der wie oben beschrieben im Designer programmiert wird, muss die AgentBase erweitern. In der AgentBase liefert Domino wichtige Objekte mit. Standardmäßig benennt Domino Designer diese Klasse mit JavaAgent. Wenn auch nicht zwingend erforderlich, ist es guter Stil, diesen Namen zu ändern.
36
2 Notes @ Domino
import java.io.*; import java.net.*; import lotus.domino.*; public class UrlLadenAgent extends AgentBase { public void NotesMain() { Document doc = null; try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); doc = agentContext.getDocumentContext(); String urlString = doc.getItemValueString("F_Url"); URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); StringBuffer result = new StringBuffer(); BufferedReader urlInput = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line = ""; while ((line = urlInput.readLine()) != null) { result.append(line + "\n"); } urlInput.close(); doc.replaceItemValue("F_Code", result.toString()); DateTime dt = session.createDateTime("Today"); dt.setNow(); doc.replaceItemValue("F_Ladezeit", dt.getLocalTime()); doc.save (true,false); } catch (IOException e) { if (doc != null) { try { doc.replaceItemValue("F_Code", "Fehler beim Laden. " + e.toString()); doc.save (true,false); } catch (NotesException fatal_ne) { fatal_ne.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } }
Anmerkung: Der Einfachheit halber werden Imports immer mit Joker-Symbol * dargestellt. Best practice wäre natürlich, alle Pakete einzeln aufzulisten.
Listing 2-1 URL-Loader-Java-Agent – Sourcecode
Insbesondere bei Exceptions und wenn viele Agenten auf einem Server betrieben werden, ist dies unerlässlich, um feststellen zu können, welche Klasse, also welcher Agent, eine Fehlermeldung verursacht hat. Einstiegspunkt für einen derartigen Agenten ist die Methode NotesMain ()3, die immer implementiert werden muss. 3
Die Großschreibung der Methode NotesMain ist eine Eigenart des Domino-Java-Agenten, die leider nicht den Standards entspricht.
2.1.4 Agent anlegen
37
Best Practice: Es ist guter Stil, den Namen der Basisklasse zu ändern. Insbesondere bei Exceptions und wenn viele Agenten auf einem Server betrieben werden, ist dies unerlässlich, um feststellen zu können, welche Klasse, also welcher Agent, eine Fehlermeldung verursacht hat.
Neben Informationen über den Agenten ist hierin insbesondere die Notes Session enthalten. Eine Notes Session ist immer notwendig, wenn auf DominoObjekte zugegriffen werden soll, da alle Objekte von der Session abhängen. Die Notes Session bildet nicht nur die Verbindung zu einer Datenbank (lokal) oder einem Server (remote) ab, handelt diese und kümmert sich zum Beispiel ums Caching, sondern in der Session ist vermerkt, wer der aktuell angemeldete Benutzer ist. Der AgentContext liefert Informationen über den aktuell betriebenen Agenten. Neben der aktuellen Datenbank, die durch getCurrentDatabase geliefert wird, liefert der AgentContext Informationen über den Status des Agenten, also ob der Agent z.B. im Schedule schon einmal gelaufen ist, und auf wieviele Dokumente der Agent zugegriffen hat. Im AgentContext ist ebenfalls gespeichert, welches das aktuell geöffnete Dokument ist. Dies kann entweder das ausgewählte Dokument in einer Ansicht sein, oder aber wie in unserem Fall das geöffnete Dokument. Wird ein vom Benutzer geöffnetes Dokument wie in dem Beispiel durch einen Java-Agenten im Backend manipuliert, ist Vorsicht geboten. Wenn dieser Agent Daten im Dokument manipuliert, dann muss nach Beendigung des Agenten das Dokument aktualisiert werden. Dies geschieht durch die oben beschriebene @Formel @PostedCommand ([ViewRefreshFields]); Speichern darf der Java-Agent das Dokument auf keinen Fall, sonst kommt es zu Speicherkonflikten (s. Kap. 2.3.2). Die Standardmethode, um Items eines Dokuments auszulesen, ist getItemValueString. Diese Methode ignoriert, ob ein Item mehrere Werte hat und liefert immer nur den ersten. Um alle Werte zu erhalten, muss getItemValue verwendet werden. Mit den Methoden getItemValueDateTimeArray, getItemValueDouble, getItemValueInteger, getItemValueCustomData und getItemValueCustomDataBytes können Items anderen Datentyps ausgelesen werden. Wird mit getItemValueString auf ein Datum- oder Zahl-Objekt zugegriffen, gibt sie null zurück. Gibt es kein Item mit dem geforderten Namen, gibt sie ebenfalls null zurück. Wird versucht ein RichText Item auszulesen, wird nur der reine Text geliefert ohne Gestaltungsinformationen oder eingebettete Objekte. Der Code der gewünschten HTML-Seite wird hier mit einfachen Standardmitteln der Java-IO- und Java-Net-Klassen geladen. Das Ergebnis, also der HTML-Code als String, wird im Item F_Code abgelegt. Der Datentyp des Items bei Verwendung von replaceItemValue ist abhängig vom übergebenen Objekt.
38
2 Notes @ Domino Die aktuelle Zeit wird als Zeit-Objekt dem Item F_Ladezeit übergeben. Dieses Feld kann dann in der Ansicht angezeigt werden. Das Dokument wird gespeichert. Die Parameter (true, false) erreichen, dass einerseits ein Speichern erzwungen wird, auch wenn zwischenzeitlich eine Änderung durch wen auch immer erfolgte – dies sollte eigentlich während der Agent läuft nicht der Fall sein –, und durch den zweiten Parameter false wird erreicht, dass in einem Konfliktfall kein Speicherkonfliktdokument gespeichert wird. Das Exception Handling darf natürlich nicht fehlen. Bei einer IOException wird versucht, den Fehlercode noch ins Item F_Code zu schreiben, damit es für den Benutzer sichtbar wird. Unangenehm ist die Tatsache, dass ein extra try catch für die NotesException benötigt wird, die in diesem Fall noch geworfen werden könnte. Dies könnte z.B. in einer eigenen Exception-Handling-Methode abgewickelt werden, um eventuelle Exceptions schon dort abzufangen.
2.1.5
Die Anwendung testen
Um die neue Anwendung zu testen, schließen Sie nun den Designer und öffnen den Notes Client. Wählen Sie im Menü „Datei“ -> „Datenbank“ -> „Öffnen“. Öffnen Sie auf dem Server „lokal“ am unteren Ende der Liste das gelbe Ordnersymbol „djbuch“ durch Doppelklick. Öffnen Sie nun die Datenbank mit Titel „K2 URL Loader“ durch Doppelklick. Die Datenbank öffnet sich im Notes Client und zeigt die Ansicht URL Loader (Abb. 2-9). Der Eintrag für diese Ansicht findet sich im linken Rahmen des Notes Clients. Beachten Sie, dass der Eintrag unterhalb des Wortes „Kapitel 2“ eingerückt ist. Der Klarname der Ansicht war als „Kapitel 2\URL Loader“ angelegt worden. Der Backslash „\“ bewirkt – und dies gilt für viele Objekte in Notes –, dass der Eintrag kategorisiert dargestellt wird. Würde jetzt eine zweite Ansicht mit Klarnamen „Kapitel 2\Andere Ansicht“ angelegt, würde auch diese sich mit dem Eintrag „Andere Ansicht“ unterhalb der Kategorie „Kapitel 2“ befinden. Links der Kategorie „Kapitel 2“ befindet sich ein kleines grünes Dreieck, mit dem die Kategorie auf- und zugeklappt werden kann. Dieses Dreieck wird auch als „Twisty“ bezeichnet. Wenn Sie bereits Dokumente angelegt haben, erscheinen diese im rechten Rahmen der Anwendung. Wenn Sie neue Dokumente anlegen möchten, wählen Sie im Menü „Create“ -> „Kapitel 2“ -> „1. URL Loader Dokument“. In der nun erscheinenden Maske können Sie die Felder Titel, URL und Beschreibung ausfüllen. Beachten Sie, dass die URL gültig ist. Im Feld Beschreibung können Sie übrigens den Text frei gestalten. Klicken Sie mit der rechten Maustaste in das Feld, um Schrifteigenschaften zu verändern. Der Button „URL Laden“ startet den Download der HTML-Seite, speichert den Code im Item F_Code und aktualisiert die Bildschirmdarstellung der Maske, wodurch der Code in der Maske sichtbar wird. Nach einer kurzen Ladezeit erscheint der HTML-Code im unteren Feld. Um die Fehlerverarbeitung kontrollieren zu können, geben Sie z.B. eine URL mit HTTPS (SSL) ein. Im unteren Feld sollte dann der Text
2.1.6 Erweiterungen und Ausblick
39
Abb. 2-9 URL-Loader-Anwendung
„Ein Fehler ist beim Laden aufgetreten. java.net.SocketException: SSL implementation not available“ erscheinen, da SSL von der Beispielanwendung nicht unterstützt wird. Der Zusammenhang zwischen Items und Feldern lässt sich leicht durch die Dokumenten-Eigenschaften sichtbar machen. Klicken Sie mit der rechten Maustaste in eine beliebige Stelle der Maske und wählen Sie zunächst Feld- oder Text-Eigenschafen. Im Eigenschaften-Fenster können Sie dann im oberen Aufklappmenü „Document“ auswählen. Im zweiten Tabellenreiter (Dreieck-Symbol) findet sich eine Liste aller Items des Dokuments (s. Abb. 2-9) Gut erkennbar ist, dass das Dokument mehr Items besitzt, als die Maske in Feldern anzeigt. So wird z.B. im Item $UpdatedBy eine Liste der letzten Autoren vom System gepflegt.
2.1.6
Erweiterungen und Ausblick
Der neu erstellte Agent hat „ganz nebenbei“ noch eine weitere Funktion erhalten. Mit dieser Form von Agenten, wie er in Kapitel 2.1.4 angelegt wurde, können einzelne Dokumente in Ansichten manipuliert werden.
40
2 Notes @ Domino
Wenn Sie in der Ansicht „URL Loader“ ein einzelnes Dokument markieren, indem Sie einfach auf das Dokument klicken (es wird dann dick umrandet), können Sie im Menü „Actions“ den Eintrag „Kapitel 2“ -> „1. URL Laden“ wählen. Für einen kurzen Moment wird durch den Mauszeiger angezeigt, dass der Agent arbeitet. In der Ansicht können Sie dann beobachten, wie der Eintrag für die Ladezeit aktualisiert wird. Hat sich die Web-Seite verändert, ist auch der Code entsprechend neu im Item F_Code abgelegt. Die fertige Anwendung können Sie auch in der Datenbank djbuch\djbuch.nsf finden und die einzelnen Designelemente und Programmierungen nachvollziehen. Um aus dieser Anwendung eine vollwertige Domino-Anwendung zu machen, fehlen noch einige Punkte, die berücksichtigt werden könnten, im Wesentlichen sind dies: •
•
•
•
• • •
•
Die Sicherheit der Anwendung wurde bisher noch nicht berücksichtigt. Empfehlenswert ist, die Planung von Rollen für die Berechtigung für die Durchführung der einzelnen Aktionen festzulegen. Außerdem sind Leser- und Autorenfelder eine Möglichkeit, die erstellten Dokumente zu schützen. Lesen Sie mehr zur Sicherheit von Domino-Anwendungen in den Kapiteln 2.6, 4.16, 7.1.3, 7.5 und 9.5. Der Refresh der Maske nach Ablauf des Agenten und die Unterscheidung, ob der Agent manuell aus der Maske oder aus dem Menü „Aktionen“ gestartet wurde, lässt sich eleganter lösen. Hierfür wird jedoch LotusScript benötigt. Die Gestaltung der Anwendung kann noch verbessert werden. Zum Beispiel ermöglicht der Einsatz von Rahmengruppen, Outline, Seiten und Grafiken, die bereits sichtbaren Elemente „links -> Navigation“ und „rechts -> Anzeige einer Ansicht“ zu gestalten [Programming Bible]. Die Seiten werden jeweils nur auf Anforderung des Benutzers aktualisiert. Ein periodischer Agent kann die Aufgabe übernehmen, regelmäßig für alle URL-Loader-Dokumente die Daten abzuholen. So lässt sich einfach ein XML Feed realisieren, der per HTTP XML Daten abholt und z.B. nach einer Verarbeitung über ein XSL Stylesheet in Dokumenten speichert. Die Überprüfung der Eingaben kann verbessert werden. Die Verarbeitung von SSL-Seiten kann ermöglicht werden. Eine Interpretation des Mime-Types der geladenen Seite könnte zum Beispiel ermöglichen, HTML-Seiten von anderen Inhalten zu unterscheiden, und dann z.B. Bilder als Anhänge zu speichern. Ein Logging und Auditing sollte die Anwendung überwachen, um den dauerhaften und stabilen Betrieb zu gewährleisten. Fehlermeldungen und Exceptions sollten in einem zentralen Log gespeichert werden.
2.2 Das Domino-Speichermodell
2.2
41
Das Domino-Speichermodell
Die Domino-Datenbank wird als NSF (Notes Storage Facility) gespeichert. Wie bereits gezeigt wurde, enthält sie alle Bestandteile einer Domino-Anwendung. In der NSF-Datei sind alle Objekte vom WYSIWYG-Gestaltungselement zur Anzeige von User-Interfaces bis hin zum einzelnen Datensatz gespeichert. Die einzelnen Objekte werden in einer Domino-Datenbank als so genannte Notes gespeichert. Der elementare Datensatz in einer Domino-Datenbank wird als Dokument bezeichnet. Ein Dokument enthält Items, in denen ein oder mehrere Werte gespeichert sind. Die NSF-Datei ist also ein Container von Containern von Containern. In einer Domino-Datenbank gibt es für die Speicherung von Daten keine zwingend vorgeschriebene Struktur. Mit Masken wird lediglich ein äußerer Rahmen für die User-Interaktion geschaffen. Im Extremfall könnte jedes Dokument andere Items mit anderen Inhalten und anderen Namen als jedes andere Dokument haben (der Regelfall ist dies natürlich nicht). Das Verständnis dieser Struktur ist wichtig, um Java-Programme für Domino zu erstellen. Insbesondere wird der Java-Programmierer wenig, wenn überhaupt, mit der Erstellung von Masken zu tun haben, sondern eher im Backend Programme zur Manipulation von Domino-Daten erstellen. Daher werden die so erzeugten Daten nicht durch eine Maske gefiltert oder in bestimmten Strukturen gehalten und über das Item „Form“ in lose (gedachten) Gruppen organisiert. Planung und Vorbereitung von Java-Agenten ist also auch für Java-Anwendungen in Domino sehr wichtig.
2.2.1
Die Domino-„Note“
Alle Objekte einer Domino-Anwendung werden in so genannten Notes gespeichert. Eine Note besteht aus zwei Teilen, einem Header und einem Body. Im Header werden allgemeine Informationen über die Note abgespeichert, z.B. welcher so genannten Klasse die Note angehört. Im Body einer Note werden die eigentlichen Daten einer
Tabelle 2-1 Die Note-Typen
1 2 3 4 5
NOTE – Ein allgemeiner Container beliebigen Inhalts DOCUMENT – Ein Daten-Objekt PROFILEDOCUMENT – Ein spezielles im Hauptspeicher gecachtes (und dadurch für zeitkritische Operationen verwendetes) DOCUMENT ACL – Enthält die Zugriffskontrollliste einer Datenbank DESIGN-ELEMENT – Eines der folgenden Designelemente: form, subform, page, frameset, view, folder, sharedfield, sharedactions, imageresource, appletresource, agent, agentdata, scriptlibrary, databasescript, helpusingdocument, helpaboutdocument, dataconnection
42
2 Notes @ Domino
Tabelle 2-2 Die Note-Klassen
1 2
3
4 5 6 7 8
9
10
DOCUMENT – Enthält Dokumente FILTER – Enthält Agenten, Datenbank-Skripte (Spezieller Code, der bei bestimmten Datenbank-Events, z.B. Drag-And-Drop oder Dokumente Löschen ausgelöst wird und somit eine Customization erlaubt), Outlines (Navigationselemente) und Script-Bibliotheken (s. Kap. 3.5). FORM – Enthält Masken, Rahmengruppen (ähnlich den Framesets im HTML), Seiten (Designelement zur Darstellung statischer Informationen) und Teilmasken (vergleichbar den Masken, können jedoch als Container in andere Masken eingefügt werden). INFO – Enthält das spezielle Datenbankdokument „Über diese Datenbank“, das nur einmal in einer Datenbank vorkommen kann. HELP_INDEX – Eine datenbankinterne Note, zur Speicherung eines Hilfeindexes, der aber nur für die Notes-eigene Client-, Designer- und Administratoren-Hilfe verwendet wird. Existiert nur einmal pro Datenbank. HELP – Enthält das spezielle Datenbankdokument „Benutzen dieser Datenbank“, das nur einmal in einer Datenbank vorkommen kann. ICON – Das Datenbank-Icon. REPLFORMULA – Die so genannten Replikationsformeln werden in dieser Note gespeichert. Sie dienen dazu, die zwischen zwei Instanzen (Repliken) einer Datenbank zu replizierenden Dokumente anhand von @Formeln genau zu spezifizieren. FIELD – Enthält so genannte gemeinsame Felder. Dies sind Felder, die zentral definiert werden, d.h. für die Datentyp, Default-Wert, Validierung, etc. einmal festgelegt werden. Gemeinsame Felder werden in Masken als Felder eingesetzt, dennoch nicht zu verwechseln mit den Feldern einer Maske, die individuell pro Maske definiert werden und nur als Item einer FORM Note vorliegen. VIEW – Enthält Ansichten und Ordner (vergleichbar mit Verzeichnissen von Verweisen auf Dokumente).
Note abgelegt, wobei eine Note verschiedene Klassen annehmen kann, die die verschiedenen Anwendungsbereiche definieren. So wird im Wesentlichen nach Gestaltungs-, administrativen und Dokumenten- Notes unterschieden. Eine Note wird näher bestimmt über den Type einer Note. Der Type einer Note bestimmt sich aus der Klasse und einem (vorhandenen oder nicht vorhandenen) Item in der Note. Die möglichen Typen und Klassen der Notes finden Sie in Tabelle 2-1 und Tabelle 2-2. Eine sehr gute Möglichkeit, die Struktur einer Notes Storage Facility und insbesondere die Note-Struktur zu verstehen, ist die Auseinandersetzung mit der Definition des Domino-XML (DXL) und insbesondere mit der DTD des DXL. Nähere Informationen hierzu finden Sie in Kapitel 17.7 oder im Web bei www.lotus.com [Lotus, DTD] und [Lotus, DesignerR6]. Auch die Designer-Hilfe ist eine sehr gute Quelle für Informationen zur DTD.
2.2.2 Notes Document und Notes Form 2.2.2
43
Notes Document und Notes Form
Die wichtigste Note für den Java-Programmierer ist unweigerlich das Dokument, das durch die Klasse Document repräsentiert wird. Sämtliche Daten, die gespeichert, geladen, verändert oder gelöscht werden sollen, befinden sich in Dokumenten und müssen als Document geladen werden. Einen etwas nachrangigen Charakter hat die ACL Note, die durch die Klasse ACL repräsentiert wird und die PROFILEDOCUMENT Note, die ebenfalls durch die Klasse Document repräsentiert wird, wobei jedoch zu beachten ist, dass Profildokumente nur über die Klasse Database per getProfileDocument geladen werden können (s. Kap. 7.2). Da der Java-Programmierer mit real existierenden :-) Domino-Datenbanken arbeiten wird, ist der FORM Note neben dem Document die Aufmerksamkeit in diesem Kapitel sicher. Die übrigen Notes sind wichtig für das Verständnis der Datenstruktur. Die NotesMaske ist das Designelement in Notes, um Dokumente anzuzeigen und dem Benutzer die Möglichkeit zu geben, Dokumente zu ändern, also ein gestaltetes, interaktives Element des grafischen User Interfaces. Ein Designelement in Notes ist ein Container, in dem, neben Layout-Informationen für die Darstellung im GUI, ProgrammierCode und interaktive Schaltflächen gespeichert sind. Zu Designelementen zählen alle Note-Objekte, die Code oder Gestaltungsinformationen aufnehmen. Mögliche Designelemente werden in Kapitel 3 vorgestellt, eine Übersicht finden Sie in Tabelle 2-1, Punkt 5. Die wichtigsten Designelemente sind Maske, Seite, Frameset, Ansicht, BildRessource, Agent und Script-Bibliothek. Prinzipiell besteht die Möglichkeit, jedes Dokument mit einer beliebigen Maske und somit mit variierenden Darstellungen anzuzeigen, über das Item „Form“ ist jedoch ein Default-Zusammenhang zwischen Dokument und Maske hergestellt. Domino evaluiert die Default-Maske eines Dokuments in der folgenden Reihenfolge: Zunächst wird überprüft, ob es ein Item mit dem Namen Form gibt, falls nicht, wird überprüft, ob es ein Item mit dem Namen $Title gibt. In beiden Fällen wird die Maske mit dem Namen verwendet, der als String in diesen Items gespeichert ist. Falls es diese Items nicht gibt, wird überprüft, ob die Datenbank eine so genannte DefaultMaske hat, und das Dokument wird mit dieser Maske angezeigt. Ist auch dies nicht der Fall, wird eine entsprechende Fehlermeldung ausgegeben. Masken und Dokumente sind zwar über den Pointer im Item Form miteinander verknüpft, es ist aber wichtig für das Verständnis, im Auge zu behalten, dass beide unabhängig voneinander sind. Wird z.B. ein Feld aus einer Maske entfernt, werden keinesfalls alle korrespondierenden Items aus Dokumenten entfernt, die mit dieser Maske erstellt wurden oder die über das Item Form diese Maske referenzieren. Beim Öffnen eines Dokuments mit einer derart veränderten Maske wird lediglich das Item nicht angezeigt, wobei es weiterhin im Dokument verbleibt. Die Maske ist also tatsächlich ein reines Gestaltungselement (mit sicherlich vielen programmatischen Fähigkeiten). Beinhaltet im umgekehrten Fall eine Maske ein
44
2 Notes @ Domino
Feld, das nicht als Item im anzuzeigenden Dokument vorliegt, wird dieses Feld schlicht als nicht initialisiert betrachtet, wie dies für alle Felder in einem neuen, noch nicht gespeicherten Dokument der Fall ist. Wie bereits in der Beispielanwendung in Kapitel 2.1.2 deutlich wurde, dienen Masken in Domino dem Anwender als interaktive Schnittstelle zwischen Benutzer und Datenbank. • • •
Über Masken werden Daten vom Benutzer eingegeben, gegebenenfalls manipuliert und in Backend-Dokumenten gespeichert. Masken können Backenddaten anzeigen, wobei diese Daten zuvor möglicherweise durch @Formeln (oder LotusScript) in der Maske manipuliert wurden. Masken dienen der Gestaltung der grafischen Benutzer-Oberflächen.
Dieses Verhalten von Masken muss bei der Java-Programmierung berücksichtigt werden, wenn Dokumente im Backend manipuliert oder erzeugt werden, damit diese Daten von den Masken (und auch von Ansichten, wie noch zu zeigen sein wird) korrekt verarbeitet werden. Bei der Verarbeitung von Dokumenten, die später mit Hilfe von Masken angezeigt werden sollen, ist zu beachten: • • • • •
•
Das Item „Form“ muss den Namen der Maske enthalten, mit dem das Dokument dargestellt werden soll. Jedes Dokument sollte das Item „Form“ enthalten. Das Dokument sollte alle Items enthalten, die in der Maske in Feldern angezeigt werden sollen. Das Dokument muss alle Items enthalten, die in der Maske für Berechnungen benötigt werden. Alle Items sollten den gleichen Datentyp haben wie die Felder, mit denen sie angezeigt werden sollen. Stimmen die Datentypen nicht überein, wird der Notes Client versuchen, soweit möglich, eine Datenkonvertierung durchzuführen. Wurde im Document durch Benutzung von replaceItemValue (String, Vector) ein Item mit mehreren Werten angelegt, und hat das korrespondierende Feld nicht die Eigenschaft „mehrere Werte zulassen“, dann wird nur der erste Wert im Feld angezeigt und beim Speichern auch nur gespeichert. Weitere Werte gehen verloren.
Gleichzeitig muss natürlich damit gerechnet werden, dass die über Masken zur Verfügung gestellten Daten den Regeln der Masken unterliegen: •
•
Das durch die Maske generierte Item „Form“ enthält den Namen oder sofern vorhanden den Alias einer Maske, sofern das Feld Form nicht explizit durch Berechnung in der Maske verändert worden ist. Die Items erhalten den Datentyp, der durch die jeweiligen Felder der Maske vorgegeben ist. Durch automatische Typ-Konvertierung kann sich der Datentyp eines Items geändert haben, falls das Item vor dem Öffnen und Speichern mit der Maske einen anderen Datentyp hatte.
2.2.2 Notes Document und Notes Form • • • • •
• •
45
Durch Berechnungen oder Benutzereingaben in der Maske können Items verändert worden sein. Durch das Speichern mit einer Maske können Items gelöscht worden sein (durch die @Formel @deleteField) Neue Items können durch die Maske hinzugefügt worden sein. Einzelne Items können durch die Maske verschlüsselt worden sein. Die Berechtigungen für das Dokument können sich durch Verändern oder Hinzufügen von Leser- oder Autorenfeldern geändert haben. Hat der Ausführende eines Agenten keine entsprechenden Rechte für das Dokument, wird es nicht in Ansichten angezeigt und bleibt somit möglicherweise unsichtbar für einen Agenten. Durch Masken neu erstellte Dokumente können durch die Maske Default Rechte in Form von speziellen Autoren- oder Leserfeldern erhalten haben. Hat ein Feld die Eigenschaft „mehrere Werte zulassen“, so kann ein Benutzer eine Liste von Werten in dieses Feld eingeben. Diese Felder dürfen nicht mit getItemValueString, getItemValueInteger etc. ausgelesen werden, sondern müssen mit getItemValue ausgelesen werden.
Eine Maske kann neben Text und Feldern etliche andere Gestaltungselemente, wie Tabellen, berechnete Texte zur Darstellung von Berechnungsergebnissen als Fließtext, eingefügte Bilder oder referenzierte Bilder aus den Ressourcen, eingebettete Ansichten, die dann inline als Teilbereich der Maske angezeigt werden, RichText und Teilmasken, enthalten. Teilmasken sind Masken, die den normalen Masken sehr ähnlich sind, jedoch nicht als allein stehendes Designelement verwendet werden können, sondern lediglich dazu dienen in eine Maske eingefügt zu werden. Hierdurch lassen sich Masken modularisieren und Teile von Masken wieder verwenden. Eine Notes-Maske kann durch das Java-Objekt Form repräsentiert werden. Hierdurch können Eigenschaften einer Maske programmatisch ausgelesen und zum Teil auch verändert werden. In Listing 2-2 wird gezeigt, wie die Form-Objekte einer Datenbank ausgelesen und angezeigt werden können. Ziel des Agenten ist ein neues Dokument anzulegen, in dem in einer übersichtlichen Form allgemeine Informationen über alle Masken der Datenbank und deren Felder angezeigt wird. Der Agent soll über das Menü „Actions“ gestartet werden. Die erstellten Übersicht-Dokumente sollen mit der Maske FO_Report angezeigt werden. Um den Agenten zu erstellen und einsetzen zu können, müssen Sie zunächst eine Maske erstellen, die den Alias „FO_Report“ hat. Den Klarnamen können Sie frei wählen. Da die Dokumente, die mit dieser Maske erstellt werden sollen, nur über den Agenten erzeugt werden und nicht durch eine interaktive Eingabe durch den Benutzer, sollten Sie in den Maskeneigenschaften das Häkchen „Display“ -> „Include in Menu“ deaktivieren, damit die Maske nicht im Menü „Create“ erscheint. Fügen Sie der neuen Maske das Feld F_Titel (Textfeld) und Body (RichText) hinzu.
46
2 Notes @ Domino
Erstellen Sie einen neuen Agenten mit Klarnamen Tools\1. Masken anzeigen und mit Aliasnamen AG_ShowForms. Dieser Agent kann später über das Menü „Actions“ -> „Tools“ -> „1. Masken anzeigen“ gestartet werden. Übernehmen Sie das Listing 2-2 als Code in den Agenten. Erstellen Sie eine Ansicht mit Klarnamen „Alle Dokumente“ und Alias „V_all“. Geben Sie dieser Ansicht die Select-Formel „SELECT @All“ und fügen Sie der Ansicht die Spalte „Titel“ hinzu, die das Item „F_Titel“ anzeigt. Beachten Sie, dass Ansichten in Domino die Daten selbst nicht beeinflussen, sondern lediglich in gestalteter Form anzeigen. Diese Anzeige wird alle Dokumente der Datenbank anzeigen und von diesen Dokumenten, sofern vorhanden, das Feld F_Titel. Experimentieren Sie mit der Ansicht, indem Sie in einer zusätzlichen Spalte das Erstelldatum und die verwendete Maske anzeigen lassen. Wenn Sie Ihre Datenbank im Notes Client öffnen, sehen Sie im linken Rahmen die neue Ansicht „Alle Dokumente“. Klicken Sie auf diesen Eintrag. Starten Sie nun den Agenten über das Menü „Aktionen“. Nach Beendigung des Agenten erscheint ein neues Dokument in dieser Ansicht mit dem Titel „Alle Masken dieser Datenbank“. Dieses Dokument enthält nun Informationen über die FORM Notes der Datenbank. Listing 2-2 zeigt den Sourcecode des Agenten: Die Session und die Datenbank werden wie gewohnt über getSession und getAgentContext().getCurrentDatabase() geladen. Ein neues Dokument wird mit database.createDocument erstellt. Das Item Form wird mit dem String „FO_Report“ belegt. Hierdurch wird festgelegt, dass das erzeugte Dokument mit der Maske FO_Report geöffnet werden soll. document.createRichTextItem("Body") erzeugt ein RichText Item mit Namen „Body“, in dem die Übersicht später gespeichert werden soll. Beachten Sie, dass zwar die zuvor erstellte Maske das Feld Body zur Verfügung stellt, aber das Dokument ohne Verwendung der Maske erstellt wird. Daher müssen alle Items manuell über den Java-Code erstellt werden. Bei der Erstellung muss der Datentyp berücksichtigt werden, mit dem die Maske später das Dokument anzeigen soll. Um die Masken aus der Datenbank zu laden, wird die Methode database.getForms verwendet, die einen Vector von Form-Objekten liefert. Ein RichText Item kann nicht direkt mit Inhalt gefüllt werden, wie z.B. ein Text Item, sondern wird mit Teilobjekten, wie z.B. Text, Style-Informationen oder eingebetteten Anhängen, wie Bildern, gefüllt. appendText fügt einen Text zum RichText Item hinzu. Das Form-Objekt hat einige getter Methoden, um allgemeine Eigenschaften auszulesen. Neben Namen und Aliasnamen wird in dem Agenten mit isSubform ausgelesen, ob es sich um eine Teilmaske (Subform) handelt. Hier zeigt sich das Konzept der Klassen und Typen von Note-Objekten. Ist eine Note aus der Klasse FORM (Tabelle 2-2), so entscheidet sich über das Vorhandensein eines speziellen internen Items, ob die Note vom Typ SUBFORM ist (Tabelle 2-1, Punkt 5).
2.2.2 Notes Document und Notes Form
47
import lotus.domino.*; import java.util.Vector; import java.io.*; public class ShowFormJavaAgent extends AgentBase { public void NotesMain() { try { Session s = getSession(); Database database = s.getAgentContext().getCurrentDatabase(); Document document = database.createDocument(); document.replaceItemValue("Form", "FO_Report"); document.replaceItemValue("F_Titel", "Masken der Datenbank"); RichTextItem rt = document.createRichTextItem("Body"); Vector forms = database.getForms(); rt.appendText("Masken in \"" + database.getTitle() + "\":"); for (int i = 0; i < forms.size(); i++) { Form form = (Form) forms.elementAt(i); rt.appendText("\n\nMaskenname : " + form.getName()); Vector alias = form.getAliases(); rt.appendText("\n\tAlias "); for (int j = 0; j < alias.size(); j++) { rt.appendText("\n\t \" \"\t: " + alias.elementAt(j)); } rt.appendText("\n\tGeschützte LeserFelder\t: " + form.isProtectReaders()); rt.appendText("\n\tGesch. AutorF.: "+form.isProtectUsers()); rt.appendText("\n\tIst Teilmaske\t: " + form.isSubForm()); Vector users = form.getFormUsers(); rt.appendText("\n\tBenutzer, die Dokumente mit d. Maske "+ "\n\terstellen dürfen:"); for (int j = 0; j < users.size(); j++) { rt.appendText("\n\t\t: " + users.elementAt(j)); } Vector readers = form.getReaders(); if (readers.size() != 0) { rt.appendText("\n\tBenutzer, die Dokumente lesen dürfen, die mit dieser Maske \n\terstellt wurden:"); for (int j = 0; j < readers.size(); j++) { rt.appendText("\n\t\t: " + readers.elementAt(j)); } } Vector fields = form.getFields(); rt.appendText("\n\tFelder"); for (int j = 0; j < fields.size(); j++) { String fName = (String) fields.elementAt(j); int typ = form.getFieldType(fName); String typS = ""; switch (typ) { case Item.AUTHORS : typS = "Autor"; break; case Item.DATETIMES : typS = "Datum-Uhrzeit"; break; case Item.NAMES : typS = "Namen"; break; case Item.NUMBERS : typS = "Zahl"; break; case Item.RICHTEXT : typS = "Richtext"; break; case Item.READERS : typS = "Leser"; break; case Item.TEXT : typS = "Text oder Text - Liste"; break; default : typS = "anderer Typ";} rt.appendText("\n: " + fName + " " + typS + " - " + typ); } } document.save(true, false); } catch (Exception e) { e.printStackTrace(); } } }
Listing 2-2 Form-Objekte und -Eigenschaften anzeigen
48
2 Notes @ Domino
Beachten Sie: Default-Rechte, die über eine Maske vergeben werden können, werden nur dann in einem Dokument gespeichert, wenn es a) neu angelegt wird und b) interaktiv über eine Maske angelegt wird. Das Setzen der Default-Rechte ist eine Leistung des Notes Clients und muss bei der Java-Programmierung im Backend manuell durchgeführt werden.
Werden Dokumente interaktiv durch die Verwendung einer Maske erstellt, so können über die Maskeneigenschaften Default-Berechtigungen vergeben werden. Diese können über getFormUsers und getReaders in Form ausgelesen werden. Beachten Sie, dass diese Default-Rechte nur dann vergeben werden, wenn ein Dokument mit einer Maske NEU erstellt wird. Die Namen aller in einer Maske verwendeten Felder können über getFields als Vector geladen werden. Felder legen fest, welchen Datentyp das korrespondierende Item erhalten soll. Diese Vorgabe kann über getFieldType als Integer ausgelesen werden. Zur Vereinfachung gibt es im Paket lotus.domino.* Konstanten, mit denen die Typen beschrieben werden können. Das neu erstellte Dokument muss nachdem die gewünschten Informationen in dem Dokument abgelegt wurden, durch den Aufruf von save gespeichert werden. Der Agent AG_ShowForms erstellt eine Übersicht über die in einer Datenbank befindlichen Masken und deren Felder. Eine Sonderstellung nimmt das Profildokument ein, also ein Dokument mit dem Typ PROFILEDOCUMENT. Ein Profildokument wird mit der Methode public Document getProfileDocument(String profile, String profilekey) throws NotesException erzeugt oder geöffnet. Der Parameter profile ist der
Name der Maske, mit der das Profildokument erstellt wurde bzw. angezeigt werden soll, also vergleichbar mit dem Feld „Form“. Der Parameter profilekey ist ein Schlüssel, unter dem das Profil gespeichert werden soll, so können z.B. benutzerspezifische Profile angelegt werden. Der Parameter profilekey darf null sein. Existiert ein Profildokument mit den gegebenen Parametern beim Aufruf von getProfileDocument noch nicht, wird es automatisch angelegt und gespeichert (!), d.h. hier ist Vorsicht geboten, falls die Erstellung von Profildokumenten unter bestimmten Bedingungen unerwünscht ist.
2.2.3 Item und Feld
49
Profile unterliegen einem speziellen Caching-Mechanismus und werden im Hauptspeicher des Servers zwischengespeichert. Dadurch sind sie sehr viel performanter als normale Dokumente, haben aber auch einige Nachteile, die in Kapitel 7.2 diskutiert werden. Wichtig für das Verständnis des Zusammenhangs zwischen Masken und Dokumenten ist die Kenntnis der Eigenschaften und Unterschiede von Feldern und Items.
2.2.3
Item und Feld
Das Speicherkonzept von Domino sieht die Organisation der Daten in losen Schemata innerhalb von Dokumenten vor. Die einzelnen Teilinformationen werden in so genannten Items im Dokument gespeichert. Im Gegensatz zu einer SQL-Datenbank ist ein Dokument (vgl. Datensatz im SQL) nicht an ein festes Schema (vgl. Tabelle im SQL) gebunden. Jedes Dokument kann seine eigenen Items unabhängig von anderen Dokumenten enthalten. Die Organisation der Daten und die Zuordnung der Daten (Dokumente) zueinander wird in Domino über Ansichten (vgl. Views im SQL) erreicht. Items sind die Container, in denen die Daten innerhalb eines Dokuments gespeichert werden. Sie können von verschiedenen Typen sein. Felder sind diejenigen Elemente einer Maske, die in der Ausprägung verschiedener Eingabehilfen dem Benutzer die Eingabe von Daten ermöglichen, aber auch zur Anzeige berechneter Informationen dienen. Items, die sich in einem Dokument befinden, werden nicht automatisch durch Felder in einer Maske sichtbar. Sie sind aber dennoch im Dokument vorhanden und können programmatisch verarbeitet werden. Um Items in einer Maske anzuzeigen, muss der Maske ein Feld mit gleichem Namen (oder einer Berechnung, die z.B. den Wert eines Items anzeigt) oder mit einem berechneten Text, der den Wert des Items lädt und anzeigt, hinzugefügt werden. Auch Felder haben verschiedene Ausprägungen mit unterschiedlichen Typen. Die Datentypen der Items und der Felder können als lose miteinander verknüpft betrachtet werden. Wie bereits im vorigen Kapitel aufgezeigt, werden Daten, deren Typ zwischen Feld und Item abweicht, sofern möglich, konvertiert. Unstimmigkeiten zwischen Feld- und Itemtypen können zu Fehlern führen und müssen stets bei der Programmierung im Auge behalten werden. In Tabelle 2-3 sind wichtige Datentypen von Items und Feldern gelistet. Zusätzlich wird aufgezeigt, durch welche Arten von Items und Feldern diese Datentypen erzeugt werden können. Beispiel A: In eine Maske wird ein Feld mit Typ Text eingefügt. Zusätzlich wird die Feld-Eigenschaft „Mehrere Werte zulassen“ angekreuzt. Füllt ein Benutzer ein solches Feld aus, werden alle eingegebenen Werte, die z.B. durch ein Semikolon (welches Trennzeichen verwendet wird, hängt wiederum von Feldeigenschaften ab) vom Benutzer getrennt wurden, im gleichnamigen Item als Textliste gespeichert.
50
2 Notes @ Domino
Tabelle 2-3 Wichtige Datentypen und Arten von Items und Feldern Datentyp des Items
Art des Items
Datentyp des Feldes
Art des Feldes
Java-Konstante (**)
Text List
Text, Textliste
Text, Textliste
Text, Passwort (*), Dialogliste, Radiobutton (*), Checkbox, Listbox, Aufklappmenü (*), Timezone-Wähler (*), Farbwähler (*)
TEXT
Text List
Leser, Leserliste
Leser, Leserliste
Leser
READERS
Text List
Autoren, Autorenliste
Autoren, Autorenliste
Autoren
AUTHORS
Text List
Namen, Namenliste
Namen, Namenliste
Namen
NAMES
NoteRef_List
Response Reference list (Verwendung im Item $Ref)
(wird nicht als Designelement in Masken verwendet)
NOTEREFS
NoteLink_List
DocLink Reference list (Verwendung im Item $Links)
(wird nicht als Designelement in Masken verwendet)
NOTELINKS
Number Range
Zahl, Zahlliste
Zahl, Zahlliste
Zahl
NUMBERS
Time Range
Datum/Uhrzeit, Datum/UhrzeitListe
Datum/Uhrzeit, Datum/UhrzeitListe
Datum-, Zeitwähler oder Datum/Zeit-Eingabefeld
DATETIMES
Composite
RichText, RichText light, Embedded Object
RichText, RichText light
RichText, RichText light
RICHTEXT, EMBEDDEDOBJECT, ATTACHMENT und andere
MimePart
MimePart
(wird nicht als Designelement in Masken verwendet)
MIME_PART
(*) kann nicht als Textliste auftreten
(**) Item.getType(): Name der Konstanten in Item
2.2.3 Item und Feld
51
Beispiel B: In eine Maske wird ein Feld mit Typ Radiobutton eingefügt. Wählt der Benutzer einen Button aus, wird der dahinter (über Feldeigenschaften) verborgene TextWert im gleichnamigen Item als Text gespeichert. Andere Werte als Text können nicht über einen Radiobutton übergeben werden. Beispiel C: In eine Maske wird ein Feld mit Typ Autoren eingefügt. Zusätzlich wird die FeldEigenschaft „Mehrere Werte zulassen“ angekreuzt. Füllt ein Benutzer ein solches Feld aus, werden alle eingegebenen Werte, die z.B. durch ein Semikolon (welches Trennzeichen verwendet wird, hängt wiederum von Feldeigenschaften ab) vom Benutzer getrennt wurden, im gleichnamigen Item als Textliste gespeichert. Da das Feld ein Autorenfeld war, wird im Item ein spezielles Flag gesetzt. Über Java kann dieses Flag mit setAuthors und isAuthors gelesen und gesetzt werden. RichText (und RichText light) ist ein etwas komplexerer Feld- und Itemtyp. RichTextItems sind Container, die wiederum Objekte enthalten können. Das wichtigste Objekt innerhalb eines RichText-Items ist der Paragraph. RichText-Items können eingebettete Dateien, Grafiken etc. aufnehmen. Diese werden nicht im RichText-Item selbst gespeichert, sondern in einem separaten Item mit Datentyp Composite als embedded Object gespeichert und über einen Zeiger verlinkt. Embedded Objects, die über eine Maske in ein RichText-Feld eingegeben werden, werden im Item $File gespeichert. In Kapitel 2.2.2 und Listing 2-2 wurde aufgezeigt, wie mit einfachen Mitteln Informationen über Masken angezeigt werden können. Ebenso lassen sich die Items, die in einem Dokument gespeichert sind, extrahieren und anzeigen (Listing 2-3). Der Agent befindet sich in der Buch-Datenbank djbuch.nsf und heißt AG_ShowItems. Er kann über das Menü „Actions“ -> „Tools“ -> „2. Items anzeigen“ gestartet werden. Die Aufgabe dieses Agenten ist es, über ein vorgegebenes, durch den Benutzer in der Ansicht „Kapitel 2\Dokumente“ markiertes Dokument Informationen über die Items dieses Dokuments einzusammeln und in einem so genannten Antwortdokument abzuspeichern. Antwortdokumente sind normale Dokumente, die aber über das Item $Ref ein Mutter-Dokument referenzieren. In Ansichten lassen sich solche Dokumente kategorisiert darstellen, so dass deutlich wird, zu welchem Mutter-Dokument (auch Parent- oder Hauptdokument) das Antwortdokument gehört. Ursprünglich waren Antwortdokumente in Diskussionen für Antworten auf Beiträge entwickelt worden (daher der Name Antwortdokument). Antwortdokumente wurden daher in Ansichten in einer für Diskussionsforen typischen, kategorisierten Form angezeigt. Das Konzept der Antwortdokumente ist jedoch in Domino erweitert worden und wird in der Regel allgemein gültig für die Referenzierung zwischen Mutter- und Tochter-Dokumenten verwendet. Dieses Konzept hat viele Vorteile, aber auch einige Nachteile, die in Kapitel 7.3f. diskutiert werden.
52
2 Notes @ Domino
import lotus.domino.*; import java.util.Vector; import java.util.Enumeration; public class ShowItemsJavaAgent extends AgentBase { public void NotesMain() { try { Session session = getSession(); Database db = session.getAgentContext().getCurrentDatabase(); Document doc = session.getAgentContext().getDocumentContext(); if (doc != null) { Document responsedoc = db.createDocument(); responsedoc.replaceItemValue("Form", "FO_Report"); RichTextItem rt = responsedoc.createRichTextItem("Body"); responsedoc.replaceItemValue("F_Titel","Items von Dok. \"" + doc.getItemValueString("F_Titel") + "\""); responsedoc.makeResponse(doc); Enumeration items = doc.getItems().elements(); while (items.hasMoreElements()) { Item item = (Item) items.nextElement(); String type; switch (item.getType()) { case Item.TEXT : type = "Text o. Textliste"; break; case Item.READERS : type = "Leser"; break; case Item.AUTHORS : type = "Autoren"; break; case Item.NAMES : type = "Namen"; break; case Item.NOTEREFS : type = "Referenz auf das übergeordnete Dokument"; break; case Item.NOTELINKS : type = "Link auf Datenbank, eine Ansicht oder ein Dokument"; break; case Item.NUMBERS : type = "Zahl / Zahlliste"; break; case Item.DATETIMES : type = "Datum/Uhrzeit o. Bereich/Liste von Datum/Uhrzeit"; break; case Item.RICHTEXT : type = "RichText"; break; case Item.EMBEDDEDOBJECT : type = "Eingebettetes Object"; break; case Item.ATTACHMENT : type = "Dateianhang"; break; case Item.HTML : type = "HTML-Quelltext"; break; default : type = "Anderer Typ"; } rt.appendText("\n" + item.getName() + "\n" + type); rt.appendText("\n" + item.getValueLength() + " bytes"); rt.appendText("\nLast modified " + item.getLastModified().getLocalTime()); if (item.getText().length() == 0) { rt.appendText("\n[kein darstellbarer Text]\n"); } else { rt.appendText("\n" + item.getText(80) + "\n"); } } responsedoc.save(true, false); } } catch (Exception e) { e.printStackTrace(); } } }
Listing 2-3 Items eines Dokuments anzeigen
2.2.3 Item und Feld
53
Die Informationen, die in diesem Agenten gesammelt werden, sollen in einem neuen Dokument gespeichert werden. Dieses neue Dokument soll eine Referenz zu dem ursprünglichen Dokument erhalten. Die Methode makeResponse wird auf dem neuen Dokument ausgeführt und erhält als Parameter das Mutter-Dokument, das referenziert werden soll. Durch Anwenden dieser Methode wird ein Item $Ref im Antwortdokument angelegt. Dieses Item hat den Datentyp NoteRef_List und enthält die so genannte UniversalID des Dokuments (s. auch Kap. 2.5). getItems liefert einen Vector von Item-Objekten.
Items und Felder: Item werden die einzelnen Datenobjekte innerhalb eines Dokumentes genannt. Ein Dokument kann eine beliebig große (nur durch einige Speicherlimits begrenzte) Anzahl von Items enthalten. Items enthalten Daten unterschiedlichster Datentypen. Felder heißen die korrespondierenden Objekte in einer Notes-Maske. Ein Feld dient dazu, das Item gleichen Namens anzuzeigen oder zu erzeugen. Ein Feld dient der Anzeige von Items, oder Ein Feld dient der Eingabe von Daten. Diese vom Benutzer eingegebenen Daten werden im gleichnamigen Item gespeichert. Ein Feld kann einerseits verschiedene Erscheinungsformen (Textfeld, Listbox, Aufklappmenü, Radiobox, etc.) haben. Andrerseits kann ein Feld verschiedene Datentypen haben. Diese Datentypen werden bei der Eingabe vom Notes Client validiert. Ein Item, das entstand, weil Daten durch ein Feld im Dokument gespeichert wurden, hat den gleichen Datentyp wie das Feld. Ein Feld, das Daten eines Items anderen Typs anzeigen soll, versucht diese Daten zu konvertieren. Felder und Items können mehrere Werte gleichzeitig enthalten. Ein Feld mit mehreren Werten zeigt diese unterschiedlich, je nach Eigenschaft des Feldes an (z.B. durch Semikolon oder durch neue Zeilen getrennt).
54
2 Notes @ Domino Der Typ, der via getType erhalten wird, beschreibt das Item bereits sehr detailliert, so dass nicht extra noch separate Methoden zu Rate gezogen werden müssen, um das Item genauer zu analysieren. So liefern z.B. isAuthor oder isReader Informationen, ob es sich bei einem Item um Autoren- oder Leseritems handelt. Die im Code gelisteten Konstanten beschreiben nicht das ganze Spektrum. Es stehen noch weitere Typen zur Verfügung, die aber aus Sicht der Programmierung nicht relevant sind und durch Domino intern verwendet werden bzw. nur über das C++ API angesprochen werden sollten. Neben Name und Größe besitzt jedes Item Angaben über den Zeitpunkt der letzten Änderung. Da nicht jedes Item Text-Informationen oder durch Text repräsentierbare Informationen besitzt, wird in dem Agenten überprüft, ob getText Ergebnisse liefert. Ein eventueller Datei-Anhang muss aus dem RichText (s. Kap. 11.1ff.) mit speziellen Methoden extrahiert werden.
Grundsätzlich werden Items mit der Methode getFirstItem () gelesen, wobei ein Objekt der Klasse Item zurückgegeben wird. Den Wert oder die Werte, sofern es sich um ein Listen-Item handelt, eines Items können mit der Methode getItemValue (String itemName) gelesen werden. Hierbei wird immer ein Vector mit einem oder mehreren Elementen der zum Datentyp des Items passenden Klasse zurückgegeben. Einzelwerte werden mit den Methoden getItemValueString (String itemName), getItemValueInteger (String itemName) oder getItemValueDouble (String itemName) gelesen, wobei diese Methoden null, 0 oder 0.0 zurückgeben, wenn das entsprechende Item nicht vorhanden ist. Geschrieben werden Items mit der Methode replaceItemValue (Object o), wobei der Datentyp des geschriebenen Items von der Klasse von o abhängt (siehe auch Beispiel aus Listing 2-4). Alternativ kann die Methode appendItemValue verwendet werden, die jedoch ein weiteres Item mit gleichem Namen erzeugt. Die Verwendung dieser Methode ist eher nicht zu empfehlen. Durch die konsequente Einführung von XML für Domino und die Abbildung der Domino-Objekte durch DXL ergibt sich für das Verständnis von Dokumenten, Masken, Items und Feldern die Möglichkeit, diese Objekte auch als XML darzustellen und zu interpretieren. In der Buch-Datenbank djbuch.nsf finden sich zwei Agenten, die dies verdeutlichen. Öffnen Sie die Datenbank djbuch.nsf und markieren Sie ein beliebiges Dokument in der Ansicht „Alle Dokumente“. Starten Sie im Menü „Actions“ -> „Tools“ den Agenten „3. Dokument als XML“. Der Agent4 erzeugt ein Antwortdokument, das unterhalb des zuvor markierten Dokuments erscheint. Öffnen Sie das Dokument, um den erzeugten XML-Code zu sehen. 4
Dieser Agent kann nur auf einer lokalen Replik auf dem Notes Client gestartet werden. Dies liegt an einer Eigenart des verwendeten Domino XML Exporters (DXL). Dieser startet einen eigenen NotesThread. Da das anzuzeigende Dokument bereits als so genanntes Context-Dokument (das markierte Dokument) vorliegt, kann es nicht gleichzeitig vom DXL Exporter verarbeitet werden. Bei einer lokalen Verarbeitung tritt diese Situation nicht auf.
2.2.4 Datentypen
55
Der Agent „Actions“ -> „Tools“ -> „4. Gesamtes Design als XML“ erzeugt ein neues Dokument mit dem Titel „Alle Designelemente dieser Datenbank als XML“. Dieses Dokument enthält die XML-Darstellung aller Designelemente in der Datenbank. Hier finden Sie zum Beispiel die Maske FO_URLLoader_k2 und die darin befindlichen Felder in der Darstellung als XML. Weiterführende Informationen zu diesem Thema finden Sie in Kapitel 17.7ff.
2.2.4
Datentypen
2.2.4.1
Text und Textliste
Die meisten Felder speichern Ihre Daten in Items vom Datentyp Text oder Textliste. Hierzu gehören Text, Password (*), Dialog list (spezielle in Notes übliche Dialogliste), Radio button (*) (Auswahl einer aus mehreren), Checkbox (Auswahl mehrere aus mehreren), Listbox (Auflistung), Combobox (*) (Aufklappmenü), Time zone (*) (Zeitzonen-Wähler), Color (*) (Farbwähler), wobei die mit (*) markierten Typen keine Textliste sein können. Die Feldtypen Text, Radio button, Checkbox, Combobox, Listbox und Dialog list sind Felder, die direkt Ihren eingegebenen oder ausgewählten Text in dem Item speichern. Die unterschiedlichen Typen dienen lediglich dazu, unterschiedliche Benutzerinterfaces zur Verfügung zu stellen. Diese Typen sind direkt vergleichbar mit den < INPUT> Feldern mit type= Text, Radio, Checkbox und <SELECT> mit size= 1 für ein Aufklappmenü bzw. size > 1 für eine Listbox im HTML. Die Dialogliste ist vergleichbar mit einer Listbox, die jedoch eine eigene Darstellung im Notes Client erhält. Diese Unterscheidung nach Feldtypen ist nur relevant, falls die Daten über ein Feld in das Dokument eingegeben werden sollen und erleichtern dem Benutzer die Eingabe. Items, die als Text über das Backend und eine der Methoden replaceItemValue oder appendItemValue in das Dokument eingefügt wurden, können über sämtliche obigen Felder in einer Maske dargestellt werden. Durch die impliziten Konvertierungsmöglichkeiten in einer Notes-Maske können auch z.B. Items vom Datentyp Zahl als Textfeld angezeigt werden, wobei dies wegen der möglichen Verwechslungen und dadurch entstehenden Fehler nicht empfohlen wird. Time zone, Color und Password sind spezielle Felder mit besonderen GUIs. Über den Typ Time zone kann eine Zeitzone ausgewählt werden, die dann z.B. für die Lokalisierung von Date/Time-Feldern benutzt werden kann. Dieser Typ steht nur als Feld in einer Maske zur Verfügung. Die ausgewählte Zeitzone wird als Text in einem Item gespeichert. Gleiches gilt für den Typ Color, der den RGB-Wert der ausgewählten Farbe als 3x2 Byte-Texte, ähnlich dem HTML, speichert. Felder vom Typ Password sind Felder, die beim Eintippen von Werten Sternchen statt der getippten Buchstaben anzeigen. Dies ist keine (!) Sicherheitsfunktion. Der eingegebene Wert wird im Klartext gespeichert.
56
2 Notes @ Domino
Daten werden in Textfeldern als so genanntes Lotus Multi-Byte Character Set LMBCS [Batutis, LMBCS] abgespeichert. Das LMBCS wurde 1989 entwickelt und hatte somit schon lange vor der Entwicklung von Unicode die Fähigkeit multilinguale Zeichensätze abzubilden. LMBCS hat drei Ziele: Abwärtskompatibilität Keinen Mehrbedarf an Speicherplatz in Standard-Situationen Abbilden aller internationaler Zeichensysteme in einer Struktur Um dies zu erreichen, werden die Zeichen in LMBCS-Gruppen unterteilt. Jede LMBCS-Gruppe bildet ein Standard-Industrie-Encoding ab. Die Gruppe 0 entspricht dem ASCII-Standard. Da durch eine spezielle Technik erreicht wird, dass Zeichen der Gruppe 0 kein extra Byte für die Gruppenzugehörigkeit benötigen, können die ersten beiden Forderungen erreicht werden. Durch die Abbildung der Industrie-StandardTabellen wird die dritte Forderung erreicht. So werden zum Beispiel in Gruppe 01 Zeichen des Encodings aus der Microsoft Codepage 850 oder in Gruppe 14 (hex) die Zeichen aus UTF-16 abgebildet. Da das Handling von Encodings und der LMBCS-Zeichenumwandlung komplett von den Core-Klassen in Domino übernommen wird, muss der Java-Programmierer sich nicht um LMBCS kümmern.
2.2.4.2
Namen, Autoren und Leser
Namens- und auch Autoren- oder Leser-Items sind Text-Items, mit einem zusätzlichen Flag, das Sie als Namensfelder auszeichnet. In Namensfeldern werden Namen von Benutzern, Gruppen oder Servern gespeichert. Domino arbeitet mit einer so genannten hierarchischen Namensstruktur, die der des LDAP-Standards sehr ähnlich ist. Namen können in der abgekürzten Form /// oder in der kanonischen Form CN=< >/OU=/O=/C= dargestellt werden. In beiden Fällen können bis zu vier Hierarchien von Organisationseinheiten optional vorliegen. Das Land ist ebenfalls optional. Intern in den Items speichern Namensfelder den Namen als Text in der kanonischen Form. Der Notes Client konvertiert bei der Eingabe von Namen in Felder einer Maske automatisch zwischen der einfachen und der kanonischen Form der Namen. In Java muss der kanonische Name manuell oder unter Zuhilfenahme der Methoden der Klasse Name umgewandelt werden. Ein Name-Objekt kann über die Session aus einem kanonischen oder einfachen Namen erstellt werden. Beispiele hierzu finden Sie in Kapitel 12.1ff. Leser- oder Autorenfelder sind spezielle Namensfelder. Befindet sich mindestens ein Namensfeld in einem Dokument, so dürfen nur Benutzer, die in diesem Feld ge-
2.2.4.3 Datum / Uhrzeit
57
speichert sind oder die einer Gruppe oder Rolle angehören, deren Name im Feld gelistet ist, das Dokument öffnen. Autorenfelder arbeiten ähnlich. Durch sie wird bestimmt, wer ein Dokument bearbeiten darf.
2.2.4.3
Datum / Uhrzeit
Datums- und Uhrzeit-Items speichern Datums- oder Zeitangaben und Listen oder Bereiche solcher Werte. In der Programmierung in Java können diese Items sehr einfach über java.util.Calendar, lotus.domino.DateTime oder lotus. domino.DateRange erzeugt und per replaceItemValue im Dokument abgelegt werden. Für die verschiedenen Erscheinungsformen gibt es für die Felder in einer NotesMaske sehr praktische Zeit- und Datumswähler. Wichtig ist hierbei, dass solche Felder nur bestimmte Items verarbeiten können, so wird ein Zeitwähler immer ein Item mit einem reinen Zeit-Objekt (ohne Datum) erwarten. Listing 2-4 zeigt einführend den Umgang mit DateTime-Objekten: Ein DateTime-Objekt kann durch createDateTime in Session entweder aus einem java.util.Calendar (oder java.util.Date) oder direkt aus einem String, wie z.B. „12.12.97 16:30:00“ oder „Today“ erzeugt werden. Beachten Sie, dass die Trennzeichen systemabhängig sind, dass also durch die Verwendung des Strings Inkompatibilitäten auftreten können und eine Invalid date Exception geworfen wird, falls der String nicht interpretiert werden kann. Wird ein DateTime-Objekt sowohl mit einer Datums-, als auch mit einer Zeitangabe erzeugt, dann enthält es zusätzlich automatisch die lokal eingestellte Zeitzone. DateTime-Objekte können zusätzlich ein Flag für Sommer- und Winterzeit enthalten. DateTime-Listen werden wie gewohnt über einen Vector und replaceItemValue im Dokument abgelegt. In der Darstellung werden die einzelnen Werte durch ein Trennzeichen, das in den Feldeigenschaften spezifiziert werden kann, separiert. Zeitbereiche (Range) werden über lotus.domino.DateRange aus DateTime-Objekten erzeugt. Date/Time-Ranges können nicht mit einem speziellen Feld angezeigt werden, sondern erscheinen in einem normalen Date/Time-Feld, wobei der Anfangs- und Endwert durch ein Minuszeichen getrennt dargestellt werden. Sollen nur ein Datum oder nur eine Zeit erzeugt werden, kann der jeweils nicht benötigte Teil mit setAnyDate oder setAnyTime „entfernt“ werden. Hierdurch werden auch Zeit-Ranges möglich, für die es einen sehr praktischen Time-Range-Wähler gibt (s. Abb. 2-10). In dieser Abbildung sehen Sie außerdem die Eigenschaften des Dokuments. Im zweiten Tabellenreiter mit dem Dreiecks-Symbol können die Items des Dokuments angezeigt werden. Sehr schön ist der Datentyp „Time/Date List or Range“ erkennbar.
58
2 Notes @ Domino
import java.util.*; import lotus.domino.*; public class CreateTimeItemsJavaAgent extends AgentBase { public void NotesMain() { Document doc = null; try { Session session = getSession(); Database db = session.getAgentContext().getCurrentDatabase(); //Neues Dokument erzeugen doc = db.createDocument(); doc.replaceItemValue ("Form", "FO_TimeExample_k2"); doc.replaceItemValue ("F_Titel", "Beispiel für Zeit Items."); //ZeitObjekt aus Calendar erzeugen Calendar jetzt = Calendar.getInstance(); DateTime dtsecond = session.createDateTime(jetzt); //zeitObjekt als DateTime DateTime dtfirst = session .createDateTime ("Yesterday 11:35:07"); //Date / Time Liste ins Dokument Vector list = new Vector (); list.addElement(dtfirst); list.addElement(dtsecond); doc.replaceItemValue ("F_datetimeList", list); //Date / Time Range ins Dokument DateRange dr = session.createDateRange(dtfirst, dtsecond); doc.replaceItemValue("F_datetimeRange", dr); //Einzelne Zeit und Datums Objekte ins Dokument doc.replaceItemValue ("F_datetime", dtsecond); dtsecond.setAnyDate(); doc.replaceItemValue ("F_time", dtsecond); dtfirst.setAnyTime(); doc.replaceItemValue ("F_date", dtfirst); //Zeit-Range ins Dokument DateTime timeFirst = session.createDateTime ("10:00"); DateTime timeSecond = session.createDateTime ("11:00"); timeFirst.setAnyDate(); timeSecond.setAnyDate(); DateRange tr = session.createDateRange(timeFirst,timeSecond); doc.replaceItemValue("F_timeRange", tr); doc.save (true, false); } catch (Exception e) { e.printStackTrace(); } } }
Listing 2-4 Date / Time Items in einem Dokument erzeugen
Felder in einer Notes-Maske können Datumsangaben vom 1.1.0001 bis zum 31.12.9999 und Zeitangaben mit einer Genauigkeit von einer Sekunde aufnehmen. Intern kann Notes die Zeitangaben zwar genauer und für die Datumsangaben auch Jahre vor Christi speichern, dies ist aber nur von theoretischer Bedeutung, da Items nicht über das Java-API mit diesen erweiterten Werten erzeugt werden können. Zeit-Objekte können entweder mit der Methode getItemValue oder mit der Methode getItemValueDateTimeArray aus dem Dokument gelesen werden. In beiden Fällen wird ein Vector aus Elementen der Klasse lotus.domino. local.DateTime oder lotus.domino.local.DateRange für den lokalen Zu-
2.2.4.4 Zahl und Zahlliste
59
Abb. 2-10 Time-Range-Wähler-Feld
griff und für den Remote Zugriff ein Vector mit Elementen der Klassen lotus.domino.cso.DateTime oder lotus.domino.cso.DateRange zurückgegeben. Die Methode getItemValueDateTimeArray ist neu seit Version 6.5.
2.2.4.4
Zahl und Zahlliste
Numerische Felder und Items sind vergleichbar mit Textfeldern, kennen jedoch nur eine einzige Art eines Eingabefeldes (Radiobuttons etc. erzeugen ja bekanntlich Text). Numerische Items können entweder eine einzelne Zahl oder eine Liste von Zahlen aufnehmen. Erzeugt wird ein Zahl-Item durch doc.replaceItemValue ("F_ZahlItem", new Double (17.123));
Wichtig ist es, ein Objekt zu übergeben, da die Methode replaceItemValue ein Object erwartet. Zur Zeit gibt es keine Signatur, mit der eine Zahl direkt übergeben werden könnte. Die direkte Übergabe einer Konstanten oder Variablen ist daher nicht möglich. Intern werden Zahlen von Domino im IEEE 64-Bit Floating Point Format (ANSI/IEEE Standard 754-1985) gespeichert. Dieses Format besteht aus einem Bit für
60
2 Notes @ Domino
das Vorzeichen, 11 Bit für den Exponenten und 52 Bit für die Mantisse. Das Format erlaubt die Darstellung des Wertes +0, -0, +Unendlich, -Unendlich, NaN (Not a Number – keine Zahl) und positiver und negativer Werte zwischen 4.9 x 10^-307 und 1.8 x 10^+308. Zu beachten ist, dass die Methode replaceItemValue zwar auch Long- oder Integer-Objekte annimmt, diese intern aber immer als Floating Point speichert. Wie immer bei der Speicherung von Floating-Point-Werten in Ihrer binären Repräsentation muss mit Rundungsfehlern bei der Umrechnung gerechnet werden. Natürlich ist auch mit Ungenauigkeiten zu rechnen, wenn mit sehr großen oder sehr kleinen Zahlen gerechnet wird. Zahllisten werden wie Textlisten als Vector aus Number-Objekten gespeichert.
2.2.4.5
Antwortreferenzliste
Antwortreferenzen können nur über spezielle Maskeneigenschaften oder über die Java-Methode makeResponse erzeugt werden. Diese Referenzen werden im Item $Ref gespeichert und bilden den Bezug zwischen Tochter- und Mutterdokument bei der Erzeugung von Antwortdokumenten, wie bereits in 2.2.3 im Zusammenhang mit Listing 2-3 beschrieben.
2.2.4.6
DokLink-Referenzliste
In RichText Items können Referenzen (vergleichbar mit Links im HTML) auf andere Dokumente, aber auch Ansichten und Datenbanken, eingefügt werden. Diese Links werden als Textstring in einem Item mit dem Datentyp DocLink gespeichert. Ein solcher Link auf ein Dokument kann z.B. wie folgt aussehen: RFC1256EEE:002C549F VF45181C0A:43AAAB77-VNC1256EF4:002F94EA NF49B9679F:13111BF9-NNC1256EF3:0043E611
Er besteht aus drei Angaben, der Replik ID der Datenbank, der ID der Ansicht und der Universal ID des Dokuments. Alle drei IDs identifizieren die jeweiligen Objekte eindeutig. Die im Link angegebene Ansicht ist diejenige Ansicht, über die das Dokument geöffnet worden war.
2.2.4.7
Mime Part
Ein Mime Part Item speichert, wie der Name schon sagt, einen so genannten Mime Part. Ein Mime Part ist ein Element des in RFC 2045 – Multipurpose Internet Mail Extensions (MIME) – beschriebenen Formats zur Speicherung von Internet-E-MailInhalten, die über die reine Darstellung von Text hinausgehen. Das Mime-Format ist
2.2.4.8 RichText, RichText Light und Embedded Object
61
interessant, da es möglich ist, RichText als MIME abzuspeichern und MIME Items zu manipulieren und so über Java und Domino internetkonforme HTML- oder E-MailInhalte zu erzeugen.
2.2.4.8
RichText, RichText Light und Embedded Object
RichText Items sind die flexibelsten, aber auch am schwersten zu handhabenden Items. Bis einschließlich Version 5 war es mit Java lediglich möglich einige Style-Informationen an ein RichTextItem anzuhängen, Text anzuhängen oder Dateien einzubinden. Mit Version 6 und insbesondere mit Version 6.5 kommen nun viele Klassen und Methoden hinzu, mit denen RichText gelesen und verändert werden kann. Folglich war und ist die Eingabe von RichText in der Regel dem Benutzer des Notes Clients vorbehalten. RichText dynamisch zu erzeugen ist möglich, erfordert aber in jedem Fall auch mit den neuen Klassen viel Erfahrung. RichText ist ein Container, der aus vielen verschiedenen Elementen bestehen kann. Das Basis-Element eines RichTextes ist der Paragraf. Jeder RichText besteht aus einem oder mehreren Paragrafen. Text-Stile, wie Schriften und Farben, können einem Paragrafen hinzugefügt werden. Paragrafen können nicht ineinander verschachtelt werden. Diese Information ist recht wichtig für das Verständnis, insbesondere, wenn es einmal zu einem unerwarteten Verhalten eines Stils kommt. Paragrafen können als so genannter Abschnitt zusammengefasst werden. Ein Abschnitt ist ein Bereich eines RichTextes, der mit einer optionalen Überschrift versehen ist, die von einem Twisty, also dem dreieckigen Symbol, das auch in Ansichten verwendet wird, ausgestattet ist. Abschnitte können manuell oder programmatisch aufund zugeklappt und sogar mit eigenständigen Benutzerrechten ausgestattet werden, wobei auch hier zu beachten ist, dass diese Rechte lediglich bei der Benutzung der Maske zum Tragen kommen. Die Daten selbst sind wie gewohnt unverschlüsselt im Dokument gespeichert. Ein Benutzer, der Zugang zu einer Java-Umgebung und z.B. Leserecht auf ein Dokument in einer Datenbank hat, wird, entsprechende Programmierkenntnisse vorausgesetzt, in der Lage sein, auch Inhalte von Feldern innerhalb von Abschnitten auszulesen, für die er, bei der Benutzung des Abschnitts in der Maske, keinen Zugriff gehabt hätte. In einen RichText können Dateianhänge eingefügt, Bilder und OLE-Objekte eingebettet werden (so ist es z.B. möglich innerhalb eines RichTextes ein Microsoft Word Dokument zu bearbeiten), Tabellen erstellt, Links auf Internet-URLs oder auf NotesObjekte, wie Datenbanken, Ansichten oder Dokumente eingefügt werden. Bilder können mit Hotspots, aktiven Flächen, mit einer Verlinkung versehen werden. Es gibt die Möglichkeit, Ansichten aus der Datenbank einzubetten, und sogar Buttons und berechneter Text können eingefügt werden. RichText Items haben keine Größenbeschränkung, wobei in Version 5 noch Limits für die Größe eines Paragrafen bestehen und natürlich die Gesamtgröße der Datenbank gewisse Limits (64 GB) nicht überschreiten kann (s. Anhang 20.1). Um dies zu ermöglichen, teilt Notes ein RichText Item in mehrere Items gleichen Namens auf. Dies ist zulässig, es ist jedoch nicht empfehlenswert, dies auch für andere Items anzuwenden.
62
2 Notes @ Domino
Wichtig ist die Möglichkeit, Dateianhänge in RichText Items abzulegen. Tatsächlich wird zwar intern ein zusätzliches Item $File (oder mehrere davon) vom Typ Embedded Object angelegt und durch einen Pointer referenziert, aber aus Sicht der JavaProgrammierung sind die Methoden zur Behandlung von Dateianhängen transparent, so dass man sich nicht mit diesen Items beschäftigen muss. In RichText Items können, bis auf wenige Einschränkungen, alle Objekte eingefügt werden, die auch zur Gestaltung von Masken verwendet werden können. Genau genommen ist es sogar so, dass Masken ein eigenes RichText Item haben, in dem das Layout der Maske gespeichert wird. Beim Anzeigen eines Dokuments mittels einer Maske werden die Werte der Items des Dokuments in die Felder, die sich im RichText der Maske befinden, eingefügt und das Ergebnis angezeigt. Da Paragrafen, wie eingangs erwähnt, nicht verschachtelt werden dürfen, wird bei der Darstellung eines RichText Items eines Dokuments ein neuer Paragraf begonnen und der vorige Paragraf der Maske beendet.
2.3
Replikation
Die Replikation ist eines der Hauptargumente, die immer wieder für Domino ins Feld geführt werden. Tatsächlich ist die Replikation in Domino sehr ausgereift und einfach zu handhaben. Jede Datenbank kann prinzipiell auch auf einem anderen Server als Replik vorliegen. Beim Öffnen von Datenbanken hat dies zur Konsequenz, dass Code geschrieben werden kann, der unabhängig von der Kenntnis des tatsächlichen Servers ist, wobei die ausdrückliche Auswahl eines Servers natürlich möglich ist. In einigen Situationen wird Domino sogar auf andere Server ausweichen und dort nach Repliken suchen, wenn die Replik auf dem ursprünglich gewünschten Server nicht zur Verfügung steht. Auch wenn die Handhabung der Replikation natürlich eine administrative Aufgabe ist, kommt auch der Domino-Programmierer nicht ohne genaue Kenntnis der Abläufe bei der Replikation aus.
2.3.1
Replik ID und Universal ID
Jede Domino-Datenbank hat neben ihrem Dateinamen (.nsf) und ihrem Titel eine so genannte Replik ID. Die Replik ID ist ein 8 Byte langer String, der als 16 Zeichen lange ASCII-Hexadezimal-Zeichenkette dargestellt wird. Alle Repliken einer Datenbank haben auf allen Servern die gleiche Replik ID. Diese ID ist für Domino der eindeutige Schlüssel, der zusammengehörige Repliken identifiziert. Um eine Datenbank aus Java heraus zu identifizieren und zu öffnen, wurde bisher die Verwendung der Methode getCurrentDatabase im AgentContext beschrieben. Diese Vorgehensweise ist angebracht, solange Java-Agenten innerhalb des Domino Designers entwickelt werden und keine externen Datenbanken auf womöglich externen Servern angesprochen werden sollen.
2.3.1 Replik ID und Universal ID
63
Wird versucht, eine entfernte Datenbank über einen servergesteuerten periodischen Agenten zu öffnen, ist dies aus Sicherheitsgründen erst ab Domino Version 6 über Vertrauensstellungen möglich. Zugriffe von lokal auf einen Server sind immer möglich, sofern der Benutzer authentifiziert ist und Zugriff auf die Datenbank hat. Abb. 2-11 und Listing 2-5 zeigen verschiedene Möglichkeiten, Datenbanken über das Java-API zu öffnen. Ähnlich wie die Datenbank hat auch jede Note, insbesondere jedes Dokument, eine eindeutige ID. Die so genannte Note ID ist (nur!) innerhalb einer Datenbank eindeutig und kann mit getNoteID im Document abgefragt werden. Die Universal
import lotus.domino.*; public class DatenbankOeffnenJavaAgent extends AgentBase { public void NotesMain() { try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); //String serverName = "yourServer/yourDomain"; String serverName = null; DbDirectory dir = session.getDbDirectory(serverName); Database db = dir.getFirstDatabase(DbDirectory.DATABASE); String rid = db.getReplicaID(); showStatus(db); db.open(); showStatus(db); db = dir.openDatabaseByReplicaID(rid); showStatus(db); db = dir.openDatabase("djbuch/djbuch.nsf"); showStatus(db); Document doc = db.getAllDocuments().getFirstDocument(); String unid = doc.getUniversalID(); doc = db.getDocumentByUNID(unid); System.out.println( "Dokument mit Maske: " + doc.getItemValueString("Form") + ".\nDoc UNID = " + doc.getUniversalID()); } catch (Exception e) { e.printStackTrace(); } } private void showStatus(Database db) throws NotesException { System.out.println( "Server: " + (db.getServer().equals("") ? "lokal" : db.getServer()) + "\nReplikID: " + db.getReplicaID()); if (db.isOpen()) { System.out.println( "Datenbank \"" + db.getTitle() + "\" ist geöffnet."); } else { System.out.println( "Datenbank \"" + db.getTitle() + "\" ist nicht geöffnet."); } } }
Listing 2-5 Datenbanken öffnen
64
2 Notes @ Domino
ID, auch Unique ID oder Document ID genannt, ist über alle Repliken einer Datenbank eindeutig. Über diese Universal ID kann also ein Dokument eindeutig in allen Repliken einer Datenbank identifiziert werden . Das Verständnis, dass ein und dasselbe Dokument mehrfach über mehrere Repliken verteilt existieren kann, ist wichtig für das Verständnis des Speichermodells von Domino. Man kann sich diese Dokumente als verschiedene Instanzen ein und desselben Dokuments vorstellen. Durch die eindeutige Universal ID kann ein Dokument eindeutig identifiziert werden. Egal auf welchem Server.
Datenbanken öffnen Listing 2-5 zeigt verschiedene Möglichkeiten zum Öffnen von Datenbanken. Hierbei ist zu beachten: • •
•
•
•
Die Methode getCurrentDatabase des AgentContext-Objekts öffnet die aktuelle Datenbank, wenn es sich um einen Java-Agenten handelt. Die Klasse lotus.domino.DbDirectory repräsentiert ein Verzeichnis sämtlicher Datenbanken auf einem Server, wobei auch die lokale Arbeitsstation als Server betrachtet wird. Referenziert wird in diesem Fall kein Servername, sondern null (s. Listing 2-5) . Diese Art des Zugriffs wird auch als lokaler Zugriff bezeichnet. In der Klasse DbDirectory kann mit getFirstDatabase und getNextDatabase navigiert werden, wodurch der Zugriff auf alle Datenbanken des Servers ermöglicht wird. Eine derart referenzierte Datenbank ist noch nicht automatisch geöffnet und muss mit open geöffnet werden. Mit der Methode openDatabase der Klasse DbDirectory kann eine Datenbank anhand ihres Namens direkt geöffnet werden. Hierbei ist zu beachten, dass der Dateipfad relativ zum Datenverzeichnis (dies ist auf einer Windows-Standard-Installation des Clients C:\Lotus\Notes\data) einschließlich eventueller Unterverzeichnisse angegeben werden muss. Der Slash „/“ (Unix) oder Backslash „\“ (Windows) als Trennzeichen zwischen Verzeichnissen sollte immer als Slash angegeben werden. Domino erkennt dies. Äquivalent hierzu ist getDatabase in Session. Empfehlung: Verwenden Sie im Normalfall getDatabase mit der Signatur public Database getDatabase(String server, String db) throws NotesException. Auch hier kann server null sein, um eine lokale Datenbank zu öffnen. Ist die Replik ID bekannt, kann über openDatabaseByReplicaID in DbDirectory eine Datenbank geöffnet werden. Die Replik ID einer Datenbank kann z.B. über getReplicaID erhalten werden. Vergleichbar hiermit ist openByReplicaID in Database, wobei die Signatur dieser
Methode noch die Angabe eines Servernamens verlangt, denn es liegt in der Natur der Replik ID, nicht den Server zu spezifizieren, auf dem die Replik liegt.
Abb. 2-11 Datenbanken öffnen
2.3.2 Replikations- und Speicherkonflikte
65
Abb. 2-12 Java-Debug-Konsole
Die Ausgaben des Agenten aus Listing 2-5 landen übrigens in der Java-DebugKonsole. Diese können Sie über das Menü „File“ -> „Tools“ -> „Show Java Debug Console“ anzeigen lassen (Abb. 2-12).
2.3.2
Replikations- und Speicherkonflikte
Bei der Durchführung der Replikation, also bei dem Abgleich zweier Datenbanken zwischen zwei Repliken, die sich in der Regel auf zwei unterschiedlichen Servern befinden, wird nun Note für Note anhand der Universal ID auf beiden Repliken identifiziert und verglichen, ob, wann und auf welcher Replik Änderungen vorgenommen wurden. DOCUMENT Notes werden bei dem Vergleich gesondert behandelt. Wird festgestellt, dass ein Dokument, genauer gesagt beide Repliken (man könnte auch sagen Instanzen) eines Dokuments, in beiden Repliken geändert wurde, wird eines der beiden Dokumente zum Hauptdokument, das andere Dokument wird zum Antwortdokument und als Replikationskonflikt markiert.
Vorsicht mit Replik IDs In einigen Versionen von Domino gab es Bugs im Bezug auf die Methoden openDatabaseByReplicaID und openByReplicaID.
Verwenden Sie diese Methoden, da sie eine gute Möglichkeit darstellen, serverunabhängigen Code zu erstellen. Aber: Stellen Sie sicher, dass diese Methoden mit formal gültigen RIDs (insbesondere kein null oder "") verwendet werden.
66
2 Notes @ Domino
Hierbei ist zu beachten, dass ja ursprünglich die beiden Dokumente dieselbe Instanz ein und desselben Dokuments waren, also dieselbe Universal ID tragen. Bei der Entstehung von Replikationskonflikten wird ein völlig neues Dokument angelegt (Abb. 2-13), das die als nachrangig eingestuften Änderungen beinhaltet. Das Dokument mit der ursprünglichen Universal ID bleibt unter dieser ID und mit beiden Instanzen in beiden Datenbanken als Hauptdokument erhalten. Ein Dokument, das als Replikationskonflikt markiert wurde, enthält das Feld $Conflict, das keinen besonderen Wert (in der Regel "") enthält, und natürlich das Feld $Ref mit der Referenz auf das Hauptdokument. Befindet sich in einem Dokument das Feld $ConflictAction mit dem Wert "1" (als Text), so wird bei dem Vergleich nicht das gesamte Dokument betrachtet, sondern Feld für Feld verglichen und die Änderungen beider Dokumente aus den verschiedenen Repliken werden in einem neuen Dokument zusammengefügt. Nur wenn in beiden Repliken des Dokuments das gleiche Feld verändert wurde, wird wie zuvor vorgegangen und ein Konfliktdokument erstellt. Mit dem Wert "3" im Feld $ConflictAction kann die Erstellung von Konfliktdokumenten komplett deaktiviert werden. Es setzt sich dann eines der beiden Dokumente durch.
Ändert Dokument
Ändert Dokument Benutzer B
Benutzer A A
B
1
Replik A (Universal ID 1)
1 1
Replik B (Universal ID 1)
Verschiedene Repliken ein und desselben Dokuments mit gleicher Universal ID
Nach der Replikation zwischen Server A und B 2
2
Replik A (Universal ID 1)
$Ref="Universal ID 1" $Conflict="" Konfliktdokument (Universal ID 2)
Abb. 2-13 Replikationskonflikt
Nach der Replikation entsteht ein Konfliktdokument (Replikationskonflikt). Es wird neu erstellt und erhält eine neue Universal ID. Das Item $Conflict wird erstellt. Das Item $Ref enthält die Universal ID des Originals
2.4 Das Domino-Objektmodell (Einführung)
67
Das Feld $ConflictAction wird automatisch gesetzt, wenn in der Maske, mit der das Dokument erstellt wird, die Eigenschaften für die Behandlung von Replikationskonflikten geändert werden. Das Feld kann aber auch manuell über eine entsprechende Java-Programmierung im Backend eingefügt werden. Für alle anderen Notes ist dieses Verhalten Standard, d.h. nur Dokumente, nicht jedoch Designelemente können Konfliktdokumente verursachen. Auch Profildokumente erzeugen keine Konfliktdokumente. Wenn zwei Benutzer auf demselben Server zur gleichen Zeit oder kurz nacheinander parallel dasselbe Dokument bearbeiten und speichern, entsteht ebenfalls eine Konfliktsituation, in der entschieden werden muss, welche Änderungen Vorrang haben. Auch in diesem Fall wird wie bei den Replikationskonflikten vorgegangen. Das Prinzip der Konfliktdokumente ist sehr wichtig in einer verteilten Umgebung, wie sie eine Domino-Infrastruktur darstellt. Das Konzept von Domino geht davon aus, dass kein Benutzer eine dauerhafte und garantierte Verbindung zu seinem Server hat. Insbesondere Notebook-Nutzer, die lokale Repliken ihrer Daten anlegen, werden nur gelegentlich eine Verbindung zum Server aufbauen, um ihre Daten abzugleichen. Unter dieser Prämisse ist das Locking von Dokumenten oder Datensätzen, wie es bei anderen Datenbanken üblich ist, nur schwer möglich. Daher haben sich die Entwickler der NSF-Struktur entschieden, mit den Konfliktdokumenten einen effizienten und von den Benutzern leicht zu handhabenden Mechanismus einzuführen, um Konfliktsituationen zu behandeln. Mit Version 6.5 führt nun auch Domino das Prinzip des Document Locking ein. Natürlich soll das Konzept der verteilten Infrastruktur nicht aufgegeben werden, daher ist das Locking aufgeteilt in ein provisorisches und endgültiges Locking, damit Situationen, in denen Benutzer nicht (oder nicht zuverlässig) mit dem Server verbunden sind, gehandhabt werden können (s. Kap. 7.7). Bisher unbeantwortet geblieben ist die Frage, wie Domino entscheidet, welche Änderungen im Hauptdokument verbleiben und welche Änderungen im neu zu erstellenden Konfliktdokument gespeichert werden. Domino geht hierbei nicht nur nach dem Änderungsdatum, sondern nach folgenden Regeln vor: • • •
2.4
Das Dokument, das am häufigsten geändert wurde, wird zum Hauptdokument. Wurden beide Repliken des Dokuments gleich häufig geändert, setzen sich die Änderungen des zuletzt geänderten Dokuments durch. Wurde ein Dokument in der einen Replik verändert und in der anderen gelöscht, dann setzt sich die Löschung durch, es sei denn, das geänderte Dokument wurde häufiger geändert als das gelöschte, oder das geänderte Dokument wurde nach der Löschung geändert.
Das Domino-Objektmodell (Einführung)
Wenn man über das Domino-Objektmodell spricht, ist es notwendig auch ein wenig über LotusScript zu sprechen.
68
2 Notes @ Domino
Einige Objekte in der Programmierung können nur durch LotusScript angesprochen werden. Es handelt sich hier um alle so genannten Frontend-Klassen. Dies sind diejenigen Klassen, die sich auf das GUI des Notes Clients beziehen. Mit ihnen können die Objekte des grafischen Interfaces, also Buttons, Fenster, aber auch tatsächlich vom Benutzer geöffnete Datenbanken oder Ansichten, angesprochen werden. Der NotesUIWorkspace ist die Arbeitsoberfläche des Benutzers, genauer gesagt ist es die momentan vom Benutzer geöffnete Instanz einer Arbeitsoberfläche. Wichtig wird dies z.B. bei dem NotesUIDocument. Dieses (LotusScript-) Objekt beschreibt die im Notes Client vom Benutzer tatsächlich geöffnete Instanz des Dokuments. Folgerichtig hat diese Klasse Methoden, mit denen Interaktionen ausgeführt werden können, die der Benutzer auch mit der Maus oder der Tastatur ausführen könnte, wie z.B. Cut-and-Paste-Operationen oder die Möglichkeit ein Feld mit dem Cursor „anzuspringen“. Zusätzlich kennen diese Klassen verschiedene Events, mit denen auf Benutzerinteraktionen reagiert werden kann. Der NotesUIWorkspace enthält die Klassen NotesUIDatabase, NotesUIView und NotesUIDocument. Letztere Klassen enthalten nun wiederum das zugehörige eigentliche Speicher- oder besser Backend-Objekt, also NotesDatabase, NotesView und NotesDocument in der LotusScript-Notation. Für (fast) alle LotusScript-Backend-Klassen gibt es ein Äquivalent in Java. Die zuvor angesprochenen Klassen heißen Database, View und Document in der Java-Notation. Die Parallelität zwischen LotusScript und Java-Backend-Objekten ist übrigens äußerst konsequent und durchgängig. Dies liegt daran, dass beide Sprachen eigentlich nur eine Hülle für die dahinter liegenden C++ Klassen sind. Die Darstellung in Abb. 2-14 ist stark vereinfacht, aber auch hier ist schon erkennbar, dass es nicht nur quantitativ wesentlich mehr Backend-Klassen gibt. IBM hat alle Power von Domino in diese Backend-Klassen gelegt. Java wird vorwiegend für die Backend-Programmierung in Domino eingesetzt. Wie später noch aufgezeigt wird, eignet sich Java auch unter Domino hervorragend für die Entwicklung von Webanwendungen. Für die Java-Programmierung ist die Session das zentrale Objekt, auf dem das gesamte Modell aufgebaut ist. Zwar implementieren alle Klassen die Basisklasse Base, letztendlich stammen aber alle Klassen entweder von Session ab oder können über die Session erzeugt oder erreicht werden. Eine Ausnahme macht hier die AgentBase. Da sie die zu erweiternde Basis für alle Agenten ist, die innerhalb von Domino entwickelt werden, erweitert sie wiederum die Klasse NotesThread, die die Klasse java.lang.Thread erweitert. Den genauen Zusammenhang und die Möglichkeit der Erweiterung und Implementierung von Thread oder Runnable wird in Kapitel 4 besprochen.
2.5
Suchkonzepte (Einführung)
Eine Domino-Datenbank oder eine Notes Storage Facility (NSF) ist ein Container von Containern von Containern. Sie besteht aus einzelnen Note-Objekten, die mehr oder weniger unstrukturiert in der Datenbank gespeichert sind. Domino hat eigene
NotesThread AgentBase
lotus.domino.Base Agent NotesUIWorkspace
Session
DateRange DateTime
NotesUIDatabase
DbDirectory
Database NotesUIView DocumentCollection NotesUIDocument
Document ACL
View
Form
ViewEntryCollection
Item RichTextItem MimeEntry
2.5 Suchkonzepte (Einführung)
Abb. 2-14 Domino-Objektmodell (vereinfacht)
java.lang.Thread
Backend-Klassen (LotusScript / Java)
Frontend-Klassen (LotusScript)
ViewEntry
69
70
2 Notes @ Domino
Strategien, um effizient und LotusScript vs. Java schnell – sowohl im Zugriff als auch in der Entwicklung – auf LotusScript und Java sind Wrapper der dahinter lieseine Daten zuzugreifen, diese zu genden C++ Klassen und -Bibliotheken. indizieren, übersichtlich anzuzeigen und schnell zu durchsuchen. Daher sind sich die Java- und LotusScript-Klassen In Kapitel 2.3.1 (Listing 2-5) durchweg ähnlich. wurde eine erste Möglichkeit aufgezeigt, Dokumente mit ihrer In Java stehen nur die Backend-Klassen zur VerfüUniversal ID über getDocugung, in denen die eigentliche Power von Domino mentByUNID anzusprechen. Dies liegt. ist schnell und oft das Mittel der Wahl, wenn z.B. Bezüge zwischen Folgerichtig wird Java vorwiegend für die Backend Dokumenten mit Hilfe der UniProgrammierung in Domino und, wie noch zu versal ID hergestellt werden oder zeigen sein wird, für Webanwendungen eingewenn durch geschickte Gestalsetzt. tung von Ansichten die Universal ID als Suchergebnis generiert werden kann. Wie aber können Dokumente dynamisch selektiert werden? Domino bietet verschiedene Möglichkeiten, bis hin zur sehr mächtigen Volltextsuche über ganze Servergruppen. Die wichtigste und effizienteste Methode ist die Suche über eine Ansicht (View). Gesucht wird nicht auf der gesamten Datenbank (dies wird später diskutiert), sondern über ein „Result Set“ einer Ansicht, also über eine Sammlung von vorselektierten Dokumenten. Eine Ansicht ist eine solche Sammlung von vorselektierten Dokumenten. Wenn Sie das Beispiel aus Kapitel 2.1 verfolgt haben, kennen Sie bereits die Verwendung einer Ansicht zur Darstellung von Dokumenten im Notes Client. Ansichten werden nicht nur für die Darstellung von Daten verwendet, sondern ihr primärer Zweck ist die Selektion von Dokumenten und die Suche nach Dokumenten. Um diese Suche effizient zu gestalten, indiziert Domino alle sortierten Spalten von Ansichten automatisch im Hintergrund. Ansichten besitzen eine so genannte Auswahlformel, auch SELECT-Formel genannt, die mit Hilfe der @Formel Sprache beschrieben wird und Ihnen ermöglicht zu definieren, welche Dokumente ausgewählt werden sollen. Eine Select-Formel beginnt mit dem Schlüsselwort SELECT und wird gefolgt von einer booleschen Bedingung, die angibt, welche Dokumente ausgewählt werden sollen. Eine solche Formel wird oft der Vergleich einer Konstante mit einem Item sein, z.B. also SELECT Form="FO_meineMaske". Weitere Beispiele: •
SELECT @LowerCase (Form) = "fo_meinemaske"
2.5 Suchkonzepte (Einführung) •
71
SELECT @Contains (Form;"_k2") | (form = "FO_Report" & @IsResponseDoc)
Das Pipe-Symbol ist der boolesche Oder-Operator, das buchhalterische Und-Zeichen ist der boolesche Und-Operator. @isResponseDoc ist wahr, falls ein Dokument ein Antwortdokument ist, also das Feld $Ref enthält. •
SELECT @All
Wählt alle Dokumente einer Datenbank Die in der SELECT-Formel verwendeten @Formeln können wie gewohnt verwendet werden, Items in Dokumenten werden hierbei direkt über ihren Namen angesprochen. Das Java-API sieht verschiedene Möglichkeiten vor, Ansichten zu durchsuchen, und liefert mit der Klasse View eine Repräsentation des Ansicht-Objekts mit einerseits allen zugehörigen Gestaltungsinformationen, z.B. über Spalten, Farben und Überschriften bis hin zur SELECT-Formel, die seit Version 6.5 sogar über setSelectionFormula verändert werden kann, und andrerseits einer impliziten Sammlung von Referenzen auf die im View selektierten Dokumenten, durch die navigiert werden kann. Zur Einführung in das Thema enthält die Buch-Datenbank djbuch.nsf einen Agenten AG_searchView, der die Suche über eine Ansicht demonstriert. Zu diesem Agenten gehört die Ansicht V_Lookup_k2. Diese Ansicht hat als Klarnamen „(Lookup K2)“. Am Rande ein Hinweis auf die Sonderfunktion der Klammern, die hier im Klarnamen verwendet werden. Sie dienen dazu, die Ansicht vor dem Benutzer zu verbergen, schließlich soll sie ausschließlich für Lookups im Backend verwendet werden. Ohne die Klammern würde die Ansicht automatisch im linken Frame des Clients auftauchen. Mit der Select-Formel „SELECT @Contains (Form;"_k2") | (form = "FO_Report" & @IsResponseDoc)“ werden alle Dokumente der Datenbank ausgewählt, deren zugehörige Maske den String "_k2" enthält, oder alle Antwortdokumente, deren Maske "FO_Report" ist. Die erste Spalte der Ansicht zeigt berechnete Werte an, und zwar: @LowerCase (F_Titel), also das Titel-Item in Kleinschreibung. Diese Spalte kann über die Spalteneigenschaften (rechte Maustaste auf die Spaltenüberschrift) sortiert werden. Dies ist unerlässlich, wenn eine Ansicht mit den im Folgenden beschriebenen Methoden durchsucht werden soll. Anhand des Listing 2-6 wird mit einem kleinen Beispiel demonstriert, wie in Java Notes Dokumente selektiert werden können. Der beschriebene Agent bezieht sich auf die Ansicht V_lookup_k2, wird also nur Dokumente finden, die in dieser Ansicht angezeigt werden. Der Agent kann eine Ansicht direkt aus dem Database-Objekt abfordern und somit implizit öffnen. Ein View ist ein Java-Objekt, das alle Dokumente, die der View anzeigt, referenziert. In einem View kann ähnlich der DocumentCollection (s.u.) navigiert werden, was in Kapitel 10ff. näher beschrieben wird. Um einen View zu durchsuchen, kann die Methode getAllDocumentsByKey angewendet werden. Diese Methode liefert eine DocumentCollection. Dies ist eine unsortierte Sammlung von Document-Objekten, d.h. die gefundenen Dokumente werden keineswegs in der Reihenfolge geliefert, in der diese in der Ansicht sor-
72
2 Notes @ Domino
import lotus.domino.*; public class AnsichtDurchsuchenJavaAgent extends AgentBase { public void NotesMain() { Document doc = null; Document nextDoc = null; View view = null; DocumentCollection dc = null; try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); view = agentContext.getCurrentDatabase() .getView("V_Lookup_k2"); dc = view.getAllDocumentsByKey("t",false); doc = dc.getFirstDocument (); while (doc != null) { nextDoc = dc.getNextDocument (doc); System.out.println("Dokument gefunden: " + doc.getItemValueString("F_Titel")); doc.recycle(); doc = nextDoc; } } catch (Exception e) { e.printStackTrace(); } finally { try { if (doc != null) {doc.recycle(); doc = null;} if (nextDoc != null) {nextDoc.recycle(); nextDoc = null;} if (view != null) {view.recycle(); view = null;} if (dc != null) {dc.recycle(); dc = null;} } catch (NotesException ne) { System.out.println ("Fatal error while recycling."); ne.printStackTrace(); } } } }
Listing 2-6 Ansicht durchsuchen
tiert waren. Dies ist für den Domino-Anfänger ein unerwartetes Verhalten, soll daher an dieser Stelle betont werden und kann nur über die Methode getAllEntriesByKey umgangen werden. Eine DocumentCollection kann mit getFirstDocument und getNextDocument durchschritten werden. In diesem Beispiel ist der Schritt getNextDocument auf zwei Einzelschritte verteilt. Dies ist notwendig, um den durch die einzelnen Dokumente belegten Hauptspeicher während des Durchschreitens in der Collection im While Loop wieder freigeben (s.u.) zu können . Neben der schrittweisen Navigation gibt es auch die Möglichkeit, ein Document in der DocumentCollection mit getNthDocument direkt zu selektieren. Diese Methode sollte aber soweit möglich vermieden werden, da sie nicht sehr performant ist. Soll nur der erste Treffer ausgewählt werden, kann die Methode getDocumentByKey verwendet werden. Diese liefert das erste Dokument, das bei Berücksichtigung der Reihenfolge der ersten Spalte der Ansicht gefunden wird. Welche Treffer sind nun bei der Suche zu erwarten ?
2.5 Suchkonzepte (Einführung)
73
Wenn Sie die Definition der Ansicht im Domino Designer betrachten, können Sie feststellen, dass in der Definition für die erste Spalte der Ansicht eine Berechnung stattfindet, die die Titel der Dokumente in Kleinschreibung angezeigt. Diese Spalte soll durchsucht werden. Die Methode getAllDocumentsByKey wird mit dem String "t" als erstem Suchparameter gestartet. Dies ist der Schlüssel, nach dem gesucht werden soll. Der zweite Parameter hat den Wert false und steuert, ob die Werte der ersten Spalte exakt übereinstimmen sollen, oder ob solche Dokumente gefunden werden sollen, für die die Werte in der ersten Spalte mit dem Suchschlüssel beginnen. Im Beispiel werden also alle Dokumente gefunden, deren Feld F_Titel einen Wert enthält, der mit "t" oder "T" beginnt. Folgerichtig ist eine mögliche Ausgabe des Agenten (in der Java-Konsole): Dokument gefunden: Testdokument
Best Practice Verwenden Sie getAllDocumentsByKey, um Dokumente in einer Ansicht zu suchen. Sortieren Sie die erste(n) Spalte(n) von Ansichten, die für Lookups verwendet werden. Verwenden Sie nie gleichzeitig dieselbe Ansicht zur Anzeige von Dokumenten und im Backend zur Suche nach Dokumenten. Beachten Sie Damit eine Ansicht mit getAllDocumentsByKey und getDocumentByKey durchsucht werden kann, müssen alle zu durchsuchenden Spalten, aber mindestens die erste Spalte, sortiert sein. Beachten Sie Die Methode getAllDocumentsByKey durchsucht die Einträge in den ersten n Spalten einer Ansicht und liefern ein unsortiertes Ergebnis als DocumentCollection. Die Methode getAllEntriesByKey durchsucht die Einträge in den ersten n Spalten einer Ansicht und liefern ein sortiertes Ergebnis als ViewEntryCollection, arbeitet aber auf einem höheren Abstraktionslevel und ist daher etwas weniger performant. Diese Methode beeinflusst die Sammlung der in einem View (zur Zeit) angezeigten Objekte, so dass sich nach Verwendung dieser Methode Operationen, die sich auf diesen View beziehen, nur noch auf eine reduzierte Menge von Treffern beziehen. Um den ersten Treffer in einer Ansicht selektieren zu können, stehen die Methoden getDocumentByKey und getEntryByKey zur Verfügung.
74
2 Notes @ Domino Dokument gefunden: Theoretischer Titel Dokument gefunden: tosender Beifall
Voraussetzung ist natürlich, dass entsprechende Dokumente mit entsprechenden Werten im Feld F_Titel in der Datenbank vorliegen. Um bei der Suche eine exakte Übereinstimmung zu erreichen, kann die Signatur der Methode ohne den zweiten Parameter benutzt werden. Wie zuvor angesprochen, kümmert sich der Beispiel-Agent um das Recycling von Dokumenten. Da es sich hier um ein sehr wichtiges Thema für die Java-Programmierung in Domino handelt, soll dieses Thema bereits an dieser Stelle anhand des vorliegenden Beispiels angesprochen werden. Recycling? In Java? Leider ist es so, dass die Java Garbage Collection nichts von den dahinterliegenden, eigentlichen Objekten in C++ weiß. Daher würden bei einem turnusmäßigen Recycling durch die Garbage Collection diese C++ Objekte nicht (rechtzeitig) erreicht und freigegeben. Insbesondere bei einem Loop durch eine DocumentCollection ist es unerlässlich, in der vorgegebenen Art jedes einmal referenzierte Document mit recycle freizugeben. Da ein Document, das mit recycle freigegeben wurde, nicht mehr referenziert werden kann, würde die Methode getNextDocument (doc) eine Exception werfen, oder unbestimmte Ergebnisse liefern. Daher wird über das zweite Dokument nextDoc der Zeiger in der DocumentCollection weitergesetzt und kann dann für den nächsten Durchgang durch den Loop wieder erreicht werden . Im finally-Block werden auch die übrigen Objekte freigegeben. Um dies zu ermöglichen, müssen diese Objekte außerhalb des try deklariert werden. Weitere wichtige Informationen und Techniken zum Recycling in Domino lesen Sie in Kapitel 14. Alternativ zur DocumentCollection kann mit getAllEntriesByKey in View eine ViewEntryCollection erzeugt werden. Der Vorteil einer solchen ViewEntryCollection ist, dass die Ergebnisse im Gegensatz zur DocumentCollection in derselben Reihenfolge erzeugt werden, wie die Ansicht sie vorgibt. Diese Methode ist jedoch etwas komplexer und soll in Kapitel 10.5 und 10.7.2 erläutert werden. Mit der Klasse View und deren Methoden getAllDocumentsByKey und getDocumentByKey haben Sie die wichtigste Technik zur Auswahl von Dokumenten kennen gelernt. Sie ist schnell, flexibel und wird durch die automatische Indizierung im Hintergrund unterstützt und liefert dazu, dank des Note-Konzeptes in Domino, noch ganz nebenbei und gratis die gesamten Dokumente in der DocumentCollection, wodurch Zugriffe auf alle Items der Dokumente und nicht nur auf eventuelle Spalten einer Ansicht ermöglicht werden.
2.6
Sicherheitskonzepte
Ein Buch, das sich wie dieses mit der Programmierung von Domino-Anwendungen auseinandersetzt, kommt nicht umhin, sich auch mit deren Sicherheit zu beschäftigen
2.6 Sicherheitskonzepte
75
auch wenn man argumentieren könnte, dass dies im Wesentlichen eine administrative Aufgabe ist. Sicherlich würde die erschöpfende Darlegung aller Methoden zur Sicherung von Domino-Servern und -Infrastrukturen den Rahmen dieses Buches sprengen, doch eine wesentliche Basis sicherer Infrastruktur sind sichere Anwendungen. Soweit also die Anwendungssicherheit betroffen ist, insbesondere bei der Benutzung von Java, wird auch bei der Programmierung die Sicherheit eine Rolle spielen. Anwendungs- und Serversicherheit verteilt sich bei Domino einerseits in den Authentifizierungsprozess am Server und zum anderen in den Autorisierungsprozess, der sich zunächst über die Zugriffskontrollliste der Anwendung und dann über die Vergabe von Berechtigungen auf einzelne Objekte erstreckt. Im Einzelnen stellt sich dies wie folgt dar: Ein Benutzer muss sich zunächst am Server authentifizieren. Hier wird grundsätzlich nach zwei Zugriffsarten unterschieden: Der Zugriff über einen der DominoClients und der Zugriff über Web. Ein Zugriff wird als Client-Zugriff gewertet, wenn dieser über eine der drei Domino-Client-Anwendungen, also den Notes Client, den Domino Designer, oder den Domino Administrator erfolgt. Eine Anwendung, die über das C++ API lokal eine Session von einem PC (mit installierter Client-Software) auf lokale Ressourcen herstellt, aber auch eine Verbindung auf entfernte Server abfordert, wird ebenfalls als Client erkannt, denn bei dieser Art des API-Aufrufs bedient sich dieser der lokal installierten Bibliotheken und der lokal installierten Benutzer-ID, wobei diese möglicherweise programmatisch mit einem Passwort versorgt werden muss. Der Zugriff über ein lokales (stand-alone) Java-Programm ist ein klassischer Fall eines solchen API-Zugriffs. Client-Verbindungen benötigen immer eine Benutzer-ID, in der die privaten RSA Keys gespeichert sind, das Passwort dient zum Zugriff auf diese Benutzer-ID, der Benutzername wird dem Key entnommen und gegen den Eintrag im Notes Adressbuch (names.nsf) authentifiziert. Webzugriffe sind klassische Verbindungen über einen Web-Browser, aber auch über alle anderen Standard-Web-Protokolle, wie POP, LDAP oder SMTP. Eine Verbindung einer Java-Anwendung über DIIOP (Domino IIOP) und Verbindungen über Single Sign On werden ebenfalls als Webzugriff gehandhabt. Web-Verbindungen können entweder anonym, mit Benutzername und Passwort oder über die Verwendung von S/MIME-Zertifikaten hergestellt werden. Benutzername und Passwort für einen Webzugriff werden entweder mit dem Hashwert im Item „HTTPPassword“ des Personendokuments im Notes-Adressbuch oder mit einem über die Directory Assistance verbundenen LDAP-Server abgeglichen. Nachdem der Benutzer authentifiziert ist, muss überprüft werden, ob er autorisiert ist, die gewünschte Datenbank zu öffnen und den gewünschten Vorgang durchzuführen. Egal, ob der Benutzer sich über eine Web-Verbindung oder über eine Client-Verbindung anmeldet, wird zunächst überprüft, mit welchen Rechten der Benutzer in der ACL (Access Control List – Zugriffskontrollliste) geführt wird, wobei der Benutzer Rechte als Einlieferer (nur Schreiben), Leser (nur Lesen), Autor (Lesen und eigene Dokumente erstellen und ändern), Editor (Autor plus fremde Dokumente ändern), Designer (Editor plus Gestaltungselemente erstellen und ändern), Administrator
76
2 Notes @ Domino
(Designer plus ACL anpassen) haben kann. Zusätzlich können noch Sonderrechte und frei definierbare Rollen vergeben werden, so ist z.B. selbst für einen Administrator die Erlaubnis zum Löschen von Dokumenten nicht obligatorisch. Eine Rolle ist ein frei definierbares Schlüsselwort, anhand dessen in der Anwendung spezielle Rechte für einzelne Objekte nur an die Träger dieser Rolle vergeben werden können. Mit diesen Rechten und Rollen betritt der Benutzer nun die Anwendung. Für jede Aktion und für jede Anforderung, ein Objekt zu öffnen, zu ändern oder zu bearbeiten, werden diese Rechte abgeglichen. Ein Autor darf also z.B. keine der Designelemente (Design Notes) bearbeiten, ändern oder löschen. Eine Ansicht könnte z.B. mit einem Leserfeld für die Rolle [AnsichtenBenutzer] geschützt sein, so dass dieser Autor in der ACL für die Rolle [AnsichtenBenutzer] autorisiert werden muss, soll er diese Ansicht zu Gesicht bekommen. Dokumente könnten ein Autorenfeld enthalten, das einen bestimmten Benutzernamen, eine bestimmte Gruppe oder Rolle enthält (oder mehrere als Liste). Dann werden solche Dokumente nur für den/die Benutzer sichtbar, dessen Namen explizit oder indirekt über eine Gruppe oder Rolle in dem Autorenfeld erwähnt ist. Eine kleine Randbemerkung sei noch auf die so genannte ECL-Sicherheit verwendet (Execution Control List – Ausführungskontrollliste). Alle Designelemente tragen automatisch durch das Speichern die Signatur des letzten Bearbeiters, wobei diese über den Domino Administrator auch geändert werden kann. Öffnet nun z.B. ein Benutzer eine Maske und startet durch den Klick auf einen Button eine Aktion oder eine solche Aktion läuft automatisch ab, dann wird überprüft, ob der aktuelle Benutzer den Signierer dieser Aktion als vertrauenswürdig einstuft. Ist dies (noch) nicht der Fall, wird für bestimmte, als sicherheitsrelevant eingestufte Aktionen eine so genannte Sicherheitsmeldung angezeigt, mit der der Benutzer diese Aktion blockieren oder ausführen kann. Weiterführende Informationen zu Sicherheitskonzepten, insbesondere zur Verschlüsselung, finden Sie in Kapitel 7.5ff.
2.7
Zusammenfassung
Die Programmierung von Java-Anwendungen für Domino setzt das Verständnis des Domino-Frameworks, des Domino-Speichermodells und der Domino-Objektstruktur voraus. Dieses Kapitel gab einen Überblick über diese Themen und die wichtigsten Techniken zur Beherrschung der Domino-Daten. Anhand einer Beispiel-Anwendung wurde der Zusammenhang zwischen Maske und Dokument aufgezeigt und demonstriert, wie eine einfache Domino-Anwendung aufgebaut ist. Zentrales Konzept in Domino ist die Struktur offener Schemata, die im Konzept der Notes Note, insbesondere im Dokument und den Items zum Ausdruck kommt. Nach der ausführlichen Beschreibung der Datentypen für Note und Item wurden Techniken in Java erörtert, mit denen diese beherrscht werden können.
2.7 Zusammenfassung
77
Dokumente zu suchen und zu selektieren ist eine der wichtigsten Aufgaben, die sich dem Programmierer bei der Handhabung von Domino-Daten stellt. Mit den Methoden getAllDocumentsByKey wurde eine grundsätzliche und effiziente Methode eingeführt und erläutert. Mit den in diesem Kapitel erlangten Kenntnissen über wichtige Techniken in Domino, der Replikation, den Suchstrategien und Sicherheitskonzepten haben Sie den Sprung in die Welt der Domino-Programmierung geschafft und sind vorbereitet für fortgeschrittene Techniken in Java für Domino. Das nächste Kapitel informiert Sie über das IDE des Domino Designers, den Sie schon in diesem Kapitel kennen gelernt haben, um die Techniken zu beherrschen, mit denen Sie die Basis schaffen, auf der die Java-Anwendungsentwicklung ab Teil II in diesem Buch basiert.
3
3 Notes IDE
In diesem Kapitel: Entwicklung von Basiskomponenten von DominoAnwendungen Die wichtigsten Gestaltungselemente Entwicklung von Java-Anwendungen im Domino Designer Ansichten als Brennpunkt der Anwendungsentwicklung
80
3 Notes IDE
Das Entwicklungswerkzeug für Domino wurde bereits im zweiten Kapitel vorgestellt und soll nun näher betrachtet werden. Insbesondere für die Erstellung von Masken, Ansichten und weiteren für Domino spezifischen Gestaltungselementen ist der Domino Designer das Werkzeug der Wahl und wird mit der neuen Version 7 von Server und Client nun ebenfalls in der siebten Version aufgelegt. Codevervollständigung gehört ebenso zum Leistungsumfang wie kontextsensitive Hilfe und Kontextmenüs, die Arbeit des Programmierers wird durch die Referenzen für LotusScript, @Formeln, JavaScript und eine umfassende Java-Referenz für alle Standard Java-Klassen plus viele gern verwendete Pakete von AWT bis hin zu Xerxes, dem Apache XSLT-Paket, ergänzt. Java-Entwicklung innerhalb des Domino IDEs ist möglich, kann jedoch auch außerhalb des Domino Designers in verbreiteten und beliebten Entwicklungstools und jedem anderen beliebigen Java IDE5 erfolgen. Obwohl die externen Java-Entwicklungswerkzeuge einige Vorteile aufzuweisen haben, die die reine Java-Entwicklung in diesen Umgebungen außerhalb des Domino Designers sinnvoll machen, sind Grundkenntnisse, insbesondere bei der Erstellung von Ansichten, für den Java-Entwickler für Domino wichtig. Die folgende Einführung erhebt daher keinen Anspruch auf Vollständigkeit in Bezug auf die Domino-Werkzeuge und -Programmelemente, sondern erfasst lediglich die Bereiche, die für einen Java-Entwickler wichtig sind, oder Schnittstellen zu externem Java-Code darstellen.
3.1
Der Domino Designer
Die Arbeitsoberfläche (Abb. 3-1) des Domino Designers ist in mehrere Arbeitsbereiche aufgeteilt. Unterhalb der im oberen Bereich des Fensters angeordneten, kontextabhängigen Menü-Leisten und Buttons befinden sich die drei Hauptfenster. Links befindet sich eine Übersicht über alle für eine Datenbank zur Verfügung stehenden Designelemente, unterhalb derer jeweils die tatsächlich vorhandenen Elemente in Kategorien angezeigt werden. In der rechten oberen Hälfte findet sich das Layout-Fenster, in dem alle Designelemente gestaltet werden. Die untere rechte Hälfte ist zweigeteilt und zeigt zum einen kontextabhängig die in einem Designelement vorhandenen Code-Objekte wie Felder, aber auch mögliche Ereignisse an. Zum anderen können hier objektgebundene Programmierungen vorgenommen werden; so werden hier zum Beispiel Spaltenoder Auswahlformeln in Ansichten oder Feldvalidierungen und Vorbelegungen programmiert. Am linken Rand des Fensters können eigene Bookmarks abgelegt werden. Hier befinden sich des Weiteren Links zu den anderen Clients von Domino. Element-Ei5
Am Markt gibt es eine Vielzahl von IDEs. Hierzu gehören z.B.BEA Workshop Studio, Borland JBuilder, Eclipse, IntelliJ IDEA, JCreator, Oracle JDeveloper, Rational Application Developer, Sun Java Studio und andere, die sich alle für die Java-Entwicklung mit Domino eignen. Für einige (z.B. Eclipse und Rational) gibt es spezielle Plugins, die einige Aspekte der Java-Programmierung für Domino erleichtern.
3.1 Der Domino Designer
81
Vorschau-Buttons
Eigenschaften-Box öffnen
Bookmarks
andere Werkzeuge Designelemente Layout-Fenster Objekte und Referenzen Objekt-Code-Fenster Eigenschaften-Box
Abb. 3-1 Domino Designer
genschaften öffnen Sie entweder durch einen Klick mit der rechten Maustaste auf das entsprechende Element oder durch Anwahl des entsprechenden Buttons in der Button-Leiste. Alle diese Bereiche stehen in unterschiedlicher Ausprägung für die verschiedenen Designelemente zur Verfügung. Im Einzelnen können Sie mit dem Designer die folgenden Elemente erstellen, deren wichtigste kurz besprochen werden: •
•
Frameset (Rahmengruppe) Ähnlich wie im HTML können Domino-Anwendungen in Frames unterteilt werden, also Teilbereiche der Anwendung können verschiedene Elemente zur Darstellung aufnehmen. Wird ein Frameset im Browser dargestellt, werden HTMLFrames gerendert. Page (Seite) Eine Seite in einer Domino-Anwendung ist ein einfacher Layoutbereich, der einer Maske ähnelt, aber nur zur Darstellung von Informationen dient und von jedem Leser der Anwendung geöffnet werden darf, sofern keine besonderen Rechte definiert sind. Seiten können keine Felder enthalten.
82 •
•
•
•
•
3 Notes IDE Form (Maske) Eine Maske in einer Domino-Anwendung ist ein Layoutbereich, in dem durch Felder Benutzereingaben ermöglicht werden. Eine Maske kann viele unterstützende Elemente wie Buttons, Tabellen oder Grafiken enthalten. Eine Maske zeigt ein Dokument an oder erstellt ein neues Dokument und kann Berechnungen (zur Anzeige oder vor dem Speichern) auf den Items eines Dokuments durchführen. View (Ansicht) Eine Ansicht dient einerseits zur tabellarischen Darstellung einer bestimmten Untermenge von Dokumenten einer Anwendung. Zum anderen wird sie in Programmcodebestandteilen zum Durchsuchen dieser Dokumente und zur Selektion von weiteren Untermengen von Dokumenten verwendet. Ansichten können in ihren Spalten Berechnungen, Items der selektierten Dokumente oder auf den Items basierende Berechnungen anzeigen. Views selektieren die Menge der anzuzeigenden Dokumente auf Basis von so genannten @Formeln oder seit Domino R7 auch durch SQL (sofern die Notes-Datenbank in DB2 gespeichert ist). Folder (Ordner) Ordner sind verwandt mit den Ansichten und ähneln diesen gestalterisch. Ordner haben jedoch keine SELECT-Formel zur programmatischen Selektion von Dokumenten, sondern enthalten frei zuordenbare Verweise auf Dokumente, die entweder durch Drag-and-Drop-Operationen von Benutzern oder programmatisch erstellt werden können. Werden diese Verweise gelöscht, wird das zugehörige Dokument nicht gelöscht. Wird ein Dokument gelöscht, werden auch alle Verweise, die sich in Ordnern befinden, gelöscht. Normalerweise ist nur für den Ordner gespeichert, welche Dokumente er enthält. Damit von Dokumenten rückgeschlossen werden kann, in welchen Ordnern sie sich befinden, muss die Datenbank für die Verwaltung von Ordner-Referenzen eingerichtet sein (s. Kap. 9.6.1). Ordner werden programmatisch wie Ansichten behandelt und können mit denselben Methoden geöffnet und durchsucht werden. Shared Code (Gemeinsamer Code) Unter dieser Überschrift sind im Designer Code-Bestandteile zusammengefasst, die hier zentral definiert und dann von den anderen Designelementen oder von außen (Agenten, WebServices) genutzt oder referenziert werden. Einige Designelemente in dieser Rubrik können auch datebankübergreifend genutzt werden. Agent Ein Agent ist ein Container für ausführbaren Code in Domino. Agenten können in LotusScript, Java, JavaScript und mit @Formeln programmiert werden und haben verschiedene Trigger, mit denen sie entweder eventgesteuert oder periodisch ausgelöst werden, sie können aber auch manuell, z.B. über ein Menü gestartet werden.
3.1 Der Domino Designer
•
•
•
•
•
•
83
Agenten tragen die Signatur der Benutzer-ID, mit der sie zuletzt gespeichert wurden und werden mit den Benutzerrechten des Benutzers, der diese Signatur ausgestellt hat, ausgeführt, sofern sie nicht manuell gestartet wurden. Seit Domino Version 6 können Agenten auch unter einer anderen Benutzer-ID als der, unter der sie gespeichert wurden, gestartet werden, sofern zuvor entsprechende Rechte im Serverdokument des Notes-Adressbuches names.nsf vergeben wurden. (s. Kap. 4.16) Web Service Die Möglichkeit, Web Services direkt im Domino Designer erstellen zu können, ist neu und steht ab Version 7 zur Verfügung. Ein WebService in Domino ist eine Implementation des gleichnamigen W3CStandards basierend auf WSDL und Soap [W3C, WebService]. Ein WebService, der im Designer erstellt wurde, muss auf einen Server repliziert werden, der über HTTP erreichbar ist. Ein WebService in Domino ähnelt einem Agenten und arbeitet mit denselben Berechtigungsmechanismen. Im Designer kann sowohl der Programmcode des WebServices als auch das WSDL-Dokument erstellt bzw. verwaltet werden und kann in Java- oder -LotusScript programmiert werden. Outline Ein Outline ist eine Navigations-Baumstruktur. Dieses Navigationselement kann in Seiten oder Masken eingefügt werden und dient dazu, die Bedienungselemente oder Objekte einer Domino-Anwendung übersichtlich in einer Baumstruktur darzustellen. Üblicherweise werden Outlines im linken Frame einer Anwendung angezeigt und enthalten eine Übersicht über die zur Verfügung stehenden Ansichten. Subform (Teilmaske) Teilmasken sind Masken, die in andere Masken eingefügt werden können und die Gruppierung von wiederverwendbaren Elementen in Masken ermöglichen. Teilmasken können mit den gleichen Gestaltungselementen versehen werden wie Masken. Bis auf einige Trigger, die nur in Masken eingestellt werden können, sind Teilmasken weitgehend identisch mit Masken. Field (Feld) Im Designer können gemeinsam genutzte Felder definiert werden. Solche gemeinsamen Felder sind mehrfach referenzierbare und wiederverwendbare Definitionen von normalen Feldern. Column (Spalte) Im Designer können gemeinsam genutzte Ansicht-Spalten definiert werden. Solche gemeinsamen Spalten sind mehrfach referenzierbare und wiederverwendbare Definitionen von normalen Ansichtsspalten. Gemeinsam genutzte Spalten sind neu ab Domino Version 7. Action (Aktion) Masken oder Ansichten können Aktions-Buttons zugeordnet werden. Diese können entweder jeweils pro Maske individuell definiert oder unter Actions als gemeinsam genutzte Aktionen definiert werden. Solche gemeinsamen Aktionen sind wiederverwendbare Definitionen von normalen Aktionen.
84 •
•
•
•
•
•
•
•
3 Notes IDE Script Libraries (Skript-Bibliotheken) Ausführbarer Programmcode kann im Domino Designer in so genannten Script Libraries zusammengefasst werden. Diese Bibliotheken können in den entsprechenden Designelementen referenziert und somit auch in den dort definierten Methoden oder Klassen referenziert werden. Skript-Bibliotheken können LotusScript, JavaScript oder Java enthalten. In Java-Bibliotheken können wiederum Jar- oder Zip- Dateien importiert werden. Für Java ist interessant, dass seit Version 6 auch Skript-Bibliotheken anderer Datenbanken referenziert werden können, so dass ein zentrales Repository mit den Jar-Dateien in einer zentralen Skript-Bibliothek geführt werden kann. Shared Resources (Gemeinsame Resourcen) Unter dieser Überschrift sind im Domino Designer Designelemente zusammengefasst, die in den Designer importiert wurden. Einige Designelemente in dieser Rubrik können auch datebankübergreifend genutzt werden. Images (Bilder) Bilder im BMP-, JPG- oder GIF-Format können in den Designer importiert werden und an verschiedenen Stellen in Designelementen wiederverwendet werden. Derart importierte Bilder werden als Binärobjekte in der Notes Storage Facility gespeichert, müssen also nicht als separate Datei verwaltet werden. Wie alle anderen Designelemente werden importierte Bilder über alle Server und Repliken mit repliziert. Hier zeigt sich einer der großen Vorteile des Notes-Replikationsmechanismus. Gestaltungsänderungen müssen nicht durch einen Rollout über die verschiedenen Installationen verteilt werden, sondern können einfach über die Replikation verteilt werden. Die Möglichkeit, Bilder zu importieren, ist neu seit Domino Version 5. Files (Dateien) Auch Dateien (beliebigen Formats) können in Domino importiert und referenziert werden. Dies ist neu seit Version 6. Applets Java-Applets können seit Version 4.6 in Domino verwendet werden und werden seit Version 5 als gemeinsame Ressourcen verwaltet. Sie werden in Masken oder Seiten referenziert. Style Sheets CSS Style Sheets können seit Version 6 im Designer verwaltet und z.B. in Masken oder Seiten referenziert werden. DB2 Access Views Seit Domino R7 gibt es die Möglichkeit, Domino-Daten auch für DB2-Datenbanken und den Zugriff über SQL zur Verfügung zu stellen. Damit diese Ressource zur Verfügung steht, muss die Datenbank im so genannten NSFDB2-Format vorliegen, also in einer DB2-Datenbank gespeichert sein. DB2 Access Views werden in Kapitel 13.4.5 vorgestellt. Data Connections (Datenverbindungen) Mit Datenverbindungen können seit Version 6 Verbindungen zu externen Unternehmensdatenquellen hergestellt werden.
3.2 Masken
85
Domino-Anwendungen werden aus diesen Designelementen gebildet. Viele sind für die GUI-Programmierung von Domino-Anwendungen wichtig. Insbesondere Masken, Ansichten, Agenten und Programmbibliotheken sind für die Java-Programmierung von Bedeutung und deren Erstellung und Handhabung im Designer werden im Folgenden erläutert.
3.2
Masken
Eine neue Maske, deren Funktionsweise Sie bereit in den Kapiteln 2.1.2 und 2.2.2 kennen gelernt haben, erstellen Sie im Designer über das Menü „Create“ -> „Design“ -> „Form“. Die Gestaltung von Masken erfolgt zeilenweise, durch Eingabe von Text oder durch das Einfügen von Objekten, wie Felder, Buttons, Tabellen, Bilder, oder eingebettete Objekte, wie Ansichten oder OLE-Objekte. Zur Gestaltung einer Maske stehen dieselben Möglichkeiten zur Verfügung, die auch in einem RichText-Feld erzeugt werden können. Die Gestaltung einer Maske wird intern folgerichtig in einem RichText Item gespeichert (in der Design Note, die das Designelement Maske definiert). Die zeilenorientierte Eingabe hat zur Folge, dass Sie Objekte nicht frei positionieren können, sondern alle Objekte dem Fluss und Umbruch der Zeilen unterworfen sind. Sie können eine Positionierung von Elementen durch Tabellen oder durch so genannte Layout-Regionen vornehmen, wobei letztere recht eingeschränkt in den Möglichkeiten sind (in Layout-Regionen können nur wenige weitere Elemente eingefügt werden). Teilmasken sind ein wichtiges Designelement, da sie es ermöglichen, Bereiche von Masken zusammenzufassen und in verschiedenen Masken zu verwenden, seit Version 6 auch datenbankübergreifend. Masken sind die grafische Benutzerschnittstelle für Anwender, über das Daten ein- und ausgegeben werden. Benutzerinteraktionen können durch die Lotus-UI (User Interface)-Klassen programmiert werden (s. Abb. 2-14). Hierfür stehen unter anderem sämtliche in den Menüs des Notes Clients verfügbaren Funktionen zur Verfügung. Sie dienen dazu, Benutzerinteraktionen zu automatisieren. Diese UI-Methoden können jedoch nur über LotusScript programmiert werden. Dennoch ist es möglich, über Buttons oder Aktionen Java-Agenten zu starten, mit denen das aktuell geöffnete Dokument verändert werden kann, wie im Beispiel aus Kapitel 2.1 gezeigt wurde. Da Java-Agenten über @Formeln oder über LotusScript gestartet werden können, ist es möglich, Java-Agenten auch über die Events einer Maske anzusteuern. Die Definition von Events finden Sie im Designer im rechten unteren Frame in der Objekt-Palette. Klicken Sie auf den gewünschten Event und codieren Sie das Ereignis im Coding Frame. Events können in LotusScript, JavaScript oder mit @Formeln programmiert werden und gelten für den Domino-Client, die Benutzung der Maske im Browser oder für beide, wobei nur die Events onHelp, onLoad, onUnload und onSubmit sowohl für die Verwendung im Browser als auch für die Verwendung im Notes Client zur Verfügung stehen. Diese Events können nur in JavaScript programmiert werden.
86
3 Notes IDE
Werden Masken über einen Browser angezeigt, können zwei spezielle Events, WebQueryOpen und WebQuerySave, genutzt werden (s. Kap. 4.14). Mit diesen Events kann jeweils ein Agent gestartet werden, mit dem das aktuell in Bearbeitung befindliche Dokument verändert werden kann (Context Document). Haben Sie eine Maske fertiggestellt, kann diese über die Preview Buttons (Vorschau – Abb. 3-1) angezeigt werden. Masken können von Domino entweder im Notes Client verwendet werden oder über den HTTP-Task im Browser angezeigt werden. Sofern Ihre Datenbank auf einem Server liegt, wird die Maske, sofern sie für die Verwendung im Browser gerendert werden soll, über den HTTP-Task des Servers angezeigt. Wird eine lokale Datenbank verwendet, wird der lokale Hilfs-HTTP-Task des Notes Clients gestartet, um die Vorschau anzuzeigen. Um Masken zu gestalten, stehen verschiedene Objekte und Werkzeuge zur Verfügung. Prinzipiell ist die Gestaltung der Masken zwar zeilenorientiert aufgebaut, seit Version 6 können aber, durch die Einführung von Ebenen, Objekte frei angeordnet werden. Dies war bis Version 5 nur mit so genannten Layout-Regionen möglich. Alle zur Verfügung stehenden Objekte sind hierarchisch aufgebaut und können bis auf wenige Ausnahmen ineinander verschachtelt werden, d.h. soweit dies sinnvoll ist, können alle Elemente innerhalb der anderen Objekte platziert werden. Zum Beispiel kann ein Feld innerhalb einer Tabelle angeordnet werden oder ein Abschnitt (s.u.) innerhalb einer Tabelle und umgekehrt.
3.2.1
Feld
Mit Hilfe von Feldern werden die Items der Dokumente in Domino durch den Benutzer eingegeben oder angezeigt. Felder können mit verschiedenen Funktionen ausgestattet werden, mit Hilfe derer das GUI optimiert werden kann. Felder können verschiedene Typen von Daten aufnehmen (s. Kap. 2.2.3). Felder können mit verschiedenen Berechnungs-Optionen belegt werden: •
•
•
•
Bearbeitbares Feld In ein Standardfeld kann der Benutzer Daten eingeben. Sind im zugehörigen Item bereits Daten enthalten, werden diese im Feld angezeigt. Berechnetes Feld In ein berechnetes Feld können keine Daten eingegeben werden. Es wird über @Formeln bei jedem Aktualisieren der Seite neu berechnet. Der errechnete Wert wird im gleichnamigen Item gespeichert. Berechnetes Feld – Berechnet zur Anzeige Der in diesem Feld berechnete Wert wird lediglich angezeigt und nicht in einem Item gespeichert. Berechnetes Feld – Berechnet beim Erstellen. Die Berechnung dieses Feldes wird nur einmal ausgeführt, und zwar beim erstmaligen Erstellen des Dokuments.
3.2.1 Feld
87
Felder können mit Ereignissen und Verarbeitungsanweisungen (Abb. 3-2) versehen werden, die jeweils in der Objektreferenz im Designer programmiert werden können. Auf diese Events wird hier etwas ausführlicher eingegangen, da sie eine gute Möglichkeit darstellen, externen Java-Code anzusprechen. Der Default Value wird von Domino gesetzt, sofern beim Öffnen eines Dokuments noch kein Item zu diesem Feld zur Verfügung steht. Dies gilt insbesondere beim Neuanlegen eines Dokuments. Der Default Value kann ein Wert (in Anführungszeichen) oder eine Berechnung (@Formel) sein. Für das Verständnis einer Notes-Datenbank ist zu beachten, dass der Default Value an dieser Stelle nur bei der Verwendung einer Notes-Maske gesetzt wird. Auch hier nochmals der Hinweis auf den Unterschied zwischen Feld und Item. Wird ein Dokument direkt aus dem Java-Backend angesprochen, verhält sich das Dokument wie ein reiner Datencontainer und kennt die Feld-Berechnungen seiner Maske nicht. Die Input Translation (Eingabeumsetzung) dient dazu Benutzereingaben zu formatieren. Das Ergebnis der hier einzugebenden @Formel wird ins Feld übernommen. Mit Hilfe der Input Validation (Eingabevalidierung) kann (pro Feld) festgelegt werden, welchen Regeln der ins Feld eingegebene Wert genügen muss. Eine Eingabevalidierung ist eine spezielle @Formel, mit deren Hilfe der Wert eines Feldes ausgewertet und bei Bedarf eine Fehlermeldung angezeigt wird: @if (; @success; @failure ("Validierungsfehler")) Der in der @if-Abfrage verwendete boolsche
Ausdruck wird sich in der Regel auf den Wert des zu validierenden Feldes beziehen. Die Formel @success Abb. 3-2 bewirkt die Fortsetzung der Verarbeitung (z.B. beim Feld-Events Schließen des Fensters oder beim Speichern), @failure () erzeugt eine Fehlermeldung und bricht die Verarbeitung ab (kein Speichern, kein Schließen des Fensters...). Die Feldvalidierungen werden jeweils bei einem Refresh des aktuellen Fensters ausgelöst. Ein Refresh kann entweder programmatisch auf dem NotesUIDocument (per LotusScript) erzwungen werden oder wird beim Speichern, beim Schließen des Fensters oder beim Wechseln zwischen den Feldern ausgelöst, sofern die Maskeneigenschaft „Felder automatisch aktualisieren“ aktiviert ist. Da im Notes Client JavaScript verwendet werden kann, können einige der Events, wie onFocus, gleichzeitig für den Notes Client und die Verwendung der Maske im Browser programmiert werden. Die Verwendung der Events im Einzelnen beschreibt Tabelle 3-1.
88
3 Notes IDE
3.2.2
Ebene
Seit Version 6 bietet der Designer die Möglichkeit Teilbereiche einer Maske als Ebene zu definieren. Sie sind vergleichbar mit Layern (DIV) im HTML und werden konsequenterweise als solche gerendert, sofern die Maske im Browser angezeigt wird. Ebenen können dieselben Elemente enthalten, die auch Masken enthalten können. Ebenen können transparent oder mit einer Hintergrundfarbe definiert werden. Im letzteren Fall überdecken sie darunter befindliche Gestaltungselemente.
3.2.3
Layout-Ebene
Layout-Ebenen sind veraltet und wurden in Version 4 verwendet, um annähernd eine freie Positionierung von Elementen zu ermöglichen.
3.2.4
Aktionen
Aktionen sind Buttons, die in der so genannten Aktionsleiste am oberen Rand einer Maske erscheinen und dazu dienen, Programmcode zu starten. Aktionen können auf den Gestaltungselementen „Page“, „View“ und „Form“ platziert werden. Sie erscheinen dann in allen drei Fällen in der Aktionsleiste. Sinnvollerweise werden in der Aktionsleiste diejenigen Funktionen angesteuert, die sich ausschließlich auf diese Maske, Ansicht etc. beziehen. Um eine Aktion für eine Maske anzulegen, öffnen Sie die gewünschte Maske im Designer; sollte die Palette mit den Aktionen (ganz rechts) nicht sichtbar sein, können Sie diese mit dem Menü „View“ einblenden. Für das Beispiel aus Abb. 3-3 öffnen Sie die Maske „Kapitel 2\1. URL Loader Dokument“ in der Beispieldatenbank. Über das Menü „Create“ -> „Action“ -> „Action“ erzeugen Sie eine neue Aktion. Dieser Aktion muss ein Programmcode zugeordnet werden. Für dieses Beispiel soll die Aktion die gleiche Funktion wie der Aktionsbutton erhalten, der sich bereits auf der Maske befindet. Hierfür markieren Sie zunächst den Aktionsbutton „URL laden“, kopieren den @Formel-Code aus dem Objekt-Code Fenster (s. Abb. 3-1), markieren dann die neue Aktion und fügen hier den Code ein. Über die Vorschau können Sie die Maske und die Aktion testen (Abb. 3-4). Die Gestaltung der Aktionsleiste kann über das Kontext-Menü erfolgen.
3.2.5
Ressourcen
Ressourcen bezeichnen im Zusammenhang mit der Gestaltung einer Maske externe Dateien oder Teilmasken. Im Einzelnen können dies Bilder, JavaScript-Bibliotheken, gemeinsame Felder, Teilmasken, CSS Stylesheets oder HTML-Dateien sein. Bilder, CSS Stylesheets und HTML-Dateien werden im Domino Designer in der Rubrik „Shared Resources“ über einen Import einer externen Datei definiert. Diese können
3.2.5 Ressourcen
89
Tabelle 3-1 Feld-Events im Client und Browser Eigenschaft default Value Input Validation Input Translation Input Enabled HTML-Attribute
Verwendung im Client @Formel
Event onBlur
Verwendung im Client JavaScript gemeinsames JavaScript LotusScript (***) JavaScript gemeinsames JavaScript LotusScript (***)
onChange
onClick onDblClick onFocus
@Formel @Formel (**)
@Formel
JavaScript gemeinsames JavaScript LotusScript (***)
onKeyDown onKeyPress onKeyUp onMouseDown onMouseMove onMouseOut onMouseOver onMouseOut onSelect Skript (Options) (Declarations) Entering Exiting Initialize Terminate *
Verwendung im Browser @Formel @Formel (*) @Formel
Verwendung im Client LotusScript LotusScript LotusScript LotusScript LotusScript LotusScript
Verwendung im Browser JavaScript gemeinsames JavaScript (***) JavaScript gemeinsames JavaScript (***) JavaScript JavaScript JavaScript gemeinsames JavaScript (***) JavaScript JavaScript JavaScript JavaScript JavaScript JavaScript JavaScript JavaScript JavaScript Verwendung im Browser
scheitert eine Eingabevalidierungsformel, so wird der Text des @failure auf einer eigenen leeren Browserseite angezeigt. ** Die Feldeigenschaft „Native OS Style“ muss aktiviert sein *** Wird gemeinsames JavaScript verwendet, dann gilt dies sowohl für den Notes Client, als auch für den Browser. Für den Client kann in diesem Falle Lotus Script nicht mehr verwendet werden.
90
3 Notes IDE
Abb. 3-3 Aktionsleiste im Designer
dann über die Menü-Funktion „Create“ -> „Resource“ -> „Insert Resource“ importiert werden. Bilder werden direkt in der Maske angezeigt, CSS Stylesheets und HTML-Dateien werden eins zu eins in die Maske importiert. Falls HTML-Dateien JavaScript-Code enthalten, wird dieser sowohl im Notes Client, als auch im WebBrowser ausgeführt, sofern die Maske im Browser angezeigt wird (z.B. über die Voransicht im Browser). Teilmasken können im Abschnitt „Shared Code“ des Designers programmiert werden und über das Ressourcen-Menü in eine Maske eingefügt werden. Wurde eine Teilmaske in eine Maske eingefügt, wird diese als Bestandteil der Maske dargestellt und behandelt. JavaScript-Bibliotheken definieren Sie ebenfalls im Abschnitt „Shared Code“ und importieren sie in die Maske. JavaScript-Code kann sowohl im Notes Client als auch im Browser ausgeführt werden. Sind Methoden in der JS-Bibliothek definiert, können diese über die in Kapitel 2.1.2 definierten Events angesteuert werden.
3.2.6 Seitengestaltungselemente
91
Abb. 3-4 Aktionsleiste im Client
3.2.6
Seitengestaltungselemente
Die Gestaltung einer Maske erfolgt über die zeilenweise Eingabe von Text, Bildern oder anderen importierten Elementen, bzw. über die Platzierung dieser Elemente in Ebenen (ab Version 6). Hierfür stehen neben gebräuchlichen Elementen, wie Textstile, Lineale, Seitentrenner und Bilder, auch die Möglichkeit zur Verfügung, Anhänge oder OLE-Objekte in die Maske einzufügen.
3.2.7
Abschnitte
Abschnitte (s. Kap. 11.5 – RichTextSection) sind eine Besonderheit in der Gestaltung von Masken in Notes. Sie dienen der inhaltlichen und optischen Gruppierung von Inhalt und können – wie die meisten Gestaltungselemente von Masken – sowohl in Masken als auch in RichText-Feldern in Dokumenten eingesetzt werden. Um einen Abschnitt zu erstellen, markieren Sie den Bereich einer Maske, den Sie in einem Abschnitt zusammenfassen möchten und wählen im Menü „Create“ den Befehl „Section“. Sie können zwischen einem normalen Abschnitt („Standard“) und einem zugriffsgeschützten Abschnitt („Controlled Access“) wählen, wobei letzteres keine Sicherheitsfunktion darstellt, sondern lediglich in einer Maske einen bestimmten Bereich nur für bestimmte Benutzer zur Verfügung stellt, die dahinter liegenden Da-
92
3 Notes IDE
ten jedoch sind in keiner Weise zusätzlich geschützt. Ein Abschnitt wird durch eine Überschrift und einen so genannten „Twistie“ – ein dreieckiges Grafik-Symbol – dargestellt und kann durch einen Klick auf die Überschrift auf- und zugeklappt werden.
3.2.8
Berechneter Text
Durch das Einfügen von berechnetem Text über das Menü „Create“ -> „Computed Text“ wird ein Platzhalter erstellt, der über die Programmier-Palette mit @Formeln versehen werden kann, deren Ergebnis an der Stelle des Platzhalters angezeigt wird. Berechneter Text ist eine typische Methode, um Items eines Dokuments anzuzeigen, für die keine Bearbeitungsfunktion oder eine Repräsentation als Feld in der Maske gewünscht oder benötigt wird.
3.2.9
Hotspots
Hotspots sind aktive Flächen, über die @Formeln oder LotusScript Code ausgelöst, eine URL geöffnet oder ein Hilfetext angezeigt wird. Erstellen Sie einen Hotspot, indem Sie den Bereich in der Maske markieren, der später als aktive Fläche – dies wird in der Regel ein Text sein – dienen soll, und wählen Sie im Menü „Create“ den Befehl „Hotspot“. Hotspots sind eine typische Möglichkeit, um einen Java-Agenten zu starten, wenn die interaktive Ausführung von Java-Code aus dem Benutzerinterface des Notes Clients benötigt wird.
3.2.10
Eingebettetes Element
Mittels so genannter eingebetteter Elemente können andere Notes-Designelemente – z.B. Ansichten – innerhalb einer Maske angezeigt werden.
3.2.11
Verwendung von Masken im Notes Client und im Browser
Bereits seit Version 4.6 von Domino gibt es neben der Darstellung von Masken im Notes Client die Möglichkeit, Masken von Domino für den Browser rendern zu lassen (Abb. 3-5). Dies ist insofern eine Besonderheit, da hierfür (zunächst) keine Veränderung an einer Maske vorgenommen werden muss, die eigentlich für den Notes Client entwickelt wurde. Um sich einen Eindruck hiervon zu verschaffen, können Sie einfach im Domino Designer die Maske aus dem URL-Loader-Beispiel markieren und über den Vorschau-Button mit dem „Internet Explorer“-Symbol im Browser öffnen. Der Notes Client hat einen eingebauten HTTP-Task, mit dem er die Seite anzeigen kann. Da es noch kein Dokument gibt, das mittels der Maske angezeigt werden soll, wird im Hintergrund ein temporäres Dokument erzeugt, das dann im Browser bearbeitet werden kann.
3.2.11 Verwendung von Masken im Notes Client und im Browser
93
Abb. 3-5 Im Browser geöffnete Maske
Eine grundsätzliche Technik, die auch für die Java-Programmierung wichtig ist, ist das so genannte Kontextdokument in Notes. Dieses Dokument meint das jeweils geöffnete oder markierte Dokument während einer Benutzer-Interaktion. Es wird vom Notes Client, bzw. vom Domino-Server automatisch zur Verfügung gestellt. Öffnet ein Benutzer ein Dokument im Notes Client, so ist das Kontextdokument dieses im Moment geöffnete Dokument. Wird ein Agent gestartet, so steht über das Objekt AgentContext, das aus der aktiven session bezogen werden kann, über die Methode getDocumentContext() das Kontextdokument zur Verfügung. Wird nun die Maske wie beschrieben im Browser geöffnet, so steht ebenfalls ein Kontextdokument zur Verfügung, und zwar auch in diesem Fall das aktuell in Bearbeitung befindliche Dokument bzw. ein neues Dokument, sofern mit der Maske ein neues Dokument erstellt wird. Sie werden feststellen, dass Notes die Umsetzung der Maske recht gut durchführt. Die Aktionsleiste wird über ein Java-Applet dargestellt, sofern Sie der Aktionsleiste diese Eigenschaft mitgegeben haben. Auffällig ist die fehlerhafte Darstellung der Schrift. Es wird die Standardschrift des Browsers verwendet, da die Default-Schrift des Notes Clients nicht im Browser zur Verfügung steht. Klicken Sie auf den Button „Speichern“, können Sie im Notes Client feststellen, dass ein neues Dokument mit Ihren Angaben angelegt wurde. Das ist eine wirklich große Sache, die doch recht unspektakulär daherkommt. Domino hat „on the fly“
94
3 Notes IDE
nicht nur die Darstellung für den Das Kontextdokument referenziert immer das Browser gerendert, sondern alle aktuelle, vom Benutzer geöffnete Dokument. @Formel-Programmierungen fürs Web umgesetzt. Und weiter: Das Kontextdokument stellt die Schnittstelle Ändern Sie in den zwischen Benutzer und einem Agenten dar. Formularfeldern Daten, so werden diese in diesem neu Auch im Browser verwaltet Domino ein Kontextdoerzeugten Dokument geändert, kument und bietet so dem Programmierer eine d.h. Notes verwaltet im transparente Session-Verwaltung. Hintergrund automatisch (!) eine Browser Session, so dass der Bezug zu dem geöffneten Dokument erhalten bleibt. Der Button „URL Laden“ scheint im Browser nicht zu funktionieren. Woran liegt das? Wenn über die @Formel @command ([ToolsRunMacro]) ein Agent gestartet wird, dann geschieht dies im Browser leider ohne das Kontextdokument. Hierfür sind zum Glück spezielle Events eingebaut, damit auch im Web diese Art der Programmierung erhalten bleiben kann und natürlich Java wieder ins Spiel gebracht werden kann, indem ein Java-basierter Agent gestartet wird. Um die URL-Loader-Maske aus dem Beispiel auch im Browser betreiben zu können, gehen Sie wie folgt vor: 1
2
Öffnen Sie die Buch-Datenbank djbuch.nsf im Domino Designer. Zeigen Sie die Liste der Masken an und markieren Sie die Maske „Kapitel 2\1. URL Loader Dokument“ und duplizieren Sie diese durch Kopieren und Einfügen. Markieren Sie diese Maske erneut und zeigen Sie durch Klicken mit der rechten Maustaste die Eigenschaften des Designelements an. Sie können nun festlegen, dass diese Maske nicht mehr für die Anzeige im Browser vorgesehen ist (s. Abb. 3-6).
Abb. 3-6 Maske im Browser verbergen
3.2.11 Verwendung von Masken im Notes Client und im Browser
3
4 5
95
Für die neu eingefügte Maske legen Sie fest, dass diese für Notes R4.6 oder später, nicht jedoch für Web-Browser verborgen sein soll. In der Ansicht der Masken im Designer können Sie anhand der Häkchen in den Spalten mit den Symbolen für Domino, Web und Mobile Devices erkennen, für welche Art von Client die jeweiligen Masken vorgesehen sind (Abb. 3-7). Öffnen Sie die neue Maske und benennen Sie um in „Kapitel 2\1. URL Loader Dokument Web | FO_URL_Loader_k2“. Beachten Sie, dass beide Masken den selben Alias erhalten haben. Hierdurch erreichen Sie, dass, egal ob Sie fürs Web oder für den Notes Client programmieren, die jeweils richtige Maske geöffnet wird, da die jeweils andere durch die im vorigen Schritt getroffene Einstellung verborgen ist. Zusätzlich erreichen Sie, dass alle Dokumente, egal ob übers Web oder über den Notes Client erstellt, der Maske „FO_URL_Loader_k2“ zugeordnet werden, und somit z.B. in Ansichten gleich behandelt werden. Erinnern Sie sich, dass die Ansicht zur Anzeige der URL-Loader-Dokumente eingeschränkt war auf Dokumente dieser Maske (@SELECT Form = „FO_URL_Loader_k2"). Ändern Sie die Headline der Maske in „URL Loader - Web Browser Darstellung“. Entfernen Sie die Tabellenzeile mit dem „URL Laden“-Button, indem Sie die Zeile Markieren und im Menü den Befehl „Table“ -> „Delete Selected Row(s)“ wählen. Er wird durch eine andere Technik ersetzt. Entfernen Sie ebenfalls die URL-Laden-Aktion aus der Aktionsleiste.
Abb. 3-7 Maskeneigenschaften
96 6
7
3 Notes IDE Lassen Sie das Kontext-Menü der Maske anzeigen, indem Sie in den weißen Hintergrund der Maske klicken, wobei in der unteren mittleren Palette der Baum mit der Überschrift „Objects“ erscheint. Hier finden Sie den Eintrag „FO_URL_Loader_k2 (Form)“ und etwas unterhalb den Eintrag „WebQuerySave“. Im Programmierfenster finden Sie die Voreinstellung „@Command([ToolsRunMacro]; "")“. WebQuerySave ist ein Event der Maske, der ausgeführt wird, sobald das Speichern (über den Web-Browser) angefordert wird und bevor das eigentliche Speichern durchgeführt wird. Tragen Sie hier den Code „@Command ([ToolsRunMacro]; "AG_QS_URLLaden")“ ein. Nun benötigen Sie noch den Agenten AG_QS_URLLaden. Dieser Agent ist eine Kopie des Agenten AG_URL_Laden, Sie müssen lediglich eine Eigenschaft anpassen. Öffnen Sie den neuen Agenten und zeigen Sie die Eigenschaften an. Benennen Sie den Agenten mit „(URL Laden Query Save)|AG_QS_URLLaden“. Der Klarname steht in Klammern, damit dieser Agent nicht im Aktionen-Menü des Notes Client erscheint. Ändern Sie den Target von „All selected documents“ in „none“. Im Kontext-Menü mit den Eigenschaften des Agenten gibt es einen zweiten Tabellen-Reiter mit Sicherheitseinstellungen für den Agenten. Stellen Sie die „Runtime security“ auf „2. Allow restricted operation“. Dies ist notwendig, damit der Agent eine TCP Socket Connection aufbauen darf.
Nun können Sie die neue Maske über den Vorschau-Button im Browser anzeigen. Füllen Sie die Felder aus und klicken Sie auf den „Speichern“-Button – die von Ihnen eingetragene URL wird geladen und der HTML-Code im Browser angezeigt. Beachten Sie, dass Sie den Java-Code nicht ändern mussten. Das Kontextdokument wird im Kontext des Browsers auf die gleiche Weise eingesetzt wie im Notes Client. Fazit Die Anzeige von Notes-Masken im Browser ist eine schnelle und transparente Art, Notes-Anwendungen in Web-Anwendungen umzuwandeln. Die Möglichkeiten, die sich durch die Verwendung von J2EE-Techniken ergeben, gehen jedoch weit über die Möglichkeiten der Notes-Masken hinaus und sind, vor allem so lange man sich in der Java-Welt bewegt, nahe liegender und leichter in vorhandene Java-Anwendungen einzufügen. Die Kenntnis der Möglichkeit, Notes Masken für den Browser aufzubereiten, ist jedoch für die Entwicklung von Java-Anwendungen wichtig, um vorhandene Anwendungen anpassen und mit bereits vorliegenden Notes-Anwendungen interagieren zu können.
3.2.12 Verwendung von Java in Masken 3.2.12
97
Verwendung von Java in Masken
Da die GUI-Programmierung über die LotusScript-UI-Klassen erfolgt und es kein Äquivalent dieser Klassen in Java gibt, kann Java nur indirekt in Masken verwendet werden, indem Java-Agenten gestartet werden. Sie haben bisher folgende Möglichkeiten kennengelernt Java in Masken zu verwenden: • • • •
Starten eines Java-Agenten über einen Hotspot-Button (Kapitel 2.1.2) Starten eines Java-Agenten über das Aktionen-Menü. (Kapitel 2.1.4 Starten eines Java-Agenten über die Aktionsleiste. (Kapitel 3.2.4) Starten eines Java-Agenten als WebQuerySave Agent. (Kapitel 3.2.11)
Im Laufe dieses Buches werden weitere Techniken gezeigt, wie Java genutzt werden kann, um mit den Domino-Objekten zu interagieren und auch die Schnittstelle zum GUI herzustellen. Insbesondere Kapitel 4 liefert hierzu wichtige Informationen.
3.3
Ansichten und Ordner
Ansichten und Ordner erfüllen in Domino zwei wesentliche Funktionen. Zum einen dienen sie dazu, im Domino-GUI Daten – oder genauer gesagt Dokumente – in übersichtlicher Form darzustellen und zum anderen, um Dokumente zu finden, wobei letzteres sowohl über die Backend-Klassen in Java (oder LotusScript) als auch über das Domino-GUI erfolgen kann. Dieses Kapitel wird sich nicht schwerpunktmäßig mit der GUI-Entwicklung von Ansichten auseinandersetzen, es sollen lediglich einige Grundlagen vermittelt werden. Ausführlich werden Selektionsformeln für Ansichten behandelt, als Grundlage für Such- und Selektionsansichten in Java. Eine neue Ansicht erstellen Sie im Domino Designer über den Befehl „Create“ -> „Design“ -> „View“. Erzeugen Sie Spalten für die Ansicht, indem Sie in der Titelzeile der Ansicht doppelt klicken. Jeder Spalte kann über das Objekt-Code-Fenster eine Programmierung zugeordnet werden, die bestimmt, welche Werte in dieser Spalte angezeigt werden. Diese Programmierung kann entweder schlicht der Name eines Items sein. Beachten Sie, dass hier die Namen der Items referenziert werden, d.h. es werden Inhalte von anzuzeigenden Dokumenten referenziert, unabhängig von eventuell vorhandenen Masken. Wie bereits in Kapitel 2.5 angedeutet, kennt Domino zwei Arten, mit Ansichten in der Programmierung umzugehen. Grundsätzlich sind Ansichten die Referenz, um Dokumente zu selektieren. Das Ergebnis einer solchen Selektion, die natürlich auch alle Dokumente einer Ansicht einschließen kann, ist entweder eine DocumentCollection – eine „lose“ Sammlung von Dokumenten – oder eine ViewEntryCollection. Im Gegensatz zu der DocumentCollection, die eine Sammlung von direkten Verweisen auf Dokumente ist – auch unabhängig von einer Ansicht –, ist eine ViewEntryCollection eine sortierte Sammlung von ViewEntry-Objekten, von denen jedes eine Zeile der Ansicht repräsentiert, folglich stehen
98
3 Notes IDE
im ViewEntry-Objekt die in den Spalten sichtbaren Werte der Ansicht zur Verfügung. Insofern ist eine ViewEntryCollection durchaus vergleichbar mit einem ResultSet einer SELECT-Operation auf einem View in SQL. Ganz im Gegensatz zum SQL hält jedoch das ViewEntry-Objekt eine Referenz auf das Originaldokument vor und ermöglicht so den Zugriff auf alle Items des Dokuments. Ein entscheidender Unterschied zwischen der ViewEntryCollection und der DocumentCollection ist die Tatsache, dass in der DocumentCollection lediglich Referenzen auf Dokumente geliefert werden. In der ViewEntryCollection wird das Suchergebnis selbst repräsentiert, so dass neben den Spalteninformationen (mit allen Berechnungsergebnissen) auch z.B. Informationen über das Ranking in der Suche zur Verfügung stehen.
1 2 3
4 5
Hieraus ergeben sich folgende Regeln: Verwenden Sie eine Ansicht, um ein Subset der Dokumente einer Datenbank darzustellen. Die SELECT-Formel der Ansicht bestimmt dieses Subset. Um eine Ansicht durchsuchen zu können, muss mindestens die erste Spalte sortiert sein. Verwenden Sie Spalten mit Berechnungen, wenn Sie Berechnungen für bestimmte Items aller Dokumente benötigen. Referenzieren Sie diese dann mit einer ViewEntryCollection. Verwenden Sie Spalten mit Berechnungen, um gestaltete Ansichten im Notes Client darzustellen. Wenn Sie lediglich direkt auf die Items der Dokumente zugreifen möchten, referenzieren Sie diese mit einer DocumentCollection und lesen die benötigten Werte direkt aus den Dokumenten aus. Benötigen Sie eine Sortierung, referenzieren Sie die Dokumente mit einer ViewEntryCollection und benutzen Sie ViewEntry.getDocument (), um die Referenzen auf die Dokumente zu beziehen.
Seit Domino R7 gibt es die Möglichkeit Ansichten auch auf Basis von SQL zu erstellen. Man spricht dann von einem Query View. Eine derartige Ansicht kann entweder Domino-Daten oder reine UDB-Daten aus einer DB2-Datenbank, aber auch einen JOIN aus solchen Daten darstellen. Query Views werden in Kapitel 13.4.6 vorgestellt. Ordner werden wie Ansichten erstellt und gestaltet. Einziger Unterschied ist das zugrunde liegende Konzept der Dokumenten-Selektion. Ansichten zeigen immer eine dynamische Liste von Dokumenten, die über die SELECT-Formel bestimmt wird. Ordner referenzieren lediglich eine Liste von ausdrücklich, entweder durch den Benutzer über eine Drag- and Drop Operation im GUI, oder programmatisch über putInFolder(String name) in Document oder putAllInFolder(String folderName) in DocumentCollection und ViewEntryCollection, zugeordneten Dokumenten. Mit diesen beiden Methoden können z.B. Suchergebnisse, die sich in einer DocumentCollection befinden, zwischengespeichert werden. Genau genommen wird hierbei eine Liste von Referenzen auf Dokumente zwischengespeichert. Wird ein Dokument aus einem Ordner entfernt, wird lediglich der Verweis entfernt. Ordner können wie Ansichten, z.B. über getAllDocumentsByKey, durchsucht werden.
3.3.1 Spaltenberechnungen
99
Ordner werden verwendet, um Dokumente zu referenzieren, die nicht über eine schematisch über eine Ansicht abbildbare Formel referenziert werden können, oder die sporadisch aufgrund von Ereignissen oder Benutzerinteraktionen selektiert werden sollen. Dokumente kennen per Default nicht die Ordner, in denen sie sich befinden, über eine Datenbankeigenschaft kann dieses Verhalten aber aktiviert werden (s. Kap. 9.6.1).
3.3.1
Spaltenberechnungen
Neben verschiedenen Eigenschaften für die GUI-Darstellung (Farben, Schriften etc.) können Sie für die Spalten einer Notes-Ansicht den anzuzeigenden Wert festlegen. Dieser kann entweder über eine vordefinierte Funktion, die Angabe eines Feldnamens oder eine @Formel erfolgen. Die ersten beiden haben jeweils eine Repräsentation als @Formel, die in der Tabelle 3-2 gelistet wird. Um ein Item anzuzeigen, genügt es, den Typ der Spaltenberechnung auf „Formula“ zu schalten und den Namen des Items einzugeben. Alternativ kann als Typ auch „Field“ ausgewählt werden und der Name des Items aus einer Liste ausgewählt werden, allerdings werden in dieser Liste nur Namen angezeigt, die aus Deklarationen in Masken bekannt sind. Items, die nur über eine Backend-Programmierung (z.B. durch einen Java-Agenten) ins Dokument geschrieben wurden, werden hier nicht erfasst. Bei der Verwendung von Datumsfunktionen (z.B. @now, @day oder @Adjust(time-date; y; m; d; h; m; s)) ist zu bedenken, dass diese weder gecached werden noch in Indizes aufgenommen werden können, so dass bei der Verwendung von solchen Berechnungen Performanceeinbußen hingenommen werden müssen. In Spalten können Sie alle Items anzeigen, jedoch gibt es für einige Datentypen Besonderheiten. RichText-Felder können nicht direkt angezeigt werden, sondern müssen über eine der Funktionen @Text (Item) oder @Abstract (Item; [Anzahl der Zeichen]) ausgewertet werden. Von Listenfeldern wird nur der erste Wert angezeigt, wenn diese nicht mit @Text oder @Implode (Item; [Trenner]) interpretiert werden. Es gelten außerdem die Limitationen aus Kapitel 3.3.4.
3.3.2
Sortieren und Kategorisieren
Spalten können über das Kontextmenü mit verschiedenen Sortieroptionen versehen werden. Neben den Standard-Optionen „nicht, auf- oder absteigend sortiert“ gibt es die Möglichkeit die Sortierung abhängig von Groß- und Kleinschreibung oder von Akzenten zu machen. Im Falle der aufsteigenden Sortierung („Ascending“) wird für die „Case sensitiv“-Suche „ax“ vor „Aa“ einsortiert. Im Falle der akzentuierten Sortierung („Accent sensitiv“) wird „ax“ vor „äa“ einsortiert (Abb. 3-8).
100
3 Notes IDE
Tabelle 3-2 Ansichtenspalten berechnen Einfache Funktion
äquivalente @Formel
Erläuterung
Attachment Lengths
@AttachmentLengths
Attachment Names
@AttachmentNames
Attachments Authors (Distinguished Name)
@Attachments @Author
Authors (Simple Name)
@Name([CN]; @Author)
Collapse / Expand (+/-)
@IsExpandable
Creation Date
@Created
Last Modified Last Read or Edited
@Modified @Accessed
Size (bytes)
@DocLength
# in View (e.g. 2.1.2)
@DocNumber
# of Responses (1 level)
@DocChildren
Textwert oder Liste der Größe der Anhänge Textwert oder Liste der Namen der Anhänge Anzahl der Anhänge Textwert oder Liste der Dokument-Autoren als vollqualifizierter NotesName Textwert oder Liste der Dokument-Autoren als einfacher Name (in der Regel „Vorname Nachname“) Zeigt an, ob eine Zeile erweitert (*) werden kann Erstelldatum des Dokuments Änderungsdatum Letzte/r Zugriff oder Änderung Größe des Dokuments einschließlich Anhänge (automatisch zugewiesene) Kategorisierungsnummer innerhalb eines Views (*) Anzahl der Antwortdokumente in der ersten Unterebene Anzahl der direkten und indirekten Antwortdokumente
# of Responses (all Levels) @DocDescendants
*
Ansichten können kategorisiert werden. Die Kategorien werden mit dem „Twistie“ (dreieckiges Symbol) angezeigt und können durch Klick auf den Kategorie-Titel auf- oder zugeklappt werden.
3.3.2 Sortieren und Kategorisieren
101
Abb. 3-8 Spalteneigenschaften
Werden in Ansichten Items angezeigt, die Listenwerte enthalten, dann wird im Normalfall nur der erste Wert einer Liste angezeigt. Entweder werden über eine Berechnung (z.B. durch @text oder @implode (Liste; [Trenner]) alle Werte auf einen reduziert oder der Spalte wird die Eigenschaft „Show multiple values as separate entries“ zugewiesen. Hierdurch wird erreicht, dass für jeden Wert im Listenfeld eine eigene Zeile in der Ansicht angezeigt wird. Die übrigen Werte für derart mehrfach angezeigte Dokumente sind für alle anderen Spalten identisch. Kategorisierte Spalten gruppieren Dokumente, die in dieser Spalte gleiche Werte haben und zeigen diesen Wert jeweils als Kategorie-Titel an. Kategorisierte Spalten dürfen selbst sortiert sein, müssen aber links von allen anderen sortierten Spalten angeordnet sein, sonst kommt es zu unerwarteten Ergebnissen. Folgende erweiterten Eigenschaften können für kategorisierte Spalten gesetzt werden. • • •
„Show twistie when row is expandable“ – Durch diese Option wird erreicht, dass links des Kategorietitels das „Twistie“ angezeigt wird. „Collapse all when database is first opened“ – Alle Kategorien werden zusammengeklappt, wenn eine Datenbank neu geöffnet wird. „Don't show empty categories“ – Leere Kategorien werden nicht angezeigt.
Bedenken Sie, dass Kategorien für die Verwendung von Ansichten im Notes GUI eine Rolle spielen. Für die Verwendung im Backend, z.B. beim Durchsuchen von Ansichten, sind Kategorien eher hinderlich (s. auch Kap. 10ff.).
102
3 Notes IDE
Kategorien werden in einer ViewEntryCollection als eigener ViewEntry repräsentiert. Beim Durchsuchen werden die Werte der kategorisierten Spalte so behandelt, als ob sie nicht kategorisiert ist, wobei darauf zu achten ist, dass die Spalte sortiert ist, um sie durchsuchen zu können. Ist das Ergebnis einer Suche eine ViewEntryCollection, dann wird das Suchergebnis entsprechend der Reihenfolge, die durch die Kategorisierung entsteht, repräsentiert.
3.3.3
SELECT-Formeln
Die in einer Ansicht angezeigten Dokumente werden über die SELECT-Formel bestimmt, die im Objekte-Fenster definiert wird. Für die Berechnung von SELECT-Formeln können Sie die Operatoren & (Buchhalterisches Und), |(Pipe Symbol) und ! (Ausrufezeichen) für die boolsche Verknüpfung „UND“, „ODER“ und „NICHT“ verwenden. Sie können Gleichheit oder Größer-Kleiner-Verhältnisse mit den Operatoren „= “, „“, „“, „=“ feststellen. Sie können sowohl Textfelder als auch Zahlen- oder Datum-Items auf diese Weise vergleichen. Beachten Sie, dass Listen nur auf Gleichheit geprüft werden können. Im Folgenden finden Sie einige Beispiele und Techniken, die Sie benötigen, um SELECTFormeln zu definieren. 1
SELECT (@Contains(@Name([Abbreviate]; Nachname/DOMAIN":"Entwickler"))
@Author);
"Vorname
Selektiert alle Dokumente, deren Autor die Person „Vorname Nachname/DOMAIN“ ist. @Author liefert den kanonischen Namen der angemeldeten Person in der Form „CN=Vorname Nachname[/OU=Organisationseinheit]/O=DOMAIN“. Mit der Funktion @Name kann das Namensformat in der einfachen Form („Abbreviate“) ermittelt werden. 2
SELECT (@Created < [24.12.2005])
Selektiert alle Dokumente, die vor dem 24.12.2005 erstellt wurden. Beachten Sie, dass Datumsfunktionen die Performance von Ansichten beeinflussen. Sie können diese Formel optimieren, indem Sie in den Dokumenten berechnete Items erstellen, die das Erstelldatum einmalig festhalten. 3
SELECT (@Adjust (@Created;0;0;17;0;0;0) > @Today
Selektiert alle Dokumente, deren Erstelldatum 17 Tage vor „Heute“ liegt. 4
SELECT (@Contains(Form; "_k2"))
Wählt alle Dokumente, die mit Masken erstellt wurden, deren Name (oder deren Alias, sofern einer definiert wurde) den Text "_k2" enthalten. Beachten Sie, dass bei der Verwendung von Masken der Klarname einer Maske nur dann im Feld Form gespeichert wird, wenn kein Alias definiert ist. Haben Sie einen Alias für eine Maske erst nachträglich eingeführt, können Sie sich mit der Formel SELECT Form = "Klarname" |Form = "Alias"
behelfen.
3.3.3 SELECT-Formeln
103
SELECT-Formeln verstehen Betrachten Sie die folgende SELECT-Formel (aus dem Beispiel V_lookup_k2 in Kapitel 2.5) SELECT @Contains (Form;"_k2") | (form = "FO_Report" & @IsResponseDoc) Die SELECT-Formel dient dazu, die Menge der auszuwählenden Dokumente zu selektieren. Zum besseren Verständnis soll versucht werden diese Darstellung mit einer ähnlichen Darstellung im SQL zu vergleichen. Die Ansicht V_Lookup_k2 ist vergleichbar mit einer SQL-Ansicht mit der folgenden Definition, ohne Anspruch auf Kompatibilität mit einem real existierenden SQL-Dialekt: CREATE View V_Lookup_k2 As SELECT lowerCase (F_Titel) as Titel, createdBy() as ErstelltVon, Form as Maske from djbuch.nsf WHERE Form like '*_k2*' OR (Form = 'FO_Report' AND isResponseDoc ()) ORDER BY Titel, ASC
Der direkte Vergleich zwischen SQL und Notes-Ansichten ist sicherlich problematisch. Halten Sie sich in Erinnerung, dass Domino seine Daten entgegen einer SQL-Datenbank nur auf Basis offener Schemata organisiert. Bitte behalten sie dies beim Lesen im Auge. Das SQL müsste wie folgt interpretiert werden: Mit dem CREATE Statement wird eine Ansicht mit Namen V_Lookup_k2 erzeugt, vergleichbar mit dem Anlegen einer Ansicht im Domino Designer. Das SELECT Statement wählt drei Spalten aus, ähnlich den drei Spalten, die im Designer definiert wurden: Die Spalte Titel, als Berechnung des kleingeschriebenen Items F_Titel, die Spalte ErstelltVon als Berechnung aus einer fiktiven Funktion createdBy(), die den Dokumenten-Autor liefert und die Spalte Maske, die das Item Form enthält. Hierbei wird ein sehr wichtiger Unterschied zwischen einer Domino- und einer SQL-Ansicht deutlich. Der Einfachheit halber wurden die Spalten der SQL-Ansicht mit den im Designer verwendeten Spaltenüberschriften bezeichnet. Zwar ist es möglich in Notes, die Spaltentitel auszulesen, diese werden aber nicht für die spätere Referenzierung in einem Resultset benötigt und nur für besondere Anwendungszwecke verwendet (GUI Programmierung). In einer Domino-Ansicht werden Dokumente gesammelt. Also können Dokumente, die entweder direkt über den View oder über ein Resultset aus einer Suche selektiert wurden immer als Dokument angesprochen werden, so dass immer alle Items eines Dokuments zur Verfügung stehen. Im Gegensatz hierzu sind im SQL die Spaltennamen („... as ErstelltVon...“) die Referenz, unter der im Resultset die Daten zur Verfügung stehen. Weitere Daten aus dem ursprünglichen Datensatz stehen nicht zur Verfügung.
5
SELECT F_Nachname="Weihnachtsmann" | F_Datum=[24.12.2005]
Wählt alle Dokumente, deren Item F_Nachname gleich dem Wert „Weihnachtsmann“ ist, oder deren Item F_Datum gleich dem Datum 24.12.2005 ist.
104 6
3 Notes IDE SELECT Form = "myForm" & @isUnavailable ($Conflict)
Wählt alle Dokumente, die mit der Maske „myForm“ erstellt wurden und die kein Speicher- oder Replizierkonfliktdokument sind. Speicher- oder Replizierkonfliktdokumente enthalten das Feld $Conflict – und das Feld $Ref, da Konfliktdokumente automatisch als Antwortdokument zum Original angelegt werden. Gerade für Suchoperationen ist es oft sinnvoll Speicher- und Replikationskonflikte auszuschließen, da hierdurch redundante Daten entstehen, die Suche aber eine Ausschließlichkeit und Eindeutigkeit der Daten erfordert. Sie müssen aber im Einzelfall entscheiden, welche Dokumente in Ihrer Anwendung durchsucht werden müssen. Eine Anzeige ausschließlich von Konfliktdokumenten ist aufgrund ihrer Eigenschaft als Antwortdokumente nicht möglich. Antwortdokumente können immer nur gleichzeitig mit Ihrem Hauptdokument in einer Ansicht angezeigt werden. 7
SELECT Form = "myForm" & !@isResponseDoc
Wählt Dokumente der Antwortdokument sind. 8
Maske „myForm“, die
kein
(Ausrufezeichen)
SELECT @If (@Left (@UpperCase (F_Titel);1) = "A":"B":"C";@True; Form = "FO_URL_Loader_k2"; @True; @False Im Beispiel würden nur Dokumente selektiert, deren Titel entweder mit "A", "B" oder "C" beginnen oder (ausschließliches ODER) deren Maskenname "FO_URL_Loader_k2" ist.
Hierbei kommen zwei Techniken zum Einsatz: der Vergleich mit einer Liste und die @Formel „@if“. Die @Formel „@if“ hat die Syntax @if (Bedingung1; Aktion1; [Bedingung2; Aktion
Aktion2;
...;
Bedingung99;
Aktion99;]
Else-
Eine Liste wird in der @Formel-Syntax durch den Doppelpunkt dargestellt. Der Vergleich eines Strings mit einer Liste von Strings liefert immer dann wahr, wenn der Einzelwert mindestens mit einem Element der Liste übereinstimmt. In Kapitel 15 werden Sie eine Technik kennenlernen, für die Sie programmatisch eigene dynamische Suchkriterien festlegen und diese Suche entweder auf eine Ansicht oder auf eine gesamte Datenbank anwenden können.
3.3.4
Größenbeschränkungen in Ansichten
Für Ansichten gelten folgende Beschränkungen: • •
Maximal 289 Spalten á 10 Zeichen – abhängig von der Anzahl der Zeichen pro Spalte Beliebige Anzahl von Dokumenten pro Ansicht, nur beschränkt durch die Maximalgröße einer Datenbank (entweder durch die Datenbankeigenschaften eingeschränkt, oder durch die maximale Dateigröße des Betriebssystems. Insgesamt maximal 64 GB)
3.4 Agenten •
• • • •
105
Beliebige Anzahl von Ansichten pro Datenbank (beschränkt durch die Datenbankgröße), wobei zu bedenken ist, dass zu viele Ansichten die Performance negativ beeinflussen. Maximal 15 Kilobyte eines Textfeldes (oder eines Berechnungsergebnisses) werden in einer Spalte angezeigt. Maximal 31 Kategorisierungslevel Maximal 31 Antworthierarchieebenen mit jeweils maximal 300.000 Dokumenten Ansichtenname maximal 64 Zeichen Länge
3.4
Agenten
Agenten sind Container für Programmcode in Domino. Agenten können über einen Wizard („simple Action“), mit @Formeln, LotusScript oder in Java programmiert werden. Im Agenten wird bestimmt, wann oder durch welchen Event der Code gestartet werden soll und auf welche Dokumente der Agent angewendet werden soll, bietet also neben einer Event- oder Scheduling-Funktion ein – für jeden einzelnen Agenten separat verwaltetes – Arbeitsset von Dokumenten. In Notes spricht man vom Trigger, wenn beschrieben wird, wann oder wodurch der Agent gestartet wird und vom Target, in dem beschrieben wird, auf welche Dokumente der Agent angewendet werden soll. Auch wenn Sie später Techniken kennen lernen werden, um Java-basierte Anwendungen außerhalb von Domino zu entwickeln, ist der Agent der Ort der Wahl, in dem Sie ihre zeitgesteuerten Batch-Prozesse speichern oder wenn Sie Agenten benötigen, die der Benutzer auf eine bestimmte, von ihm gewählte Anzahl von Dokumenten anwenden können soll. Besonders reizvoll ist die Möglichkeit, als Trigger „Before new mail arrives“ und „After new mail has arrived“ auszuwählen, da Sie in Domino Mail-Nachrichten direkt in eine Domino-Datenbank umleiten und dort als Dokument speichern lassen können. Agenten können entweder privat oder öffentlich sein, wobei private Agenten nur von dem Ersteller des Agenten benutzt werden können. Wenn Sie Anwendungen für Domino entwickeln, sind Agenten die Basis für viele Anwendungsfälle. Java-Anwendungen können als Java-Agenten entwickelt werden und sind dadurch einfach über Trigger oder über eine periodische Steuerung ausführbar. Den Code für Ihren Agenten können Sie entweder im Domino Designer entwickeln (s. Abb. 3-9), der bereits ein recht ordentliches IDE für Java darstellt. Sollen die Anwendungen in einem externen Tool entwickelt werden, kann der Code in Bibliotheken (s. Kap. 3.5) importiert und im Agenten referenziert werden.
3.4.1
Agenten-Trigger
Für die Auslösung eines Domino-Agenten stehen verschiedene Auslöser zur Verfügung, die entweder durch bestimmte Ereignisse oder periodisch ausgelöst (Abb. 3-10) werden.
106
3 Notes IDE
Abb. 3-9 Java-Agent im Domino Designer
Eventgesteuerte Trigger • Action menu selection: Jeder Agent (dessen Name nicht in Klammern steht) erscheint im Menü „Action“ des Notes Clients und wird über diesen Trigger gestartet. • Agent list selection: Agenten mit diesem Trigger bleiben unsichtbar. Dieser Trigger soll verwendet werden für Agenten, die nur von anderen Agenten gestartet werden oder sich noch in Entwicklung befinden. • Before new mail arrives: Ein Agent mit diesem Trigger wird ausgelöst, wenn neue Mail eintrifft und zwar bevor die neuen Dokumente in der Datenbank gelistet werden. Über die Methode getUnprocessedDocuments() in AgentContext können die neuen Dokumente referenziert werden (s. Kap. 3.4.2 – Agenten-Targets). Der Agent wird gestartet, bevor die Dokumente in der Datenbank gespeichert werden und dient z.B. dazu Dokumente in einen Ordner zu verschieben oder eintreffende Dokumente zu filtern (Spam-Filter). Beachten Sie, dass die Eigenschaft „gelesen“ erst nach Ablauf des Agenten beim Speichern der Dokumente in der Datenbank gesetzt wird. Maximal ein Agent pro Datenbank kann diesen Trigger haben. • After new mail has arrived: Dieser Agent wird ausgelöst, nachdem neue Mail in der Datenbank gespeichert wurde, um eingetroffene Dokumente weiterzuverarbeiten (z.B. Beantworten von Mail).
3.4.1 Agenten-Trigger
•
•
107
Diesen Trigger können mehrere Agenten pro Datenbank haben. After documents are created or modified: Dieser Trigger ist eigentlich ein periodischer Trigger und wird mit einer Verzögerung von 5 bis 30 Minuten nachdem Dokumente geändert oder erstellt wurden ausgelöst. Für diesen Trigger können zusätzliche Optionen zur Ausführung vor oder nach einem bestimmten Datum und zur Ausführung am Wochenende eingestellt werden. Es kann festgelegt werden, ob dieser Agent lokal oder auf einem beliebigen oder bestimmten Server laufen soll. Wird Abb. 3-10 Agenteneigenschaften die Ausführung für einen bestimmten Server festgelegt, muss der Agent in einer Replik auf diesem Server liegen, damit er dort ausgeführt wird. In Repliken auf anderen Servern wird der Agent nicht ausgeführt. When documents are pasted: Mit diesem Trigger kann ein Einfüge-Controller programmiert werden, der auf neu eingefügte Dokumente (im Notes Client) reagiert.
Periodische Trigger • Für den periodischen Trigger kann die Ausführungshäufigkeit auf „Mehrmals Täglich“, „Täglich“, „Wöchentlich“, „Monatlich“ und „Nie“ eingestellt werden. Die letzte Option wird gewählt, wenn ein Agent weder durch einen Event noch periodisch ausgeführt werden soll. Dies gilt für Agenten, die übers Web oder durch andere Agenten ausgelöst werden. Zusätzlich können Sie einstellen, in welchem Zeitraum und auf welchen Server der Agent laufen soll. Auch hier gilt: Wurde die Ausführung auf einen bestimmten Server eingeschränkt, dann wird der Agent nur auf Repliken, die sich auf diesem Server befinden, ausgeführt. Soll ein Agent nur auf mehreren bestimmten, aber nicht auf allen Servern ausgeführt werden, dann müssen Sie den Agenten zwar für alle Server aktivieren („ - any Server - “), aber dann im Code den aktuellen Servernamen überprüfen. Dies kann z.B. mit der Methode getServerName() in Session erfolgen. Über run( ) oder runOnServer( ) in Agent können sich Agenten gegenseitig (jedoch nicht rekursiv) aufrufen. Beachten Sie, dass der aufrufende Agent auf die
108
3 Notes IDE
Beendigung des aufgerufenen Agenten wartet, sofern nicht in den Eigenschaften dieses Agenten vermerkt wurde, dass dieser in einem Background-Prozess gestartet werden soll. Die Methode run startet den Agent lokal im Notes Client des Anwenders. runOnServer startet den Agenten auf dem Server, auf dem die Datenbank liegt, in der sich der Agent befindet, wobei diese Datenbank über getParent() abgerufen werden kann. Soll ein Agent einmalig manuell angestoßen werden, kann auch seit Domino R7 in der Serverkonsole der Befehl – tell amgr run "" '>' – verwendet werden.
3.4.2
Agenten-Targets
Wenn im Zusammenhang mit einem Domino-Agenten von Targets die Rede ist, dann ist die Menge der Dokumente gemeint, auf die sich der Agent bezieht, d.h. es wird festgelegt, welche Dokumente er verarbeiten soll. In Java können diese Dokumente als DocumentCollection referenziert werden. Normalerweise werden Sie also über den Target bestimmen, für welche Dokumente der Agent angewendet werden soll, dies kann z.B. die Menge aller vom Benutzer selektierten Dokumente in einer Ansicht sein. Dann wird z.B. über eine Aktion (s. Kap. 3.2.4) ein Agent gestartet, der eine Operation auf diesen Dokumenten durchführen soll. Der Trigger für einen solchen Agenten ist entweder „Action Menu selection“ – dann wird der Agent zusätzlich im Menü „Actions“ angezeigt – oder „Agent list selection“. Der Target ist „all selected documents“. Zusätzlich zu den unterschiedlichen Targets können Sie diese durch Suchkriterien einschränken (s. Abb. 3-11), so dass von vornherein nur diese Submenge des Targets zur Verfügung steht, die Praxis hat jedoch gezeigt, dass die Suchkriterien (bis auf eine Ausnahme) ihre Stärke für die Erstellung einfacher Agenten durch den Anwender haben. Wenn Sie Java-Agenten für die BatchVerarbeitung erstellen ist es sinnvoll Ansichten zu definieren, die Ihre Kriterien erfüllen (Indizierung!), oder über die erweiterten Suchmethoden (s. Kap. 15.4f.) zu arbeiten. Ihnen stehen folgende Targets zur Verfügung. Wird eine Suche definiert, dann basieren die Selektionen auf der durch die Suche basierten Sub-Menge. • •
All documents in database – Alle Dokumente der Datenbank. Dieser Target kann auch für periodische Agenten verwendet werden. All new & modified documents – Alle Dokumente, die seit dem letzten Lauf des Agenten neu erstellt oder geändert wurden. Hierfür gelten besondere Regeln (s.u.). Dieser Target kann auch für periodische Agenten verwendet werden.
3.4.2 Agenten-Targets
109
Abb. 3-11 Suchkriterien für Agenten •
•
•
•
All unread documents in view – Alle Dokumente, die vom Benutzer noch nicht gelesen wurden. Die „Ungelesen-Kennzeichen“ werden benutzerspezifisch ausgewertet, aber im Falle einer Replikation mit repliziert, so dass über alle Repliken ein einheitlicher „Gelesen-Status“ je Benutzer entsteht. Kann nicht in periodischen Agenten verwendet werden. Kann nur aus einer Ansicht im GUI aufgerufen werden. All documents in view – Alle Dokumente der Ansicht. Kann nicht in periodischen Agenten verwendet werden. Kann nur aus einer Ansicht oder einer Maske im GUI aufgerufen werden. All selected documents – Alle vom Benutzer ausgewählten Dokumente. Kann nicht in periodischen Agenten verwendet werden. Kann nur aus einer Ansicht im GUI heraus aufgerufen werden. None – Nur das aktuelle Dokument. Kann nicht in periodischen Agenten verwendet werden. Kann nur aus einer Ansicht oder einer Maske im GUI aufgerufen werden.
110
3 Notes IDE
Für die Java-Entwicklung sind sicherlich die beiden Targets „All documents in Database“ oder „All new & modified documents“ interessant, da diese auch in periodischen Batch-Agenten zur Anwendung kommen können. Beachten Sie, dass die Dokumente des Targets über die Methode getUnprocessedDocuments() in AgentContext als eine DocumentCollection zur Verfügung gestellt werden. Die Nutzung dieser DocumentCollection ist optional. Sie können selbstverständlich im Agenten Ihre eigenen Dokument-Selektionen, z.B. über Ansichten durchführen. Für den Target „All new & modified documents“ ist zu beachten, dass er zunächst beim ersten Aufruf eines Agenten alle Dokumente der Datenbank liefert. Hier kommt das oben erwähnte Suchkriterium ins Spiel, wenn Sie nicht alle Dokumente einer Datenbank über diesen Trigger, sondern nur eine Sub-Menge verarbeiten möchten. Nun können mit Hilfe eines Agenten mit diesem Target die Dokumente entsprechend einer Businesslogik, die im Agenten definiert wird, verarbeitet werden. Wichtig ist, dass über den Agenten durch den Programmierer festgelegt werden muss, welche Dokumente als verarbeitet gelten sollen. Dies kann mit Hilfe der Methode updateProcessedDoc(Document doc) erfolgen. Einen Beispielcode hierzu finden Sie im Agenten „Kapitel 3\1. Unverarbeitete Dokumente“ in der Buch-Datenbank und im Listing 3-1. Wenn nun der Agent beim nächsten Mal gestartet wird (manuell oder periodisch), wird er nur noch solche Dokumente auflisten, die entweder seit dem letzten Lauf geändert oder neu angelegt wurden. Die Verwaltung der Dokumente über updateProcessedDoc erfolgt pro Server und pro Agent. Diese Vorgehensweise hat Vor- und Nachteile. Ein wesentlicher Vorteil ist, dass nach der Verwendung von updateProcessedDoc die Information zur Verfügung steht, ob Sie das Dokument beim nächsten Mal verarbeiten müssen ohne (!) dieses Dokument explizit speichern zu müssen und gleichzeitig erkennen können, ob das Dokument durch den Anwender (oder durch einen Backendcode) verändert wurde, ebenfalls ohne bei der Veränderung irgendwelche Flags setzen zu müssen. Dies ist ein Vorteil, nicht nur weil dieses Verhalten transparent ist und keinen zusätzlichen Code benötigt, sondern weil das Dokument nach der Verwendung von updateProcessedDoc nicht als verändert gilt und somit keine Speicher- oder Replikationskonflikte erzeugen kann. Ein großer Nachteil dieser Vergehensweise ist, dass die Information über verarbeitete Dokumente verloren geht, sobald ein Agent upgedatet oder neu gespeichert wird. In diesem Fall liefert also getUnprocessedDocuments in AgentContext erneut alle Dokumente der Datenbank (oder alle in der Suche spezifizierten Dokumente). Muss sichergestellt werden, dass Dokumente sicher nur ein einziges Mal verarbeitet werden, dann müssen Sie diese Information selbst verwalten. Sie können entweder in einem Item im Dokument speichern, ob eine erneute Verarbeitung notwendig ist (Achtung: Replikationskonflikte) oder in einem Dokument eine Tabelle mit dieser Information verwalten. In beiden Fällen ist zu bedenken, dass jedes Verändern, sei es durch den Benutzer oder z.B. durch einen Agenten, nachgehalten werden muss.
3.5 Bibliotheken
111
import lotus.domino.*; public class NeueUnverarbeiteteDokumenteJavaAgent extends AgentBase { public void NotesMain() { Document doc = null; Document nextDoc = null; DocumentCollection dc = null; try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); dc = agentContext.getUnprocessedDocuments(); doc = dc.getFirstDocument (); System.out.println("Sie haben "+dc.getCount() +" Dokumente ausgewählt: "); while (doc != null) { nextDoc = dc.getNextDocument (doc); System.out.println("Titel: " + doc.getItemValueString("F_Titel")); agentContext.updateProcessedDoc(doc); doc.recycle(); doc = nextDoc; } } catch (Exception e) { e.printStackTrace(); } finally { try { if (doc != null) {doc.recycle();} if (nextDoc != null) {nextDoc.recycle();} if (dc != null) {dc.recycle();} } catch (NotesException ne) { System.out.println ("Fatal error while recycling."); ne.printStackTrace(); } } } }
Listing 3-1 Agent verarbeitet unverarbeitete Dokumente
3.5
Bibliotheken
In Bibliotheken („script libraries“) werden in Domino-Code Klassen und Gruppen solcher zusammengefasst. Sie können Bibliotheken für LotusScript, JavaScript und für Java-Code anlegen. Java-Bibliotheken können entweder direkt im Designer codiert werden oder aber Sie können extern entwickelte Klassen in Jar-, Zip-, Cab- oder Class-Dateien importieren. Sie können diese Dateien auch direkt in einen Java-Agenten anstatt in eine Bibliothek importieren, der Vorteil bei der Verwendung von Bibliotheken liegt natürlich auf der Hand: Alle Ihre Java-Agenten können sich auf diese Bibliothek beziehen, so dass nur an einer Stelle der Code verwaltet werden muss. Java-Code kann auch ganz außerhalb von Domino im File System über den Classpath referenziert werden. Hierzu später (s. Kap. 4.2) mehr.
112
3 Notes IDE
3.6
Zusammenfassung
Domino ist E-Mail-, Collaboration- und Anwendungsserver zugleich und bildet viele Aufgaben und Prozesse in einem Unternehmen ab, dementsprechend wird ein IDE benötigt, das der Entwicklung dieser Anwendungen und der Anwendungen, die auf dieser Plattform betrieben werden sollen, gewachsen ist. Hierfür bietet Lotus den Domino Designer, der die Entwicklung aller Komponenten von Domino-Anwendungen ermöglicht. Während die Frontendprogrammierung der Domino-eigenen Programmiersprache LotusScript vorbehalten ist, da nur dort die hierfür benötigten UI-Klassen vorliegen, eignet sich Java hervorragend für die Entwicklung von Backend Batches oder von interaktiv angesteuerten Funktionen, die über Buttons oder Aktionen oder über die Auswahl in Ansichten und im Menü angesteuert werden. Der Java-Code selbst kann im Domino Designer oder besser noch in externen Tools entwickelt und in den Designer importiert werden, andere Designelemente, die bei der GUI-Entwicklung zum Einsatz kommen, werden im Designer erstellt. Gleich wo der Code entwickelt werden soll, sind Ansichten und Ordner das zentrale Gestaltungselement für die Selektion von Daten, also Dokumenten, und werden im Designer entwickelt. Ansichten werden für die GUI-Entwicklung ebenso benötigt wie für die Backend-Programmierung. Nicht zuletzt hängt von der guten, der Aufgabe angemessenen, Gestaltung von Ansichten die Performance von Java-Anwendungen ab, unterstützt durch die automatische Indizierung sortierter Ansichtsspalten. Masken und Aktionen sind die Schnittstellen, mit denen der Benutzer Java-Anwendungen auslöst, die in der Regel in Agenten gespeichert sind. Durch die Konzentration dieses Kapitels auf den Domino Designer drängen sich neue Fragen auf: Wie können Java-basierte Domino-Anwendungen in den gewohnten IDEs entwickelt werden? Können vorhandene Java-Anwendungen in ein Domino-Umfeld integriert werden und kann umgekehrt eine Domino-Anwendung in ein Java-Umfeld integriert werden? Wie können von eigenständigen Java-Anwendungen Verbindungen zu einem Domino-Server aufgenommen werden? Antworten auf diese Fragen finden sich im zweiten Teil dieses Buches.
Teil 2
Java @Domino Mit dem Know-how über die Notes Storage Facility im Gepäck und dem Verständnis der Domino-Designelemente, der Domino-Plattform und der besonderen Datenstruktur ist der Weg frei für Domino-Java-Anwendungen. In Teil 2 sind die DominoJava-Objekte ebenso im Visier wie NotesThread und Session, auf denen diese aufbauen. Besondere Domino-Programmiertechniken, wie Java-Agenten, Servlets, Remoteverbindungen oder Connection Pooling sind ebenso Thema wie Basistechniken, wie Suche oder Recycling von Domino-Java-Objekten und bilden das Werkzeug für solide Java-Anwendungen für Domino.
4
4 Java-Anwendungen @ Domino
In diesem Kapitel: Setup der Entwicklungsumgebung NotesThread und Session Domino-Java-Anwendungen mit lokaler Domino-Session Java in Notes-Masken Java-Agenten im Domino Designer Sicherheit bei der Anwendungs- und Agentenausführung
116
4 Java-Anwendungen @ Domino
Java-Anwendungen für Domino können auf vielfältige Weise entwickelt und eingesetzt werden. Beide basieren auf dem Basisobjekt jeder Domino-Java-Anwendung, der Session. Zwei grundsätzliche Ansätze stehen für den Aufbau einer DominoJava-Anwendung zur Verfügung – einerseits können unabhängig von der DominoEntwicklungsumgebung eigenständige Java-Anwendungen entworfen werden, die über die Core-Klassen des Pakets lotus.domino.* eine Session beziehen können und somit auf alle Domino-Java-Objekte Zugriff erhalten, und andrerseits können so genannte Domino-Java-Agenten im Domino Designer entworfen werden, die sowohl benutzergesteuerte Programmausführungen ermöglichen, als auch servergesteuerte Batches abarbeiten können. Beide Ansätze haben ihr eigenes Einsatzgebiet und sollen in allen Details erläutert werden, wobei der eigentliche Unterschied nur im Speicherort des Codes (in einer Java-Anwendung oder in einer Domino-Datenbank) und der hierdurch bedingten unterschiedlichen Art, eine Session zu beziehen, besteht, woraus sich allerdings einige Unterschiede in Berechtigungen und Ausführungsart ergeben. Die eigentliche Verwendung der Domino-Java-Objekte ist in beiden Fällen gleich. Dieses Kapitel beschreibt, wie Domino-Java-Anwendungen aufgebaut werden und welche Ausführungseigenarten und Berechtigungen bei der Ausführung gelten. Die für den Aufbau einer Domino-Java-Anwendung notwendigen Objekte und Klassen werden dann ab Kap. 6 beschrieben.
4.1
Domino- und J2SE- und J2EE-Anwendungen
Bei der Entwicklung stabiler, leistungsfähiger und skalierbarer Web-Anwendungen auf Basis eines Application Servers hat sich Java immer mehr zu einem Standard entwickelt. Insbesondere der J2EE-Standard und einige Frameworks wie Struts [Apache, Struts] oder cocoon [Apache, cocoon] sind heute die Basis vieler Java-basierter Web-Anwendungen. Auch im Bereich der Software-Entwicklung muss sich Java nicht mehr verstecken und hat inzwischen bei vielen kommerziellen und nicht kommerziellen Unternehmen Einzug in die Anwendungsentwicklung gefunden. Seit Version 4.5 ist Java in Domino integriert und inzwischen wichtiger Bestandteil für die Anwendungsentwicklung für Domino. Hieraus haben sich verschiedene Ansätze entwickelt, mit denen einerseits auf Domino-Datenbanken zugegriffen werden kann, sei es durch alleinstehende J2SE-Anwendungen oder über Webanwendungen auf Basis von Servlets oder JSP. Andrerseits ist Java in Domino integriert und ermöglicht die Entwicklung und Erweiterung von Domino-Anwendungen. Ein kleines Beispiel haben Sie bereits in Kapitel 2.1 kennengelernt. Grundsätzlich kann nach drei Ansätzen unterschieden werden, in denen JavaProgrammierung für Domino zum Einsatz kommt: •
Erweiterung von Domino-Anwendungen durch den Einsatz von Java innerhalb des Notes Clients
4.2 Vorbereitungen • •
117
Entwicklung von alleinstehenden Java-Anwendungen auf Basis von J2SE (oder auch J2ME) mit Zugriff auf Domino-Datenbanken Entwicklung von Web-Anwendungen mit Zugriff auf Domino-Datenbanken auf Basis von Servlets, JSP, JSP-TagLibs und J2EE entweder für den Einsatz auf externen Application Servern oder innerhalb des Domino-HTTP-Task (nur Servlets)
Für alle drei Anwendungsfälle werden verschiedene Konzepte benötigt, um die Sicherheit zu gewährleisten, die Verbindung zwischen Client und Server herzustellen, Datenintegrität zu gewährleisten und die Interaktion mit den Benutzeroberflächen zu ermöglichen. Basis aller dieser Anwendungsfälle ist immer die Notes Session, wobei diese auf verschiedenen Wegen bezogen werden kann. Zusätzlich wird die Verbindung zu den eigentlichen Notes-Code-Klassen benötigt, die als C++ Bibliotheken im Hintergrund liegen, wobei dies immer transparent vonstatten geht und zu unterscheiden ist, ob auf dem ausführenden System eine Notes-Installation zur Verfügung steht und somit der Zugriff lokal erfolgen kann – Sie werden sehen, dass aus Sicht eines Domino oder Anwendungsserver auch Clientzugriffe lokal abgebildet werden können –, oder ob über DIIOP (Domino IIOP) Daten auf einem Remote-System abgefragt oder geändert werden sollen.
4.2
Vorbereitungen
Um allein stehende Java-Anwendungen erfolgreich mit Domino zu verbinden sind einige Setup-Voraussetzungen zu gewährleisten. Zunächst wird das Java JDK in Version 1.3.1 [Sun, JDK] benötigt. Diese ist zwar inzwischen nicht mehr allerneueste Version des Java Development Kits, aber die Basis der Java-Komponenten für Domino. Das JRE von Domino kann nicht ausgetauscht werden. Da IBM als Entwickler der Domino-Plattform sein eigenes Java Runtime JRE auf Basis von Suns Original entwickelt, werden auch die IBM-Produkte mit diesen JREs ausgeliefert. Domino unterstützt in der Version 6.5.x das JRE 1.3.1 und in der Version 7 das JRE 1.4.2 (von IBM), so dass unbedingt auch das extern verwendete JDK, mit dem auf die Domino-Core-Klassen zugegriffen wird, in dieser Version vorliegen sollte. Selbst beim Betrieb eines externen Anwendungsservers sollten Sie auf Übereinstimmung der Versionen achten, um Konflikte und unerwartete Ergebnisse zu vermeiden. Dann benötigen Sie natürlich einen Notes Client und später einen Domino-Server. Sie können eine Testversion bei IBM im Entwicklerbereich developerWorks ® downloaden [developerWorks]. Installieren Sie den Notes Client entsprechend den Anweisungen des Installers. Verwenden Sie am besten die Standardeinstellungen und -pfade. Am einfachsten ist es, wenn Sie für die Ausführung und zum Testen Ihres Codes das JRE verwenden, das mit Ihrem Notes Client installiert wird. Es befindet sich per Default im Verzeichnis C:\lotus\notes\jvm. Ab Domino Version 7 kommt die Java Virtual Machine 1.4.2 zum Einsatz.
118
4 Java-Anwendungen @ Domino
Nach der Installation des JDK muss sichergestellt werden, dass der CLASSPATH und die JAVA_HOME-Variable im System richtig gesetzt ist. Der CLASSPATH des JDK muss, neben möglicherweise bereits eingetragenen Pfaden, den Pfad zu den Domino-Java-Klassen enthalten. Für lokale Client-Zugriffe ist dies die Jar-Datei Notes.jar, die sich für eine Standardinstallation (für Windows32) im Pfad C:\lotus\ notes\ und seit Domino Version 7 im Pfad C:\lotus\notes\jvm\lib\ext befindet6. Um später für „Remote“ Zugriffe gerüstet zu sein, muss hierfür die Datei NCSO.jar7 eingebunden werden, die sich im Pfad C:\lotus\notes\data\domino\java\ befindet. Seit Domino Version 7 ist diese Jar-Datei überflüssig. Auch für Remote-Zugriffe wird die Datei Notes.jar verwendet. Um unerwartete Ergebnisse zu vermeiden, sollten sich die Dateien Notes.jar und NCSO.jar nicht gleichzeitig im Classpath befinden. Beide enthalten die jeweiligen Implementationen lotus.domino.local.* (lokale Aufrufe) und lotus.domino. cso.* (Remote-Verbindungen) der Basis-Interfaces aus lotus.domino.*8, auf denen Domino aufbaut. Anhand der verschiedenen Signaturen der Methode(n) createSession entscheidet Domino, welche Implementierung verwendet werden muss. Die JAVA_HOME-Variable muss den Installationspfad des JDK enthalten, am besten also den Pfad zur JVM des Notes Clients. Damit Sie in Ihrer Entwicklungsumgebung arbeiten können, muss nun noch der PATH gesetzt werden. Zunächst wird hier der Eintrag für die Java Binaries benötigt, der in unserem Beispiel C:\lotus\notes\ jvm\bin lautet. Außerdem muss noch der Pfad zu den Notes Binaries (für lokale Verbindungen) eingetragen werden9 (bei Remote-Verbindungen müssen auf dem Server diese Pfade entsprechend gesetzt sein – s. Kap. 5.3.1), die sich im Standardfall im Verzeichnis C:\lotus\notes befinden. Dies hat seine Ursache in der Architektur der Java-Core-Klassen von Domino. Diese Java-Klassen sind lediglich Wrapper-Klassen, die über „native“ Referenzen auf die maschinenspezifischen C++ Klassen von Notes und Domino zugreifen. Diese Bibliotheken sind übrigens dieselben, auf die LotusScript zugreift, d.h. letztendlich ist die Verwendung der Domino-Klassen nicht nur ähnlich zu der Verwendung in LotusScript – so haben fast alle LotusScript Klassen einen Repräsentanten in Java und beide haben in der Regel eine äquivalente Signatur –, sondern faktisch rufen beide denselben C++ Code auf. 6
7
8
9
In einem IDE wie Eclipse können Sie festlegen, mit welchem Java Runtime Environment (JRE) ihr Code ausgeführt wird. Es hat sich bewährt, hierfür direkt auf das JRE des Notes Clients oder des Domino-Servers zurückzugreifen. Da in Version 7 die Domino-Bibliotheken der Notes.jar sich im Extentions-Verzeichnis des JRE von Domino befinden, muss dann noch nicht einmal der ClassPath auf die Notes.jar gesetzt werden, da diese Bibliothek dann automatisch als Extention geladen wird. Das Package NCSOW.jar wurde ab Domino R5.0.4 anstelle von NCSO.jar verwendet, um in einer WebSphere-Umgebung IIOP-Aufrufe zu Domino zu machen. Seit Version R6 wird dieses Package nicht mehr benötigt. Das Package Notes.jar enthält aus Gründen der Abwärtskompatibilität neben dem Package lotus.domino* noch das bis Domino Version 4.6 gebräuchliche Package lotus.notes.*. Ab Domino R7 gibt es nur noch ein Package, das Package Notes.jar, das alle benötigten Klassen und Implementierungen enthält. Eine typische Fehlermeldung, sind diese Pfade nicht richtig gesetzt, ist „java.lang.UnsatisfiedLinkError: no nlsxbe in java.library.path“.
4.3 Lokale Notes Session
119
Eine typische Konfiguration finden Sie in Tabelle 4-1. Möchten Sie lokale Zugriffe auf einem Server ausführen (z.B. über ein Servlet, das im Domino Servlet Manager ausgeführt wird), müssen diese Pfade entsprechend Tabelle 4-2 angepasst werden. Auf Verbindungen zwischen einem lokalen Java Client – unabhängig von einer Notes-Client-Installation – zu einem Domino-Server wird in Kapitel 5.3.2 näher eingegangen.
4.3
Lokale Notes Session
Domino ist eine typische Clien- Server-Anwendung, bestehend aus dem DominoServer und dem Lotus Notes Client. Zusätzlich erlaubt die Architektur von Domino lokale Datenbanken, die direkt vom Notes Client – oder z.B. von einem lokalen JavaProgramm – geöffnet werden können, ohne dass ein Server involviert ist. Bei solchen lokalen Zugriffen gelten besondere Regeln, insbesondere: Tabelle 4-1 Systemvariablen für lokale Domino-Verbindungen Variable
Wert
CLASSPATH CLASSPATH ab Version 7 JAVA_HOME PATH (auf einer Notes Client Installation)
C:\lotus\notes\Notes.jar; C:\lotus\notes\jvm\lib\ext\Notes.jar; (*) C:\lotus\notes\jvm C:\WINDOWS\system32;C:\ WINDOWS;C:\lotus\notes\jvm\bin;C:\ lotus\notes;
Tabelle 4-2 Systemvariablen für lokale Domino-Verbindungen auf einem Server Variable
Wert
CLASSPATH CLASSPATH ab Version 7
C:\lotus\domino\Notes.jar; C:\lotus\domino\jvm\lib\ext\Notes.jar; (*) C:\lotus\domino\jvm C:\WINDOWS\system32;C:\ WINDOWS;C:\lotus\domino\jvm\ bin;C:\lotus\domino;
JAVA_HOME PATH (auf einer Domino-Server-Installation) *
Nicht erforderlich, falls das JRE von Domino genutzt wird, z.B. beim Einsatz von Servlets im Domino-Servlet Container. Jar-Dateien im Verzeichnis ext der JVM werden automatisch geladen.
120
•
•
•
4 Java-Anwendungen @ Domino
Lokale Zugriffe können nur ausgeführt werden, wenn auf dem Computer eine Installation eines Notes Clients oder eines Domino-Servers vorliegt (daher die Notwendigkeit, den Pfad der Notes oder Domino-Binaries in die Systemvariable PATH einzubinden). Lokal geöffnete Datenbanken werden exklusiv gesperrt. Wenn Sie also z.B. mit Ihrem Notes Client eine Datenbank öffnen, wird ein auf demselben Computer gestartetes eigenständiges Java-Programm diese Datenbank solange nicht öffnen können, bis der Client sie wieder freigegeben hat. Lokale Datenbanken können unabhängig von der ACL (Zugriffskontrollliste) geöffnet werden. Benutzerrollen werden nicht berücksichtigt. Durch Einschalten der so genannten lokalen Sicherheit oder durch Verschlüsselung sind Datenbanken auch im lokalen Zugriff geschützt und können nur durch eine berechtigte ID und das zugehörige Passwort geöffnet werden.
Eine Notes Session kapselt eine Notes-Verbindung. Für eine lokale Notes Session repräsentiert das Session-Objekt den Computer, auf dem der Java-Code ausgeführt wird. Eine lokale Notes Session ist nicht auf den Zugriff lokaler Datenbanken beschränkt, sondern kann Verbindungen zu Computern herstellen, die mit dem Computer, auf dem die Session eröffnet wurde, verbunden sind, ähnlich dem Notes Client, der mit seiner lokalen Session Verbindungen zu entfernten Systemen aufbaut. Eine lokale Session kann nur auf Maschinen erstellt werden, auf denen eine Notes-Installation vorliegt. Sie nutzt alle (!) Ressourcen und (!) Einstellungen dieser lokalen Installation. Hierbei gilt insbesondere: •
Die lokale Notes Session nutzt die Einstellungen in der lokalen Notes-INI-Datei.
Die lokale Notes Session (öffnet Remote-Datenbanken) Eine lokale Notes Session benötigt eine Client- oder Server-Installation Eine lokale Notes Session kann sowohl lokale Datenbanken, als auch Datenbanken auf entfernten Servern, die mit der lokalen Maschine verbunden sind, öffnen. Die lokale Notes Session kann nur auf Computern erzeugt werden, auf denen eine Notes-Client- oder Domino-Server-Installation vorliegt. Wird mit einer lokalen Notes Session eine lokale Datenbank geöffnet, kann diese mit vollen Rechten geöffnet werden, sofern keine lokale Sicherheit oder Verschlüsselung aktiviert ist. Auch eine lokal aufgebaute Notes Session kann eine Verbindung zu einem entfernten Server aufnehmen
4.4 Java und Domino
121
Die lokale Session nutzt die in dieser INI-Datei festgelegte ID-Datei10 und meldet den Benutzer mit dem hier gespeicherten Namen an. Beim Zugriff auf entfernte Server und Datenbanken muss ein Passwort für den angemeldeten Benutzer übermittelt werden, es sei denn, der Server und die Datenbank sind für den anonymen Zugriff freigegeben. Die lokale Datenbank names.nsf, die alle lokalen Einstellungen, wie z.B. Verbindungsdokumente zu entfernten Servern enthält, ist die Basis lokal erzeugter Sessions. Wird z.B. in einem solchen Verbindungsdokument festgelegt, dass der entfernte Server „Produktion01/WEBDOMAIN“ die IP 100.200.100.200 hat, so kann eine lokale Notes Session diesen Server unter seinem Namen „Produktion01/WEBDOMAIN“ ansprechen. Bei der Herstellung der TCP/IP-Verbindung (auf Port 1352), kann die IP anhand dieses Verbindungsdokuments aufgelöst werden. Hierfür ist kein DNS-Eintrag oder ein Hostfile-Eintrag notwendig.
• •
•
Eine lokale Notes Session kommt also immer dann zum Einsatz, wenn auf dem Computer, auf dem die Java-Anwendung zum Einsatz kommen soll, eine Notes-Installation vorliegt. Dies kann entweder ein Notes Client oder aber auch ein DominoServer sein. Eine typische Anwendung für eine lokale Notes Session auf einem Server ist ein Servlet, das eine Verbindung zu Domino auf demselben Server aufbaut.
4.4
Java und Domino
Java-Programme verwenden das Paket lotus.domino.*, um eine Session zu erhalten und um die Domino-Objekte instanzieren zu können. Anwendungen mit lokalen Zugriffen verwenden den NotesThread,
10
Lotus Domino nutzt für die Verwaltung und Sicherung von Benutzerzugriffen eine Private-Public-Key-Infrastruktur, vergleichbar z.B. mit PGP. Die privaten Schlüssel werden in so genannten ID-Dateien gespeichert, die über ein Passwort geschützt sind. In dieser Datei ist ebenfalls der kanonische Name des zugehörigen Benutzers gespeichert. In einer lokalen Session kann ein Benutzer sich nur mit dieser ID-Datei mit seinem kanonischen Namen authentifizieren, bzw. sich als anonymer Benutzer – sofern solche am Server zugelassen werden – anmelden. Der kanonische Name hat (in der Regel) die Form „Vorname Nachname/Unterorganisation/Organisation“, wobei die Nomenklatura von Domino erlaubt, anders als z.B. im LDAP, die Bezeichner „CN=“, „OU=“ und „O=“ wegzulassen. Alle Benutzer sind in Domino im so genannten Domino-Verzeichnis – auch Notes-Adressbuch genannt – aufgeführt. Im Domino-Verzeichnis werden die öffentlichen Schlüssel der Benutzer verwaltet. Um Namensfälschungen zu vermeiden kann am Server erzwungen werden, dass die öffentlichen Schlüssel zwischen ID-Datei und Domino-Verzeichnis verglichen werden. Die lokale ID-Datei kann neben den privaten Schlüsseln auch noch andere Verschlüsselungsschlüssel enthalten.
122
4 Java-Anwendungen @ Domino
Abb. 4-1 Domino-Java-Anwendung als Agent, NotesThread oder Runnable
•
durch Erweiterung von NotesThread, seinerseits eine Erweiterung von
• •
durch Implementierung von Runnable durch Verwendung der statischen Methoden sinitThread und stermThread in NotesThread im Sonderfall eines Agenten, der im Domino Designer erstellt wurde, durch Erweiterung von AgentBase
java.lang.Thread
•
Eine Übersicht über diese Anwendungsfälle zeigt Abb. 4-1 als (vereinfachtes) Klassendiagramm. Der Domino-Agent DominoJavaAgent wird als Erweiterung von AgentBase erstellt. Sein Einstiegspunkt ist die Methode NotesMain(), die beim Start des Agenten (dies wird durch Domino oder eine Benutzerinteraktion innerhalb von Domino veranlasst) aufgerufen wird. Eine alleinstehende Java-Anwendung wird über deren main() Methode gestartet und muss die Methoden sinitThread und stermThread in der Klasse NotesThread verwenden, die wiederum Thread erweitert. Die Klassen ExtendedNotesThread und ImplementedRunnable sind Beispiele für die Erweiterung von NotesThread oder Implementierung von Runnable, die folglich über den Einstiegspunkt main() und die Methoden runNotes(), bzw. run() gestartet werden.
4.5 Statische Notes Threads
123
Diese Anwendungen beziehen ihre Notes Session über die NotesFactory mit den Methoden createSession oder createSessionWithFullAccess, die in verschiedenen Signaturen zur Verfügung stehen. Ein Agent in einer Domino-Datenbank erweitert die Klasse AgentBase, selbst eine Erweiterung von NotesThread. Ein Notes Agent bezieht seine Session über AgentBase.getSession(). Ein Applet (nicht in der Abbildung) erweitert die Klasse AppletBase. Es bezieht seine Session durch AppletBase.openSession(). Anwendungen, die Remote-Verbindungen mit DIIOP herstellen, verwenden die Klasse NotesThread nicht. Grundsätzlich ist es so, dass der Aufbau einer lokalen Notes Session immer auch auf einen Thread angewiesen ist, denn die Basis dieser Session ist eben die Klasse NotesThread und somit auch Thread. Letzendlich fügt NotesThread dem Thread zusätzliche Fähigkeiten hinzu, die es ihm ermöglichen als Notes Session zu fungieren. Dies wird entweder über den klassischen Weg erreicht, indem Runnable implementiert wird (s.u.) oder durch die sehr einfach zu verwendenden statischen Methoden sinitThread und stermThread, die gewissermaßen ihr Zauberpulver über den ohnehin schon laufenden Thread werfen und diesen zum NotesThread „erweitern“. Ohne Thread jedoch geht es in keinem Fall.
4.5
Statische Notes Threads
Die einfachste Methode einen Java Thread zu erzeugen sind die statischen Methoden sinitThread und stermThread aus NotesThread. Sie werden immer dann verwendet, wenn aus dem bereits laufenden Thread heraus Domino-Objekte referenziert werden müssen, wenn also weder Runnable implementiert noch NotesThread erweitert werden kann. Diese Methoden werden also dazu verwendet innerhalb einer Java-Anwendung einen einzigen NotesThread zu erzeugen, genauer gesagt wird durch die Verwendung der statischen Methoden der laufende Thread in einen „Pseudo“-NotesThread umgewandelt. Diese Art der „statischen“ Threadinitialisierung kann nur einmal (zeitgleich) pro Thread verwendet werden. Mit Hilfe von sinitThread kann also während eines laufenden (nicht Notes) Threads Notes initialisiert werden, so dass eine Session bezogen und auf Domino-Objekte zugegriffen werden kann. Werden keine Domino-Objekte mehr benötigt, muss stermThread aufgerufen werden. Die beiden statischen Methoden sinitThread und stermThread müssen immer paarweise verwendet werden. Die Ausführung von stermThread muss auch nach eventuellen Exceptions gewährleistet sein, d.h. es muss in einem finally-Block untergebracht werden. Listing 4-1 zeigt ein einfaches Beispiel für eine alleinstehende Java-Anwendung, die einen NotesThread öffnet und einige Operationen auf einer Notes-Datenbank ausführt. Jede Java-Anwendung, die auf die Java-Objekte von Domino zugreifen möchte, muss das Package lotus.domino.* importieren.
124
4 Java-Anwendungen @ Domino
package djbuch.kapitel_04; import lotus.domino.*; public class LocalNotes { public static void main(String argv[]) { Session session = null; Database db = null; try { NotesThread.sinitThread(); //Login als lokaler default User, entsprechend der //Eintragung in der notes.ini session = NotesFactory.createSession(); db = session.getDatabase(null, "log.nsf"); printInfo(db); printInfo(session); } catch (NotesException e) { e.printStackTrace(System.err); } finally { try { if (db != null) {db.recycle();} if (session != null) {session.recycle();} } catch (NotesException e) { System.err.println("Fataler Fehler."); } NotesThread.stermThread(); } } private static void printInfo(Session s) throws NotesException { System.out.println("SessionClass\t= " + s.getClass().getName()); System.out.println("Platform\t\t= " + s.getPlatform()); System.out.println("Version\t\t\t= " + s.getNotesVersion()); System.out.println("User\t\t\t= " + s.getUserName()); System.out.println("Servername\t= \""+s.getServerName()+"\"\n"); } private static void printInfo(Database db) throws NotesException { if (!db.isOpen()) {db.open();} if (!db.isOpen()) { System.out.println("Database could not be opened."); return; } System.out.println(db.getFilePath()); System.out.println ("Die Datenbank " + db.getTitle() + " befindet sich auf \"" + db.getServer() + "\""); DocumentCollection dc = db.search("@isavailable (Form)"); System.out.println("Es wurden " + dc.getCount() + " Dokumente mit dem Feld \"Form\" gefunden."); System.out.println(db.getACL().getFirstEntry() .getName() + " " + db.getACL().getNextEntry().getName()); } }
Listing 4-1 Lokale Session mit statisch erzeugtem Thread
4.5 Statische Notes Threads
125
Bei der Verwendung der statischen Methoden sinitThread und stermThread wird zunächst die Methode sinitThread aufgerufen, um einen (Pseudo-) NotesThread zu erzeugen. Es empfiehlt sich dringend, wie in dem Beispiel gezeigt, diese Methode in einem try catch-Block auszuführen und in dem zugehörigen finally-Block den Thread durch stermThread wieder zu schließen. Hierdurch wird sichergestellt, dass der Notes Thread in jedem Fall wieder geschlossen wird. Zu jedem sinitThread muss genau ein stermThread ausgelöst werden. Die Klasse lotus.domino.Session ist die Basis aller Domino-Objekte, die entweder von der Session abgeleitet oder über diese erhalten werden können. Eine Session kann im Falle der Verwendung des statischen Threads über die NotesFactory mit createSession bezogen werden. Wird createSession ohne Parameter aufgerufen, wird der in der Datei notes.ini11 verzeichnete lokale Benutzername für den Zugriff verwendet. Ein Passwort ist für lokale Datenbanken solange nicht nötig, wie sie weder verschlüsselt sind noch die so genannte lokale Sicherheit aktiviert ist. Wird aus welchen Gründen auch immer der Zugriff auf eine Datenbank verweigert, fordert Domino (über den stdout) zur Eingabe eines Passwortes auf. Eine lokale Session kann Objekte auf entfernten Servern, die vom lokalen Computer aus erreichbar sind, öffnen, dies kann jedoch nur mit einer Session erfolgen, die durch einen authentifizierten Benutzer geöffnet wurde12. Um dem Beispiel ein wenig mehr Lebensbezug zu geben wird eine Datenbank geöffnet und einige Daten ausgelesen. Die Methode getDatabase in Session ist eine der Möglichkeiten eine Datenbank zu öffnen. Der erste Parameter „(String) null“ gibt an, dass die Datenbank im Environment der Session, also auf dem lokalen Computer geöffnet werden soll. Der zweite gibt den Pfad zu der Datenbank (einschließlich Subverzeichnissen) an. Beachten Sie, dass auch in einem Windows-Environment die Pfadnamen mit einem Vorwärts-Schrägstrich „/“ getrennt werden (können) 13 14.
11 12 13
14
Im Notes-„Data“-Pfad, bei einer Windows-Standardinstallation unter C:\Lotus\Notes\ data\notes.ini. Im selten anzutreffenden Fall, dass ein Domino-Server und die gewünschte Datenbank für den anonymen Zugriff geöffnet wird, wird kein authentifizierter Benutzer benötigt. Beim Öffnen einer Datenbank kann sowohl der Slash als auch der Backslash verwendet werden. Wird der Pfadname aber aus dem Datenbankobjekt mit getFilePath ausgelesen, wird er Betriebssystem-konform angezeigt. In einem Unix Environment sind die Angaben für Name und Pfad einer Datenbank grundsätzlich nicht case-sensitiv, allerdings wird dies dadurch erreicht, dass der Server intern die Pfad-Namen in Kleinschreibung übersetzt. Während und kurz nach dem Hochfahren eines Domino-Servers wurde daher wiederholt beobachtet, dass es eine kurze Phase gibt, in der Pfadnamen dennoch case-sensitiv behandelt werden. Gleiches gilt für URL, mit denen Datenbanken im Browser angesprochen werden können.
126
4 Java-Anwendungen @ Domino Auch in diesem Beispiel wurde das bereits bekannte Konstrukt für das Recycling verwendet. Beachten Sie, insbesondere die Session zu recyclen, um die geöffnete Serververbindung und die Serverressourcen wieder freizugeben. Die Session darf erst recycled werden, wenn sie nicht mehr benötigt wird. Sie sollte unmittelbar vor dem Beenden durch stermThread recycled werden. Fast alle Methoden im Package lotus.domino.* können eine NotesException werfen. Neu entwickelte Methoden, die von Notes-Objekten Gebrauch machen, werden der Einfachheit halber in der Regel ebenfalls eine NotesException werfen, es sei denn, diese muss ausdrücklich abgefangen werden. Die Session kennt viele Methoden zur Anzeige verschiedener Environment-Eigenschaften. Interessant ist die Anzeige des Klassennamens mit s.getClass().getName(). Das Ergebnis dieser Ausgabe ist nicht etwa lotus.domino.Session, sondern lotus.domino.local.Session. Die NotesFactory hat automatisch die Implementierung für die lokale Session zurückgeliefert, da createSession ohne Parameter aufgerufen wurde. Datenbanken, die mit getDatabase bezogen werden, werden normalerweise direkt geöffnet. Über die Methode isOpen kann überprüft werden, ob dies erfolgreich war. Die Ausgabe von db.getServer () liefert den leeren String "", da es sich um eine lokale Datenbank handelt. Lokale Datenbanken (und Server) werden in Domino per Konvention als leerer String dargestellt15. Dokumente in einer Datenbank können z.B. mit search gefunden werden, hier als Nachweis, dass tatsächlich eine Notes-Verbindung zu einer Datenbank besteht und Daten ausgelesen werden können. Auch wenn die Datenbank lokal geöffnet und ohne eine Authentifizierung geöffnet wurde, hat das geladene Datenbank-Objekt eine ACL (Zugriffskontrollliste), von der im Beispiel zu Anschauungszwecken die Namen der ersten beiden Einträge angezeigt werden.
4.6
Session im statischen Thread
Eine lokale Session wird, wie eben beschrieben, mit createSession () geöffnet. Dies ist gleichbedeutend mit createSession (null, null, null). In diesem Falle wird die Session zunächst ohne Authentifizierung geöffnet. Sobald ein Passwort für eine Zugriffsberechtigung benötigt wird, wird zur Eingabe aufgefordert, wobei dies in den meisten Fällen problematisch im Programmverlauf abzufragen ist. Daher wird für den Aufbau einer lokalen Session der Aufruf createSession ((String) null, (String) null, String passwort) bevorzugt, bei dem direkt das Passwort für das lokal verwendete ID-File angegeben wird. Ein Passwort müssen Sie insbesondere dann zur Verfügung stellen, wenn mit der lokalen Notes Session eine Verbindung zu einem entfernten Server hergestellt werden soll. 15
Bis auf die Ausnahme, dass für Remote-Serververbindungen hier zum Teil (String) null verwendet werden muss.
4.7 NotesThread erweitern
127
Eine wichtige Sicherheitsfunktion von Domino sind die so genannten Leser- und Autorenfelder (s. Kap. 7.1ff.), mit denen lesender oder schreibender Zugriff auf Dokumentenebene gesteuert wird. Dies kann (in der Regel für administrative Zwecke) seit Version 6 umgangen werden, indem ein erweiterter Zugriff mit createSessionWithFullAccess analog zu createSession geöffnet wird. Analog zu createSession ((String) null, (String) null, String passwort) kann createSessionWithFullAccess (String passwort) verwendet werden. Auf einem Domino-Server – also in einem Environment unter dem ein Domino Directory zur Verfügung steht – kann die lokale Session für einen bestimmten Benutzer mittels createSession (null, String benutzer, String passwort) oder für einen anonymen Benutzer mit createSession (null, "", "") geöffnet werden. Diese Session wird auch als Internet Session bezeichnet – nicht zu verwechseln mit einer HttpSession, wie es z.B. das Servlet Framework versteht.
4.7
NotesThread erweitern
Wenn das Design einer Anwendung es zulässt, kann ein NotesThread und somit eine Session durch das Erweitern der Klasse NotesThread erreicht werden (Listing 4-2). Der Einstiegspunkt für den start() des Threads ist die Methode public runNotes() . Ein Generieren eines Threads mit Hilfe von sinitThrepackage djbuch.kapitel_04; import lotus.domino.*; public class ExtendedNotesThread extends NotesThread { public static void main(String argv[]) { ExtendedNotesThread t = new ExtendedNotesThread(); t.start(); t.join(); } public void runNotes() { Session session = null; Database db = null; try { session = NotesFactory.createSession(); db = session.getDatabase(null, "log.nsf"); printInfo(db); printInfo(session); } catch (NotesException e) { e.printStackTrace(System.err); } finally { try { if (db != null) {db.recycle();} if (session != null) {session.recycle();} } catch (NotesException e) { System.err.println("Fataler Fehler."); } } } }
Listing 4-2 Lokale Session durch Erweitern von NotesThread
128
4 Java-Anwendungen @ Domino
ad ist hier nicht nötig. Der Thread wird beim Erzeugen des den NotesThread erweiternden Objektes – im Beispiel die Klasse ExtendedNotesThread – erzeugt. Ansonsten ist der Code innerhalb von runNotes identisch mit dem Code aus main im Beispiel aus Listing 4-1. Insbesondere gilt auch hier analog das Gesagte über die verschiedenen Möglichkeiten eine Session zu erhalten. Beachten Sie, dass nach dem Recycling der Datenbank und der Session diese nicht mehr verwendet werden dürfen. Ein Schließen des Threads durch stermThread ist hier nicht notwendig. Grundsätzlich wird es über diese Methode ermöglicht, multithreaded Notes-Agenten zu programmieren. Hierbei sind besondere Regeln zu beachten. (s. Kap. 13.3.7f).
4.8
Runnable implementieren
Um einen NotesThread zu öffnen, indem das Interface Runnable implementiert wird, wird ähnlich wie zuvor verfahren. Der Einstiegspunkt für den Thread ist in diesem Fall die Methode public run () (Listing 4-3). Welche der drei aufgeführten Methoden jeweils zum Einsatz kommt, hängt von den jeweiligen Gegebenheiten ab, unter denen eine Notes Session benötigt wird. In der Regel wird immer die Implementierung von Runnable oder die Erweiterung von NotesThread der Weg erster Wahl sein; welche dieser beiden zum Einsatz kommt, ist Geschmackssache, wobei natürlich die Implementierung von Runnable ausfällt, wenn ein weiteres Interface implementiert werden muss. public class ImplementedRunnable implements Runnable { public static void main(String argv[]) { ImplementedRunnable t = new ImplementedRunnable(); NotesThread nt = new NotesThread((Runnable) t); nt.start(); nt.join(); } public void run() { ... } }
Listing 4-3 Lokale Session durch Implementation von Runnable
4.9
Domino-Java-Applets
Applets, am weitesten verbreitet als eingebettete Anwendungen im Browser, können auch in Domino-Anwendungen eingebunden werden. Im Domino Designer werden
4.9 Domino-Java-Applets
129
ssie in Masken und Seiten eingebunden und können über ein entsprechendes GUI auch in Dokumenten (im RichText) verwendet werden (was aber eher selten der Fall sein wird). Besonders interessant ist die Verwendung eingebetteter Applets in Seiten oder Masken, wenn Anwendungen sowohl im Notes Client als auch im Browser Verwendung finden sollen. Java-Applets in Domino basieren auf der Klasse AppletBase, die die Klasse java.applet.Applet erweitert oder auf der Klasse JAppletBase, die die Klasse javax.swing.JApplet erweitert, wenn mit Swing gearbeitet werden soll, so dass Domino-basierte Applets im Wesentlichen wie gewohnt programmiert werden können. Lediglich beim Bezug der Session sind einige Besonderheiten zu beachten. Auch Applets können ihre Session entweder lokal oder remote aufbauen, wobei eine lokale Session immer bei der Verwendung von Applets im Notes Client oder in einem Applet Viewer zur Anwendung kommt. Bei der Verwendung im Browser wird eine Remote-Session (über DIIOP) vom Browser zum Server aufgebaut16. Wegen dieser Zwitterbenutzung der AppletBase wird hier schon in einer kleinen Vorwegnahme auf DIIOP-Verbindungen17 eingegangen. Praktischerweise wird die Entscheidung, um welche Art des Verbindungsaufbaus es sich handelt, durch die (J)AppletBase18 getroffen, so dass eine Session immer durch getSession in AppletBase bezogen werden kann. getSession existiert in den Signaturen getSession () und getSession (String benutzer, String passwort), wobei letztere nur in Remote-Verbindungen per DIIOP verwendet werden kann. Parallel zu getSession existiert die Methode openSession mit denselben Signaturen (s.u.). Die AppletBase erweitert die Klasse Applet und definiert zunächst folgende Methoden: • • • • 16
17
18
notesAppletInit () notesAppletStart () notesAppletStop () notesAppletDestroy () Theoretisch wäre es möglich mit einem Applet eine lokale Session aufzubauen, wenn auf dem Computer, auf dem der Browser läuft, in dem das Applet angezeigt wird, eine NotesInstallation vorliegt. Hierfür müsste aber der Security Context im Browser für das (unsignierte) Applet so eingestellt werden, dass die Ausführung lokal gestattet wird, und das Applet müsste Zugriff auf die benötigten Ressourcen, also im Wesentlichen auf die Binaries und dlls der NotesInstallation aus dem Path (von Notes) erhalten. Mindestvoraussetzung für die Verwendung des Beispiels im Browser ist, dass auf dem verwendeten Domino-Server der DIIOP Task läuft. Sie können diesen entweder durch „show tasks“ in der Console anzeigen lassen oder durch „load diiop“ laden. Zusätzlich sollte der für die Verbindung verwendete Benutzer Zugriff auf den Server haben und im Personendokument ein so genanntes Internetpasswort vergeben sein. Die Beispiel-Datenbank muss diesem Benutzer ebenfalls den Zugriff erlauben und darf in den erweiterten Einstellungen der ACL keine zu niedrige Einstellung für den maximalen Internetzugriff haben (s. Kap. 5.3.1. Im Folgenden wird nur von AppletBase gesprochen, auch wenn das Gesagte immer auch für JAppletBase gilt.
130
4 Java-Anwendungen @ Domino
Diese sind natürlich entsprechend den in Applet verwendeten Methoden init, start, stop und destroy zu verwenden und reagieren auf die entsprechenden Events. Die Methoden init, etc. selbst sind in AppletBase als final definiert, damit diese nicht versehentlich überimplementiert werden können. Listing 4-4 zeigt ein einfaches Gerüst eines Applets, mit dem einige Informationen über die Datenbank und den angemeldeten Benutzer angezeigt werden. Wie eingangs erwähnt erweitert das Beispiel die AppletBase und überschreibt zwei der vier Standard-Methoden der AppletBase. Die meisten fields der Klasse sind für den Aufbau der AWT-Grafiken notwendig und werden klassisch im notesAppletInit initialisiert. Für den Aufbau von grafischen Oberflächen ist es auch im Applet wichtig, dass die benötigten Objekte vor dem eigentlichen Start und der eigentlichen Darstellung des Applets initialisiert werden können – diese Arbeiten sind gut in der Methode notesAppletInit aufgehoben, bzw. ursprünglich natürlich der Methode init aus Applet. Im Notes Applet kann wie im „normalen“ bezogen Applet das Basis Window von AWT mit der Methode getGraphics werden, so dass dann die AWT-Objekte einfach erstellt werden können. Die Methode notesAppletStart wird bei jedem Laden des Applets aufgerufen, auch z.B. wenn das Applet-Fenster in einem Applet Viewer minimiert war und wieder sichtbar geöffnet wird. Folglich ist der gewählte Ansatz, die Notes Session im notesAppletStart zu öffnen und wieder zu schließen, sicher nicht für jede Anwendung geeignet. Letztendlich wird für jeden Anwendungsfall zu entscheiden sein, ob die Notes Session für den gesamten Lebenszyklus des Applets aufrecht erhalten werden soll und somit über notesAppletInit und notesAppletDestroy auf- und abgebaut werden soll oder ob dies für jeden Start des Applets über notesAppletStart und notesAppletStop erfolgen soll. Letztere Lösung ist in der Regel zu bevorzugen, da sie weniger Ressourcen blockiert, dafür aber etwas mehr Kosten beim Auf- und Abbau der Session produziert. Gleich im folgenden Beispiel (s. Listing 4-5) wird deutlich werden, dass das Schließen der Session in der Regel nicht gleichzeitig mit dem openSession in notesAppletStart abgewickelt werden kann, wie im Beispiel in Listing 4-4 zunächst der Einfachheit halber umgesetzt, sondern in den notesAppletStop verlagert werden muss. Im Beispiel wird nun zunächst eine Session aufgebaut . Hierzu wurde die Methode isNotesLocal genutzt, die die Information liefert, ob das Applet lokal (im Notes Client) oder remote (als Aufruf im Web-Browser) arbeitet. An dieser Stelle wäre dies nicht zwingend erforderlich, da die AppletBase in der Methode openSession (und getSession) bereits die Entscheidung fällt. Wie die Session aufzubauen ist, bietet aber an dieser Stelle die Gelegenheit, die Remote Session (DIIOP) mit einem Funktions-User (hier „User“) aufzubauen. Wird die Methode openSession () ohne Parameter verwendet, wird eine anonyme DIIOP-Verbindung aufgebaut. Umgekehrt könnte für den lokalen Fall die Methode openSession (oder getSession) mit den Parametern für Benutzername und Passwort aufgerufen werden, dies hat jedoch keine Auswirkung. In diesem Fall wird immer mit dem lokalen Benutzer angemeldet.
4.9 Domino-Java-Applets
131
package djbuch.kapitel_04; import java.awt.*; import lotus.domino.*; import java.net.URLDecoder; public class AppletAwt extends AppletBase { private Graphics graphics; private Label user, localInfo, serverInfo; private String text = "", server = ""; public Session session; public void notesAppletInit() { graphics = getGraphics(); user = new Label (""); localInfo = new Label (""); serverInfo = new Label (""); add (user); add (localInfo); add (serverInfo); setVisible(true); } public void notesAppletStart() { try { if (isNotesLocal()) { session = this.getSession (); } else { session = this.getSession("User", "Passwort"); } NotesAppletContext nac=null; try { nac = getContext(session); } catch (NotesException ne) {/*ignorieren*/} server = (nac!=null?URLDecoder.decode(nac.getServer()) + "!!" + nac.getDatabase().getFilePath():""); text = "Benutzer: " + session.getCommonUserName(); serverInfo.setText (server); localInfo.setText("AWT Example: " + (isNotesLocal()?"Lokal":"Remote")); user.setText(text); } catch (NotesException e) { System.err.println (e.id + " " + e.text); } catch (Exception e) { e.printStackTrace(); } finally { try { this.closeSession(session); } catch (NotesException e) { e.printStackTrace(); } } } public void paint(Graphics g) { localInfo.setLocation(0,20); localInfo.setSize(200,50); serverInfo.setLocation(0,50); serverInfo.setSize(200,50); user.setLocation(0,80); user.setSize(200,50); } }
Listing 4-4 Einfaches Applet mit AWT
Für den Fall, dass das Applet im Browser geladen wird, ist zu bedenken, dass DIIOP immer versuchen wird sich mit demjenigen Server zu verbinden, von dem das Applet geladen wurde, da dieses ohnehin keine Berechtigung hat (als unsigned App-
132
4 Java-Anwendungen @ Domino
let) eine TCP/IP-Verbindung zu einem anderen Server zu öffnen19. Beachten Sie auch, dass die Verbindung zum Domino-Server über DIIOP in der Regel über Port 63148 geöffnet wird. Rechnen Sie damit, dass die Anwender Ihres Applets sich möglicherweise hinter einer Firewall befinden, die diesen Port nicht zulässt. Das Vorhandensein der beiden Methoden openSession und getSession bietet Gelegenheit zu Fragen nach Ähnlichkeiten und Unterschieden der beiden und dem eigentlichen Grund für diese Doppelung. Dieser ist nicht dokumentiert und die scheinbar zufällige Verwendung der beiden Methoden in der Notes-Hilfe macht das Verständnis nicht einfacher, aber die Erklärung ist recht schnell gefunden. openSession kapselt einen Aufruf der Methode getSession, führt zuvor jedoch noch einen internen Aufruf von sinitThread aus, der aber in der aktuellen Implementation der Domino-Java-Klassen offenbar ohne Wirkung bleibt. Folglich kann zusammengefasst werden: Die Verwendung von openSession und getSession ist gleichbedeutend. Da an vielen anderen Stellen ohnehin die Methode zum Bezug der Session getSession lautet, ist zu empfehlen diese Methode auch hier zu verwenden. Gleiches gilt im Übrigen für closeSession – diese Methode kapselt ein recycle der Session. Folglich ist bei der Verwendung von closeSession die gleiche Vorsicht geboten wie bei der Verwendung von recycle. Die Verwendung dieser Methoden ist notwendig, unterliegt aber strengen Regeln. Im vorliegenden Beispiel wird der Aufruf von closeSession keine weiteren Nebenwirkungen20 haben, da die Session weiterhin nicht mehr benötigt wird. Im nächsten Beispiel wird ein Applet einen AWT Event verwenden. Dort muss dann berücksichtigt werden, dass die Eventhandler in einem eigenen Thread gestartet werden, so dass die Session erst im notesAppletStop wieder geschlossen werden darf (s.u.), ansonsten riskieren Sie eine NotesException, die auf eine bereits aufgeräumte Session21 hinweist. Der weitere Verlauf des Applets ist schnell erläutert. Wird das Applet im Kontext von Domino – also in einem Notes Client oder über den Browser im HTTP-Task des Domino-Servers – angezeigt, dann kann mit der Methode getContext (Session) aus AppletBase der AppletContext gezogen werden, der noch einige zusätzliche Informationen z.B. über die Datenbank, aus der das Applet geladen wurde, enthält. Da die Verwendung von IDEs, die einen Applet Viewer unterstützen, sehr verbreitet ist, wird der Aufruf von getContext in diesem Beispiel nochmals in ein eigenes try-catch-Konstrukt eingebunden, damit die Vorschau im IDE nicht zu ungewollten Exceptions führt. Im Applet Viewer kann getContext leider nicht aufgerufen 19
20 21
Für alle Ihre Applets bedeutet dies, dass Sie die Applets von derjenigen Domain oder IP abrufen müssen, an die der HTTP Task von Domino gebunden ist. Im Domino Directory (names.nsf) befindet sich in der Ansicht „Konfiguration/Server/Alle Serverdokumente“ das Serverdokument für jeden Server. Im Serverdokument findet sich in der Rubrik „Internet Protocols/HTTP/Basics/Host Name(s)“ der/die Name(n) oder IPs des Domino HTTP Tasks. Ist dieses Feld nicht ausgefüllt, bindet sich der HTTP Task an alle IPs der lokalen Maschine, was teilweise zu Schwierigkeiten führt. Statt der sonst auch üblichen Bezeichnung „Seiteneffekt“, wird das Wort „Nebenwirkung“ verwendet. Notes Exception mit der ID 4376 und der Meldung „Object has been removed or recycled“
4.9 Domino-Java-Applets
133
Abb. 4-2 Notes Applet im Notes Client
werden , da er dort nicht zur Verfügung steht. Mit getCommonUserName in Session kann der Name des Benutzers angezeigt werden, der diese gerade ausführt. Für das Beispiel ein hilfreiches Indiz, wo das Applet gerade ausgeführt wird. Im Notes Client sollte hier der Name des aktuellen Benutzers erscheinen, im Web-Browser der Benutzer, mit dem getSession die DIIOP-Verbindung geöffnet hat. In der Ausgabe wird in einem AWT Label der Server und Datenbankname, der Benutzername und mit Hilfe von isNotesLocal die Information, wie das Applet ausgeführt wird, angezeigt (s. Abb. 4-2 und Abb. 4-3). Die beiden Abbildungen zeigen drei Applets. Von
Abb. 4-3 Notes Applet im Browser
134
4 Java-Anwendungen @ Domino
links nach rechts sind dies ein Applet auf Basis von JAppletBase mit Swing und einem Eventhandler (aus Listing 4-5) , dasselbe Applet auf der Basis von AWT und ein einfaches AWT Applet ohne Eventhandler (aus Listing 4-4) Möchten Sie Eventhandler in einem Applet verwenden – und dies wird meistens der Fall sein – muss, wie bereits erwähnt, im Falle eines lokalen Aufrufs der Tatsache Rechnung getragen werden, dass ein Eventhandler in AWT oder Swing einen eigenen Thread öffnet, der mit den statischen Methoden sinitThread und stermThread in einen Pseudo-NotesThread umgewandelt werden muss. Das Beispiel in Listing 4-5 ist ein Applet, das auf Basis von Swing mittels eines Eventhandlers auf einen Button-Klick reagiert. Die Klasse des Beispiel-Applets erweipackage djbuch.kapitel_04; import java.awt.*; import java.awt.event.*; import java.net.URLDecoder; import javax.swing.*; import lotus.domino.*; public class AppletSwingEvent extends JAppletBase implements ActionListener { private private private private private private
JApplet applet; JTextPane textPane = new JTextPane(); LayoutManager springLayout; String text = ""; public Session session; int startedCount = 0; private int stoppedCount = 0; JButton showButton = new JButton();
public void notesAppletStart () { startedCount++; final JPanel panel = new JPanel(); panel.setLayout(springLayout); panel.setBackground(new Color (255,255,255)); getContentPane().add(panel); textPane.setBounds(0, 50, 250, 100); panel.add(textPane); panel.add(showButton); try { if (isNotesLocal()) { session = this.getSession (); } else { session = this.getSession("User", "Passwort"); } } catch (NotesException e) { text = e.id + " " + e.text; } catch (Exception e) { e.printStackTrace(); } setupGUI(); } public void notesAppletStop() { stoppedCount++; try { this.closeSession(session); } catch (NotesException e) { e.printStackTrace(); } } private void setupGUI() { showButton.addActionListener(this); showButton.setBounds(0, 0, 200, 30); showButton.setText("Aktuellen Benutzer anzeigen.");
4.9 Domino-Java-Applets
135
} public void actionPerformed(ActionEvent e) { try { if (isNotesLocal()) { NotesThread.sinitThread(); } NotesAppletContext nac=null; try { nac = getContext(session); } catch (NotesException ne) {/*ignorieren*/} text = ("Swing (Event) Example: " + (isNotesLocal() ? "Lokal" : "Remote")) + (nac!=null?"\nServer: " + URLDecoder.decode(nac.getServer()) + "\nDatabase: " + nac.getDatabase().getFileName():"") + "\nBenutzer: " + session.getCommonUserName() + "\nStart/Stop: " + startedCount + "/" + stoppedCount; } catch (NotesException e1) { text = e1.id + " " + e1.text; } catch (Exception e2) { e2.printStackTrace(); } finally { if (isNotesLocal()) { NotesThread.stermThread(); } } textPane.setText(text); } }
Listing 4-5 Applet mit Swing und Eventhandler (eigener Thread)
tert diesmal die Klasse JAppletBase, um Swing implementieren zu können, und implementiert einen ActionListener. Letzteres geschieht aus reiner Bequemlichkeit, um nicht eine weitere Klasse codieren zu müssen, die als ActionListener fungiert. Ist das Applet geladen, wird automatisch notesAppletInit aufgerufen, wo zunächst die benötigten Swing-Variablen initialisiert werden. Die Notes Session wird in diesem Beispiel ebenfalls an dieser Stelle auf die gleiche Weise wie im vorigen Beispiel mit getSession bezogen . Ebenfalls in dieser Methode wird der ActionListener initialisiert , der den Button-Klick erkennen wird. Hier nun ist zu beachten, dass dieser Listener in einem eigenen Thread läuft, also muss mit sinitThread und stermThread der Pseudo Notes Thread initialisiert und natürlich auch wieder aufgeräumt werden, natürlich nur für den Fall, dass das Applet lokal läuft. Wird es im Browser (also remote) betrieben, wird ohnehin nur indirekt über DIIOP mit dem Server gesprochen und die Notes Session dort, auf dem Server geöffnet, d.h. der Eventhandler läuft auf der lokalen Maschine und der NotesThread auf dem Server. Vergessen Sie nicht, stermThread in einem finally-Abschnitt unterzubringen, so dass gewährleistet ist, dass jederzeit der Thread auch wieder zurückgesetzt wird. Erfolgt dies nicht, kann es durchaus zu „maschinenweiten Hängern“ kommen, so dass das gesamte System durchgestartet werden muss, da der auf diese Weise überge-
136
4 Java-Anwendungen @ Domino
bliebene „Phantom“-Thread nicht wieder abgebaut werden kann. Dieses Applet lässt sich natürlich ohne weiteres auch ausschließlich mit AWT ohne die Verwendung von Swing realisieren. Erweitert wird in diesem Fall AppletBase statt JAppletBase. Allerdings gibt es hier noch eine kleine Besonderheit zu beachten. Offenbar wird bei der Erweiterung von AppletBase eine Session, die im notesAppletStop durch closeSession geschlossen wird, nicht abgebaut. Dies hat zur Folge, dass – im Falle, Sie öffnen die Session im notesAppletInit – für jedes neue Laden der Seite mit dem Applet im Browser eine neue Session geöffnet wird, die zunächst nicht terminiert wird. Wird nun der Browser geschlossen und die Java-Klassen durch die Garbage Collection aufgeräumt, schließt die AppletBase ordnungsgemäß alle geöffneten Sessions. Prinzipiell bleibt also zwar letztendlich keine Session offen, aber mittelfristig müssen Sie bei stärker belasteten Sites mit Engpässen rechnen. Lösen können Sie diesen Bug, indem Sie für die Erweiterung der AppletBase getSession und closeSession jeweils in notesAppletInit und notesAppletDestroy unterbringen. Soweit die Theorie. Ein Applet binden Sie z.B. in eine Seite über den Domino Designer ein und gehen dabei wie folgt vor: 1
2
3
4
5 6
Compilieren Sie den Beispielcode aus Listing 4-5 und exportieren Sie den Code als Jar-Datei als AppletSwingEvent.jar. Beachten Sie, dass im Code ein hart codierter Username und Passwort vorkommt. Dieser Benutzer muss Zugriffsrechte auf den Server und die Datenbank djbuch.nsf haben. Alternativ können Sie für beide Werte "" angeben, dann wird eine anonyme Verbindung aufgebaut. Die Kapselung in eine Jar-Datei ist notwendig, da die Beispielklasse sich nicht im Default Package befindet. Ohne die Jar-Datei würde Domino die Klasse nicht erkennen. Öffnen Sie den Domino Designer und wählen Sie „Pages“ in der Übersicht. Erstellen Sie eine neue Seite und wählen Sie im Menü „Create“ -> „Java Applet“. Es öffnet sich ein Dialogfenster. Per Default können Sie ein Applet als .class oder .jar aus dem File System wählen. Hierfür klicken Sie auf das gelbe „Ordnersymbol“ im Bereich „Location of the Applet“. Wenn Sie die compilierte und gepackte JAR-Datei aus dem Beispiel ausgewählt haben, trägt Domino den Pfad in das Dialogfenster ein. Leider gibt es einen kleinen Bug, auf Grund dessen Packages nicht erkannt werden. Daher müssen Sie den Eintrag im Feld „Base Class“ von „AppletSwingEvent.class“ auf „djbuch.kapitel_04.AppletSwingEvent.class“ korrigieren. Ein OK bestätigt die Auswahl und das Applet wird als graue Fläche angezeigt. Nach diesem ersten Schritt kann das Applet bereits in Notes angezeigt werden. Probieren Sie es aus, indem Sie der neuen Seite einen Namen (Alias nicht vergessen!) geben und das Icon „Preview in Notes“ klicken.
4.10 Notes Agent im Domino Designer erstellen 7
8
137
Um das Applet im Browser anzuzeigen, muss noch die NCSO.jar, die auf jedem Domino-Server automatisch zur Verfügung steht, als Archiv angegeben werden. Dies kann über das Kontext-Menü mit den Eigenschaften des Applets erledigt werden. Im Eigenschaften-Fenster (rechte Maustaste auf die „graue Fläche“ des Applets -> „Java Applet Properties“) gibt es eine Checkbox „Applet uses Notes Corba Classes“. Ist diese Box angehakt, wird automatisch in den Parametern für das Applet die NCSO.jar als Archiv mit angegeben. Beim Speichern des Applets führt Domino folgende Arbeiten durch: Upload der Jar-Datei AppletSwingEvent.jar in das Designdokument der neu angelegten Page. Die Jar-Datei wird als Dateianhang der Page verwaltet und gespeichert. Hieraus ergibt sich auch die codebase für den Aufruf des Applets. Sie berechnet sich als: / djbuch/djbuch.nsf/A8CF1A43FB00D75FC1256F98003C5FEF/$FILE, wobei die lange Hexadezimalzahl „A8C...“ die UniversalID der Design Note der Page ist. Zusätzlich generiert Domino automatisch den für die Anzeige im Browser benötigten embed-Code wie folgt: <param name="NOI_IOR" value="IOR:..."> <param name="NOI_COOKIE_URL" value="/djbuch/djbuch.nsf?GetOrbCookie">
Mit den Parametern NOI_IOR und NOI_COOKIE_URL wird die AppletBase mit den benötigten Initialisierungsparametern für die DIIOP-Verbindung versorgt. Beachten Sie, dass der DIIOP Task bereits vor dem Design der Page und des Applets auf dem Server gestartet sein sollte, um Probleme zu vermeiden. Selbstverständlich können Sie eine HTML-Seite mit eingebetteten Applets auch im HTML-Entwicklungswerkzeug Ihrer Wahl ganz unabhängig vom Domino Designer erstellen und die Jar-Datei des Applets als File auf dem Domino-Server ablegen. Ein Beispiel-Applet finden Sie unter dem Namen „Applet | P_Applet“ in der Beispieldatenbank. Sie können das Beispiel ansehen, indem Sie im Notes Client die Aktionen „Actions -> Kapitel 4 -> 3. Applet im Browser“ oder „Actions -> Kapitel 4 -> 4. Applet in Notes“ auswählen.
4.10
Notes Agent im Domino Designer erstellen
Einen Java-Agenten im Domino Designer direkt zu erstellen ist eine einfache Möglichkeit, Java-Programme für Domino zu entwickeln, wie bereits in mehreren Beispielen gezeigt wurde. Der wesentliche Unterschied zu einer alleinstehenden JavaAnwendung ist, dass wesentliche Ressourcen durch die Erweiterung der Klasse Agent-
138
4 Java-Anwendungen @ Domino
Abb. 4-4 Java-Agent im Domino Designer
Base zur Verfügung gestellt werden. Folgerichtig wird ein Java-Agent im Domino Designer so angelegt, dass er AgentBase erweitert. Das Grundgerüst mit dieser Erweiterung gibt Notes bereits beim Erstellen eines Agenten vor. Einen typischen Java-Agenten erhalten Sie, indem Sie im Domino Designer in der Rubrik „Shared Code“ -> „Agents“ einen neuen Agenten erstellen und in dem dann neu geöffneten Code-Fenster den Typus des Agenten auf „Java“ umschalten (Abb. 4-4). Der Einstiegspunkt für Java ist die Methode NotesMain (). Die Schreibweise mit dem großen N am Anfang ist korrekt, denn leider hat sich hier Notes nicht an die Java-Namenskonventionen gehalten. Die Session wird einfach über den Aufruf von getSession (Listing 4-6) bezogen22. Eine weitere wichtige Ressource, die nur in Java-Anwendungen, die als JavaAgenten angelegt wurden, zur Verfügung steht, ist der AgentContext . Der AgentContext bezeichnet den Kontext, in dem der Agent läuft. Hierzu gehören nicht nur die Datenbank , in der der Agent gestartet wurde (egal über welchen Event), sondern auch der effektive Benutzername , mit dessen Rechten der Agent ausgeführt wird. Ein typischer Java-Agent wird diese Objekte direkt nach dem Starten beziehen (Listing 4-6). Über den AgentContext kann auch das so genannte Kontextdokument über getDocumentContext () bezogen werden. Das Konzept des Kontextdokuments ist eine Besonderheit von Lotus Domino und sehr umfassend angelegt. Grundsätzlich gilt, dass das Kontextdokument immer das Dokument ist, in dessen Kontext eine Anwendung gerade abläuft. Wenn also, wie im Beispiel Listing 2-1, ein 22
Das Recycling für diesen Agenten entfällt, da er keine recyclingpflichtigen Elemente enthält. Alle Domino-Objekte (currentDoc, db) wurden aus dem agentContext abgeleitet. Dieser ist selbst für das Recycling verantwortlich (s. Kap. 14.2).
4.10 Notes Agent im Domino Designer erstellen
139
import lotus.domino.*; public class DominoJavaAgent extends AgentBase { public void NotesMain() { try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); Document currentDoc = agentContext.getDocumentContext(); Database db = agentContext.getCurrentDatabase(); Agent agent = agentContext.getCurrentAgent(); System.out.println ("Name des effektiven Benutzers = " + agentContext.getEffectiveUserName()); System.out.println ("ContextDocument - " + ((currentDoc == null)?" nicht vorhanden.":"Subject = " + currentDoc.getItemValueString("Subject"))); System.out.println("Datenbanktitel = " + db.getTitle()); System.out.println ("Agentenname = " + agent.getName()); System.out.println ("Letzte Ausführung: " + agent.getLastRun()); } catch(Exception e) { e.printStackTrace(); } } }
Listing 4-6 Java-Agent und AgentContext
Agent über einen Button aus einem Dokument heraus gestartet wird, dann steht über den AgentContext das Dokument zur Verfügung, das geöffnet war, als der Button geklickt wurde, mit dem der Java-Agent gestartet wurde. Ein Agent, der mit dem Target „Ausgewählte Dokumente“ startet, besitzt ebenfalls ein Kontextdokument. Welches Dokument dies ist, hängt davon ab, wie der Agent gestartet wird. Bei einem Start während ein Dokument geöffnet ist, wird das Kontextdokument auch in diesem Fall das geöffnete Dokument sein. In einer Ansicht ist das Kontextdokument für diesen Fall das aktuell markierte Dokument, wobei zu beachten ist, dass Dokumente auf zweierlei Art markiert sein können. Zum einen gibt es links der Dokumente in der Ansicht den Bereich, in dem mit „Häkchen“ Dokumente selektiert werden können, und zum anderen ist immer genau ein Dokument mit einem dicken Rahmen belegt und identifiziert das zuletzt angeklickte Dokument. Beim ersten Öffnen einer Ansicht ist per Default entweder das zuletzt markierte Dokument weiterhin oder das erste oder letzte Dokument einer Ansicht selektiert. Der Anwendungsbereich für Java-Agenten, die direkt in einer Domino-Datenbank erstellt werden, sind all die Fälle, in denen die Trigger, die in Domino zum Ausführen von Agenten zur Verfügung stehen (Kapitel 3.4.1), für den Start von Anwendungen genutzt werden sollen. Planen Sie vor dem Entwurf Ihrer Anwendung, welche Art von Ausführungsstart Sie benötigen. Nutzen Sie die Möglichkeit, einen Agenten regelmäßig periodisch oder z.B. beim Eintreffen neuer Mail zu starten. Die eigentliche Programmierung des Java-Codes muss nicht im Agenten erfolgen. Erstellen Sie die benötigten Klassen und Bibliotheken in dem IDE23 Ihrer 23
Neben den gängigen Java IDEs gibt es noch ein Eclipse Plugin, mit dem Domino-JavaAgenten direkt in Eclipse editiert werden können [Domino Eclipse Plugin].
140
4 Java-Anwendungen @ Domino
Wahl und packen Sie diese in eine Jar-Datei. Erstellen Sie nun im Domino Designer eine Bibliothek (Kapitel 3.5) und importieren Sie Ihren Code in diese Bibliothek. Im Domino-Java-Agent selbst kann nun diese Bibliothek referenziert werden, so dass Ihr Agent die benötigten Methoden und Klassen ausführen kann. Planen Sie Ihre Anwendung so, dass diese auf einem Basis-Objekt basiert, das eine Methode besitzt, die den Einstiegspunkt in Ihre Anwendung darstellt. Ein typischer Agent im Domino Designer wird dann lediglich den Code Session session = getSession(); AgentContext agentContext = session.getAgentContext(); MyApplication ma = null; try { ma = new MyApplication (session, agentContext); ma.doAllTheWork (); } catch (MyApplicationException e) { e.printStackTrace(); } finally { if (ma != null) { ma.recycle (); } }
enthalten. Beachten Sie, dass Ihr Objekt entweder in der ausführenden Methode oder im Konstruktor die Session und den AgentContext enthält, damit Ihnen zur Ausführungszeit eine Session zur Verfügung steht und Sie Zugriff auf das Environment, bestehend aus Datenbank und Kontextdokument, haben. Die Vorgehensweise, den Code in einer Bibliothek zu speichern und im Agenten lediglich Code zu programmieren, der als Einstieg dient, hat noch einen weiteren Vorteil. Da ein derartiger Starter-Agent in der Regel nach einem anfänglichen Setup nicht mehr geändert werden muss, wird das Risiko vermieden, versehentlich den Agenten unter einer falschen Benutzer-ID zu speichern und somit die Ausführung auf entfernten Servern zu unterbinden.
Best Practice Berücksichtigen Sie in der Planung einer Domino-Java-Anwendung die benötigten Ausführungsrechte für Agenten. Importieren Sie Java-Code in Bibliotheken. Referenzieren Sie in Agenten immer Bibliotheken und vermeiden Sie es, Java-Code direkt in den Agenten zu codieren. Planen Sie Agenten als Starter-Agenten und steuern Sie den hierfür definierten Einstiegspunkt Ihrer Anwendung an.
4.11 Agent in Domino Designer importieren
141
Zum Verständnis muss hier kurz auf die Mächtigkeit von Domino in verteilten Umgebungen und die Sicherheitsrichtlinien, unter denen Agenten ausgeführt werden, eingegangen werden. Grundsätzlich gilt für jede Note, also auch für einen Agenten, der bekanntlich in einer Design-Note gespeichert wird, dass sie beim Speichern mit der Benutzer-ID des authentifizierten Benutzers signiert wird (PGP Konzept). Da beim Ausführen eines Agenten anhand eines Triggers kein authentifizierter Benutzer zur Verfügung steht – der Agent wird auf einem Server vom Agent Manager gestartet –, wird der Agent mit den Rechten des Benutzers ausgeführt, der den Agenten signiert hat24. In einer verteilten Umgebung wird es nun häufig vorkommen, dass auf den unterschiedlichen Servern verschiedene Rechte für verschiedene Benutzer vergeben sind, bzw. bestimmte Benutzer werden nur für bestimmte Server zur Verfügung stehen. Oft wird man Agenten mit einem Funktions-User signieren, der auf allen beteiligten (oder allen beabsichtigten) Servern die nötigen Rechte hat. Wird nun durch ein Update ein Agent aktualisiert, würden auch die Signatur und somit der Benutzer, in dessen Namen der Agent ausgeführt wird, aktualisiert. Wenn jedoch der eigentliche Agent bei einem Update unberührt bleibt und lediglich der Code in einer Bibliothek ausgetauscht wird, besteht dieses Risiko nicht. Grundsätzlich kann diese Vorgehensweise jedoch auch unerwünscht sein, wenn aus Sicherheitsgründen nur ein bestimmter Berechtigter die Erlaubnis erhält, durch seine Signatur Änderungen zu bestätigen. Hierfür gibt es seit Domino R6 die Möglichkeit, auch die abhängigen Bibliotheken in die Sicherheitsüberprüfungen einzubeziehen. Planen Sie in jedem Fall Ihre Anwendung so, dass sie Ihren Anforderungen bezüglich der Ausführungsrechte entspricht.
4.11
Agent in Domino Designer importieren
Grundsätzlich können Sie auch den kompletten Code im IDE entwickeln und dann in einen Domino-Java-Agenten importieren. Sie können dann die Jar-Datei in den Agenten laden und geben eine Basisklasse an, die Domino bei der Ausführung des Agenten als Einstiegspunkt dient. Allerdings können Sie dann keinen Code referenzieren, der in eine Bibliothek importiert worden ist. Um Java-Code zu importieren, gehen Sie wie folgt vor: Erstellen Sie eine Klasse EclipseDebugJavaAgent im Package djbuch/kapitel_04 mit folgendem Code im IDE Ihrer Wahl:
24
Seit Domino Version 6 gibt es zusätzlich die Möglichkeit einen Benutzer in den AgentenEigenschaften zu bestimmen, unter dessen Namen der Agent ausgeführt werden soll. Dieser Benutzer muss einerseits die nötigen Rechte in der ACL der Datenbank haben, in der der Agent ausgeführt wird, und zum anderen im Domino Directory berechtigt worden sein, dass Agenten in seinem Namen ausgeführt werden (s. Kap. 4.16).
142
4 Java-Anwendungen @ Domino
Tipp Importiertes Java kann im Gegensatz zu direkt im Agenten codierten Java in einem Package definiert werden. Beachten Sie beim Importieren, dass Sie die Klassen und Basisklassen entsprechend Ihrer Package-Definition in der Directory-Struktur auswählen.
package djbuch.kapitel_04; import lotus.domino.*; public class EclipseDebugJavaAgent extends AgentBase { public void NotesMain() { try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); System.out.println ("Hello World"); } catch(Exception e) { e.printStackTrace(); } } }
Diese Basisklasse erweitert, wie im vorigen Kapitel beschrieben, die Klasse AgentBase. Compilieren Sie diese Klasse und erstellen Sie, falls gewünscht, eine Jar-Datei. Erstellen Sie nun einen neuen Agenten im Domino Designer. Wählen Sie als Typ „imported Java“. Über den Button „Import Class Files“, bzw. „Reimport Class Files“ öffnen Sie den Dialog zur Wahl Ihrer Klassen-Dateien und navigieren Sie zum Verzeichnis oberhalb des Verzeichnisses „djbuch“, das das Subverzeichnis „kapitel_04“ und darin Ihre Klassen-Datei enthält. Öffnen Sie den Ordner djbuch und den darin enthaltenen Ordner kapitel_04 und wählen Sie die Klasse EclipseDebugJavaAgent und im rechten Fenster dieses Dialogs (Abb. 4-5) dieselbe Klasse als Basisklasse.
Abb. 4-5 Java importieren
4.12 Agent in eine Java-Anwendung umwandeln
143
Es ist wichtig, dass Sie beim Auswählen von Klassen und Basisklasse diejenige Ordnerstruktur wählen, die dem Package entspricht (Listing 4-4). Beim Start des Agenten wird Domino die hier angegebene Basisklasse nach einer Methode NotesMain() durchsuchen und diese starten. Wählen Sie als Name des Agenten „Kapitel 4\2. Hello World (Debug me)| AG_HelloWorldDebug“, speichern und schließen Sie den Agenten. Wählen Sie als Event „Action menu selection“ und als Trigger „none“. Sie können den Agenten nun testen, indem Sie im Notes Client die Aktion „Actions“ -> „Kapitel 4“ -> „2. Hello World (debug me)“ auswählen.
4.12
Agent in eine Java-Anwendung umwandeln
In den Kapiteln 4.5 bis 4.8 haben Sie die verschiedenen Möglichkeiten kennengelernt, einen NotesThread zu beziehen, um eine Domino-Session aufbauen zu können. Soll ein vorhandener JavaAgent einer Domino-Anwendung in eine alleinstehende JavaAnwendung umgewandelt werden, dann sind einige Vorüberlegungen und Schritte notwendig. Prüfen Sie zunächst, ob Ihr Agent einen Trigger verwendet, der in einerallein stehenden Anwendung nicht oder nur durch einigen Aufwand realisiert werden kann (z.B. „Bei Eintreffen neuer Mail“). Im nächsten Schritt ist zu klären, auf welchen Client- oder Server-Computern und mit welchen Rechten der Agent ausgeführt werden soll. Liegt auf derselben Maschine, auf der die Java-Anwendung ausgeführt werden soll, eine Notes Client- oder Domino-Server-Installation vor, so kann eine Session sehr einfach, wie in den Kapiteln 4.5 bis 4.8 aufgezeigt, bezogen werden. Steht keine lokale Notes-Installation zur Verfügung, kann eine Verbindung via DIIOP (s. Kap. 5.3ff.) hergestellt werden. Soll die Anwendung nicht mit den Rechten der lokalen Installation ausgeführt werden, müssen Sie entweder das Passwort hart codiert in der Anwendung zur Verfügung stellen, was natürlich ein erhebliches Sicherheitsrisiko darstellt, oder Sie müssen den Benutzer beim Start der Anwendung nach dem Passwort fragen, das zu dem aktuellen lokalen Benutzer, der in der notes.ini-Datei aufgeführt ist, passt, damit mittels dieser lokalen Session auch auf serverbasierte Datenbanken zugegriffen werden kann. Für lokale Sessions auf einem Client-System gibt es keine Möglichkeit, einen anderen als den lokalen Benutzer zu verwenden. Eine Alternative bietet hier der Verbindungsaufbau via DIIOP. Nach diesen Vorüberlegungen müssen Sie lediglich eines der drei Konzepte „statischer NotesThread“, „Runnable Implementieren“ oder „NotesThread erweitern“ verwenden, um dem Code aus dem Java-Agenten eine Session zur Verfügung stellen zu können. Bedenken Sie, dass der Code möglicherweise Methoden aus dem AgentContext verwendet, die Sie ersetzen müssen. Jede Domino-Anwendung wird sich auf bestimmte Dokumente und Datenbanken beziehen. Im Domino-Java-Agent ist hier in der Regel der Einstiegspunkt die ak-
144
4 Java-Anwendungen @ Domino
tuelle Datenbank, in der der Agent gestartet wurde, die in einem Java-Agenten automatisch über getCurrentDatabase in AgentContext zur Verfügung steht und z.B. das Kontextdokument. Keines der beiden steht in einer alleinstehenden Java-Anwendung zur Verfügung. Ein Kontextdokument können Sie eventuell manuell erzeugen, den Benutzer auswählen lassen oder über eine Suche referenzieren, sofern dies Sinn macht – siehe auch in Listing 4-1. Dies gilt ebenfalls für die aktuelle Datenbank – siehe auch in Listing 4-1.
4.13
Agenten in Notes-Masken
Eine der wesentlichen Methoden zur Verwendung von Java-Agenten in Masken haben Sie bereits in dem Beispiel in Kapitel 2 kennengelernt. Im Wesentlichen wird dort mittels eines Aktions-Buttons (oder einer Aktion) ein Java-Agent gestartet, der Berechnungen auf dem aktuellen Kontextdokument ausführt, wobei natürlich auch Agenten denkbar sind, die unabhängig vom Kontextdokument arbeiten oder z.B. lediglich Setup-Informationen aus dem Kontextdokument beziehen. Sollen Berechnungen auf dem Kontextdokument ausgeführt werden, sind jedoch einige Einschränkungen zu beachten. Zunächst muss gewährleistet sein, dass das Kontextdokument gespeichert wurde, bevor der Agent gestartet wird. Da der Java-Agent nur eine Referenz auf die BackendRepräsentation des Dokuments erhält, wird er immer nur die Änderungen seit dem letzten Speichern des Dokumentes sehen, im Falle eines neu angelegten Dokumentes würde es sogar die Backend-Repräsentation überhaupt noch nicht geben. Das UI Document muss also entweder über die Verwendung von @Formeln oder die Verwendung von LotusScript gespeichert werden, bevor ein JavaAgent Berechnungen auf dem Kontextdokument anstellen kann. In dem Beispiel in Kapitel 2 wurde dies durch @Command ([FileSave]); erreicht. Eine weitere Einschränkung der bisherigen Version des URL-Loader-Beispiels aus Kapitel 2 ist, dass der geladene HTML-Inhalt in ein Item geladen wird, für das es kein Feld gibt, d.h. der Code ist nicht bearbeitbar. Für das Beispiel ist dies in Ordnung, aber für andere Anwendungen ist dies möglicherweise eine Einschränkung. Dieses Verfahren hat folgende Ursache: Wird durch das Backend (also durch den Java-Agenten) die Backend-Repräsentation des UI-Dokuments geändert, so kann das UI-Dokument diese Änderung nicht durch einen Refresh nachladen. Seit Version 6 kann dies durch einen kleinen Trick erreicht werden, indem das Dokument nach dem Update durch den Agenten geschlossen und danach erneut geöffnet wird. Ersetzen Sie in der Maske „FO_URL_Loader_k2“ den Code des AktionsButtons durch folgende @Formeln: REM {Abgewandelte Version - Code wird in bearbeitbares Feld geladen.}; @Command ([FileSave]); REM {Dokumenten ID merken}; unid := @DocumentUniqueID;
4.13 Agenten in Notes-Masken
145
REM {Agent wie gewohnt ausführen.}; @Command ([RunAgent];"AG_UrlLaden"); REM {UI Dokument schließen und komplett neu laden.}; @Command ([CloseWindow]); @Command ([OpenDocument];"1";unid)
Um das in Bearbeitung befindliche Dokument erneut öffnen zu können, muss die UniversalID, also der eindeutige Schlüssel des Dokuments zwischengespeichert werden , so dass es mittels der @Formel @Command ([OpenDocument]) später wieder geöffnet werden kann . Die @Formel @Command ([CloseWindow]) ist neu in Domino R6. Im Gegensatz zu der Vorgänger-Version @Command ([FileCloseWindow]), wird @Command ([CloseWindow]) sofort während der Bearbeitung ausgeführt, wordurch die nachfolgende Anwendung des Befehls OpenDocument überhaupt erst gültig wird. In der Beispieldatenbank wurde eine Ansicht und eine Maske vorbereitet. Sie können das Beispiel ausprobieren, indem Sie den Menü-Befehl „Create“ -> „Kapitel 4.“ -> „1. URL Loader Dokument“ wählen. Derart erzeugte Dokumente befinden sich in der Ansicht „Kapitel 4\URL Loader“. Es zeigt sich, dass die Verwendung von Agenten in Masken mit einigen Umständen und Nebenwirkungen behaftet ist, wenn das aktuelle Kontextdokument geändert werden soll. Der beschriebene Weg stellt jedoch eine gute Möglichkeit dar, auch hier Java-Code zum Einsatz zu bringen. Eine Alternative ist übrigens der Einsatz von Applets, die in Masken eingebunden werden können (s. Kap. 4.9). Im Kapitel 3.2.1 und Tabelle 3-1 wurden bereits die Events für Felder vorgestellt. Masken haben ebenfalls einige Events, über die mittels LotusScript, JavaScript und @Formeln Programmcode ausgelöst werden kann. Interessant sind hier natürlich insbesondere diejenigen, für die über @Formeln oder LotusScript ein JavaAgent gestartet werden kann. Eine Übersicht über derartige Events wurde in Tabelle 4-3 zusammengestellt. Beachten Sie, dass für alle diese Events die gleichen Bedingungen gelten, die zuvor für den Aktions-Button beschrieben wurden. Die beiden Events WebQueryOpen und WebQuerySave gelten nur bei Benutzung über den Browser und können ausschließlich zum Starten von Agenten genutzt werden, folglich ist dies für die Verwendung von Masken natürlich der bevorzugte Ort für den Einsatz von Java in Masken. Die Events sind unterschiedlich gut geeignet, einen Java-Agenten zu starten, insbesondere wenn dieser Daten des Dokuments ändern soll. So kann z.B. im Event QuerySave zwar ein Agent gestartet werden, allerdings nur, wenn die Daten bereits gespeichert wurden. Dies macht in den seltensten Fällen Sinn. Dagegen ist der QueryRecalc Event ein perfekter Ort, einen Agenten zu starten, da er gewissermaßen im letzten Moment, bevor die Felder aktualisiert werden, gestartet wird. An dieser Stelle ist ein Agent insbesondere geeignet, wenn er Items erzeugt oder aktualisiert, auf die sich zwar Felder beziehen oder auf deren Basis Felder eigene Berechnungen anstellen, die aber selbst keine Repräsentation als Feld in der Maske haben. Die Verwendung von Java in Masken zeigt erneut, dass Java in Domino nicht dafür geeignet ist, auf die direkten Benutzerinteraktionen zu reagieren oder die vom Be-
146
4 Java-Anwendungen @ Domino
Tabelle 4-3 Masken-Events, in denen @Formeln verwendet werden können Event
Verwendung Verwendung (7) Anmerkung im Client im Browser
@Formel, LotusScript, JavaScript onLoad (1) @Formel, LotusScript, JavaScript PostOpen (3) @Formel, LotusScript OnSubmit (1) @Formel, LotusScript, JavaScript QuerySave (4) @Formel, LotusScript OnUnload (1) @Formel, LotusScript, JavaScript QueryClose (5) @Formel, LotusScript PostMode@Formel, Change LotusScript, JavaScript PostRecalc @Formel, LotusScript, JavaScript PostSave @Formel, LotusScript, JavaScript @Formel, PostSend (2) LotusScript, JavaScript @Formel, QueryModeLotusScript, Change JavaScript @Formel, QueryOpen LotusScript, JavaScript QueryRecalc (2) @Formel, LotusScript, JavaScript QuerySend (2) @Formel, LotusScript, JavaScript
JavaScript
Ja Beim Auslösen einer HilfeAnfrage (F1)
JavaScript
Ja Nach dem Laden, vor dem Anzeigen
–
Ja Nach dem Laden, vor dem Anzeigen Ja Vor dem Speichern des Dokuments
WebQueryOpen
@Formel (6)
onHelp
–
JavaScript
JavaScript
Ja Vor dem Speichern des Dokuments Ja Vor dem Schließen
–
Ja Vor dem Schließen
–
Ja Nach dem Wechseln zwischen Lesen- und Editmode
–
Ja Nach dem Refresh der Felder
–
Ja Nach dem Speichern
–
(8) Nach dem Versenden als EMail
–
Ja Vor dem Wechseln zwischen Lesen- und Editmode
–
Ja Vor dem Laden des Dokuments
–
Ja Vor dem Refresh der Felder
–
(8) Vor dem Versenden als EMail
–
Nach dem Laden, vor der Anzeige
4.14 WebQueryOpen und WebQuerySave Event
Verwendung Verwendung (7) Anmerkung im Client im Browser
WebQuerySave
–
@Formel (6)
147
Vor dem Speichern
(1) Verwendung von @Formeln und LotusScript erstmals seit Version 6 (2) Seit Version 6 (3) Verwenden Sie onLoad, für Version 6 und aufwärts (4) Verwenden Sie onSubmit, für Version 6 und aufwärts (5) Verwenden Sie onUnload, für Version 6 und aufwärts (6) als @Formel in der Form @Command([ToolsRunMacro];"Agent") und seit Version 6 @Command([RunAgent];"Agent") (7) Agenten dürfen gestartet werden (8) Bei Verwendung der System-Aktion „Dokument senden“ nutzer geöffneten Objekte direkt zu referenzieren. Hierfür sind die Domino-UI-Klassen vorgesehen, für die lediglich LotusScript-Implementierungen vorliegen. Die aufgezeigten Möglichkeiten demonstrieren aber, dass Java indirekt angesteuert werden kann, vor allem natürlich durch den Befehl @Command ([RunAgent];"").
4.14
WebQueryOpen und WebQuerySave
Die Basis für die Verwendung des Events WebQuerySave-Agenten haben Sie bereits in Kapitel 3.2.11 kennengelernt. Der einzige Unterschied zwischen der Verwendung des URL-Loader-Beispiels im Notes Client und im Browser bestand darin, dass zum einen der versteckte Agent AG_QS_URLLaden statt des Agenten AG_URLLaden angesteuert wurde. Beide Agenten unterscheiden sich im Target, da der vom Agenten AG_URLLaden verwendete Target „All selected documents“ im Web nicht unterstützt wird. Hier muss der Target „none“ verwendet werden. Die Dokumente, auf die der Agent angewendet werden soll, können dann im Agenten-Code selbst selektiert werden. In der Beispieldatenbank djbuch.nsf können Sie mit dem Domino Designer die Maske Kapitel 4\1. URL Loader Dokument (Web) als Vorschau im Browser anzeigen. Es wurde noch eine weitere Verbesserung umgesetzt. Der @Formel-Code des Buttons „URL Laden“ wurde durch den Befehl @command ([FileSave]) ersetzt. Dies genügt, um die URL zu laden, da ja bereits beim Speichern der Agent für die Berechnung gestartet wird. Der Event WebQueryOpen arbeitet vergleichbar zum Event WebQuerySave. Durch diesen Event kann ein Agent vor dem Öffnen eines Dokuments ausgeführt werden.
148
4 Java-Anwendungen @ Domino
4.15
Parameterübergabe zwischen Agenten und runOnServer
Wie in den vorangegangenen Kapiteln gezeigt wurde, werden Agenten durch die verschiedenen manuellen, eventgesteuerten und periodischen Trigger gestartet. Darüber hinaus ist es selbstverständlich möglich, Agenten auch programmatisch zu starten. Diese Technik kommt zum Einsatz, wenn entweder ein steuernder Agent verschiedene aufgabenspezifische Agenten steuern muss oder wenn der Computer, auf dem ein Event ausgelöst werden soll – zum Beispiel, wenn ein Anwender einen Button-Click auslöst –, nicht derselbe ist wie die Maschine, auf der der Agenten-Code ausgeführt werden soll. Hierbei ist insbesondere zu berücksichtigen, dass manuell ausgelöste Agenten immer lokal auf der Client-Maschine ausgeführt werden. Um die Funktion der beiden Agenten zu verdeutlichen hat sich die Bezeichnung Starter-Agent für den aufrufenden und Worker-Agent für den aufgerufenen Agenten bewährt. Dabei stellt sich natürlich die Frage, wie Parameter von Agent zu Agent übergeben werden können. Die Antwort liegt auf der Hand – in Lotus Domino können Daten zwischen Agenten über ein Dokument ausgetauscht werden, wobei hier von den Methoden runOnServer und der Klasse Agent bestimmte Parameter bereitgehalten werden. Ein häufiger Anwendungsfall für das Starten eines Agenten durch einen anderen Agenten ist die Situation, in der ein Anwender einen Worker-Agent sofort starten möchte und nicht erst auf z.B. eine eventbasierte Ausführung warten möchte, wobei in diesem Fall, wie eingangs erwähnt, der Starter-Agent auf dem lokalem Client gestartet wird. Der Starter-Agent wiederum kann nun durch die Methode runOnServer in Agent die Ausführung des Worker-Agenten auf einem Server auslösen. Dabei spielt es keine Rolle, wo sich der Starter-Agent befindet. Egal, ob in einer lokalen Datenbank oder auf einer Server gespeicherten Datenbank – beim Aufruf eines Agenten durch einen Notes Client wird der Code immer auf dem lokalen Computer aufgeführt –, ein Grund für die Aufteilung in Starter- und Worker-Agent, denn das manuelle Auslösen des Worker-Agenten durch den Client hätte eine Ausführung auf dem lokalen Computer zur Folge. Erst der Aufruf von runOnServer startet einen Agenten auf dem Server. Dieser manuelle Trigger ist jeder Hinsicht – also bezüglich Sicherheits- oder Ressourcenbezügen – vergleichbar mit der periodischen Ausführung eines Agenten, wobei jedoch kein zeitlicher, sondern ein manueller Trigger zum Zuge kommt. Ein typischer Anwendungsfall für diese Art einen Agenten zu starten ist die Notwendigkeit, auf Ressourcen zuzugreifen, die nur auf einem Server verfügbar sind, wie z.B. Enterprise-Datenbanken, so dass die Infrastruktur für die Verbindung zu solchen Datenbanken nur auf dem Server zur Verfügung gestellt werden muss. Die Clients können dann durch den indirekten Start des Worker-Agenten auf dem Server diese Ressourcen nutzen. Hierfür ist es notwendig, dass Parameter und Ergebnisse zwischen dem aufrufenden und dem aufgerufenen, serverbasierten Agenten ausgetauscht werden können.
4.15 Parameterübergabe zwischen Agenten und runOnServer
149
Dafür ist in der Methode runOnServer (String NoteID) der Parameter NoteID vorgesehen, aber Achtung – es handelt sich hierbei um die Replik-spezifische ID eines Notes-Dokuments, nicht zu verwechseln mit der bereits vielfach erwähnten Universal ID, die über alle Repliken gleich und eindeutig ist. Dies ist insofern unproblematisch, da sich aufrufender und aufgerufener Agent immer in derselben Datenbank befinden werden, da runOnServer immer automatisch auf dem Computer ausgeführt wird, auf dem sich die beinhaltende Datenbank befindet. Der aufrufende Agent übergibt die Note ID eines beliebigen Dokuments, also z.B. des Kontextdokuments oder auch eines neu, lediglich für die Parameterübergabe erzeugten Dokuments, der aufgerufene Agent kann mittels getParameterDocID in Agent diese ID beziehen und mit getDocumentByID in Database dieses Dokument öffnen. Wird ein Dokument extra als Parameterdokument für die Übergabe angelegt, ist zu berücksichtigen, dass dieses Dokument via save in der Datenbank gespeichert werden muss, ein bisher nur als Speicherobjekt neu angelegtes Document hat noch keine Notes ID und kann nicht wie beschrieben durch getParameterDocID bezogen werden, da der aufgerufene Agent in einem eigenen Notes Thread gestartet wird. Wird ein Dokument lediglich zur Parameterübergabe angelegt, sollte es nach Gebrauch wieder gelöscht werden25. In Listing 4-7 finden Sie ein einfaches Beispiel für einen Agenten, der einen anderen Agenten auf einem Server startet. Sie finden diesen Agenten in der Beispieldatenbank djbuch\djbuch.nsf und können ihn über das Menü „Actions“ -> „Kapitel 4“ -> „1. Starte Agent auf Server“ starten. Im Designer finden Sie diesen Agenten unter dem Alias Namen AG_runOnServer. Der Agent soll das Kontextdokument laden und an den serverbasierten Agenten übergeben. Dieser soll zur Verdeutlichung im Parameterdokument mit den Items F_ranOnServerTime und F_ranOnServerName dokumentierten, wann und auf welchem Server der aufgerufene Agent ausgeführt wurde. Nach der Rückkehr sollen diese Items angezeigt werden. Das Kontextdokument und die aktuelle Datenbank wird wie gewohnt aus dem AgentContext bezogen . Um den Worker-Agenten starten zu können, muss er als Java-Objekt geladen werden. Dies wird mit getAgent in Database erledigt . 25
Werden in einer Datenbank sehr häufig Dokumente angelegt und wieder gelöscht, wird die Datenbank anwachsen, ohne den belegten Plattenplatz voll auszunutzen. Sie muss dann regelmäßig, am besten durch einen periodischen Batch Job, komprimiert werden. Eine Datenbank können Sie durch einen Serverkonsolen-Befehl komprimieren: Load compact [Datenbankpfad] [Optionen]. Die Angabe des Datenbankpfades ist optional. Es kann eine Datenbank oder ein Verzeichnis angegeben werden. Informationen zu den Optionen finden Sie in der Administrations-Hilfe (sofern ein Domino Administrator der Version 6.5 installiert ist, ist dies die Datei help\help65_admin.nsf). Eine wichtige Option ist „-D“. Sie komprimiert die Datenbank und verringert gleichzeitig die Dateigröße. Beispiel: load compact djbuch\djbuch.nsf -D Einen regelmäßigen Batch können Sie mit Hilfe des Administrations-Clients anlegen, indem Sie unter „Configuration“ -> „Server“ -> „programs“ ein neues Programmdokument anlegen. Geben Sie für das Feld „command line“ den oben beschriebenen Konsolen-Befehl ein.
150
4 Java-Anwendungen @ Domino
import lotus.domino.*; public class StartAgent extends AgentBase { public void NotesMain() { Document parameter = null; try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); Database db = agentContext.getCurrentDatabase(); parameter = agentContext.getDocumentContext(); Agent ag = db.getAgent("AG_serverBasedAgent"); System.out.println( "Starte AG_serverBasedAgent auf Server "); String noteID = parameter.getNoteID(); int result = ag.runOnServer(noteID); System.out.println( "AG_serverBasedAgent wurde " + (result == 0 ? "erfolgreich" : "fehlerhaft") + " ausgeführt."); parameter.recycle(); parameter = db.getDocumentByID(noteID); System.out.println("Ausführungszeiten: " + parameter .getItemValue("F_ranOnServerTime").toString()); System.out.println("Servernamen: " + parameter .getItemValue("F_ranOnServerName").toString()); } catch(Exception e) { e.printStackTrace(); } finally { ... recycle parameter ... } } }
Listing 4-7 Aufrufender Agent startet serverbasierten Agenten
Der Server, auf dem der Worker-Agent gestartet wird, ist derjenige Server, auf dem die Datenbank liegt, in der dieser Agent gespeichert ist. Dies kann verdeutlicht werden, indem der Name dieser Datenbank angezeigt wird . Als Übergabeparameter an den serverbasierten Agenten dient die Note ID eines Notes-Dokuments. Die Note ID ist nicht mit der Universal ID zu verwechseln. Die Note ID ist nur innerhalb einer Replik einer Datenbank eindeutig. Der Start des serverbasierten Agenten wird mit der Methode runOnServer in Agent erreicht. Als (optionaler) Parameter wird ein String mit der Note ID angegeben. Als Ergebnis liefert diese Methode den Wert 0, falls der Agent ausgeführt werden konnte. Falls ein Fehler aufgetreten ist, wird ein Wert ungleich 0 zurückgegeben. Ist die Ausführung des aufgerufenen Agenten beendet, wird die Kontrolle an den aufrufenden Agenten zurückgegeben. Die Ausführung des aufgerufenen Agenten ist synchron, d.h. der aufrufende Agent wird die Ausführung des serverbasierten Agenten abwarten und erst danach mit der eigenen Ausführung fortfahren. Hat alles geklappt, wurden im Parameterdokument mit der übergebenen Note ID die Felder F_ranOnServerTime und ranOnServerName um die Ausführungszeit auf dem Server und dessen Name erweitert.
4.15 Parameterübergabe zwischen Agenten und runOnServer
151
Nun gilt es, die durch den Worker-Agenten erzeugten oder veränderten Daten im Starter-Agenten zu laden. Um dieses Verfahren zu verstehen, muss man sich verdeutlichen, dass es letztendlich drei Instanzen des Dokuments gibt. Zunächst wurde im Starter-Agenten ein Document als Java-Objekt erzeugt. Dieses Objekt wurde verändert und in der Datenbank gespeichert, wo sich die zweite Instanz (wenn man dies denn so bezeichnen möchte) befindet. Der Worker-Agent erhält nun die Notes ID dieses Dokuments und kann die Daten in ein weiteres – unsere dritte Instanz – JavaObjekt Document laden, erhält also die aktuellen vom Starter-Agenten gelieferten Daten. Nun verändert der Worker-Agent dieses Document und speichert diese Daten in der Datenbank. Das Java-Objekt wird recycled und der Worker-Agent beendet. Wenn nun die Kontrolle dem Starter-Agenten zurückgegeben wird, liegt dort weiterhin die unveränderte erste Instanz des ursprünglichen Java-Objekts im Hauptspeicher. Leider gibt es keine Möglichkeit, das Document zu aktualisieren, es muss durch den Aufruf des recycle ausdrücklich aus dem Hauptspeicher entfernt und mittels der NoteID und getDocumentByID neu geladen werden . Bei diesem Verhalten unterscheiden sich Domino R5 und R6 erheblich. In Version 6 wurde das gesamte Speichermanagement überarbeitet und insbesondere das Recycling und die Verbindung zwischen den Java- und den dahinter verborgenen C++ Objekten überarbeitet und hierdurch das Risiko von Speicherleaks, die durch eine mangelhafte Verwendung von recycle() bei der Verwendung von Domino-JavaObjekten entstehen konnten, verringert. Zum Glück ist dieses Beispiel einer der wenigen Anwendungsfälle, in denen das Dokument in der beschriebenen Art recht umständlich neu geladen werden muss, denn in den meisten Fällen wird eine Anwendung keinen derartigen anwendungsübergreifenden Austausch von Domino-Objekten benötigen. Abschließend können nun die vom serverbasierten Agenten geänderten Items angezeigt werden . Die Ausgabe des Agenten landet in der Java Console (Abb. 4-6). Beachten Sie, dass in der (lokalen) Java Console nur Meldungen angezeigt werden, die von lokal gestarteten Agenten ausgegeben werden. Der serverbasierte Agent schreibt seine durch System.out.println generierten Ausgaben in das Serverlog, das ist die Datenbank log.nsf auf dem Server. Sie können übrigens auch hier im Eigenschaften-Menü des Dokuments die neu geschriebenen Items beobachten (Abb. 4-7). Den aufgerufenen Worker-Agenten können Sie ebenfalls in der Beispieldatenbank finden. Er trägt den Namen „(serverbasierter Agent)“ und den Alias-Namen „AG_serverBasedAgent“. Den Code finden Sie im Listing 4-8. Damit dieser Agent auf das Parameterdokument zugreifen kann, benötigt er dessen Note ID, die über getParame- Abb. 4-6 Java Console nach einem Aufruf terDocID aus dem aktuellen des Starter-Agenten auf einem Server Agent bezogen werden kann.
152
4 Java-Anwendungen @ Domino
import import import public
java.util.Calendar; java.util.Vector; lotus.domino.*; class ServerBasedAgent extends AgentBase {
public void NotesMain() { Document parameter = null; try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); Agent ag = agentContext.getCurrentAgent(); Database db = agentContext.getCurrentDatabase(); String noteID = ag.getParameterDocID(); parameter = db.getDocumentByID(noteID); DateTime now = session .createDateTime(Calendar.getInstance().getTime()); Vector val = parameter.getItemValue("F_ranOnServerTime"); val.add(now); parameter.replaceItemValue("F_ranOnServerTime",val); String server = session.getServerName(); server = (server == null || server.length() == 0) ?"Lokal":server; val = parameter.getItemValue("F_ranOnServerName"); val.add(server); parameter.replaceItemValue("F_ranOnServerName",val); parameter.save (true,false); } catch(Exception e) { e.printStackTrace(); } finally { ...recycle... } } }
Listing 4-8 Aufgerufener serverbasierter Agent verändert Parameterdokument
Mittels dieser ID und der Methode getDocumentByID in Database kann der Agent nun das Parameterdokument öffnen. Beachten Sie, dass es sich hier nur um die aktuelle Datenbank (also diejenige, in der der Agent gestartet wurde) handeln kann, da die übergebene Note ID ohnehin nur in dieser Datenbank gültig ist. Als Beispieldaten werden zunächst die aktuellen Werte für die Items F_ranOnServerTime und F_ranOnServerName als Vector bezogen, dann durch die aktuellen Laufzeitparameter ergänzt und mittels der Methode replaceItemValue wieder zurück ins Dokument geschrieben . Hierbei ist eine Besonderheit zu beachten. Ziel ist es, für jeden Aufruf des Agenten im Item F_ranOnServer- Abb. 4-7 Items Name eine Liste aller Server, auf denen
4.15 Parameterübergabe zwischen Agenten und runOnServer
153
Beachten Sie Ein manueller Start eines Agenten auf einem Server (über das GUI) kann nicht direkt erfolgen. Manuell gestartete Agenten werden immer auf dem lokalen Computer ausgeführt. Verwenden Sie das Designpattern „Starteragent und Workeragent“, um serverbasierte Agenten auf einem Server durch einen manuellen Trigger zu starten. Ein durch runOnServer gestarteter Agent wird in einem eigenen NotesThread gestartet. Das Java-Objekt „Document“ und das dahinter geführte C++ Core-Objekt wird nach einer Veränderung durch einen entfernten Prozess zunächst nicht aktualisiert. Um Veränderungen anzuzeigen, muss das Objekt Document initialisiert und neu geladen werden.
dieser Agent für dieses Dokument ausgeführt wurde, zu führen. Würde an dieser Stelle die Methode appendItemValue verwendet, würde für jeden Aufruf des Agenten ein neues (!) Item (mit gleichem Namen) angelegt. Daher muss zunächst die aktuelle Liste in einen Vector geladen werden und kann dann durch den aktuellen Wert ergänzt und mit der Methode replaceItemValue wieder zurückgeschrieben werden. Zum Abschluss muss das Dokument dann noch gespeichert werden . Abschließend sei noch angemerkt, dass der Starter-Agent (und Worker-Agent) aus dem Beispiel dafür konzipiert ist, dass er sich in einer Datenbank auf einem Server befindet, er wird aber auch funktionieren, wenn er sich in einer lokalen Datenbank befindet. Dies liegt daran, dass Domino die Methode runOnServer implizit auf die Methode run mappt, um eine lokale Ausführung anzustoßen, was im Beispiel am leeren String erkennbar wird, der durch db.getServer zurückgegeben wird (Abb. 4-8). Die Methode run kann natürlich auch explizit benutzt werden, falls der sekundär gestartete Agent nicht auf dem Server, sondern lokal auf der gleichen Maschine wie der Starter-Agent ausgeführt werden soll.
Abb. 4-8 Java Console nach einem lokalen Aufruf des Starter-Agenten
154
4 Java-Anwendungen @ Domino
4.16
Sicherheit bei der Agentenausführung
Das folgende Kapitel ist ein kleiner Ausflug in ein Thema, das für die Programmierung von Domino-Anwendungen unerlässlich ist: die Sicherheit. In Domino sind die Sicherungsmechanismen, wie bereits verschiedentlich angedeutet wurde, sehr differenziert ausgeprägt, auf verschiedenen Ebenen granulär steuerbar und dadurch sehr komplex. Bereits in Kapitel 1.4 wurden die verschiedenen Stufen der Sicherungsmechanismen dargestellt: Diese Stufen (s. Abb. 4-9) lassen sich in drei Typen der Sicherheit zusammenfassen:
Internet
Notes-IDDatei
Internet User
Notes Client
Stufe 1 - Server Prüfung des Serverzugriffs Prüfung der Notes ID
Stufe 1 - Server Prüfung des Serverzugriffs Prüfung des Internet-Passworts
Stufe 1a - Agentenberechtigungen
Domino Stufe 2 - Datenbank Prüfung der ACL Prüfung der Rollen
Stufe 3 - Dokument Prüfung von Leserund Autorenfeldern
Stufe 4 - Feld Feldverschlüsselungen
Abb. 4-9 Sicherheit in der Agentenausführung
4.16 Sicherheit bei der Agentenausführung
155
1
Programmatische Sicherheit Unter die programmatische Sicherheit fallen alle Einstellungen, die steuern, wer unter welchen Bedingungen auf einen Server oder allgemeiner auf eine Ressource zugreifen darf. Hierzu gehört auch die Steuerung, wie festgestellt wird, unter welcher Benutzerkennung eine Anwendung, ein Agent oder ein Prozess läuft und wie festgestellt wird, ob und in welchem Umfang dieser Prozess Ressourcen benutzen darf. Wichtige Bestandteile sind das Serverdokument, die Ausführungskontrollliste (ECL), die steuert, wer welche Aktionen auf einem lokalen Client ausführen darf und die im Serverdokument und den Agenteneigenschaften definierten Ausführungsrichtlinien für Agenten.
2
Zugriffssteuerung Die Zugriffssteuerung reicht von der obersten Ebene der Zugriffskontrollliste (ACL) in Datenbanken über die Zuordnung von Rollen bis hin zur Vergabe von Leser- und Autorenfeldern auf Dokumentenebene.
3
Verschlüsselung Die Verschlüsselung kann auf verschiedene Weise und an verschiedenen Orten eingesetzt werden. Verschlüsselt werden kann entweder mit den Benutzer-IDs (also den darin enthaltenen privaten Notes-Schlüsseln), mit eigenen Schlüsseln für die Verschlüsselung auf Feldebene, aber auch mit allgemeinen Standards, wie S/MIME.
Zwar sprengt die abschließende Behandlung des Themas Sicherheit in Domino den Rahmen dieses Buches, dennoch soll an dieser Stelle darauf eingegangen werden, da die fehlende Kenntnis in diesem Bereich bei der Programmierung zu erheblichen Designfehlern führen kann, zumindest aber ein frustrierter Programmierer der mindeste Schaden ist, der entstehen wird. An dieser Stelle steht ein Teilbereich von Punkt 1, die Ausführungsrechte von Agenten, im Vordergrund. Bei der Ermittlung, mit welchen Rechten ein Java-Agent oder auch eine Java-Anwendung auf Domino-Objekte zugreift, gilt zunächst eine einfache Regel: Bei der Überprüfung von Zugriffsrechten in der ACL oder in Leser- oder Autorenfeldern wird der Benutzername des Users verwendet, der den Agenten / die Session (für Standalone-Java-Anwendungen) gestartet hat. Komplizierter wird es, falls ein Agent periodisch oder z.B. über einen WebBrowser (s. Kap. 5.1) gestartet wurde. Zunächst sieht Domino vor, dass die Ausführung von Agenten grundsätzlich programmatisch limitiert ist. Dies wird über das Serverdokument im Domino Directory gesteuert. Im Serverdokument findet sich in der Rubrik „Security“ (zweiter Tabellenreiter von links) die Sektion „Programmability Restrictions“. Hier wird festgelegt, welche Benutzer grundsätzlich Agenten ausführen dürfen oder nicht und welche Art von Agenten sie ausführen dürfen. Abb. 4-10 zeigt ein typisches Beispiel, wobei in die verschiedenen Restriktionsfelder und Delegationseinstellungen Gruppen eingetragen sind. Die Reihenfolge der
156
4 Java-Anwendungen @ Domino
Abb. 4-10 Agenten-Sicherheit im Serverdokument (R6) in der Datenbank names.nsf
Ausführungsrechte finden Sie in Tabelle 4-4; sie stellt gleichzeitig die Hierarchie der Ausführungsrechte dar. Die verschiedenen Delegationsberechtigungen, mit Hilfe derer Benutzern das Recht gegeben werden kann, den Benutzernamen anderer als ausführende Person eines Agenten festzulegen, finden Sie in Tabelle 4-5. Im Einzelnen bedeuten die verschiedenen Sicherheitseinstellungen: 1
2
3 4
26
Benutzer, die uneingeschränkte Rechte haben, dürfen alle denkbaren Funktionen und Operationen einschließlich Java, JavaScript, LotusScript, COM, DIIOP und File I/O ausführen. Hierzu gehört auch die Manipulation von Systemparametern oder die Ausführung von Systemkonsolenbefehlen. Benutzer, die eingeschränkte Agenten ausführen dürfen, dürfen die wichtigsten Lotus-Domino-Methoden und -Funktionen ausführen, jedoch im Wesentlichen keine Funktionen, mit denen größerer Schaden angerichtet werden könnte (kein Zugriff auf Systemressourcen)26. Einfache und @Formel-Agenten können lediglich @Formeln ausführen. Benutzer, die eine Ausführungsdelegation ausführen dürfen, können in den Eigenschaften eines Agenten festlegen, unter wessen Namen dieser Agent ausgeführt wird. Dies ist eine mächtige Funktion. Dieses Recht sollten nur wenige vertrauenswürdige Administratoren erhalten. Diese Einstellung ist neu seit Version 6. Im Einzelnen sind nicht erlaubt: Log openFileLog(path); Session getEnvironmentString(); Session SetEnvironmentVar(); Session GetEnvironmentValue(SystemVariable,true); Document sign(); Document encrypt(); RichTextItem embedObject(); EmbeddedObject extractFile(path); außerdem kein File oder Netzwerk I/O und es dürfen keine externen Programme gestartet werden.
4.16 Sicherheit bei der Agentenausführung
157
Tabelle 4-4 Programmatische Ausführungseinschränkungen Restriktionsstufe 1
2
3
5
6
Run unrestricted methods and operations
Enthaltene Rechte
„Uneingeschränkt“: Agenten dürfen alle denkbaren Operationen einschließlich File-System-Zugriffe, Ändern der Systemzeit oder von Environmentvariablen oder Systembatches ausführen. „Eingeschränkt“: Agenten dürfen Run mit eingeschränkten Rechten gestarrestricted LotusScript / tet werden. Eingeschränkte Rechte Java-Agents sind im Wesentlichen solche, die auf das Domino-Environment beschränkt sind, d.h. z.B. File-Systemoder Betriebssystem-Zugriffe sind untersagt. Run simple „Einfache und @Formel-Agenten“: and Agenten, die mittels einer @Formel Formula oder eines Wizards erstellt wurden. Agents
Default
Version
Aktueller Server, Lotus Template Development R5,R6 Keiner
R5,R6
Alle R5,R6
Lediglich Web-Agenten (s. Kap. 5.1) können die Eigenschaft tragen, dass sie mit den Rechten des angemeldeten Benutzers und nicht mit der Signatur des Agenten ausgeführt werden. Benutzer, die in diesem Feld eingetragen sind, dürfen Web-Agenten diese Eigenschaft geben. Ist dieses Feld leer, gilt der Zustand von Version 5, d.h. alle Benutzer (sofern sie Designer Rechte haben) dürfen Web-Agenten diese Eigenschaft geben. Ist mindestens ein Benutzer eingetragen, dürfen nur noch die eingetragenen Benutzer diese Aktion durchführen. Wie bereits zuvor beschrieben, können Agenten Methoden aus Script Bibliotheken referenzieren. Sind in diesem Feld Benutzer eingetragen, dürfen Agenten nur Methoden aus solchen Script Bibliotheken ausführen, die von Benutzern aus dieser Liste signiert wurden.
Um nun die programmatischen Limitierungen anwenden zu können, wird, wenn ein Agent gestartet wird, zunächst überprüft, auf welche Art der Benutzername ermittelt werden soll. Dies hängt von Speicher- und Ausführungsort und von der Startart eines Agenten ab.
158
4 Java-Anwendungen @ Domino
Tabelle 4-5 Delegationserlaubnis „sign on behalf of“
4
5
6
Restriktionsstufe
Enthaltene Rechte
Default
Sign agents to run on behalf of someone else
„Ausführungsdelegation an jeden“: Benutzer mit diesem Recht dürfen Agenten die Eigenschaft geben, dass diese unter einem anderen Benutzernamen ausgeführt werden. Dokumente, die durch einen solchen Agenten verändert oder Mails, die von einem solchen Benutzer verschickt werden, tragen den Namen, der als „on behalf of“ eingetragen wurde. „Ausführungsdelegation an den Auslösenden“: Bei einer derartigen Delegation wird der Agent nicht mit den Rechten der Signatur, sondern mit den Rechten des Aufrufenden ausgeführt. Gilt (zur Zeit – R6) nur fürs Web.
Keiner
Sign agents to run on behalf of the invoker of the agent
Sign script libraries to run on behalf of someone else
Version
R6
Alle (wg. Abwärtskompatibilität) R6
Alle (wg. Abwärtskompatibilität)
R6
A
Ermittlung der Delegation
1
Start eines Agenten im Notes Client Egal ob ein Agent im Notes Client benutzergesteuert oder periodisch gestartet wird, gibt es bei einer lokalen Ausführung keine Einschränkungen.
2
Start eines Agenten auf einem Server Egal ob ein Agent auf einem Server als Web-Agent, als Web-Agent mit der Eigenschaft „Run as Webuser“, periodisch oder durch die Methode runOnServer gestartet wurde, wird die Signatur des Agenten herangezogen, um entsprechend der Tabelle 4-5 zu ermitteln, ob der Signierende berechtigt ist eine Delegation durchzuführen. Liegt eine Delegation vor27 und ist der Signierende nicht berechtigt diese anzuwenden, wird der Agent nicht ausgeführt.
27
Eine Delegation wird im Domino Designer in den Agenteneigenschaften eingestellt.
4.17 Debugging von Domino-Java-Agenten
159
Steht die Delegationsermittlung fest, kann der tatsächliche Benutzername ermittelt werden. B
Ermittlung des tatsächlichen Benutzers
1
Start eines Agenten im Notes Client Egal, ob ein Agent im Vorder- oder Hintergrund (dies ist seit R6 möglich) oder periodisch im Notes Client gestartet wird, ist der Benutzername des Aufrufenden der verwendete Benutzername.
2
Start eines Agenten auf einem Server als Web-Agent (mit Eigenschaft „Run As Webuser“) Aufrufender (im HTTP angemeldeter Benutzer – Aufruf über Browser) (Tabelle 4-5, Punkt 5)
3
Start eines Agenten auf einem Server als Web-Agent Signatur oder delegierter Benutzer („run on behalf of“) (Tabelle 4-5, Punkt 4)
4
Start eines Agenten auf einem Server – periodisch oder via runOnServer Signatur oder delegierter Benutzer („run on behalf of“) (Tabelle 4-5, Punkt 4)
Steht nun der Benutzername fest, unter dem ein Agent ausgeführt wird, können die programmatischen Einschränkungen anhand des Serverdokuments ermittelt werden. C
Ermittlung der programmatischen Einschränkungen Steht nun fest, mit welchem tatsächlichen Benutzernamen ein Agent ausgeführt wird, ergibt sich anhand von Tabelle 4-4, welche programmatischen Einschränkungen vorliegen. Liegt also eine Einschränkung auf z.B. „Run simple and Formula Agents“ vor, wurde aber ein Java-Agent gestartet, wird der Agent nicht ausgeführt. Oder liegt z.B. eine Einschränkung auf „Run restricted LotusScript / Java Agents“ vor und wird während der Programmausführung ein File I/O verlangt, so wird dies mit einer Security Exception beantwortet.
Stehen die programmatischen Einschränkungen fest, wird der Agent mit dem ermittelten Benutzernamen gestartet und mit den in C ermittelten Einschränkungen einerseits und den Rechten (Autor, Leser, etc.) aus der ACL andrerseits gestartet. Beim Öffnen von Dokumenten werden anhand dieses Benutzernamens Autoren- und Leserfelder ausgewertet.
4.17
Debugging von Domino-Java-Agenten
Solange ein Java-Projekt eine gewisse Größe nicht überschreitet, kann es durchaus im Domino Designer als Java-Agent oder als Agent mit importiertem Java entwickelt
160
4 Java-Anwendungen @ Domino
werden. Da jedoch ein IDE in der Regel wesentlich mehr Funktionen zum RAD bietet, ist es empfehlenswert, auf ein IDE zurückzugreifen. Insbesondere die bei vielen IDEs integrierten Möglichkeiten zum Refactoring, zu Code Completions, zur Versionskontrolle und zum Debugging machen diesen Schritt empfehlenswert. Anhand des frei erhältlichen IDEs Eclipse [Eclipse] soll näher auf den letzten Punkt, auf das Debugging, eingegangen und die Vorgehensweise beschrieben werden, mit der Sie serverbasierte Java-Agenten remote debuggen können. Eine Möglichkeit, in Domino Java-Agenten zu debuggen, ist der so genannte AgentRunner, der aber einige Schwierigkeiten mit sich bringt und natürlich als eleganteste Lösung die Entwicklung von Domino-spezifischen JUnit Tests. Auf beides soll später (Kapitel 19) noch eingegangen werden. Die einfachste und am schnellsten aufzusetzende, aber in der Domino 6.5 Hilfe nicht dokumentierte Möglichkeit ist, den Remote-Debugger der JVM anzusprechen, wie in Kapitel 19.4 beschrieben wird.
4.18
Der Domino Agent Manager
Agenten sind in Domino der Container für Programmcode, egal ob durch einen Trigger oder eine Periodik ausgelöst. Verantwortlich für die korrekte Verarbeitung und zeitliche Steuerung der Agenten ist der Agent Manager. Dies ist ein eigener Task im Domino-Server mit Namen amgr. Darüber hinaus ist der Agent Manager ebenfalls für einige Aspekte in der Verarbeitung von Servlets zuständig, insbesondere von Start und Stop dieser. Die JVM periodischer Agenten ist dem Task des Agent Manager zugeordnet, die JVM von Servlets die des HTTP-Tasks. Agenten sind in Domino streng reglementiert, nicht nur durch ACL und Agentenausführungsberechtigungen, sondern auch in ihrer Laufzeit. Diese wird für Tag und Nacht separat im Serverdokument des Servers (im Abschnitt „Server Tasks“ -> „Agent Manager“ -> „Daytime|Nighttime Parameters“ -> „Max LotusScript/Java execution time:“) freigegeben. Überschreitet ein Agent diese maximale Laufzeit, so wird der Agent Manager diesen Agenten stoppen. Bei Java-Agenten sollte unbedingt vermieden werden, dass der Agent Manager einen Agenten stoppt, denn leider ist es so, dass Java-Agenten auf diese Weise nicht sauber beendet werden. Es ist ratsam, im Zweifelsfall eine eigene Zeitmessung in einen Agenten einzubauen, wenn die Situation droht, dass ein Agent zu lange laufen könnte, um dann sauber durch die Applikation selbst beendet zu werden. Eine ähnliche Situation tritt für Web-Agenten ein, wenn ein Benutzer den Browser schließt, ohne die Verarbeitung abzuwarten. Diese Java-Prozesse laufen jedoch im Hintergrund bis zu ihrer ordentlichen Beendigung weiter. Der Agentmanger kann über die Dominokonsole und den Befehl „tell amgr schedule“ abgefragt werden und so die periodisch anstehenden Ausführungen von Agenten angezeigen. Informationen über Agenten erhält man in der Konsole mit dem Befehl show stat agent.
4.19 Domino-Java-Agent und -Anwendung – Konzepte im Vergleich
4.19
161
Domino-Java-Agent und -Anwendung – Konzepte im Vergleich
Die Domino-Objekte können durch Java auf zwei Wegen mit folgenden Techniken referenziert werden: Alleinstehende Java-Anwendung • Thread durch statische Methoden öffnen • NotesThread erweitern • Runnable implementieren Java-Agent im Domino Designer • AgentBase erweitern Beide Ansätze haben ihre Berechtigung und ihre jeweiligen Einsatzgebiete. Die Verwendung eines im Domino Designer erstellten Java-Agenten kommt immer dann zum Einsatz, wenn eine der von Domino zur Verfügung gestellten Funktionen, konfigurierbaren Sicherheitsrichtlinien oder Starttrigger zum Einsatz kommen sollen, also z.B. für einen Batch Job, der im Domino-Server ausgeführt werden soll, oder wenn Ausführungsdelegationen benötigt werden. Ein nicht zu unterschätzender Punkt ist auch die in jedem Agenten zum Einsatz kommende Signatur, durch die der Ausführende identifiziert werden kann, ohne dass ein Passwort hinterlegt oder durch einen Benutzer eingegeben werden müsste. Dies ist besonders interessant, wenn ein Agent periodisch auf einem Server ausgeführt wird. Eine alleinstehende Java-Anwendung kommt zum Einsatz, wenn keine Dominooder Notes Client-Umgebung zur Verfügung steht. Auch für eine alleinstehende JavaAnwendung, die eine Notes Session öffnet, wird ein Notes-Benutzername verwendet, so dass ebenfalls die in Tabelle 4-4 beschriebenen Ausführungseinschränkungen zum Zuge kommen. In den im Kapitel 4.6 ff beschriebenen Methoden eine Session aufzubauen wurden bisher nur lokale Sessions besprochen, die es nicht erlauben einen anderen als den lokal zur Verfügung stehenden Benutzer (des Notes Clients) für eine Verbindung zu verwenden. Diese Einschränkung entfällt für den Aufbau einer Remote Session (Kapitel 5.3.1). In beiden Fällen wird für die Verbindung zu einem Server jedoch immer ein Passwort benötigt, das dann im Klartext z.B. in einer Konfigurationsdatei gespeichert oder vom Benutzer eingegeben werden muss. Eine Zwischenstellung nimmt das Servlet (Kapitel 5.2) ein. Da es über den Browser gestartet wird, der eine Verbindung zu einem Domino-Server aufgenommen hat, wird, sofern das Servlet im Servlet Manager des Domino-Servers ausgeführt wird, für die auf dem Server als lokal angenommene Verbindung die Server-ID verwendet, d.h. der Server ist der Ausführende. Da es sich hier um eine lokale Verbindung handelt, wird kein explizites Passwort benötigt, sofern das Design der Anwendung es zulässt, dass alle Operationen mit der Server-ID ausgeführt werden und keine einzelnen Benutzer identifiziert werden müssen. Diese Möglichkeit steht natürlich auch über die Techniken des SSO (Single Sign On) zur Verfügung. Mehr dazu in Kapitel 5.3.5 und 16.3.
162
4 Java-Anwendungen @ Domino
Tabelle 4-6 Vergleich zw. Java-Anwendung und Java-Agent
Speicherort Ausführungseinschränkungen beziehen sich auf
Java-Anwendung mit Domino-Session stand alone lokaler bzw. angemeldeter Benutzer
Benutzername kann anhand einer Signatur ermittelt werden Zugriffsrechte werden ermittelt anhand
nein
Programmierung in Java IDE Trigger
direkt
Target
programmatische Selektion, alle Dokumente der Datenbank, alle Dokumente einer Ansicht
lokaler bzw. angemeldeter Benutzer
manuell
Java-Agent Domino-Datenbank über Anmeldung, Signatur, Startart und Delegationen ermittelter Benutzername ja
lokaler bzw. angemeldeter Benutzer, delegierter Benutzer oder Signatur indirekt bei Eintreffen von Mail, bei Update von Dokumenten, periodisch, manuell programmatische Selektion, alle Dokumente der Datenbank, alle Dokumente einer Ansicht, neue und geänderte Dokumente, ungelesene Dokumente, ausgewählte Dokumente
Die Tabelle 4-6 zeigt Java-Anwendungen und Java-Agenten im direkten Vergleich.
4.20
Zusammenfassung
Domino-Java-Anwendungen basieren im Wesentlichen auf einer Basisklasse, der Session, von der alle Domino-Java-Objekte direkt oder indirekt abhängen. Die Session kann auf verschiedene Art bezogen werden, wobei letztendlich das zugrundeliegende Anwendungsdesign entscheidend für die Auswahl ist. Neben Triggern und Targets können auch die für Domino so typischen granulären Rechte zugrundegelegt werden, wenn ein Domino-Java-Agent zum Einsatz kommt.
4.20 Zusammenfassung
163
Wird eine alleinstehende Java-Anwendung entworfen, gelten ebenfalls die Zugriffsrechte für den jeweils angemeldeten Domino-Benutzer, lediglich einige Techniken, wie Ausführungsdelegation, können nicht zum Einsatz kommen. Java kann innerhalb von Notes und Domino nicht nur für die Abarbeitung von benutzerunabhängigen Aufgaben, wie Batches, eingesetzt werden, sondern – zugegebenermaßen mit Hilfe einiger Tricks – für die Abarbeitung von Benutzeranforderungen. Nach der Vorstellung der lokalen Session für den Aufbau einer lokalen oder Server-Verbindung drängt sich die Frage auf, ob und wie Remote-Verbindungen zu einem Domino-Server hergestellt werden können. Diese Remote-Verbindungen werden in Domino in einigen Gesichtspunkten als so genannte Webverbindung betrachtet, so dass diese im folgenden Kapitel erläutert werden, das insgesamt die verschiedenen Techniken wie Web-Agenten und Servlets vorstellt. Im Zusammenhang mit Web-Anwendungen können dann die Stärken von Domino und Java voll ausgenutzt werden. Das GUI wird durch den Browser abgebildet und die Einschränkungen durch die fehlenden GUI-Schnittstellen bei der Programmierung für den Notes Client fallen weg.
5
5 Java-Web-Anwendungen @ Domino
In diesem Kapitel: Domino-Web-Agenten Servlets im Domino Servlet Manager Web-Agenten und Servlets im Vergleich Domino-Internet-Session, RemoteVerbindungen und DIIOP Single Sign On mit Domino und WebSphere Connection Pooling Die NotesFactory
166
5 Java-Web-Anwendungen @ Domino
Lotus Notes hat bereits recht früh mit den Versionen 4.0 und insbesondere mit Version 4.5 und 4.6 angefangen, Web-Anwendungen auf Basis von Domino zu unterstützen. Dieser Tatsache wurde in Version 4.5 Rechnung getragen, als der Lotus NotesServer in Lotus Domino-Server umgetauft wurde. Anfangs stand Domino für den HTTP-Task des Lotus Notes-Servers, der in der Lage war, Notes-Dokumente und Masken generisch und on the fly in HTML-Seiten zu rendern und im Browser darzustellen. Es wurde die Möglichkeit geschaffen, so genannte Web-Agenten zu programmieren, deren Ausgabe ähnlich einem CGI-Programm direkt auf den Browser geleitet wurde. Zusätzlich wurden Masken mit speziellen Triggern ausgestattet, mit denen die so genannten WebQueryOpen- und WebQuerySave-Agenten gestartet werden können, da die LotusScript-basierten Events für Open und Save beim Rendern von Masken im Browser nicht zur Verfügung stehen. Inzwischen hat Domino einen eigenen Servlet Manager, mit dem Servlets direkt im HTTP-Task betrieben werden können. Die Domtags Tag Library ermöglicht dem Programmierer den Einsatz von JSP bei der Programmierung von Domino-basierten Anwendungen, wobei JSP vom Domino-Server selbst nicht unterstützt wird, die Domtag Tags jedoch problemlos in einem Application Server wie WebSphere eingesetzt werden können, für den seit Version 6 auch eine spezielle Lizenz mit Domino mitgeliefert wird, die es erlaubt, sofern lediglich Domino als Datenbank zum Einsatz kommt, diesen Server neben Domino zu betreiben. Die Domtags Tag Library ist umfangreich und komfortabel einzusetzen, daher ist ihr ein eigenes Kapitel gewidmet (Kapitel 17.4). Die Möglichkeit, Ansichten, Seiten, Masken bzw. Dokumente im Browser anzuzeigen, wurde bereits im Kapitel 3.2.11 vorgestellt. Prinzipiell genügt ein Notes Client, um eine Vorschau von Dokumenten im Browser anzuzeigen. Über den Befehl „Actions“ -> „Preview in Browser“ können die meisten Designelemente direkt aus dem Notes Client im Browser geöffnet werden. Wird dieser Befehl aus einer lokalen Datenbank gewählt, so startet Domino einen lokalen Hilfs-HTTP-Task, über den die Seite angezeigt wird. Befindet sich die Datenbank auf einem Server, so wird das Designelement über den HTTP-Task des Domino-Servers aufgerufen. Dieses Kapitel beschreibt nun verschiedene Möglichkeiten und Techniken, um Domino-Web-Anwendungen zu erstellen, vor allem den Einsatz von Web-Agenten, Applets und Servlets. Es erläutert die verschiedenen Ansätze für den Aufbau der Notes-Session, die Basis aller Domino-Java-Anwendungen ist, geht bis hin zu den Details des Connections Poolings auf die Besonderheiten der Notes-Session ein und liefert somit die Grundlage für alle Java-Anwendungen, die Domino-Objekte einsetzen.
5.1
Domino-Web-Agenten
Die generische Umsetzung von Designelementen durch Domino für die Darstellung im Browser ist für Anwendungen, die gestalterisch und strukturell sehr nah am Konzept von Ansicht, Maske und Dokument angelegt sind, eine schnelle und einfache
5.1 Domino-Web-Agenten
167
Möglichkeit, Web-Anwendungen für Domino umzusetzen. Einen guten Überblick über die Möglichkeiten, eine Domino-Anwendung im Browser darzustellen, erhalten Sie, indem Sie in der lokalen Datenbank djbuch/djbuch.nsf einmal ein Dokument in der Browser-Vorschau anzeigen lassen, damit der lokale Hilfs-HTTP-Task gestartet ist, und dann die URL http://localhost/djbuch/djbuch.nsf/ aufrufen. Für komplexere Aufgaben, die entweder an die Berechnung oder das Layout einer Seite weiterreichende Anforderungen stellen, stößt die automatische Umsetzung jedoch regelmäßig an Grenzen. Das Ziel soll also sein, eine Möglichkeit zu finden, Seiten programmatisch zu generieren und dabei völlig frei in der Programmierung und unabhängig von einer automatisierten Umsetzung von Designelementen zu sein. Grundsätzlich sei an dieser Stelle schon erwähnt, dass hierfür mit Sicherheit ein Modell View Controller der richtige Ansatz ist, zumal sich Domino mit Hilfe der Domtags in dieses Konzept integrieren lässt. Leider unterstützt Domino JSP nicht im Domino-Server selbst, so dass hierfür ein zusätzlicher Application Server benötigt wird. Da dieses Thema ausführlicher Behandlung bedarf, soll es an dieser Stelle nicht vorweggenommen werden. Sie finden Näheres hierzu in den Kapiteln 16.2 und 17.4. Für einfache Anwendungen, insbesondere wenn keine JSP-Unterstützung benötigt wird, können mit Hilfe von Web-Agenten CGI-Programme in Domino mit Java entwickelt werden, mit denen bereits viele Aufgaben erledigt werden können. Domino stellt hierfür die nötigen Ressourcen bereit, um CGI-Variablen auslesen zu können und liefert einen Stream, mit dem eine Seite an den Browser gesendet werden kann. Ein Domino-Web-Agent kann einfach in Java programmiert werden und ist ein gewöhnlicher Domino-Agent, der jedoch seinen Output auf den stdout des Browsers liefert. Um einen Domino-Web-Agenten zu erstellen, legen Sie einen neuen Agenten im Domino Designer an, wählen Java als Typ des Agenten und stellen „Action Menü selection“ als Trigger und „none“ als Target ein. Einem Domino-Web-Agent, der in Java programmiert wird, steht über die AgentBase ein PrintWriter zur Verfügung, der mit der Methode getAgentOutput bezogen wird. Eine Ausgabe auf diesen PrintWriter wird an den Browser geleitet, eine Ausgabe aus System.out oder System.err wird dagegen in die Serverkonsole geschrieben. Eröffnen Sie jede Ausgabe auf den PrintWriter des Browsers zumindest mit der Ausgabe von „Content-Type: text/html\n\n“, um dem Browser einen Hinweis über den Inhalt der Seite zu geben – der Ansatz des Web-Agenten bietet keine andere Möglichkeit, HTTP Header zu schreiben. Über den Content-Type Header kann natürlich im Rahmen des HTTP-Protokolls z.B. auch eine XML-Seite oder sogar ein Bild angekündigt werden. Vor den beiden abschließenden „\n\n“ können auch Cookies gesetzt oder sonstige HTTP-Parameter an den Browser übermittelt werden. An dieser Stelle ist es allerdings wichtig, den RFC-Standard genau einzuhalten. Siehe insbesondere [RFC 2068, HTTP 1.1], [RFC 2109, Cookie] und [RFC 2965, Cookie2]. Leider kann in einem Domino-Web-Agent nicht der gesamte HTTP Header überschrieben werden, so kann z.B. die Statusmeldung HTTP/1.0 200 OK nicht geän-
168
5 Java-Web-Anwendungen @ Domino
dert werden, um Redirections oder Serverfehler anzuzeigen28. Dabei wird natürlich Domino im Falle von Fehlern oder nicht gefundenen Seiten etc. den Statuscode entsprechend anpassen, aber eben nur aus Sicht von Domino. Listing 5-1 zeigt einen einfachen Web-Agenten, der wie auch ein „normaler“ Agent AgentBase erweitert, seine session über getSession bezieht und aus dieser Session einen AgentContext beziehen kann. Um nun eine Ausgabe auf den Browser machen zu können, kann über getAgentOutput der PrintWriter bezogen werden, auf den diese Ausgaben geschrieben werden. Wenn außer dem Content-Type weitere HTTP-Header Informationen geschrieben werden sollen, kann dies im ersten print-Statement erfolgen. Es gelten grundsätzlich die gleichen Regeln wie bei einer gewöhnlichen CGI-Kommunikation, so muss z.B. die Content-length exakt der tatsächlichen Anzahl der ausgegebenen Bytes entsprechen, damit der Browser nicht etwa auf noch vermeintlich ausstehende Bytes wartet. Im Beispiel wird ein Cookie gesetzt, das bei Bedarf durch die Verwendung eines in der Vergangenheit liegenden expires-Wert wieder gelöscht werden kann. Der AgentContext liefert einige Informationen über den Agenten, wann und ob er schon einmal gelaufen ist und natürlich über den angemeldeten Benutzer. In Kapitel 4.16 wurde bereits ausführlich auf die Agentensicherheit eingegangen. Am Beispiel soll nochmals auf die Besonderheiten für die Ausführung im Browser eingegangen werden. Grundsätzlich gelten die bereits beschriebenen Regeln für die Ermittlung von Benutzer, Delegationen und effektiven Berechtigungen. Ist gewährleistet, dass die maximalen Web-Rechte zumindest Leserechte auf der Datenbank zulassen29, wird der Agent aufgerufen und der effektive Benutzer ermittelt. Der Beispielsagent AG_WebAgent in der Datenbank djbuch/djbuch.nsf wird weder als „Webuser“ ausgeführt (Eigenschaften – Sicherheit – „Run as web user“) noch ist eine Delegation („Run on behalf of“) eingetragen. Folglich zählt laut Kapitel 4.16 die Signatur des Agenten. Der hiermit ermittelte effektive Benutzer kann über die Methode getEffectiveUserName in AgentContext angezeigt werden . Das Beispiel kann zu Testzwecken auf zweierlei Art aufgerufen werden. Steht kein Domino-Server, sondern nur ein Notes Client zur Verfügung, kann der lokale HTTPHilfstask durch einen kleinen Trick, durch die Anzeige eines beliebigen Designelements mit „Preview in Browser“, gestartet und dann die URL http://localhost/ djbuch/djbuch.nsf/AG_WebAgent?OpenAgent im Browser aufgerufen werden. 28
29
Zumindest für einen Redirect gibt es einen Workaround: Ist die einzige Ausgabe eines Webagenten eine URL in eckigen Klammern – also z.B. getAgentOutput().println ("[http://www.djbuch.de]") –, dann wird Domino einen entsprechenden Header erzeugen und der Browser wird auf diese Seite umgeleitet. Die maximalen Web-Zugriffsrechte werden in der Zugriffskontrollliste einer Datenbank eingestellt und kontrollieren den Zugriff auf eine Datenbank über eine sogenannte Internet-Session, indem Sie den Zugriff auf eine maximale Zugriffsart (z.B. als Autor oder Leser) festlegen. Auch wenn ein Benutzer höhere Rechte in der ACL zugeteilt bekommt, werden seine Rechte durch diese Einstellung beschränkt. Diese Einstellung findet sich in der ACL der Datenbank im Bereich „Advanced“ und wird als „Maximum Internet-Name and Passwort“ bezeichnet.
5.1 Domino-Web-Agenten
169
import java.io.PrintWriter; import lotus.domino.*; public class WebAgent extends AgentBase { private static final String [] items = { "AUTH_TYPE","CGI_PATH_INFO","CONTENT_LENGTH","CONTENT_TYPE","HTTPS", "GATEWAY_INTERFACE","HTTP_ACCEPT","HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE","HTTP_CONNECTION","HTTP_HOST","REMOTE_USER", "HTTP_USER_AGENT","PATH_INFO","PATH_INFO_DECODED","PATH_TRANSLATED", "QUERY_STRING","QUERY_STRING_DECODED","REMOTE_ADDR","REMOTE_HOST", "REMOTE_IDENT","REQUEST_METHOD","SCRIPT_NAME","SERVER_ADDR", "SERVER_NAME","SERVER_PORT","SERVER_PROTOCOL","SERVER_SOFTWARE"}; public void NotesMain() { PrintWriter out = getAgentOutput(); Document docContext = null; try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); StringBuffer buf=new StringBuffer(); buf.append("Content-Type: text/html\n") //.append ("Content-length: 1876\n") .append("Set-Cookie: variable=Java+at+Domino;") .append("path=/my/Path;domain=192.168.1.201;") .append("expires=Thu 1-Jan-2099 00:00:00 GMT\n\n") .append("\n") .append("Domino-Web-Agent\n<style>\n") .append("\tbody {font-family:Helvetica,Arial,sans-serif;}\n") .append("\th1 {color:#FF00FF;}\n\n\n") .append("\nHerzlich Willkommen\n") .append("\nDies ist ein Domino-Web-Agent\n") .append("
AgentContext: ") .append((agentContext == null ? " nicht vorhanden " : agentContext.getEffectiveUserName())) .append("
User: " + session.getUserName()); out.println (buf.toString()); docContext = agentContext.getDocumentContext(); if (docContext == null) { out.println ("Kein Context Dokument vorhanden."); } else { out.println("
ContextDocument (created): " + docContext.getCreated().toString()); for (int i = 0, k = items.length; i < k; i++) { out.println("
ContextDocument (" + items[i] + "): " + docContext.getItemValueString(items[i])); } } out.println ("\n\n\n"); } catch(Exception e) { e.printStackTrace(out); } finally { if (docContext != null) { try { docContext.recycle(); } catch (NotesException e1) { e1.printStackTrace(); } } } } }
Listing 5-1 Einfacher Domino-Web-Agent in Java
170
5 Java-Web-Anwendungen @ Domino
Die Direktive OpenAgent ist eine so genannte Domino-URL, mit der dem DominoServer mitgeteilt wird, dass es sich bei dem zu öffnenden Element um einen Agenten handelt. Ist der Name des Agenten eindeutig, d.h. gibt es kein anderes beliebiges Designelement mit diesem Namen, kann dieser Zusatz auch weggelassen werden. Im Beispiel wird zur Verdeutlichung der Situation neben dem effektiven Benutzer auch derjenige Benutzer angezeigt, der die Session geöffnet hat (session.getUserName). Bei dem lokalen Aufruf wird als effektiver Benutzer die Signatur des Agenten und als User der Session der im Notes Client angemeldete Benutzer angezeigt. Wird das Beispiel über einen Server angezeigt, ist der effektive Benutzer ebenfalls die Signatur des Agenten und der User der Session ist die ID des Domino-Servers, da der HTTPTask im Domino-Server läuft. Für den Aufruf von Web-Agenten gilt grundsätzlich, dass der Server der Eigentümer der Session ist, sofern er über einen Domino-Server angezeigt wird. Ein Web-Agent hat wie jeder Domino-Agent ein Kontextdokument , allerdings nicht, wie Sie es bisher kennen gelernt haben, etwa ein Dokument, das ein Benutzer zuvor ausgewählt hat, sondern ein spezielles Dokument, das für jeden Aufruf des Browsers neu generiert wird. Dieses Kontextdokument ist vergleichbar mit einem Session-Objekt, wie es z.B. aus dem Servlet Framework bekannt ist. In diesem Dokument sind sämtliche zur Verfügung stehenden CGI-Variablen als Items abgelegt und können entsprechend über getItemValue oder getItemValueString abgerufen werden . Domino stellt neben den üblichen CGI-Variablen noch die beiden Items „PATH_INFO_DECODED“ und „QUERY_STRING_DECODED“ zur Verfügung (Abb. 5-
Abb. 5-1 Ausgabe der CGI-Variablen aus dem AgentContext
5.2 Servlets
171
1), die beide die gleich lautenden CGI-Variablen, jedoch nach einer URL-Decodierung, enthalten. Für die Java-Programmierung spielen diese Variablen jedoch keine Rolle, da entsprechende Klassen zur Verfügung stehen, um diese Decodierung durchzuführen. Den Beispielsklassen und der Beispielsdatenbank wurde ein Agent AG_WebAgentXML hinzugefügt, der einen typischen Anwendungsfall für Web-Agenten darstellt. Mit Hilfe von Java werden Notes-Dokumente aus einer Datenbank ausgelesen und in einem spezifischen XML-Format als XML Feed wieder ausgegeben (s. Abb. 52). Das Beispiel kann unter der URL http://localhost/djbuch/djbuch. nsf/ AG_WebAgentXML?OpenAgent aufgerufen werden.
Abb. 5-2 Domino-Daten als XML-Feed über einfachen Web-Agenten
5.2
Servlets
Der Schritt vom Web-Agenten zum Servlet ist nur noch klein. Ebenso wie ein CGIProgramm ähneln auch Web-Agenten Java-Servlets, ohne jedoch die Vorteile des Servlet Frameworks nutzen zu können. Weiterführende, nicht Domino-spezifische Informationen zu Servlets finden sich z.B. in dem hervorragenden Buch zur ServletProgrammierung von Jason Hunter und William Crawford [Hunter, Servlet]. Die Programmierung eines Servlets, das Domino-Objekte einsetzt, ist ebenso einfach und straightforward wie auch die Programmierung von Java-Applets oder Java-Applikationen für Domino, wie dies in den vorigen Kapiteln beschrieben wurde.
172
5 Java-Web-Anwendungen @ Domino
Folglich ist auch die Umwandlung eines Web-Agenten in ein Servlet keine große Sache, wobei bei der Umwandlung einige Besonderheiten zu beachten sind, auf die später noch eingegangen wird. Der entscheidende Unterschied zwischen einem Servlet und einem Web-Agenten ist neben den unterschiedlichen Klassen, die erweitert werden müssen, die Tatsache, dass Domino für ein Servlet eine eigene (und damit unabhängige) Instanz der JVM, genauer gesagt des ClassLoaders, verwendet. Dies hat nicht nur zur Folge, dass z.B. statische Klassen-Variablen, die in einem Servlet gesetzt wurden, in einem Java-WebAgenten nicht sichtbar sind (und umgekehrt). Auch ist es nicht zulässig, Klassen, die im einen Kontext geladen wurden, im anderen zu benutzen, was zu Security Exceptions führt. Folgerichtig hat der Servlet Manager auch eigene Einstellungen für den Classpath (s. Abb. 5-3). Bevor Servlets auf einem Domino-Server eingesetzt werden können, müssen einige administrative Einstellungen und Vorbereitungen vorgenommen werden:
Abb. 5-3 Servlet Manager im Serverdokument
5.2 Servlets 1
2
3
4
173
Zunächst wird natürlich ein Domino-Server benötigt. Für diesen muss im Serverdokument für diesen Server der Servlet Manager aktiviert werden. Öffnen Sie das Serverdokument in der Datenbank names.nsf, dort die Sektion „Internet Protocols“ und dort wiederum die Sektion „Domino Web Engine“. Im Abschnitt „Java-Servlets“ kann der Servlet Manager im Feld „Java Servlet Support“ gewählt werden. Speichern und schließen Sie das Serverdokument. Tragen Sie im Feld Classpath den Wert „domino\servlet; domino\servlet\ DominoServlet.jar“ ein. Einzelne Werte werden hier durch ein Semikolon getrennt. Jar-Dateien müssen einzeln als Datei angemeldet werden. Speichern und schließen Sie das Serverdokument. Alternativ kann die Verzeichnisstruktur des Packages innerhalb eines im Classpath angemeldeten Verzeichnisses abgebildet werden, wenn auf die Kapselung in einer Jar-Datei verzichtet werden soll. Für diesen Fall muss dann z.B. innerhalb des Verzeichnisses domino\servlet das Verzeichnis djbuch und darin das Verzeichnis kapitel_05 angelegt werden und die Datei DominoServlet.class enthalten. Im Classpath darf dann der Eintrag „domino\servlet“ nicht fehlen. Dies ist erst seit Version 6 von Domino möglich. Compilieren30 Sie die Klasse djbuch.kapitel_05.DominoServlet und erzeugen Sie eine Jar-Datei mit Namen DominoServlet.jar. Legen Sie die Jar-Datei ins Verzeichnis \domino\ servlet, in der Regel ist dies C:\lotus\domino\data\domino\servlet. Das Verzeichnis „servlet“ müssen Sie neu anlegen. Um eine Servlet-Klasse innerhalb eines Packages ansprechen zu können, muss es in der Datei „servlets.properties“ angemeldet werden. Dort wird zumindest ein Alias für den Aufruf des Servlets vergeben: # Vergabe eines Alias Namens servlet..code=[.class] # Start Parameter servlet..initArgs==<Wert>, [=<Wert>] # Laden des Servlets beim Startup des HTTP servlets.startup= z.B. servlet.servletalias.code= djbuch.kapitel_05.DominoServlet.class servlet.servletalias.initArgs=arg1=ein wert,
30
Am besten, Sie richten Ihren ClassPath für Ihren Java-Compiler so ein, dass er die JVM von Domino verwendet. Wenn Sie auf Ihrem lokalen Computer eine Domino-Server-Installation vorliegen haben, können Sie die JVM im Promgrammverzeichnis von Domino (z.B. unter C:\Lotus\Domino\jvm) anmelden. Zusätzlich müssen Sie, um das Servlet compilieren zu können, im Classpath das servlet Package, das Sie z.B. im Domino-Programmverzeichnis in der Datei jsdk.jar und die Domino-Klassen, die Sie z.B. im Domino-Dataverzeichnis unter \domino\ java als NCSO.jar bzw. seit Version 7 im Verzeichnis \jvm\lib\ ext\Notes.jar finden, angeben.
174
5 Java-Web-Anwendungen @ Domino arg2=noch ein wert servlets.startup=servletalias
5
Die Datei servlets.properties muss sich im Dataverzeichnis des Domino-Servers befinden. Achten Sie sowohl beim Namen dieser Datei, als auch bei den Einträgen auf Groß- und Kleinschreibung. Starten Sie den Domino-Server neu. Der Neustart ist notwendig, um die Änderung an der Einstellung „Java Servlet Support“ durchzusetzen. Änderungen der Jar-Datei, an den übrigen Servlet-Einstellungen im Serverdokument oder in der Datei servlets.properties können aktiviert werden, indem der HTTP-Task mit dem Konsolenbefehl tell http restart durchgestartet wird.
Zusätzlich zu den genannten Einstellungen können noch folgende weitere Parameter bezüglich des Servlet Managers eingestellt werden: 1 2
3
4 5 6
Servlet URL path – String mit dem Beginn von URLs, unter denen Servlets erkannt werden. Servlet file extensions – Servlets können auch außerhalb dieses Pfades anhand einer Datei-Endung erkannt werden, die dann hier angegeben werden muss. Jede Datei-Endung muss mit der folgenden Direktive in der Datei servlets.properties auf eine Klasse gemappt werden: servlet..extention=<extention> <extention> Session state tracking – Ist diese Einstellung aktiv, wird Domino überprüfen, ob eine Http Session, die durch ein Servlet aktiviert wurde, noch Benutzer-Interaktion verzeichnet, und diese nach einer definierten Ablaufzeit schließen. Http Session meint eine Session, die unter der Verwendung von HttpSession entsteht, keine Domino-Session. Idle session time-out – Ablaufzeit für die durchs Servlet geöffnete Http Session. Maximum active sessions – Maximale Anzahl gleichzeitiger Http Sessions. Session persistence – Ist die Session persistance aktiviert, so werden die Inhalte von Http Sessions im Dateisystem zwischengespeichert und bleiben somit auch nach einem Server-Neustart aktiv.
5.2.1
Servlets im Domino Servlet Manager
Domino setzt mit seinem Servlet Manager auf die Standards der Sun Implementation auf, so dass Servlets unter Domino wie unter jedem anderen Servlet Manager eingesetzt werden können, wobei natürlich die etwas ungewohnte Art der Konfiguration über das Serverdokument und die Datei servlets.properties berücksichtigt werden muss. Leider unterstützt Domino nur den etwas betagten Standard Servlet 2.0. Wird ein neuerer Standard benötigt, so kann dies über einen externen Servlet Manager erreicht werden (s. Kap. 16.1ff.). Hierfür kann z.B. der Application Server WebSphere verwendet werden, für den Lotus in der Domino-Server-Lizenz eine entsprechende Web-
5.2.1 Servlets im Domino Servlet Manager
175
Sphere-Lizenz mitliefert, sofern in WebSphere nur Domino als Datenbank dient. Im Kapitel 16.2.3 wird aber aufgezeigt werden, wie auch jeder andere J2EE-Server eingesetzt werden kann, um Java-Anwendungen für und mit Domino zu betreiben. Listing 5-2 zeigt das typische Gerüst eines Domino-Servlets. Für die Entwicklung von Servlets stellt Domino zwar den Servlet Manager zur Verfügung, aber das Servlet selbst wird auf die Standardklassen von Sun aufgebaut, was nicht nur sehr angenehm ist, sofern man bereits eingearbeitet ist, sondern hierdurch steht das gesamte Servlet Framework im gewohnten Umfang zur Verfügung. Folgerichtig erweitert die Klasse DominoServlet im Beispiel die Klasse HttpServlet und definiert die beiden Methoden doGet und doPost , um die POST und GET Events der HTTP-Aufrufe abfangen und abwickeln zu können. Der Einfachheit halber ruft im Beispiel doPost wiederum die Methode doGet auf, so dass in beiden Fällen der gleiche Code ausgeführt wird. Eine Web-Anwendung, die mit einem Servlet realisiert wird, benötigt einen OutputStream, auf den es seine Ausgaben an den Browser senden kann. Im Servlet kann ein PrintWriter oder direkt der OutputStream über getWriter bzw. getOutputStream in HttpServletResponse bezogen werden . Das Objekt HttpServletResponse erhält die Methoden doGet und doPost direkt über das Servlet Framework bei der Instanzierung des Servlet-Objekts. In diesen Punkten unterscheidet sich ein Domino-Servlet nicht von einem gewöhnlichen Servlet. Um nun eine session und somit die Verbindung zum Domino-Server herzustellen, müssen – insbesondere da ja eine Servlet-Klasse bereits HttpServlet erweitert – die statischen Methoden zur Initialisierung und Beendigung eines Notes Threads sinitThread und stermThread verwendet und eine session über die NotesFactory erzeugt werden. Durch diese Vorgehensweise wird der Thread des Servlets in einen NotesThread erweitert und kann sich mit einem Domino-Server verbinden. Wird das Servlet über den Domino Servlet Manager betrieben, befindet es sich im lokalen Kontext des Domino-Servers, folglich kann die Verbindung durch createSession () ohne Parameter geöffnet werden, so dass die Verbindung im Namen des aktuellen Users der lokalen Installation geöffnet wird. Im Serverkontext ist dies der Server selbst, was sich durch die Anzeige des aktuellen Users der session deutlich machen lässt . Beachten Sie, dass createSession ohne Parameter nur dann verwendet werden kann, wenn die Server-ID nicht durch ein Passwort geschützt ist. Ist die ServerID passwortgeschützt, muss die (lokale) Session mit createSession ((String) null, (String) null, String password) geöffnet werden. Hierbei wird der Benutzer so angemeldet, dass Leser- und Autorenfelder respektiert werden, d.h. über eine derartige Session können solche Dokumente nicht gelesen werden, für die ein Eintrag in einem Leserfeld nicht vorliegt. Die Signatur createSessionWithFullAccess () und createSessionWithFullAccess (String password) öffnet eine Verbindung, in der alle Dokumente unabhängig von Leser- und Autorenfeldern, aber unter Berücksichtigung der Zugriffskontrollliste gelesen und bearbeitet werden können.
176
5 Java-Web-Anwendungen @ Domino
package djbuch.kapitel_05; import java.io.*; import java.util.Calendar; import javax.servlet.http.*; import lotus.domino.*; public class DominoServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { Session session = null; PrintWriter out = res.getWriter(); try { NotesThread.sinitThread(); session = NotesFactory.createSession(); HttpSession httpSession = req.getSession(true); res.setContentType("text/html"); out.println ("\n" + "Domino-Servlet\n<style>\n" + "\tbody {font-family:Helvetica,Arial,sans-serif;}\n" + "\th1 {color:#FF00FF;}\n\n\n" + "\nHerzlich Willkommen\n" + "\nDies ist ein Domino-Servlet\n"); out.println("
Request Methode = " + req.getMethod()); out.println("
\n"); out.println("" + "\n"); out.println("
QueryString: " + req.getQueryString()); out.println("
Param: " + req.getParameter("parameter")); out.println("
Java Version: " + System.getProperty("java.version")); out.println("
Java JVM Hersteller: " + System.getProperty("java.vendor")); out.println("
Java Installationsverzeichnis: " + System.getProperty("java.home")); out.println("
Session isNew: " + httpSession.isNew() + " / " + httpSession.getValue("lastaccess")); httpSession.putValue("lastaccess", "Ich war hier: " + Calendar.getInstance().getTime()); out.println("
Domino-User: " + session.getUserName()); out.println("
Init Parameter arg1: " + this.getServletConfig().getInitParameter("arg1")); out.println("
Init Parameter arg2: " + this.getServletConfig().getInitParameter("arg2")); out.println ("\n\n\n"); out.flush();out.close(); } catch (NotesException e) { e.printStackTrace(); } finally { if (session != null) { try {session.recycle(); } catch (NotesException e1) {e1.printStackTrace();} } NotesThread.stermThread(); } } public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException { doGet(req, res); } }
Listing 5-2 Einfaches Domino-Servlet
5.2.2 Domino-Web-Agenten versus Domino-Servlets
177
Ruft man sich die Konstellation eines Domino-Web-Agenten vor Augen, entspricht diese Art der Anmeldung einem Domino-Web-Agenten, für den die Eigenschaft „run as web user“ nicht gesetzt ist. Domino stellt die über die Datei servlets.properties definierten Parameter dem Servlet zur Verfügung, so dass diese über getInitParameter in HttpServlet ermittelt werden können . Für eine HTTP-Sessionverwaltung musste im Web-Agenten mit dem Kontext Document gearbeitet werden. Für ein Servlet steht wie gewohnt die HttpSession zur Verfügung, und kann entsprechend genutzt werden. Sie wird über getSession in HttpServletRequest bezogen. Eine HttpSession kann mit putValue und getValue mit Werten bestückt werden, isNew zeigt an, ob die Session neu ist . Verwechseln Sie in keinem Fall die HttpSession mit einer Session aus lotus.domino.Session. Im Domino-Server kann HttpSession nur verwendet werden, wenn diese im Serverdokument (s. Kapitel 5.2) aktiviert ist. Wurde im Serverdokument die Persistenz aktiviert, bleiben Http-Session-Daten auch nach einem Server- oder HTTP-Neustart erhalten. Sieht man von dem etwas veralteten Servlet-Standard 2.0 ab, so können also im Domino Servlet Manager sehr einfach Servlets betrieben werden, die vollen Zugriff auf alle Domino-Objekte erlangen, da alle Domino-Objekte direkt oder indirekt von einer Notes-Session abhängen.
5.2.2
Domino-Web-Agenten versus Domino-Servlets
Welches der beiden Konzepte – Domino-Web-Agent oder Domino-Servlet – zum Einsatz kommen soll, hängt von verschiedenen Parametern ab. Der Domino-Web-Agent ist voll in die Domino-Infrastruktur integriert und profitiert daher von allen hier zur Verfügung stehenden Mechanismen. An erster Stelle ist die Replikation zu nennen, die eine Distribution einer Anwendung aus Web-Agenten in einer Domino-Infrastruktur sehr einfach gestaltet, da lediglich Domino-Datenbanken zu verteilen und deren Replikation zu aktivieren ist. Servlets dagegen müssen separat verwaltet und manuell über die Dateisysteme verteilt und auch auf jedem Server separat administriert werden (servlets.properties). Der zweite wichtige Punkt, der zu berücksichtigen ist, ist die Sicherheit. Ein WebAgent startet automatisch im Dominokontext, kennt also alle zugehörigen Sicherheitsmechanismen. Ein Servlet läuft ähnlich wie ein Web-Agent zunächst per Default unter der ID des Servers. Soll anhand des Usernamens eines angemeldeten Users eine Session aufgebaut werden, so ist hier etwas mehr Aufwand zu betreiben (s.u. 5.3.2ff.), aber es stehen ebenfalls alle Sicherheitsmechanismen zur Verfügung. Ein großer Vorteil des Servlets ist, dass es, ist es erst einmal geladen, immer im Hauptspeicher bleibt, so dass Ladezeiten für die Klassen entfallen, ganz im Gegensatz zu einem Web-Agenten, für den nicht nur der ClassLoader neu gestartet werden muss – jeder Web-Agent erhält seinen eigenen ClassLoader – , sondern auch die Klassen selbst neu geladen werden müssen. Dies hat nicht nur (erhebliche) Konsequenzen für
178
5 Java-Web-Anwendungen @ Domino
die Laufzeit, sondern auch für die Programmierung. Dadurch, dass ein Web-Agent bei jedem Aufruf in einem eigenen ClassLoader jeweils neu geladen wird, stehen statische Klassen-Variablen nur dieser einen Instanz des Web-Agenten zur Verfügung, können also nicht verwendet werden, um z.B. möglicherweise teuer berechnete Konfigurationen oder Einstellungen über mehrere Instanzen zu teilen. Beim Beenden eines Web-Agenten werden alle geladenen und erzeugten Objekte der Garbage Collection übergeben und diese (in der Regel) auch sofort ausgeführt. Hieraus ergibt sich direkt eine wichtige Konsequenz für den Einsatz von WebAgenten: Nur schlanke, schnelle Anwendungen sollten über Web-Agenten abgebildet werden, sonst wird wegen des relativ hohen Aufwandes einer Erzeugung und Beendigung einer Instanz ein erheblicher Engpass erzeugt. In Stresstests gegen eine Anwendung, die zunächst über Web-Agenten realisiert war und dann in Servlets überführt wurde, konnte ein Performancegewinn von rund 35% gemessen werden, wobei dieser Wert im Wesentlichen dadurch beeinflusst wurde, dass für die Anwendung große Bibliotheken geladen werden mussten, die für einen Web-Agenten für jeden Aufruf zunächst aus den Designelementen (Bibliothek oder Agent) extrahiert und über den ClassLoader geladen werden müssen. Werden differenzierte Anmeldemechanismen benötigt, so sollten dennoch auch bei aufwändigeren Web-Anwendungen Servlets verwendet werden, auch wenn hier etwas Mehraufwand zu erwarten ist. Umgekehrt ergibt sich aber auch folgender Punkt, der unbedingt berücksichtigt werden muss: Da ein Servlet (bis es sich die Garbage Collection anders überlegt) immer im Hauptspeicher verbleibt, muss das Recycling der Domino-Objekte unbedingt beachtet werden, damit nicht die speicherintensiven C++ Backend-Objekte (s. Kap. 14) im Hauptspeicher verbleiben. Wird ein Servlet über DIIOP mit dem Domino-Server verbunden, ergibt sich eine weitere Stärke: Über ORB-Objekte kann ein Connection Pooling realisiert werden (s. Kap. 5.3.6). Tabelle 5-1 gibt einen kleinen Überblick über verschiedene Aspekte für den Vergleich von Web-Agenten und Servlets.
Tabelle 5-1 Web-Agent und Servlet im Vergleich
Deployment
Administration
Recycling
Web-Agent
Servlet
Nativ über Replikation, da Bestandteil einer Domino-Datenbank Über Notes-Adressbuch (Sicherheit, Delegation) und Datenbank (ACL, Agentensicherheit)
Als Class oder Jar-Datei im File System
Über Datei servlets.properties und Notes-Adressbuch (ClassPath, Allgemeine Zugriffsrechte) und Datenbank (ACL) Weniger kritisch, da für jeden Auf- Dringend erforderlich, da die ruf am Ende die GC angestoßen Klassen im Hauptspeicher bleiwird ben.
5.2.3 Login und Internet-Session für Web-Agenten und Servlets
Life Cycle
Session Management Verändern von HTTP Headern Einsatzbereich
Web-Agent
Servlet
Läuft in jeweils eigenem ClassLoader. Kompletter Life Cycle, vom Laden der Klassen bis zur Garbage Collection Über Kontextdokument
Läuft im ClassLoader und der JVM des HTTP-Tasks des Domino-Servers. Klassen verbleiben im Hauptspeicher über HttpSession
Eingeschränkt über out.println In vollem Umfang über das Statements möglich Servlet Framework Leichtgewichtige Web-Anwen- Beliebig dungen; falls direkter DominoKontext erforderlich; falls Replikation erwünscht nicht möglich möglich
Connection Pooling Pro Instanz Gültigkeit statischer Klassenvariablen Verwendung exter- Über Bibliotheken (Deployment ebenfalls über Replikation) oder ner Klassen über Jar-Dateien im Dateisystem (s. Kap. 5.4 und 19.8) Kurzfristiger, unkomplizierter SeTypisierung tup und Entwicklung kleiner Web-Anwendungen im DominoKontext
Entwicklung Debugging Anmeldung erzwingen Performance
5.2.3
179
Im Domino Designer oder IDE Über System.out.println über URL-Parameter ?login
Über alle Instanzen der Klasse hinweg Über Jar-Dateien im Dateisystem
Aufbau komplexer aber schneller und belastbarer Web-Anwendungen mit gleichzeitigem Zugriff auf Domino-Objekte, die leicht auf externe Application Server (Clustering) übertragen werden können Im IDE Im IDE Indirekt über names.nsf
Nur für leichtgewichtige Anwen- Deutlicher Performancegewinn dungen geeignet gegenüber dem Web-Agenten, da das jeweils erneute Laden der Klassen und mögliche Initialisierungen entfallen.
Login und Internet-Session für Web-Agenten und Servlets
Die verschiedenen Situationen und Möglichkeiten, die sich für Web-Agenten und Servlets bieten, wurden zuvor beschrieben. Nun stellt sich noch die Frage, wie eine Anmeldung für den Benutzer, der eine Anwendung über den Browser benutzt, hergestellt werden kann. Für Web-Agenten stellt sich dies recht einfach dar, denn die URL, mit der der Agent im Browser aufgerufen wird, kann um den URL-Parameter „&login“ ergänzt werden, um ein Login zu erzwingen. Domino fordert dann zur Eingabe eines Userna-
180
5 Java-Web-Anwendungen @ Domino
mens und Passwortes auf. Zugelassen sind für diesen Fall Benutzer aus dem Domino Directory, die ein so genanntes Internet-Passwort haben, das im Personendokument des Benutzers eingegeben werden kann. Der Benutzername eines solchen Benutzers wird in der CGI-Variable REMOTE_USER angezeigt. Ist die Variable REMOTE_USER null, so spricht Domino auch von dem Benutzer „Anonymous“. Hier ist noch eine sehr praktische Eigenschaft des Domino-Servers erwähnenswert. Wird eine Ressource aufgerufen, für den Anonymous, oder auch ein angemeldeter Benutzer, keine Zugriffsrecht hat, so wird der Request automatisch auf eine Domino-Login-Maske redirected. Dies gilt für Designelemente wie Masken, Seiten, Ansichten, aber auch Dokumente, sofern sie direkt im Browser angesprochen werden. Dies kann direkt über den Namen des Designelements in der URL erfolgen, indem dieser nach dem Datenbanknamen und einem Slash „/“ verwendet wird, in der Art „http://< >/Pfad/zur/DominoDatenbank.nsf/Name_des_Gestaltungselements“. Leider kann diese Vorgehensweise nicht auf Web-Agenten übertragen werden. Für Web-Agenten kann zwar die Eigenschaft „Default access for viewing and running this agent“ gesetzt werden, hat aber ein Benutzer keine entsprechenden Rechte, dann bleibt dieser Agent lediglich „unsichtbar“ und ein Aufruf wird mit einer 404Fehlermeldung vom Server quittiert, d.h. es muss, sofern ein angemeldeter Benutzer gewünscht ist, immer der oben skizzierte Weg über den URL-Parameter „login“ beschritten werden. Wird ein Web-Agent ohne die Eigenschaft „Run as web user“ über den Browser gestartet, hat ein angemeldeter Benutzer aus Sicht von Domino zunächst keine Bedeutung außer dem Eintrag in der CGI-Variable REMOTE_USER, denn der Code des Agenten wird über die Server-ID ausgeführt. Erst wenn in den Eigenschaften des Web-Agenten die Eigenschaft „Run as web user“ markiert ist, ist ein derart angemeldeter Benutzer gleichzeitig der effektive Benutzer und sein Name wird zur Ermittlung der Berechtigungen herangezogen. In der Praxis hat es sich oft bewährt, diese Eigenschaft nicht zu vergeben, sondern den Agenten tatsächlich mit der Server-ID oder unter der Signatur eines FunktionUsers zu betreiben, wobei hierfür die Eigenschaft „Run on behalf of“ genutzt werden oder der Agent mit einer entsprechenden ID signiert werden muss. Dann kann auf diese Art Zugriff auf Unternehmensdaten gewährt werden, ohne allen (angemeldeten) Web-Usern auf diese Daten direkt Zugriffsrechte einräumen zu müssen. Die CGI-Variable REMOTE_USER kann dann zusätzlich herangezogen werden, um für jeden Einzelnen z.B. über Profile in Notes-Dokumenten weitere Einschränkungen der Rechte vorzunehmen. Diese Vorgehensweise ergibt sich für ein Servlet zunächst automatisch, wenn entsprechend dem obigen Beispiel-Servlet vorgegangen wird, das mit den Rechten der Server-ID ausgeführt wird. Die Initiierung eines Logins über einen URL-Parameter ist nicht möglich, da dies im Servlet Framework nicht vorgesehen ist, kann jedoch recht einfach nachempfunden werden. Zunächst müssen Sie sich für eine Methode entscheiden, mit der ein Benutzer nach Eingabe von Benutzername und Passwort am Server angemeldet werden soll. Hierfür stehen verschiedene Signaturen von createSession zur Verfügung, die in
5.2.3 Login und Internet-Session für Web-Agenten und Servlets
181
den folgenden Kapiteln beschrieben werden. Die wichtigsten sind createSession ("", Benutzer, Passwort) und createSession (String host, HttpServletRequest req). Erstere eröffnet eine so genannte Internet-Session mit den definierten Parametern als Benutzername und Passwort. Sind Benutzername und Passwort leere Strings, wird eine anonyme Internet-Session aufgebaut.
• •
•
•
•
Für eine Internet-Session sind einige Regeln zu beachten. Als Internet-Session werden alle Verbindungen (Sessions) zu einem DominoServer bezeichnet, die nicht unter Benutzung einer Notes-ID hergestellt wurden. Internet-Sessions werden entweder direkt über den Browser oder über die Verwendung von DIIOP hergestellt. Das Öffnen einer Notes-Maske oder die Ausführung eines Web-Agenten als Web-User („Run as web user“) ist z.B. eine InternetSession, oder auch die Session, die über createSession (Server, Benutzer, Passwort) geöffnet wurde, ist eine Internet-Session. Zusätzlich zur DIIOP-Verbindung gibt es noch die so genannte lokale InternetSession, die über die Signatur createSession (String server, String user, String pwd) oder createSession ("", HttpServletRequest) aufgebaut wird, wobei Server der leere String "" sein muss. Eine derartige Session wird ohne DIIOP aufgebaut, kann aber nur auf einem Server und in dessen Kontext, nicht über einen Client oder eine Standalone-Java-Anwendung aufgebaut werden. Eine Internet-Session kann entweder anonym sein oder für einen Benutzer, der einen Eintrag im Domino Directory hat und dem im Personendokument ein Internet-Passwort vergeben wurde, oder für einen Benutzer, der dem Domino-Server auf eine andere Art bekannt ist (z.B. über die Anbindung externer LDAP-Verzeichnisse), aufgebaut werden. Anonyme Internet-Sessions müssen für den Server erlaubt sein, um hergestellt werden zu können. Datenbanken können über ihre ACL den Zugriff für Internet-Sessions programmatisch einschränken, indem in den erweiterten Einstellungen der Zugriff für Internet-Sessions auf eine bestimmte Rolle (Leser, Autor usw.) limitiert wird. Dies ist eine häufige Fehlerquelle, so dass diese Eigenschaft bei Autorisierungsproblemen über Internet-Sessions immer geprüft werden muss.
Während über createSession ("", Benutzer, Passwort) eine einfache Eingabe der Benutzerdaten über ein Web-Formular erfolgen kann, wird bei der Signatur createSession (HttpServletRequest) der bereits im Request angemeldete Benutzer zur Authentifizierung herangezogen. Wie ein Benutzer gegen einen Domino-Server zu authentifizieren ist, hängt von den Servereinstellungen ab. Im Serverdokument kann in der Rubrik „Internet Protocols“ -> „Domino Web Engine“ im Feld „Session authentication“ zwischen den Einstellungen „Multiple Servers (SSO)“, „Single Server“ und „disabled“ gewählt werden. Ist die Session-Authentifizierung abgeschaltet („disabled“), so erfolgt eine Authentifizierung mit dem Domino-Web-Server über den „Basic“-Mechanismus, der den im RFC für HTTP1.1 [RFC 2068, HTTP 1.1] definierten Mechanismus für die „Basic HTTP Authentication“ implementiert. Dieser Mechanismus tauscht Authentifizierungsinformationen über entsprechende Header aus.
182
5 Java-Web-Anwendungen @ Domino
Im Servlet kann über eine einfache Abfrage des Headers „Authorisation“ geprüft werden, ob der Benutzer bereits authentifiziert ist, und im Falle, dass nicht, der Browser über einen entsprechenden Response Header aufgefordert werden, sich zu authentifizieren. Die Vorgehensweise ist hier übrigens völlig unabhängig vom Domino-Server, sondern wird gelöst, wie dies auf jedem anderen Servlet Manager auch der Fall wäre. Der Code if (req.getHeader("Authorization") == null) { res.setStatus(HttpResponse.SC_UNAUTHORIZED); res.setHeader("WWW-Authenticate", "Basic"); } fragt im Request-Objekt des Servlets ab, ob bereits ein Authentifizierungsheader vorliegt und setzt im Response-Objekt den Status SC_UNAUTHORIZED, falls nicht. Dieser Response Header hat zur Folge, dass der Browser ein Login-Fenster öffnet, in dem der Benutzer seine Anmeldedaten eingeben kann. Nun kann das Request-Objekt des Servlets verwendet werden, um eine lokale (oder über DIIOP Remote-) Internet-Session aufzubauen. Übrigens können Benutzername und Passwort aus dem Header „Authorization“ durch einen Base64 Decoder extrahiert werden. Im decodierten Header sind Benutzername und Passwort durch einen Doppelpunkt getrennt. Soll eine Authentifizierung über die Domino-Session-Authentifizierung erfolgen, also über die Vergabe eines Session Cookies durch den Domino-Server, um Single Sign On zu ermöglichen, so muss etwas anders vorgegangen werden. Das Feld „Session authentication“ muss auf einen der beiden Werte „Multiple Servers (SSO)“ oder „Single Server“ gestellt werden. Eine Anmeldung und die Verwaltung der Anmeldesessions erfolgt dann über ein Cookie. Um ein solches Cookie zu erhalten, kann entweder über den Aufruf eines Domino-Designelements und den URL-Parameter „login“, also z.B. über die URL http:///names.nsf?login oder über einen POST oder GET Request auf die Datenbank names.nsf mit folgenden Parametern ein Login und der Bezug eines Session Cookies erreicht werden. Eine typische derartige Anfrage hat die Felder „username“, „password“ und optional „redirectto“ und liefert dem Domino-Server Benutzername und Passwort und eine Zielressource, die nach dem Login geöffnet werden soll. Dies kann z.B. auch das Servlet sein. Die Action eines solchen Formulars, bzw. der GET-Anfrage hat das Ziel / names.nsf?login, so dass sich folgendes Beispiel für den Login für das Servlet eignet:
5.2.3 Login und Internet-Session für Web-Agenten und Servlets
183
oder
oder als URL http://192.168.1.201/names.nsf?login&username=Benutzer&password=Passwort&redirectto=%2Fservlet%2Fservletalias
Beachten Sie im zweiten und dritten Beispiel, dass bei der Übermittlung der Daten per GET der Parameter „?login“ als Hidden Parameter bzw. in der URL und dort immer an erster Stelle übermittelt wird, damit der Domino-Server die Login-Aufforderung erkennt. Ist der Benutzer erfolgreich authentifiziert, so erhält er ein Session Cookie. Dies ist entweder eine einfache Seriennummer im Cookie „DomAuthSessId“, die aus 32 hexadezimalen Zeichen besteht für die „einfache Session-Authentifizierung“ oder ein Authentifizierungstoken, das in der Regel im Cookie mit dem Namen „LtpaToken“ gespeichert ist. Der Setup des Multi Server SSO ist etwas aufwändiger und muss wie folgt durchgeführt werden: Öffnen Sie zunächst die Ansicht „Web Configurations“ oder „Internet Sites“ in der Ansicht „Configuration“ -> „Web“ im Domino Directory, also in der Datenbank names.nsf. Welche der beiden Ansichten geöffnet werden muss, hängt von der grundsätzlichen Entscheidung ab, wie Websites für diesen Server konfiguriert werden sollen. Nähere Informationen hierzu finden Sie in der Administrations-Hilfe des Domi-
Anmerkung zur Sicherheit Objekte, für die der Besitzer einer Session keine Berechtigung hat, wird er in der Regel gar nicht erst laden können; z.B. wird getView für eine Ansicht, die ein Benutzer nicht lesen darf, null zurückgeben. Wenn allerdings detaillierte Informationen über ein Objekt (z.B. die UNID eines Document) bekannt sind, kann das Objekt geladen werden und es ist ungleich null. Beim Versuch einer Benutzung dieses Objekts wird jedoch eine NotesException 4000 „You are not authorized to perform this operation“ geworfen.
184
5 Java-Web-Anwendungen @ Domino
no-Servers. Wenn Sie hier keine Vorgaben beachten müssen, verwenden Sie der Einfachheit halber die Ansicht „Web Configurations“. Dort erstellen Sie ein neues SSODokument, indem Sie das dort sichtbare Serverdokument öffnen und dann in der Buttonleiste am oberen Rand des Dokuments den Befehl „Create Web“ -> „SSO Configuration“ wählen. In diesem Dokument können Sie den Namen des zu verwendenden Cookies ändern, was aber nicht empfohlen wird. Tragen Sie hier außerdem den DNS-Namen der Domäne ein, innerhalb der das Single Sign On aktiviert werden soll. Bevor Sie das Dokument speichern und schließen, müssen Sie über den hierfür vorgesehenen Button den Key erzeugen. Im Serverdokument können Sie dann nach der Aktivierung von „Multi Server (SSO)“ Session-Authentifizierung diesen Key auswählen. Nach einem Neustart des Servers ist das SSO aktiv und die Authentifizierung über eines der oben beschriebenen HTML-Formulare generiert ein Cookie mit dem SSO Token. Beachten Sie, dass die SSO Session-Authentifizierung nur für die im SSO Key eingestellte Domäne gültig ist. Für Ihre Testumgebung müssen Sie daher einen voll qualifizierten Namen für den Domino-Server zur Verfügung haben. Notfalls kann ein solcher Name über das Host File simuliert werden. In jedem Fall muss der HTTP-Task des Domino-Servers auf diesen Domain-Namen reagieren. Eine derart erhaltene Domino-HTTP-Session (gemeint ist *nicht* die lotus.domino.Session) ist über das Cookie im HttpServletRequest vorhanden, so dass auch für diesen Fall der HttpServletRequest für den Bezug einer lotus.domino.Session über createSession (HttpServletRequest req) erhalten werden kann. Sie können also für ein Servlet mit recht geringem Aufwand ebenfalls alle denkbaren Authentifizierungsszenarien gegen den Domino-Server realisieren, egal ob Sie sich entscheiden, einen Funktionsuser mit einer lokalen Internet-Session am Server anzumelden, die Server-ID für die Authentifizierung zu verwenden oder ob jeder einzelne Benutzer über eine Basic-Authentifizierung oder ein Single Sign On am Server angemeldet werden soll.
5.3
Remote Computing via DIIOP / CORBA
Remote Computing in verteilten Umgebungen ist für Domino seit der ersten Version einer der Basis Bausteine der Domino-Architektur. Ein Notes Client kann über die Notes-Session Verbindungen zu beliebigen entfernten Domino-Servern aufnehmen und mit Ihnen über verschiedenste Protokolle und Transport Layer kommunizieren. In allen diesen Fälle handelt es sich immer um echte Client-Server-Anwendungen. Wird durch den Client eine Anwendung gestartet, wird ihr Code auf dem Client ausgeführt und alle erzeugten Objekte werden auf dem Client vorgehalten. Eine Notes-Session kommuniziert mit dem Server über dessen generisches Protokoll z.B. über einen TCP/IP Layer auf Port 1352, und öffnet z.B. eine Datenbank, die als C++ Objekt oder über einen Java-Wrapper lokal abgebildet wird, um Datenbankeigenschaften und Methoden anwenden zu können.
5.3 Remote Computing via DIIOP / CORBA
185
In jedem Fall ist es immer erforderlich, dass der generische Domino-Code, durch den die Domino-Objekte in C++ definiert sind, lokal auf dem Client zur Verfügung stehen. Typische Anwendungen, die auf einem Client betrieben werden können und die eine lokale Notes-Session aufbauen, haben Sie in Kapitel 4 kennen gelernt. Um nun Java-Anwendungen auch auf Clients verteilen zu können, die über keine lokale Notes-Installation verfügen, wird eine Technik benötigt, die es ermöglicht, einerseits über die gewohnten Domino-Objekte in Java zu verfügen, ohne andrerseits eine konkrete Implementierung der Domino-Objekte in Java unabhängig von C++ notwendig zu machen. Bei Lotus hat man sich entschieden – ohnehin immer darauf bedacht, möglichst die Industriestandards mit breiter Verwendung zu unterstützen –, hierbei auf „Distributed Computing“ auf der Basis von CORBA, der „Common Object Request Broker Architecture“ zu setzen. Ursprünglich in Version 5 eingeführt war DIIOP die Technik der Wahl, um die Kommunikation zwischen Applets und dem Domino-Server herzustellen, wird es inzwischen vielfältig in allen denkbaren Remote-Szenarien eingesetzt. Beim Distributed Computing werden die Objekte lokal angelegt und über einen transparenten Layer die Ausführung des Codes auf dem entfernten Server als Instanz geführt und der eigentliche Code über die Request-Architektur auf dem entfernten Server auch ausgeführt. Hierdurch wird es möglich, dass auf dem Client lediglich die leichtgewichtigen Java-(Wrapper)-Objekte geführt werden und die für die Verwendung der DominoJava-Objekte eigentlich unerlässlichen, dahinter geführten C++ Implementierungen des Domino-Codes nur auf dem Server vorliegen müssen. CORBA ist der wichtige Standard im Distributed Computing. Die Spezifikation wird von der OMG – Open Management Group [OMG] – entwickelt und weiterentwickelt. Die OMG ist ein Konsortium aus zur Zeit rund 450 Unternehmen, Universitäten und anderen Einrichtungen, die mit unterschiedlicher Beteiligung in der OMG dort zur Zeit über 100 (Teil-) Spezifikationen entwickeln. Die wichtigsten sind MDA (Modell Driven Architecture), UML (Unified Modelling Language) und CORBA. Der Kern von CORBA ist der Object Request Broker, der Objekte befähigt, über entsprechende Requests in homogenen, aber auch heterogenen Umgebungen zu kommunizieren und verteilte Anwendungen zu implementieren. In der Spezifikation [OMG, CORBA] heißt es hierzu: •
Object Request Broker – enables objects to transparently make and receive requests and responses in a distributed environment. It is the foundation for building applications from distributed objects and for interoperability between applications in hetero- and homogeneous environments.
Um die Interoperabilität dieser Objekte zu gewährleisten, sieht CORBA so genannte Object Services vor:
186 •
5 Java-Web-Anwendungen @ Domino Object Services – a collection of services (interfaces and objects) that support basic functions for using and implementing objects. Services are necessary to construct any distributed application and are always independent of application domains. For example, the Life Cycle Service defines conventions for creating, deleting, copying, and moving objects; it does not dictate how the objects are implemented in an application.
Die Spezifikation der CORBA-Architektur sieht vor, dass in ihr zwar die Kommunikation, die möglichen Services und Ausbaulevel festgelegt werden, aber die Implementierung der eigentlichen CORBA-fähigen Objekte selbst lediglich im Rahmen dieser Architektur und der hier definierten Standards und Schnittstellen operieren muss, ohne selbst in dieser Spezifikation definiert zu sein. Folgerichtig implementiert Domino den CORBA-Standard, insbesondere das IIOP-Protokoll, als Domino-spezifische Ausprägung, dem DIIOP (Domino IIOP). CORBA ist in Domino konsequent transparent umgesetzt, so dass es aus Sicht des Programmierers keinen Unterschied macht, ob eine Notes-Session als lokale Session oder über DIIOP aufgebaut wurde (wenn sie denn erst einmal aufgebaut ist), dennoch sollte er sich immer dessen bewusst sein, dass seine Objekte „eigentlich“ auf dem Server liegen und dort verarbeitet werden. Dies hat z.B. beim Recycling Konsequenzen, da beim Aufruf von recycle () für ein Domino-Objekt stets die RemoteInstanz auf dem Server recycled wird – und auch nur recycled werden kann, da nur dort die schwergewichtigen C++ Objekte vorliegen. Lokal kümmert sich die JavaGarbage-Collection um die Beseitigung nicht mehr benötigter Java-Objekte. Wie in Kapitel 4.2 bereits erwähnt, kennt Domino zwei Implementierungen der Interfaces aus lotus.domino.*. Zum einen die Implementation lotus. domino.local.* (lokale Aufrufe) und lotus.domino.cso.* (DIIOP-Aufrufe), wobei die Auswahl, welche der beiden verwendet werden soll, ebenfalls transparent vonstatten geht und beim Aufbau der session durch createSession erledigt wird. DIIOP-Verbindungen kennen in einem Domino-Umfeld verschiedene Einsatzgebiete. So benötigen z.B. Applets grundsätzlich eine DIIOP-Verbindung, da sie lokal auf einem Client-Computer im Kontext des Browsers betrieben werden und dort natürlich in der Regel keine Notes-Client-Umgebung vorfinden. Bis einschließlich Domino Version 5 wurden DIIOP-Verbindungen benötigt, wenn serverbasierte Agenten – z.B. also periodische Agenten – auf entfernte Server zugreifen sollten, da die Sicherheitsarchitektur von Domino solche Remote-Zugriffe über eine lokal geöffnete Notes-Session nicht erlaubte. Inzwischen ist es seit Version 6 möglich, Vertrauensstellungen zwischen Servern einzurichten, so dass auch eine durch einen Server lokal geöffnete Domino-Session Ressourcen auf einem entfernten Server öffnen kann. Das wichtigste Einsatzgebiet für DIIOP-Verbindungen sind Client- oder ServerAnwendungen, die nicht über ein lokales Notes Client- oder Server-Environment verfügen. Dies können z.B. Servlets sein, die im Servlet Manager eines Drittherstellers betrieben werden oder Java-Anwendungen, die außerhalb des Notes-Kontextes, z.B. auf nicht unterstützten Betriebssystemen, betrieben werden sollen.
5.3.1 Server und Client Setup von DIIOP / HTTP
187
Inzwischen kann man feststellen, dass die Verwendung von Remote-DominoAnwendungen zu einem der wichtigsten Anwendungsfälle für den Einsatz von Java geworden ist, schließlich ermöglicht die DIIOP-Verbindung dem Entwickler den Einsatz der Domino-Objekte im Kontext seines (externen) Application Servers und seiner Java-Anwendung, ohne (direkt) auf die Infrastruktur von Domino angewiesen zu sein. Durch die verschiedenen Möglichkeiten des Single Sign On eröffnen sich auch weitreichende Möglichkeiten der Authentifizierung der derart gestalteten RemoteDomino-Verbindungen. Um nun DIIOP in einer Anwendung zum Einsatz zu bringen, müssen für die Verwendung von DIIOP lediglich besondere Signaturen der Methode createSession verwendet werden, um eine DIIOP-Session zu erhalten. Um diese letztendlich anwenden zu können, muss der Server vorbereitet werden und insbesondere der DIIOP Task gestartet werden. Eine ausführliche Anleitung enthalten die beiden folgenden Kapitel. Die nachfolgenden Kapitel setzen mit der Erläuterung der verschiedenen Signaturen von der Verwendung von SSL bis hin zum connection pooling fort.
5.3.1
Server und Client Setup von DIIOP / HTTP
Damit DIIOP-Verbindungen zu einem Domino-Server aufgenommen werden können, sind zunächst einige Einstellungen und Vorbereitungen vorzunehmen. Viele der Einstellungen nehmen Sie zunächst im Serverdokument des Servers in der Datenbank names.nsf vor. Dort muss zunächst der voll qualifizierte Host Name des Servers eingestellt werden. Steht ein solcher nicht zur Verfügung, so kann er im so genannten Host File Ihres Computers als lokaler Eintrag, der dann nur für Ihren Computer gültig ist, eingetragen werden31. Tragen Sie den voll qualifizierten Host Name des Servers auf der ersten Seite des Serverdokuments in der Rubrik „Basics“ im Abschnitt „Basics“ im Feld „Fully qualified Internet host name:“ ein. Als nächstes müssen die Internetports für HTTP und DIIOP eingestellt und aktiviert werden. Diese finden Sie ebenfalls im Serverdokument im Abschnitt „Internet Ports“ des Abschnitts „Ports“. Im Abschnitt „Remote Java/Domino IIOP“ muss der TCP/IPPort- Status auf „enabled“ geschaltet werden (s. Abb. 5-4), alternativ oder zusätzlich kann für DIIOP SSL aktiviert werden. Die Standard-TCP-Ports, unter denen der IIOP Task zu erreichen ist, sind 63148 und 63149 für SSL, wobei für einige Linux-Installationen diese Ports nicht zur Verfügung stehen und daher die Ports 60148 und 60149 gewählt werden. Dies kann durch den Einsatz des Notes-INI-Parameters DIIOPIgnorePortLimits=1 umgangen werden. 31
Auf einem Windows-System befindet sich das Host File im Pfad C:\WINDOWS\system32\ drivers\etc\hosts (Win 2000 und XP) bzw. C:\WINNT\system32\drivers\etc\hosts, sofern es sich um eine Standardinstallation handelt. Die Datei hosts kann über einen normalen Text Editor bearbeitet werden und enthält Eintragspaare aus IP-Adresse und Name, also z.B. könnte eine Zeile dort wie folgt aussehen: 192.168.1.201 www.djbuch.de
188
5 Java-Web-Anwendungen @ Domino
Abb. 5-4 DIIOP Einstellungen im Serverdokument
Wie zuvor erwähnt, können DIIOP-Verbindungen entweder anonym oder mit Benutzername und Passwort geöffnet werden. Ob diese Optionen jeweils für DIIOP und DIIOP via SSL zur Verfügung stehen sollen, kann an dieser Stelle ebenfalls konfiguriert werden. Eine Authentifizierung mittels eines SSL-Client-Zertifikats ist für DIIOP leider nicht möglich. Im selben Abschnitt befinden sich die Einstellungen für die Ports des Web-Servers. Stellen Sie zunächst sicher, dass im Abschnitt „Web“ der
5.3.1 Server und Client Setup von DIIOP / HTTP
189
HTTP-Server unter Port 80 aktiviert und für anonymen Zugriff freigegeben ist. Weiter unten wird beschrieben, wie dies umgangen werden kann, sofern der HTTP-Task auf diesem Domino-Server überhaupt keine oder keine anonymen Verbindungen akzeptieren soll. Im Serverdokument befinden sich im Abschnitt „Internet Protocols“ -> „DIIOP“ noch weitere Einstellungen für DIIOP. Legen Sie hier den voll qualifizierten Host Name fest, unter dem Sie die Remote-Verbindung öffnen wollen. In der Regel wird dies der zuvor im Abschnitt „Basics“ eingetragene Host Name sein. Falls Sie hier keinen Eintrag machen, so wird der DIIOP-Task sich immer auf die IP beziehen und somit eine Änderung der IP eine Änderung des IOR Tokens in der Datei diiop_ior.txt im HTTP-Web-Root-Verzeichnis des Domino-HTTP-Tasks zur Folge haben. Dies ist nicht immer erwünscht. Verwenden Sie einen externen HTTP Server oder nicht das Default-Verzeichnis für HTTP, so können Sie im Abschnitt „Internet Protocols“ -> „DIIOP“ zusätzlich einen Dateipfad entweder relativ zum Dataverzeichnis oder als absoluten Dateipfad angeben, unter dem das IOR Token gespeichert werden soll. Zuletzt muss nun noch der DIIOP- (und der HTTP-) Task für den Domino-Server aktiviert werden. Manuell kann dies auf der Dominokonsole mit den Befehlen „load diiop“ und „load http“ erreicht werden. Soll nach einem Neustart des Domino-Servers gewährleistet werden, dass diese Tasks gestartet werden, muss die notes.ini-Datei32 angepasst werden. Dort befindet sich der Eintrag ServerTasks: ServerTasks=Update,Replica,Router,AMgr,AdminP,CalConn,Sched,HTTP,IMAP,LDAP,POP3,DIIOP
Stellen Sie sicher, dass hier die Einträge „DIIOP“ und „HTTP“ vorhanden sind. Nach einem Neustart des Domino-Servers werden diese Tasks automatisch gestartet. Sie können dies überprüfen, indem Sie mit dem Programm „netstat“ testen, ob der Server Host auf den Ports 80, 63148 und/oder 63149 das Netz abhört33: C:\>netstat -a Aktive Verbindungen Proto TCP TCP TCP TCP TCP
Lokale Adresse www.djbuch.de:http www.djbuch.de:https www.djbuch.de:1352 www.djbuch.de:63148 www.djbuch.de:63149
Remoteadresse www.djbuch.de:0 www.djbuch.de:0 www.djbuch.de:0 www.djbuch.de:0 www.djbuch.de:0
Status ABHÖREN ABHÖREN ABHÖREN ABHÖREN ABHÖREN
Auf einem Linux-System kann netstat -l verwendet werden: 32 33
Auf einem Windows-System, in der Regel im Pfad C:\Lotus\Domino\notes.ini Wählen Sie auf einem Windows-System im Startmenu den Befehl „ausführen“ und geben Sie „cmd“ oder „command“ ein, um eine Windows Console zu erhalten. Dort geben Sie den Befehl netstat -a ein.
190
5 Java-Web-Anwendungen @ Domino
user@linuxserver:~$ netstat -l Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address State tcp 0 0 *:lotusnote *:* tcp 0 0 *:www *:* tcp 0 0 *:60148 *:* tcp 0 0 *:60149 *:*
Foreign Address LISTEN LISTEN LISTEN LISTEN
An der Domino-Serverkonsole stehen folgende weiteren Befehle bezüglich des DIIOP-Tasks zur Verfügung: • • •
• • • • •
load diiop – DIIOP starten tell diiop quit – DIIOP beenden show tasks – Dieser Befehl listet alle aktiven Tasks im Domino-Server auf und zeigt auch den HTTP und DIIOP-Task mit Informationen über die verwendeten Ports an. tell diiop show users [d] – Zeigt die Benutzer der aktiven DIIOP-Verbindungen und optional die geöffneten Datenbanken an. tell diiop show config – Zeigt ausführlich die Einstellungen für DIIOP an. tell diiop dump config – Speichert diese in der Datei diiopcfg.txt im Dataverzeichnis des Domino-Servers tell diiop refresh – Lädt die Einstellungen des DIIOP sofort neu. Normalerweise werden diese Einstellungen erst nach einer halben Stunde neu geladen tell diiop log=n (n = 0..4) – 0 Fehler und Warnungen; 1 Informationen; 2 Session-Inititialisierung und Beendigung; 3 Session-Statistik; 4 Meldungen über Transaktionen. Jede Stufe beinhaltet die Meldungen der darüber liegenden.. Default ist 1.
Einige der beschriebenen Einstellungen lassen sich auch über Parameter in der Datei notes.ini steuern. Näheres hierzu finden Sie im Kapitel 19.8. Um festzustellen, ob die Verbindung zwischen dem Client und dem DIIOP-Server zur Verfügung steht, empfiehlt es sich einerseits, den Server Host anzupingen (ping ) und per Telnet zu überprüfen, ob der Zielhost auf dem DIIOP (und HTTP) Port 63148 bzw. 63149 und 80 reagiert (telnet 63148). Eine derart über Telnet erfolgreich aufgebaute Verbindung erkennen Sie daran, dass ein leeres Fenster aufgebaut wird, in dem der Zielserver nun auf Kommandos wartet. Scheitert die Verbindung, wird dies durch Fehlermeldungen angezeigt. Eine über DIIOP aufgebaute Session ist immer eine so genannte Internet-Session, wie sie in Kapitel 5.2.3 bereits vorgestellt wurde. Dies ist auch der Grund, warum die DIIOP-Remote-Verbindungen Platz in dem Kapitel Java-Web-Anwendungen @ Domino gefunden haben. Insbesondere gelten hier auch die in diesem Kapitel beschriebenen besonderen Einstellungen, mit denen in der ACL einer Datenbank der Internet-Zugriff begrenzt werden kann. Eine DIIOP-Session kann entweder anonym oder über einen Benutzer
5.3.1 Server und Client Setup von DIIOP / HTTP
191
mit Internetkennwort im Domino Directory geöffnet werden, sofern dies im Serverdokument wie oben beschrieben erlaubt wurde. Sie kann nicht mittels einer Notes-ID geöffnet werden. Jeder Domino-Server kennt über das Serverdokument die grundsätzlichen Zugriffe auf den Server über Einstellungen in der Sektion „Security“. Für ältere Server mit Version 5 und älter müssen hier zwei Einstellungen für DIIOP beachtet werden. Die Einstellung „Run restricted Java/Javascript/COM“ und „Run unrestrictd Java/Javascript/COM“ regeln für solche Server, wer (unter anderem) bei einem Zugriff über DIIOP bestimmte eingeschränkte Funktionen benutzen darf und wer nicht. Benutzer, die in keinem dieser Felder eingetragen sind, können keine Verbindung zu einem Domino R5 Server über DIIOP aufnehmen. Für R6 wurde die Unterscheidung von „normalen“ und „DIIOP“-Benutzern bei der Feststellung der grundsätzlichen Zugriffsrechte über das Serverdokument aufgehoben. Die Rechte für alle User werden über die Felder in den Sektionen „Server Access“ und „Programmability Restrictions“ definiert. Daher müssen DIIOP-Benutzer in das Feld „Access Server“ (statt in das Feld „Run restricted Java/Javascript/COM“) bzw. in das Feld „Run unrestricted Methods and Operations“ (statt in das Feld „Run unrestricted Java/Javascript/COM“), sofern uneingeschränkte Methoden erlaubt werden sollen, eingetragen werden. Ist im Serverdokument in der Sektion „Ports“ -> „Internet Ports“ -> „DIIOP“ die Eigenschaft „Enforce server access settings:“ gesetzt, so werden laut Dokumentation bei der Verbindung über DIIOP die folgenden Standardrestriktionen aus der Sektion „Security“ ebenfalls für DIIOP-Benutzer angewandt. Zumindest seit oder für Version R6.5.3 kann jedoch festgestellt werden, dass sich der Server grundsätzlich so verhält, als ob dieses Feld den Eintrag „Yes“ hat, also dass grundsätzlich diese Einstellungen in der Sektion „Security“ respektiert werden, egal welcher Wert in diesem Feld eingetragen ist. Dies ist vom Standpunkt der Eindeutigkeit der Sicherheitseinstellungen an und für sich zu begrüßen, es bleibt jedoch ein Unsicherheitsfaktor, ob es sich hier um einen Bug handelt, so dass sich dieses Verhalten möglicherweise in einer späteren Version wieder ändert. Im Einzelnen sind diese Einstellungen: •
Access server – Benutzer, Gruppen oder Server, die in diesem Feld eingetragen sind, dürfen mit dem Server verbinden, sofern sie in einem für den Server gültigen Domino Directory oder über die so genannte Directory Assistance verfügbar sind (und im Falle von DIIOP ein gültiges Internet-Passwort haben) und die nicht im Feld „Not access server“ eingetragen sind. Einträge sind durch Komma oder Semikolon zu trennen. Ist dieses Feld leer, werden alle in als vertrauenswürdig eingestuften Adressbüchern gelisteten Benutzer zugelassen. Für hierarchische Namen kann das Joker-Symbol * benutzt werden (z.B. */Verkauf/DJBUCH lässt alle Benutzer in der OU – Organisational Unit – Verkauf zu. Auch Benutzer von Unterorganisationen sind zugelassen, also z.B. User/International/Verkauf/ DJBUCH wäre zugelassen).
192 •
•
• •
5 Java-Web-Anwendungen @ Domino Not access server – Benutzer, Gruppen oder Server, die keinen Zugriff auf den Server erhalten. Auch hier werden Einträge durch Kommata oder Semikolon getrennt und können Joker-Symbole verwendet werden. Ist dieses Feld leer wird keinem Benutzer oder Server der Zugriff verweigert, außer solchen, die nicht im Feld „Access server“ eingetragen sind, sofern dieses Feld nicht leer ist. Ist ein Benutzer (oder Server) direkt oder indirekt über eine Gruppe in beiden Feldern vertreten, wird der Zugriff verweigert (deny/allow). Create databases & templates – Benutzer erhalten die Erlaubnis, Datenbanken oder Templates zu erstellen, sei es durch Kopie oder durch komplettes NeuErstellen. Ist dieses Feld leer dürfen alle Benutzer Datenbanken oder Templates erstellen. Create new replicas – Dito, jedoch für die Erstellung von Repliken Create master templates – Dito, jedoch für die Erstellung von Master Templates
Benutzer, die in diese Felder ein- oder ausgetragen werden, sind nicht sofort am Server mit diesen Rechten ausgestattet, sondern werden erst nach ca. 1/2 Stunde upgedated. Dies kann durch den Konsolenbefehl „tell adminp process all“ erzwungen werden, wobei hierdurch auch möglicherweise lastintensive weitere Aufgaben des Administrationsprozesses angestoßen werden. Bei der Administration und dem Setup von DIIOP ist ein weiterer wichtiger Punkt zu beachten: Da ein TCP Port genutzt wird, der in der Regel nicht in den Unternehmensfirewalls freigegeben ist, kann es beim Einsatz für Applets, aber auch für Standalone-Java-Anwendungen zu Problemen kommen. Kann nicht gewährleistet werden, dass für alle Benutzer der verwendete DIIOPPort 63148 oder 63149 freigegeben werden kann, bleibt z.B. die Lösung, den in der Regel freigegebenen HTTP Port 80 zu verwenden. Dies hat allerdings reichlich Nebenwirkungen, so kann z.B. der HTTP-Task nicht mehr betrieben werden. Alternativ kann noch über die Verwendung des SSL Port des HTTP-Tasks nachgedacht werden, sinnvollerweise bei der Verwendung von SSL für DIIOP. Bedenken Sie bei dieser Vorgehensweise aber immer, dass dies nur ein Workaround sein kann, und testen Sie diese Einstellungen für Ihre Anwendung und Zielgruppe. Sollten Sie hier Zweifel haben, versuchen Sie immer, Ihre Anwendung durch JSPs oder Servlets umzusetzen, damit die eigentliche Verbindung zum Domino-Server nicht bei den Clients (wie das bei einem Applet der Fall wäre), sondern auf dem (Application) Server aufgebaut werden muss, der in der Regel in einer Infrastruktur untergebracht sein wird, für die Sie die Firewall Settings kontrollieren können. Zum Setup der Clients sei hier nochmals darauf hingewiesen, dass die Datei NCSO.jar bzw. seit Domino R7 Notes.jar zum Java Classpath gehören muss. Ist der Servlet Container der „Client“, also ein Computer mit einer Domino-Server-Installation, befindet sie sich im Pfad /domino/java/ NCSO.jar, also z.B. C:\Lotus\Domino\Data\domino\java\NCSO.jar bzw. im Verzeichnis ext der JVM für Domino R7, also C:\Lotus\Domino\jvm\lib\ext\Notes.jar. Allerdings muss für Domino R7 aufgrund der Platzierung im Verzeichnis ext der Java Virtual Machine (JVM) der Verweis auf Notes.jar nicht (!) mehr explizit erfolgen, wenn z.B. Servlets auf einem Server eingesetzt werden. Anders formuliert: Seit Domino R7 ist keine zusätzliche ClassPath-Konfiguration mehr notwendig, um in Servlets im
5.3.2 Domino-Remote-Session
193
Domino-Servlet-Container auf Domino-Java-Klassen zugreifen zu können. Lediglich die Klasse des Servlets selbst muss weiterhin über den Servlet ClassPath im Serverdokument in der names.nsf gelistet sein.
5.3.2
Domino-Remote-Session
Für den Aufbau einer IIOP Session stehen folgende Optionen zur Verfügung • • • • • • •
Aufbau der Session über die Angabe von Host, Benutzername und Passwort Verwendung des initialen Server-Objekts IOR (Interoperable Object Reference) statt des Hosts Verwendung eines ORB-Objekts zur Etablierung eines Connection Poolings Verwendung von SSL SSO-Authentifizierung über die Verwendung eines LtpaToken (auf einem Domino-Server) SSO-Authentifizierung über ein org.omg.SecurityLevel2.Credentials-Objekt auf einem WebSphere Server SSO-Authentifizierung über ein HttpServletRequest-Objekt
Die Standardsignatur für das Öffnen einer DIIOP-Verbindung ist createSession (host, benutzer, passwort), wobei Sie im Parameter host den voll qualifizierten Domainnamen des IIOP Servers oder dessen IP angeben müssen. Sind die Parameter benutzer und passwort leere Strings, dann wird eine anonyme Verbindung aufgebaut, sofern dies am Server erlaubt ist. Wird für host ebenfalls ein leerer String angegeben, wird die bereits in Kapitel 5.2.3 beschriebene lokale Internet-Session (ohne DIIOP) geöffnet, wobei zu beachten ist, dass hierfür, da es sich um eine lokale Verbindung handelt, NotesThread verwendet werden muss. Der typische Aufruf einer DIIOP-Verbindung unter Verwendung von Benutzername und Passwort ist der Aufruf String host = "www.DiiopServer.de", user = "Vorname Nachname/ORGANISATION", pwd = "passwortString"; session = NotesFactory.createSession(host, user, pwd);
wie er auch im Beispiel aus Listing 5-3 verwendet wird . Alternativ kann für den Host eine Portnummer angegeben werden. Werden für den Benutzernamen und das Passwort leere Strings angegeben, so wird eine anonyme Verbindung aufgebaut, wobei für anonyme Verbindungen Benutzername und Passwort auch weggelassen werden können . Wenn Sie die Beispielanwendung ausführen, erhalten Sie eine Ausgabe in der Art: SessionClass = lotus.domino.cso.Session Platform = Windows/32 Version = Release 6.5.3|September 14, 2004
194
5 Java-Web-Anwendungen @ Domino
package djbuch.kapitel_05; import lotus.domino.*; public class RemoteDomino { static String host = "www.djbuch.de", user = "Vorname Nachname/ORGANISATION", pwd = "passwortString"; public static void main(String argv[]) { Session session=null; try { //DIIOP Login als User des Domino Directory session = NotesFactory.createSession(host, user, pwd); printInfo(session); recycleSession(session); //DIIOP Login ohne HTTP-Task auf dem Server session = NotesFactory .createSession ("127.0.0.1:63148", user, pwd); printInfo (session); recycleSession(session); //DIIOP Login als anonymer User ("Anonymous") session = NotesFactory.createSession(host,"",""); printInfo (session); recycleSession(session); //DIIOP Login als anonymer User ("Anonymous") session = NotesFactory.createSession(host); printInfo (session); } catch (NotesException e) { System.err.println ("Notes Error Number: " + e.id); e.printStackTrace(); } finally { recycleSession(session); } } private static void recycleSession (Session s) { try { if (s!= null && s.isValid()) { s.recycle (); } } catch (NotesException e) { e.printStackTrace(); } } private static void printInfo(Session s) throws NotesException { System.out.println ("SessionClass\t= " + s.getClass().getName()); System.out.println ("Platform\t\t= " + s.getPlatform()); System.out.println ("Version\t\t\t= " + s.getNotesVersion()); System.out.println ("User\t\t\t= " + s.getUserName()); System.out.println ("Session Token\t= " + s.getSessionToken()); System.out.println ("Servername\t\t= \n"); } }
Listing 5-3 Öffnen einer DIIOP Session
User = CN=Vorname Nachname/O=ORGANISATION Servername = Session Token = AAECAzQyMTMyQTAwNDIxMzMxMDhDTj1UaG9tYXMgRWtlcnQvTz1ESkJVQ0jGsf WYHg+6D8VNvbRoJ/u8DZ3tCA==
5.3.3 Remote-DIIOP-Aufrufe via IOR / getIOR
195
Hier kann man erkennen, dass Domino automatisch die Klasse lotus.domino. cso.Session für den Aufbau der Verbindung gewählt hat. Das in dieser Ausgabe angezeigte Session Token dient dem Domino- (und DIIOP-)Server dazu, eine Session zu identifizieren und kann ebenfalls dazu verwendet werden, eine Verbindung aufzubauen (s.u.). Für das Verständnis ist wichtig, hier zu erwähnen, dass für die DIIOP-InternetSession der Notes Thread weder benötigt wird noch verwendet werden kann. Die komplette Verbindung zum Server wird über das CORBA Framework abgewickelt und die eigentliche Domino-Session findet auf dem Domino-Server statt. Selbstverständlich können dennoch gewöhnliche Java Threads verwendet werden, wie weiter unten im Beispiel aus Listing 5-5 unter anderem gezeigt wird. Solange für den Verbindungsaufbau kein Port angegeben wird , ist es erforderlich, dass auf dem Zielserver der HTTP-Task läuft. Dort wird per HTTP unter der Adresse „http:///diiop_ior.txt“ das so genannte IOR Token abgeholt.
5.3.3
Remote-DIIOP-Aufrufe via IOR / getIOR
Das IOR Token wird vom DIIOP-Server automatisch im HTTP-Root-Verzeichnis des Domino-Servers bzw. in einem andern HTTP-Verzeichnis generiert, sofern dies in einer Einstellung unter „Internet Protocolls“ -> „DIIOP“ eingestellt wurde, und enthält in codierter Form die Angaben, welche Dienste (mit oder ohne SSL) für DIIOP zur Verfügung stehen, unter welchen Ports diese zu erreichen sind und den Hostnamen oder die IP des DIIOP-Servers. Um das IOR Token zu beziehen, kann es über die statische Methode static public String getIOR(String host) in NotesFactory bezogen werden und zum Verbindungsaufbau verwendet werden: static String host = "www.diiopServer.de", user = "Vorname Nachname/ORGANISATION", pwd = "passwortString"; String ior = NotesFactory.getIOR(host); session = NotesFactory.createSessionWithIOR(ior, user, pwd);
Auch hier wird das IOR Token über den HTTP Server bezogen, könnte aber, nachdem es erhalten wurde, z.B. für die weitere Verwendung zwischengespeichert werden, um dann z.B. als Datei auf die Clients verteilt und für den Verbindungsaufbau eingelesen zu werden. Da jedoch der gesamte Verbindungsaufbau einschließlich des Bezugs des IOR über den DIIOP-Port abgewickelt werden kann, muss dieses Verfahren typischerweise eher als unwichtig eingestuft werden, wobei für dieses Verfahren natürlich auch die Datei diiop_ior.txt direkt verwendet werden kann. Beachten Sie, dass das IOR sich ändert, sobald im Server ein Port der DIIOP-Verbindungen, die IP des Servers oder eine DIIOP-Verbindung aktiviert oder deaktiviert
196
5 Java-Web-Anwendungen @ Domino
wird. Indem Sie in der Sektion „Internet Protocolls“ -> „DIIOP“ einen voll qualifizierten Domänennamen angeben, können Sie zumindest den zweiten Punkt umgehen. Die eigentliche Verbindung wird nach dem Bezug des IOR immer über den DIIOP-Port geöffnet. Für den Bezug über den HTTP-Task besteht für den Fall, dass der HTTP-Task nur SSL und /oder nur angemeldete Benutzer zulässt, seit Domino R6.5 die Möglichkeit, das Token mittels eines angemeldeten Benutzers oder per SSL zu beziehen, wobei dies nicht derselbe Benutzer wie für den Aufbau der DIIOP-Verbindung zu sein braucht: String ior = NotesFactory.getIOR("www.diiopServer.de", userToGetToken, pwd); session = NotesFactory.createSessionWithIOR(ior, userNotes, pwdNotes); oder String ior = NotesFactory.getIOR("www.diiopServer.de", args); session = NotesFactory.createSessionWithIOR(ior, userNotes, pwdNotes); oder String ior = NotesFactory.getIOR("www.diiopServer.de", args, userToGetToken, pwd); session = NotesFactory.createSessionWithIOR(ior, userNotes, pwdNotes);
wobei args ein String Array mit speziellen Parametern für den Aufbau einer SSLVerbindung (s. Kap. 5.3.4) ist. Wird zusätzlich zum Host der Port des DIIOP-Servers angegeben, so wird das IOR Token über den DIIOP-Task erhalten, der HTTP-Task wird nicht benötigt, wobei für beide Beispiele das IOR ohne SSL und angemeldeten Benutzer bezogen wird. String ior = NotesFactory.getIOR("www.diiopServer.de:63148"); session = NotesFactory.createSessionWithIOR(ior, user, pwd); Was weiter vereinfacht werden kann und gleichbedeutend ist mit: session = NotesFactory.createSession ("www.diiopServer.de:63148", user, pwd);
Auch für dieses Beispiel kann die eigentliche DIIOP-Verbindung über SSL hergestellt werden, das IOR wird aber immer über den Standard Port bezogen.
5.3.4
SSL
Für die Verwendung einer verschlüsselten Verbindung zwischen einer ClientJava-Anwendung und dem Domino-IIOP-Server kann SSL verwendet werden.
5.3.4 SSL
197
Hierfür muss den bereits bekannten Signaturen von createSession und createSessionWithIOR ein String Array mit dem Parameter „-ORBEnableSSLSecurity“ mitgeliefert werden: static String host = "www.djbuch.de", user = "Vorname Nachname/ORGANISATION", pwd = "passwortString"; static final String [] args = new String [1]; static { args [0] = "-ORBEnableSSLSecurity"; } ... session = NotesFactory.createSession(host, args, user, pwd);
Damit SSL für DIIOP eingesetzt werden kann, muss einerseits auf dem Server SSL eingerichtet und für DIIOP aktiviert sein und andrerseits muss auf dem Client der öffentliche Schlüssel des so genannten Root-Zertifikats, mit dem das SSL-Zertifikat des Servers signiert wurde, bekannt sein. Um SSL auf einem Domino-Server zu aktivieren, müssen folgende Schritte durchgeführt werden: 1 2 3 4 5
6 7 8 9
Erstellen der Server Certificate Administration für den Domino-Server Erstellen eines Schlüsselrings für den Server Optional Einrichten einer Zertifizierungsstelle, wenn firmeneigene Zertifikate ausgestellt werden sollen Erstellen eines so genannten Server Certificate Request Signieren dieses Requests durch die eingerichtete lokale Zertifizierungsstelle, durch so genannte Selbstzertifizierung oder durch eine anerkannte Root-Zertifizierungsstelle, wie Verisign, D-Trust oder T-Online (und viele andere). Installieren des signierten Server-Zertifikats im Schlüsselring Konfiguration der SSL-Einstellungen Freigabe der SSL Ports für DIIOP Neustart des Servers
Um für eine Java-Anwendung als DIIOP-Client die Verwendung von SSL zu ermöglichen, muss die Klasse TrustedCerts mit der Signatur public class TrustedCerts implements com.ibm.sslight.SSLightKeyRing in den Classpath der Client-Anwendung aufgenommen werden. Diese Klasse wird nach erfolgreicher Aktivierung von SSL automatisch im Pfad /domino/java erstellt und muss nur noch auf den Client-Computer kopiert werden. Für das Setup von SSL benötigen Sie zunächst die Datenbank certsrv.nsf (Abb. 55). Wenn diese sich nicht bereits auf dem Server im Dataverzeichnis befindet, muss sie neu angelegt werden, indem eine neue Datenbank auf Basis der Schablone „Server Certificate Admin“ (csrv50.ntf) erstellt wird. Diese Schablone ist nur sichtbar, wenn beim Auswählen des Templates während des Anlegens der Datenbank die Eigenschaft „show advanced templates“ aktiviert wird.
198
5 Java-Web-Anwendungen @ Domino
Nachdem die Datenbank erstellt und geöffnet wurde, können über das eingeblendete Menü die Punkte 1 bis 6 durchgeführt werden. Handelt es sich bei dem Server nicht um einen produktiven Server, bietet die Certificate Administration eine einfache Abkürzung für die Punkte 1 bis 6, indem ein Schlüsselring mit einem bereits installierten, so genannten selbstzertifizierten Zertifikat erstellt wird (beachten Sie auch hier den unten beschrieAbb. 5-5 Domino-Certificate-Administration benen Punkt 2). Dieser Schlüsselring erfüllt alle für SSL notwendigen Anforderungen, sollte jedoch nicht produktiv eingesetzt werden, da er lediglich für sich selbst „bürgt“ und somit keine echte Sicherheit erreicht wird. Möchten Sie einen produktiven Server mit einem offiziellen SSL Zertifikat ausstatten, müssen Sie (kurz gefasst) die folgenden Punkte durchführen. Nähere Informationen finden Sie in der Domino-Administrationshilfe (Datenbank help/ help65_admin.nsf). 1 2
Erstellen der Certificate Administration certsrv.nsf (s.o.). Erstellen eines Schlüsselrings für den Server In der wie beschrieben neu erstellten Datenbank „Certification Administration“ (certsrv.nsf) – kurz „Cert Admin“ – wird über die Option „1. Create Key Ring“ ein Schlüsselring für Ihren Server erstellt. Vergeben Sie einen Dateinamen (am besten keyfile.kyr) und füllen Sie alle Felder aus. Dabei müssen folgende Pflichtfelder ausgefüllt werden (dies gilt auch im Falle der Erstellung eines selbstzertifizierten Zertifikats) CN (der so genannte Common Name) – Hostname des Servers, z.B. www.djbuch.de O (Organisation) – tragen Sie hier Ihren Firmennamen ein. L (Location) – tragen Sie hier die Stadt Ihres Firmensitzes ein. S (State) – tragen Sie hier optional den Staat oder die Provinz Ihres Firmensitzes ein. C (Country) – Landescode aus zwei Buchstaben, z.B. DE
5.3.4 SSL 3
4
5
6
199
Optional Einrichten einer Zertifizierungsstelle Domino kann eine eigene so genannte Root-Instanz sein. Hierfür benötigen Sie die Datenbank ca.nsf auf Basis des Templates cca50.ntf. Diese CA (Certificate Authority) kann nun Certification Requests entgegennehmen, zertifizieren und die zertifizierten Requests verteilen. Wenn Sie die Domino CA verwenden, dann muss diese als Root-Instanz im Key Ring akzeptiert werden. Nähere Informationen finden Sie in der Domino-Administrationshilfe. Erstellen eines so genannten Server Certificate Request Im Cert Admin können Sie einen so genannten Certificate Request erstellen. Dies ist eine mehrzeilige Zeichenfolge, in der verschiedene Angaben zum Server (z.B. die in 2 gemachten Angaben) codiert werden. Auf Basis dieser Angaben wird dann von der Root-Instanz (z.B. ca.nsf aus Punkt 3 oder durch Verisign, etc.) signiert. Sie erhalten von der CA ebenfalls eine Zeichenkette zurück, die den signierten Request enthält. Der gesamte Prozess wird über solche Zeichenketten abgewickelt, die über E-Mail verschickt werden können und über Copy Paste im Cert Admin im- und exportiert werden können. Signieren dieses Requests. Je nach gewähltem Dienstleister können Sie über ein Antragsformular oder über eine Web-Schnittstelle Ihren Certification Request bei der CA einreichen. Nach einigen Tagen erhalten Sie den signierten Request zurück und müssen ihn im Schlüsselring installieren. Installieren des signierten Server-Zertifikats im Schlüsselring Der von der CA signierte Request wird per Copy Paste über Punkt 4 des Cert Admins im Schlüsselring installiert. Kopieren Sie nun die Datei keyfile.kyr (Ihr Schlüsselring) ins Domino-Dataverzeichnis.
Auch wenn Sie sich für den einfachen Weg des selbst zertifizierten Zertifikats entscheiden, müssen nun folgende Punkte konfiguriert werden: 7
8
9
Konfiguration der SSL-Einstellungen Öffnen Sie das Domino-Adressbuch names.nsf und dort das Serverdokument für Ihren Server. Dort müssen im Abschnitt „Ports“ -> „Internet Ports“ die „SSL Settings“ bearbeitet werden. Tragen Sie den Dateinamen für Ihren Schlüsselring ins Feld „SSL Key filename“ ein. Sie können hier lediglich einen Dateinamen der Schlüsselringdatei im Dataverzeichnis, jedoch keinen Pfad angeben. Freigabe der SSL Ports für DIIOP Zusätzlich muss im gleichen Abschnitt für DIIOP SSL aktiviert werden („SSL Port status = enabled“). Beachten Sie, dass hier eingestellt wird, ob anonyme Verbindungen, bzw. ob Verbindungen über Benutzername und Passwort hergestellt werden dürfen. Neustart des Servers
200
5 Java-Web-Anwendungen @ Domino
Wenn Sie nun den Client wie beschrieben mit der Klasse TrustedCerts „versorgt“ haben, können Sie unter Verwendung des beschriebenen args Arrays eine DIIOPVerbindung über SSL aufbauen. Für den Verbindungsaufbau per SSL werden für DIIOP nur anonyme Verbindungen oder Verbindungen mit Username und Passwort akzeptiert. Clientseitige Zertifikate sind nicht möglich. Eine SSL-Verbindung wird clientseitig akzeptiert, wenn der Client über die Klasse TrustedCerts und der Server über ein im Schlüsselring installiertes Root-Zertifikat demselben Root-Zertifikat vertrauen. Müssen verschiedene Server mit verschiedenen Root-Zertifikaten angesprochen werden, so kann für den DIIOP-Task mit der Notes-INI-Variable DIIOP_DUP_KEYRING=filename erreicht werden, dass die Klasse TrustedCerts in eine andere Datei geschrieben wird. Diese kann dann über die Verwendung des Strings „-ORBSSLCertificates= filename“ im args Array ausgewählt und angesprochen werden. Auch über DIIOP und SSL bezogene Sessions müssen recycled werden. Es kann jedoch festgestellt werden, dass offenbar SSL Sessions serverseitig sehr viel schneller beendet (und aufgeräumt) werden, als Nicht-SSL-Verbindungen, deren Timeout in den DIIOP-Einstellungen im Serverdokument (Default = 60 Minuten) verändert werden kann. Die Folge ist, dass – wird eine Session serverseitig (vorzeitig) beendet – es bei der Verwendung der Session oder bei der Verwendung von Objekten, die zur Session gehörten, zu org.omg.CORBA.INV_OBJREF Exceptions kommen kann. Prüfen Sie vor dem Recycling mit isValid () in Session, ob Ihre Session noch gültig ist und fangen Sie diese Exceptions ab (da offenbar isValid – zumindest in Domino R 6.5.3 – nicht zuverlässig arbeitet). Erwägen Sie eine private Methode getSession für Ihre Anwendung, in der Sie über einen Vergleich mit null und mit isValid überprüfen, ob Sie eine Session neu aufbauen müssen. Typischen Code finden Sie in Listing 5-4.
SSL / recycling und timeouts Normalerweise müssen auch über DIIOP und SSL bezogene Sessions recycled werden. Es kann jedoch festgestellt werden, dass offenbar SSL Sessions serverseitig sehr viel schneller beendet (und aufgeräumt) werden, als Nicht-SSL-Verbindungen. Dadurch kann es zu org.omg.CORBA.INV_OBJREF Exceptions kommen. Prüfen Sie vor dem Recycling mit isValid in Session, ob Ihre Session noch gültig ist, und fangen Sie diese Exceptions ab. Design Pattern Session „on demand“ Erwägen Sie eine private Methode getSession für Ihre Anwendung, in der Sie über einen Vergleich mit null und mit isValid überprüfen, ob Sie eine Session neu aufbauen müssen.
5.3.4 SSL
201
package djbuch.kapitel_05; import lotus.domino.*; public class GetSessionDemo { ... private Session session=null; public static void main(String argv[]) { GetSessionDemo gsd = new GetSessionDemo(); gsd.doJob(); } private Session getSession() throws NotesException { if (session == null || !session.isValid()) { session = NotesFactory.createSession(host, args, user, pwd); } return session; } private void doJob() { try { printInfo(getSession()); db = getSession().getDatabase("","djbuch/djbuch.nsf"); } catch (NotesException e) { System.err.println ("Notes Error Number: " + e.id); e.printStackTrace(); } finally { recycleMe(); } } private void recycleMe () { try { if (db != null) {db.recycle();} if (session != null && session.isValid()) { session.recycle (); } } catch (NotesException e) { e.printStackTrace(); } catch (org.omg.CORBA.INV_OBJREF e) { //ignore } } }
Listing 5-4 getSession() liefert DIIOP Session bei Bedarf.
Die Idee dieses Designpatterns ist, dass die Instanzvariable session nicht mehr direkt, sondern nur noch indirekt über getSession () angesprochen wird. Dort kann dann für jede Benutzung entschieden werden, ob die Session noch gültig ist, und bei Bedarf (neu) bezogen werden. Wird eine session benötigt, wird nun getSession () verwendet, anstatt die Instanzvariable direkt zu verwenden. Beim ersten Aufruf von getSession () wird die session in jedem Fall neu aufgebaut, bei späteren Aufrufen wird sie entweder im seltenen Fall, dass sie nicht mehr gültig ist, erneut erzeugt, oder die Instanzvariable session zurückgeliefert. Ein weiterer Vorteil dieser Vorgehensweise ist, dass die Session erst beim ersten Aufruf erzeugt werden muss und somit „on demand“ zur Verfügung steht, also nicht unnötig lange offen gehalten wird. Bedenken Sie, dass Objekte, die in einer Session erzeugt wurden, in einer anderen (später erzeugten) nicht gültig sind und daher hierdurch ein gewisser Mehraufwand für die Kontrolle der Objekte, die innerhalb einer Session erzeugt wurden, entsteht.
202
5 Java-Web-Anwendungen @ Domino
Diese Kontrolle ist im Beispiel bisher nicht implementiert. Zusätzlich entstehen noch (Zeit-) Kosten für die Überprüfung isValid() bei jedem Aufruf von getSession(). Im vorgegebenen Beispiel wurde in der Methode doJob() zusätzlich ein zentrales Recycling etabliert, in dem alle nicht mehr benötigten Objekte (angesteuert im finally) aufgeräumt werden. Beachten Sie, dass diese Methode recycleMe neben der Überprüfung von isValid eine org.omg.CORBA.INV_OBJREF Exception catchen muss, um auf eine im letzten Moment ungültig gewordene Session reagieren zu können. Dies ist vor allem bei der Verwendung von SSL wichtig. Wesentlich eleganter ist es, diese Situation gar nicht erst auftreten zu lassen, d.h. zu kontrollieren, welche Session wie lange gültig ist. Hierfür ist es erforderlich, einen eigenen Session Manager als Erweiterung der ja rech einfach gehaltenen Methode getSession () aus Listing 5-4 zu programmieren, der dann auch gleich ein Session Pooling mitbringt. Jede Anwendung müsste dann von diesem Session Manager eine Session anfordern und gleichzeitig ein Timeout definieren, das natürlich unterhalb der im Serverdokument spezifizierten Größe liegt. Der Session Manager verwaltet die Session, kümmert sich um die Timeouts und verwaltet das Pooling (s. auch Kap. 5.3.6). Die Anwendungen, die vom Session Manager Sessions abfordern, beziehen Sessions und geben Sie nach Benutzung wieder frei. Die verschiedenen Signaturen von createSession können in der Regel durch Verwendung des args Arrays auch über SSL angesprochen werden, wie zum Beispiel im vorigen Kapitel beim Bezug des IOR über getIOR.
5.3.5
Single Sign On
Single Sign On – Die Große Freiheit. Sollen große Webanwendungen für Domino entwickelt werden, so gibt es viele gute Gründe, einen dedizierten Application Server wie JBoss, Tomcat oder WebSphere für die Verarbeitung von Servlets, JSP oder EJB einzusetzen. Domino wird dann mit seinen wesentlichen Stärken als Collaboration-, Workflow- und Redaktionstool eingesetzt, deren Frontends und GUIs schnell unter Domino entwickelt werden können. Gleichzeitig ist dann Domino der Datastore, in dem die so erfassten Daten einer Web-Anwendung zur Verfügung gestellt werden. Diese Daten über DIIOP zu beziehen ist ein Ansatz, der sich aufdrängt, so bleibt letztendlich nur noch die Frage der Authentifizierung. Auch hierfür den Domino-Server einzusetzen liegt nahe, da er mit seinen ausgefeilten Verzeichnissen und Verzeichnisstrukturen über Domino Directory, Directory Assistance und LDAP Möglichkeiten schafft, die verschiedensten Benutzer aus den verschiedensten Umfeldern in einer Adressbuch-Architektur zu bündeln. Um nun aber Benutzer entweder über mehrere Domino-Server oder über einen Application Server und einen Domino-Server zu authentifizieren, wird das so genannte Single Sign On (SSO) benötigt. Für das SSO können Sie Benutzer am Domino-Server oder aber am Application Server authentifizieren und danach wird die Anmeldung des Benutzers an sämtlichen im SSO-Verbund erfassten Servern akzeptiert, ohne dass er ein neues Passwort eingeben müsste.
5.3.5 Single Sign On
203
So lassen sich Anwendungen entwickeln, die Domino als Datastore nutzen, ohne auf die Techniken von J2EE verzichten zu müssen. Die einfachste Möglichkeit haben Sie bereits in Kapitel 5.2.3 kennengelernt, in der ein SSO über den HttpServletRequest über createSession (String host, HttpServletRequest req) beschrieben wurde. Folgende Möglichkeiten für den SSO stehen insgesamt in NotesFactory zur Verfügung (s. auch Kap. 16): 1
createSession(String host, HttpServletRequest req)
Authentifizierung von Benutzern einer Internet-Session, die bereits vom Domino-HTTP-Server authentifiziert wurden. 2
createSession(String host, String ltpaToken)
Authentifizierung von Benutzern einer Internet-Session mittels des LtpaTokens aus der Notes-Session, mittels getSessionToken in Session oder über ein LtpaToken aus einer Anmeldung am WebSphere-Server 3
createSession(String host, Credentials omgCredentials) Authentifizierung von Benutzern einer Internet-Session mittels eines Credentials-Objekts aus org.omg.SecurityLevel2. Dieses Objekt muss mit dem
4
createSession(String host, Credentials null)
LoginHelper in WebSphere erzeugt worden sein.
5
6 7
Authentifizierung von Benutzern einer Internet-Session mittels des aktuellen Credentials-Objekts in einer EJB in WebSphere. Anbindung eines Application Servers über die Domino-DSAPI-Schnittstelle. Je nach Produkt muss zusätzlich noch SSO etabliert werden, z.B. [Domino Redirector]. Der kommerzielle Hersteller Key Solutions [Domino JBoss SSO] bietet ein DSAPI Plugin für JBoss an, das SSO einschließt. Einbindung von Domino über eine Modul- oder Plugin-Schnittstelle des Application Servers. Zum Beispiel WebSphere arbeitet so. Verwaltung eines eigenen Personenprofils in der HttpSession des Application Servers und Bezug eines LtpaTokens und der Personendaten im Hintergrund, durch ein Login gegen Domino. Dies ist eine leicht zu realisierende Lösung, die es ermöglicht mit diesem Token und createSession(String host, String ltpaToken) beliebige J2EE Server per SSO mit Domino zu verbinden. Näheres hierzu finden Sie in Kapitel 16.3.
Single Sign On kann vereinfacht auch so dargestellt werden: Mehrere Server einigen sich auf ein Objekt, in dem die Anmeldeinformationen eines Benutzers vom ersten Server hinterlegt werden, den der Benutzer besucht. Besucht der Benutzer nun einen weiteren Server, der dieses Authentifizierungsobjekt akzeptiert, dann wird dieser Server allein aufgrund des Vorhandenseins dieses Authentifizierungsobjekts den Benutzer akzeptieren, sofern er den Benutzer kennt und die Anmeldung in der Realm des Authentifizierungsobjekts stattfindet. Voraussetzung ist hierfür, dass in dem Authentifizierungsobjekt unter anderem gespeichert ist, wie der anzumeldende Benutzer heißt und in welcher Realm (der „Raum“ der zulässigen Server) er sich bewegt. Optional wird eine Ablaufzeit für die Anmeldung im Objekt enthalten sein. Sinnvollerweise ist dieses Objekt codiert, um es sicher übertragen zu können.
204
5 Java-Web-Anwendungen @ Domino
Sicherheitsrisiko durch Cross Site Scripting Das häufigste Konzept des SSO, aber auch grundsätzlich der Authentifizierung, ist die Hinterlegung eines Cookies beim Client, um eine Anmeldesession zu identifizieren. Dies ist leider ein nicht zu unterschätzender Schwachpunkt für mögliche Angriffe, über den durch so genanntes Cross Site Scripting versucht wird, dieses Cookie auszuspähen, an einen entfernten Server zu senden und für ein missbräuchliches Login zu benutzen, schließlich ist ja genau die Idee des SSO, dass das Cookie für einen Login ausreicht. WebSites sind insbesondere dann für Cross Site Scripting anfällig, wenn es Benutzern der Site erlaubt ist, Daten in welcher Form auch immer in die Anwendung einzugeben, und diese Daten später wieder angezeigt (Gästebuch, Redaktionssystem etc.) und beim Anzeigen nicht escaped werden, d.h. wenn für JavaScript, HTML, etc. notwendige Zeichen, wie „“ nicht in so genannte Entities „“ umgewandelt werden und so bei der Anzeige zur „Programmierung“ missbraucht werden können.
Alle beschriebenen Methoden basieren auf diesem Konzept, lediglich der Container, in dem sich die Authentifizierungsinformationen befinden, unterscheidet sich. 1 createSession(String host, HttpServletRequest req) Die Verwendung des HttpServletRequest eignet sich für den SSO zwischen mehreren Domino-Servern. Wurde ein Benutzer bereits durch den HTTP von Domino authentifiziert, ist dies in einem Cookie im HttpServletRequest vermerkt. Dieser HttpServletRequest kann für die weitere Authentifizierung verwendet werden (s. Kap. 5.2.3). 2 createSession(String host, String ltpaToken) Sollen lediglich mehrere Domino-Server bzw. ein WebSphere-Server und Domino per SSO authentifizieren, so ist die Authentifizierung mittels des LtpaTokens eine einfache Möglichkeit, die auch in Listing 5-5 skizziert wird, wobei in diesem Listing davon ausgegangen wird, dass zwischen mehreren Domino-Servern authentifiziert wird. Soll zwischen WebSphere und Domino authentifiziert werden, so vergibt WebSphere das LtpaToken, das, sofern beide Server korrekt eingerichtet sind, dann von Domino akzeptiert wird (s. Kap. 5.3.5.1). Das LtpaToken kann als String gespeichert werden und zum Aufbau einer Session zu einem anderen Server verwendet werden. Voraussetzung für die Verwendung von SSO ist natürlich, dass der Domino-Server für SSO eingerichtet ist, was bereits am Ende von Kapitel 5.2.3 beschrieben wurde. Listing 5-5 zeigt die Verwendung des LtpaTokens zum Aufbau einer SSO Session, aufgebaut wird. Das LtpaToken wird in die wie gewohnt mit createSession einem String zwischengespeichert, nachdem es über getSessionToken bezo-
5.3.5 Single Sign On
205
package djbuch.kapitel_05; import lotus.domino.*; public class RemoteDominoWithToken implements Runnable { static String host = "www.djbuch.de:63148", user = "Vorname Nachname/ORGANISATION", pwd = "passwortString"; static final String [] args = new String [1]; static {args [0] = "-ORBEnableSSLSecurity";} public static void main(String argv[]) throws InterruptedException { RemoteDominoWithToken t = new RemoteDominoWithToken(); Thread nt = new Thread((Runnable) t); nt.start(); nt.join(); System.out.println ("done everything."); } public void run() { String token = null; Session firstSession = null; try { firstSession = NotesFactory.createSession(host, user, pwd); token = firstSession.getSessionToken(); printInfo(firstSession); } catch (Exception e) { e.printStackTrace(); } finally { recycleSession(firstSession); } Session otherSession = null; try { String host2 = "127.0.0.1:63148"; otherSession = NotesFactory .createSession(host2, args, token); printInfo(otherSession); } catch (Exception e) { e.printStackTrace(); } finally { recycleSession(otherSession); } } ... }
Listing 5-5 DIIOP Session – Single Sign On – SSO und SSL
gen wurde, und kann auch nach dem Recycling einer Session in einer anderen Session zum Aufbau einer Verbindung zu einem anderen Server verwendet werden . Bemerkenswert ist daran, dass das Token nicht an die Verbindungsart gebunden ist, sondern auch zum Aufbau einer SSL-Verbindung genutzt werden kann. Im Beispiel finden Sie nochmals ausführlich das String Array zur Verwendung als SSL-Argument . Die im Beispiel verwendete Implementierung von Runnable zum Aufbau eines Thread ist nicht zwingend, zeigt jedoch, dass bei der Verwendung von DIIOP zum Aufbau eines Thread NotesThread nicht benötigt wird. Um ein erfolgreiches SSO zwischen Domino und WebSphere per LtpaToken aufzusetzen, sind folgende Voraussetzungen einzuhalten (eine detaillierte Anleitung findet sich in Kapitel 5.3.5.1): •
Die Authentifizierung erfolgt auf WebSphere und wird von Domino akzeptiert
206 • •
• • • •
5 Java-Web-Anwendungen @ Domino WebSphere und Domino basieren auf dem gleichen User Repository (Domino als LDAP) WebSphere ist für SSO eingerichtet, benutzt den Domino als LDAP und allgemeine Sicherheit mit LtpaToken und LDAP ist im WebSphere Administrator aktiviert. Der LtpaToken Key wurde von WebSphere exportiert und nach Domino importiert. Domino ist für SSO konfiguriert. Beide benutzen die gleiche LDAP-Realm (wird beim Import des Keys eingetragen.) Anzumeldende Benutzer müssen im Domino Directory vorliegen.
3 createSession(String host, Credentials omgCredentials) Die Idee des SSO über ein org.omg.SecurityLevel2.Credentials-Objekt ist der des LtpaTokens recht ähnlich, jedoch kommt hier ein „echtes“ Java-Objekt zum Einsatz. Auch diese Art des SSO steht nur bei einer Anmeldung über WebSphere zur Verfügung. Dieser Server stellt eine kleine Utility-Klasse LoginHelper34 zur Verfügung, mit der das CORBA Login über einen ORB vereinfacht werden kann: LoginHelper loginHelper = new LoginHelper(); try { org.omg.SecurityLevel2.Credentials credentials = loginHelper.login(userid, password); loginHelper.setInvocationCredentials(credentials); String username = loginHelper.getUserName(credentials); System.out .println("Angemeldeter Benutzer: " + username); Session s = NotesFactory .createSession("www.djbuch.de", credentials); System.out.println("Domino-Session geladen für: " + s.getUserName()); } catch (org.omg.SecurityLevel2.LoginFailed e) { // Fehler verarbeiten. } catch (NotesException e) { // Fehler verarbeiten. }
Damit ein Login-Objekt bezogen werden kann, wird ein neuer LoginHelper instanziert und über CORBA das SecurityContext-Objekt credentials bezogen und im LoginHelper wieder als Default hinterlegt . Dieses Objekt kann dann für den Login am Domino-Server verwendet werden.
34
LoginHelper ist eine Klasse, die im Standardpaket des WebSphere Application Developer als Beispieldatei mitgeliefert wird. Sie befindet sich im Installations-Verzeichnis von WebSphere im Verzeichnis runtimes\aes_v4\installedApps\sampleApp.ear\default_app.war\ WEB-INF\classes
5.3.5.1 Setup von WebSphere und Domino für SSO via LtpaToken
207
4 createSession(String host, Credentials null) Werden auf einem WebSphere Application Server Enterprise Java Beans verwendet, so kann in diesem Kontext sehr einfach auf den in diesem Zusammenhang gültigen CORBA-Authentifizierungskontext zurückgegriffen werden. Es kann innerhalb der EJB schlicht durch einen Null-Parameter der aktuelle Sicherheitskontext mit in die Anmeldung der Domino-Session übergeben werden. public class DominoBean extends Object implements SessionBean { ... Session s = NotesFactory .createSession("www.djbuch.de", null); ... }
5 .. 7 Weitere Möglichkeiten Die in den Punkten 5 bis 7 angesprochenen Möglichkeiten des SSO basieren auf verschiedenen Konzepten, die aber alle eine gezielte Koppelung von Domino- und Application Server voraussetzen. Diesem Thema ist das Kapitel 16.2 gewidmet, dort wird dann erneut auch auf das SSO eingegangen. CORBA Security API versus JAAS Beachten Sie, dass das Login über die CORBA Security APIs, die vom WebSphere LoginHelper genutzt werden, für WebSphere Version 5 auslaufen und dort als deprecated behandelt werden. Sie werden dort durch die programmatischen Sicherheits-APIs von JAAS, dem Java Authentication and Authorization Service, ersetzt.
5.3.5.1
Setup von WebSphere und Domino für SSO via LtpaToken
Um (J2EE-) Java-Anwendungen fürs Web auf einem WebSphere Server zu betreiben und ein SSO zu Domino zu realisieren, müssen zuvor einige administrative Schritte auf beiden Systemen durchgeführt werden. Im Folgenden soll die Vorgehensweise für eine Installation von Domino R6.5.x oder R7 und WebSphere Application Developer (WSAD) 5.1 mit integriertem WebSphere Application Server (WAS) oder dessen Nachfolger, dem Rational Application Developer (RAD) 6.x, erläutert werden. Ziel ist es, dass sich Domino und WebSphere ein LTPAToken teilen und so gegenseitig das Login akzeptieren. Die Authentifizierung zwischen WebSphere und Domi-
208
5 Java-Web-Anwendungen @ Domino
no über LtpaToken ist einfach einzurichten, lässt sich über die Domino-LDAP-Funktion betreiben und ist aufgrund der Deprecation der CORBA Security API in WebSphere [WAS, Infocenter] ein wichtiges Verfahren. Daher wird hier ausführlich auf das Setup eingegangen. Den Beispieldateien liegen die notwendigen Dateien bei, die Sie in ein EAR-Projekt eines Workspace für WSAD 5 oder RAD 6 kopieren können. Installieren Sie Domino, den Notes Client mit Domino Administrator und WSAD oder RAD6 und öffnen Sie diesen Workspace. Kopieren Sie die Beispielsdatenbank djbuch.nsf in das Verzeichnis djbuch des Dataverzeichnisses des Domino-Servers und führen Sie für die Einrichtung des SSO für die Beispielsanwendung folgende Schritte durch: 1 2 3 4 5 6 7
Installieren eines neuen Servers mit Serverkonfiguration in WSAD / RAD Aktivieren der Administrationskonsole, Starten des Servers und Login Einrichten und Starten des LDAP-Dienstes für Domino Einrichten der Benutzer-Registry für LDAP im WebSphere Server Einrichten des Authentifizierungsverfahrens LTPA Globale Sicherheit für den WebSphere Server einrichten Konsolenbenutzer im WebSphere Server einrichten, Globale Sicherheit aktivieren und Neustart 8 Ltpa Key von WebSphere nach Domino importieren 9 Review der Sicherheitseinstellungen für die Beispielanwendung 10 Loginseite und Servlet „j_security_check“ 11 Vor- und Nachteile des SSO über Ltpa Token zwischen WebSphere und Domino 12 Troubleshooting
1 Installieren eines neuen Servers mit Serverkonfiguration in WSAD Instanzen von J2EE Servern können in WSAD über Standard Wizards angelegt werden. Wählen Sie „Datei“ -> „Neu“ -> „Server“ -> „Server mit Serverkonfiguration“, um einen neuen Server anzulegen. Wählen Sie im Dialogfenster den Version 5.0 Testserver aus und speichern Sie Ihre Einstellungen. Für die IBM Rational Software Development Platform RAD ist per default ein WebSphere 6.0 TestServer installiert. Der neue Server mit seinen Einstellungen erscheint in der Server-Perspektive, die entweder automatisch geöffnet wurde oder die Sie über das Menü „Fenster“ -> „Perspektive“ -> „Andere“ -> „Server“ öffnen können. Den Version 6 Server in Rational finden sie in der Palette rechts unten. 2 Aktivieren der Administrationskonsole, Starten des Servers und Login Durch Doppelklick auf das Server-Icon in der Server-Perspektive werden die ServerEinstellungen geöffnet. Im Tabellenreiter „Serverkonfiguration“ muss die Checkbox für die Verwaltungskonsole (Abb. 5-6) aktiviert werden, damit der Server konfiguriert werden kann; auch dies ist für Version 6 nicht mehr erforderlich und die Administrationskonsole automatisch aktiv.
5.3.5.1 Setup von WebSphere und Domino für SSO via LtpaToken
209
Abb. 5-6 WAS-Verwaltungskonsole aktivieren
Durch Klick mit der rechten Maustaste auf das Server-Symbol kann der Server, wie auch die Verwaltungskonsole gestartet werden. Der Server ist gestartet, wenn die Meldung „Der Server ist für e-business bereit“ erscheint. Die erste Anmeldung in der Administrationskonsole erfolgt über einen „Dummy“-User, dessen Namen frei gewählt werden kann und lediglich für LoggingInformationen benötigt wird. Bevor mit der Administration von WebSphere (WAS) fortgefahren werden kann, muss nun Domino für die Verwendung als LDAP-Verzeichnis eingerichtet werden. 3 Einrichten und Starten des LDAP-Dienstes für Domino Domino bietet mit seinem Domino Directory eine gute Basis für ein SSO zwischen WAS und Domino, da einerseits die Benutzer in diesem Directory dem Domino-Server ohnehin bekannt sind und andrerseits WAS die Verwendung von Domino als User Repository gut unterstützt, wobei das Directory nicht nativ, sondern als LDAPVerzeichnis angesprochen wird. Dies kann sehr einfach eingerichtet werden. Überprüfen Sie in der Domino-Serverkonsole, ob der LDAP-Task in Domino bereits läuft, ansonsten können Sie ihn über einen Eintrag in der Zeile „ServerTasks=“ in der Datei notes.ini aktivieren. Fügen Sie dieser Zeile durch Kommata getrennt das Wort LDAP hinzu und starten Sie Domino neu. Wird Domino zum ersten Mal als LDAP-Verzeichnis eingesetzt, muss noch eine administrative Seite für LDAP angelegt werden. Öffnen Sie hierfür den Domino Administrator, öffnen Sie Ihren Testserver, für den das SSO eingerichtet werden soll, mit
210
5 Java-Web-Anwendungen @ Domino
dem Befehl „File“ -> „Open Server“ und wählen Sie den Tabellenreiter „Configuration“. In der Navigation auf der linken Seite befindet sich die Ansicht „Directory“ -> „LDAP“. Wählen Sie den Befehl „Settings“. Wurde noch keine Einstellungsseite für LDAP angelegt, können Sie dies jetzt nachholen. Überprüfen Sie in derselben Ansicht, ob ein LDAP-Schema vorliegt und geöffnet werden kann. Sollte dies wider Erwarten nicht der Fall sein, so muss in der Root-Ebene des Domino-Dataverzeichnisses eine Datenbank „schema.nsf“ auf Basis der Schablone „Domino LDAP Schema (6)“ neu erstellt werden. Alle Einstellungen können Sie wie vorgegeben übernehmen und dies wird im ersten Schritt für diese Anleitung auch empfohlen. Um die erfolgreiche Installation zu überprüfen, benötigen Sie einen LDAPBrowser. Ein vorzüglicher LDAP-Browser ist die komplett in Java geschriebene Shareware „LDAP Browser/Editor 2.8.2“ [LDAP Browser]. Im Domino Directory geführte Benutzer sind in so genannten hierarchischen Namen organisiert, die den Konventionen in LDAP sehr ähnlich sind. Sie bestehen aus einem so genannten „Common Name“ (CN), keiner, einer oder mehreren optionalen „Organisational Units“ (OU), dem Organisationsnamen (O), der in Domino auch als Zertifizierer bezeichnet wird und der optionalen Länderkennung (C). Ein Domino-Name teilt diese Einheiten durch Schrägstriche, so dass ein Benutzer oder Server z.B. folgenden Namen haben kann: Testserver/DJBUCH Vorname Nachname/Entwicklung/DJBUCH
oder falls eine Länderkennung hinzukommt: Testserver/DJBUCH/DE Vorname Nachname/Entwicklung/DJBUCH/DE
Diese Namen übersetzen sich direkt in LDAP-Namen, indem die einzelnen Bestandteile mit Ihrer Kennung versehen (CN=) und jeweils durch Kommata getrennt werden, z.B. also: CN=Testserver, O=DJBUCH CN=Vorname Nachname, OU=Entwicklung, O=DJBUCH, C=DE
4 Einrichten der Benutzer-Registry für LDAP im WebSphere Server Für ein Single Sign On ist es wichtig, dass alle beteiligten Server auf das gleiche Benutzer-Repository zurückgreifen. Da Domino per se einen LDAP-Server und ein komfortables Directory für die Benutzerverwaltung mitbringt, liegt es nahe, Domino als LDAP-Server zu betreiben, um Benutzerinformationen bereitzuhalten. WebSphere unterstützt Domino als LDAP-Server, so dass die Integration einfach vonstatten geht. In der Verwaltungskonsole von WSAD finden Sie den Bereich „Sicherheit“, bzw. „Sicherheit“ -> „Globale Sicherheit“ im RAD (Rational Application Developer) und dort den Abschnitt „Benutzer-Registries“ -> „LDAP“. Da das SSO über LDAP einge-
5.3.5.1 Setup von WebSphere und Domino für SSO via LtpaToken
211
richtet werden soll, muss hier einerseits ein Funktionsuser angegeben werden, dem es erlaubt ist, das Domino Directory (per LDAP) zu öffnen und zumindest zu lesen. Folglich muss dieser Benutzer nicht nur im Domino-Adressbuch vorliegen, sondern auch in den entsprechenden Sicherheitseinstellungen Zugriff auf den Server und in der ACL der Datenbank names.nsf mindestens Leserrechte eingeräumt bekommen. Da eine LDAP-Verbindung als Internet-Session eingestuft wird, gelten hier, wie schon für Browserzugriffe und DIIOP beschrieben, die besonderen Sicherheitseinstellungen für diese Verbindungen. Es genügt, wenn Sie (für diesen Test) die erste Seite der LDAP-Benutzer-Registry konfigurieren. Stellen Sie die Werte wie in Tabelle 5-2 ein, alle übrigen Werte können Tabelle 5-2 LDAP für WAS-Benutzer-Registry konfigurieren Benutzer-ID für Server: Benutzerkennwort für Server: Typ: Host: Definierter Bindungsname (DN): Bindungskennwort: Verbindung wieder verwenden: Groß-/Kleinschreibung ignorieren:
Funktionsuser, in der Form cn=Benutzer, o=Organisation Kennwort für diesen Benutzer Domino localhost (sofern Ihr Domino-Server lokal vorliegt) Funktionsuser, in der Form cn=Benutzer, o=Organisation Kennwort für diesen Benutzer Ja Ja (dies vereinfacht die Zusammenarbeit mit Domino)
wie voreingestellt verbleiben: Nachdem Sie Einstellungen an einer (beliebigen) Stelle der WAS-Verwaltungskonsole vorgenommen haben, müssen diese Werte gespeichert werden. Die Konsole bietet eine entsprechende Option an. 5 Einrichten des Authentifizierungsverfahrens LTPA In der Sektion „Sicherheit“ -> „Authentifizierungsverfahren“ -> „LTPA“, bzw. „Sicherheit“ -> „Globale Sicherheit“ und dort „Authentifizierung“ -> „Authentifizierungsverfahren“ -> „LTPA“ für RAS müssen Sie ein Passwort und einen Dateipfad für den Speicherort des Ltpa Keys vergeben. Erst wenn die Sicherheit komplett konfiguriert und aktiviert ist, sollten Sie den Schlüssel exportieren (Schritt 8). Für das LTPA-Authentifizierungsverfahren können zusätzlich Einstellungen für SSO vorgenommen werden. Beachten Sie, dass wenn Sie eine Domäne in diesen Einstellungen angeben, die Anwendung nur noch über diese Domäne erreichbar ist, da die Benutzernamen, die der Anwendungsserver bezieht, dann nur noch in dieser Domäne als gültig angesehen werden. Wird keine Domäne angegeben, ist der Sicherheitskontext für den jeweils aktuellen Server gültig. Für das Beispiel ist dies optimal, für eine produktive Umgebung müssen Sie diesen Wert konfigurieren.
212
5 Java-Web-Anwendungen @ Domino
6 Globale Sicherheit für WAS einrichten In der Sektion „Sicherheit“ -> „Globale Sicherheit“ wählen Sie die Werte „LTPA“ im Feld „Aktives Authentifizierungsverfahren“ und „LDAP“ im Feld „Aktive BenutzerRegistry“ aus. Für WSAD 5.x aktivieren Sie die Globale Sicherheit noch nicht, da zunächst alle getroffenen Änderungen gespeichert und der Server neu gestartet werden muss, sonst riskieren Sie, sich aus der Verwaltungskonsole auszusperren. Für RAS 6 aktivieren Sie die Globale Sicherheit bereits jetzt, führen aber keinen Neustart durch. 7 Konsolenbenutzer in WAS einrichten, Globale Sicherheit aktivieren und Neustart Für WSAD wählen Sie sich nach dem Neustart erneut mit einem beliebigen Benutzernamen in die Konsole ein. In jedem Fall müssen nun für den Zugriff auf die Verwaltungskonsole berechtigte Benutzer eingerichtet werden. In der Sektion „Systemverwaltung“ -> „Konsolenbenutzer“, bzw. unter „Systemverwaltung“ -> „Konsoleneinstellungen“ -> „Konsolenbenutzer“ legen Sie mindestens einen Benutzer an, dem alle zur Verfügung stehenden Rollen zugeordnet werden sollten. Die LDAP-Verbindung ist für WSAD durch den Neustart und in RAS sofort aktiv, Sie können dies daran erkennen, dass hier nur Benutzer akzeptiert werden, die auch über das LDAP-Verzeichnis also dem Domino-Server zur Verfügung stehen. Speichern Sie diese Werte. Nun kann auch die Globale Sicherheit aktiviert werden. Nach einem erneuten Neustart müssen Sie sich nun mit dem zuvor angelegten Konsolenbenutzer anmelden. Geben Sie den Namen in der Form CN=Benutzer, o=Organisation, c=Land an, wobei Ihr Domino-Benutzer den Namen Benutzer/Organisation/Land hat. Die Angabe des Landes ist nur dann obligatorisch, wenn Ihr Domino Directory diese Angabe führt. 8 Ltpa Key von WAS nach Domino importieren Im Bereich „Sicherheit“ -> „Authentifizierungsverfahren“ -> „LTPA“ exportieren Sie den Ltpa Key durch Angabe eines entsprechenden Dateipfades und den Button „Schlüssel exportieren“. Öffnen Sie im Notes Client die Datenbank names.nsf und dort die Ansicht „Configuration“ -> „Web“ -> „Web Configurations“. Wenn Sie die Schritte zu den Beispielen verfolgt und bereits SSO eingerichtet haben, dann sollte bereits ein Dokument „Web SSO Configuration“ vorliegen. Öffnen Sie dieses Dokument, wählen Sie den Befehl „Keys“ -> „Import WebSphere LTPA Keys“ und importieren Sie den zuvor angelegten Key. Domino lädt die Einstellungen von WebSphere über diesen Key und zeigt sie in dem Dokument an. Speichern und schließen Sie das Dokument und öffnen Sie in der Ansicht „Configuration“ -> „Servers“ -> „All Server Documents“ das aktuelle Serverdokument. Überprüfen Sie die Einstellung für SSO in der Rubrik „Internet Protocols“ -> „Domino Web Engine“. In der Sektion „Http Session“ -> „Session authentication“ sollte „Multiple Servers (SSO)“ ausgewählt werden. Speichern Sie das Serverdokument in jedem Fall neu, um die neuen Keys zu aktivieren. Starten Sie den Domino-Server neu. SSO ist jetzt konfiguriert.
5.3.5.1 Setup von WebSphere und Domino für SSO via LtpaToken
213
9 Review der Sicherheitseinstellungen für die Beispielanwendung Um die Einstellungen an Ihre Bedürfnisse anzupassen und um die Möglichkeiten der LTPA-Sicherheit in WebSphere kennen zu lernen, öffnen Sie die Beispielanwendung, öffnen die „Perspektive Web“ im WSAD und kontrollieren nun die Sicherheitseinstellungen der Beispielanwendung „SSOLoginWeb“. Die Sicherheitsstruktur in WAS sieht vor, dass im Web-Modul und dort im WebImplementierungsdeskriptor (Datei web.xml) erstens so genannte Berechtigungsklassen (Rollen) definiert werden und andrerseits Integritätsbedingungen (Constraints), die eine Zuordnung zwischen diesen Berechtigungsklassen und Web-Ressourcen bzw. -Objekten herstellen (s. Abb. 5-7). Letzteres sind einerseits zuzulassende HTTP-Operationen wie POST oder GET und andrerseits Dateiobjekte, die über URL-Filter definiert werden. Zweitens werden im EAR-Implementierungsdeskriptor (Datei application.xml) im EAR-Modul den Berechtigungsklassen Benutzer und Gruppen zugeordnet. Letzteres ist eine Erweiterung des J2EE Standards in WebSphere, der zusätzlich zulässt, dass für die Ausführung von Methoden in Beans Delegationen „run on behalf of“ definiert werden können.
EAR Implementierungsdeskriptor Benutzer / Gruppen
Berechtigungsklassen
Web-Implementierungsdeskriptor Berechtigungsklassen
Integritätsbedingung
Objekte / Ressourcen
Abb. 5-7 Zusammenhang zwischen Berechtigungsklassen und Constraints
214
5 Java-Web-Anwendungen @ Domino
Letztendlich ist eine Berechtigungsklasse ein Name, unter dem eine solche Zuordnungsstruktur geführt wird. Bei der Programmierung ist es nicht notwendig die Berechtigungsklasse direkt zu referenzieren, sondern die in der Integritätsbedingung für diese Berechtigungsklasse definierte Ressourcenberechtigung ermittelt automatisch anhand der URL-Filter, ob eine JSP-Seite oder ein Servlet vom angemeldeten Benutzer ausgeführt werden darf oder nicht. Diese verschiedenen Einstellungen und Eigenschaften können wie folgt kontrolliert und konfiguriert werden.
Im WEB-Implementierungsdeskriptor •
Anlegen einer Berechtigungsklasse In der Sektion Sicherheit im Tabellenreiter „Berechtigungsklassen für Sicherheit“ sind die vorhandenen Berechtigungsklassen (s. Abb. 5-8) gelistet und es können neue erstellt werden. Hier kann lediglich neben dem Namen eine kurze Beschreibung eingegeben werden.
Abb. 5-8 Sicherheitseinstellungen in WSAD
5.3.5.1 Setup von WebSphere und Domino für SSO via LtpaToken •
•
215
Anlegen einer Integritätsbedingung und von Objektgruppen An der gleichen Stelle im Tabellenreiter „Integritätsbedingung für Sicherheit“ sind die vorhandenen Bedingungen gelistet und es können neue angelegt werden. Ist ein Eintrag markiert, können die Zuordnungen zwischen Objektgruppen und Berechtigungsklassen erstellt werden. Objektgruppen können hier angelegt werden. Eine Objektgruppe besteht aus zugelassenen HTTP-Methoden und aus URL-Filtern (s. Abb. 5-8 bzw. Abb. 5-8-1). Durch die URL-Filter wird festgelegt, beim Besuch welcher URL welche Berechtigungsklasse greift und somit welche Benutzer (durch die Zuordnung im EAR Deskriptor) berechtigt sind. Festlegen der Authentifizierungsmethode Im Abschnitt „Seiten“ des Web-Implementierungsdeskriptors wird festgelegt, welche Authentifizierungsmethode und welche Seiten für Login- oder Fehlerseite verwendet werden sollen. Für die geplante Art der Authentifizierung muss im Feld Authentifizierungsmethode der Wert „FORM“ ausgewählt werden. Im Feld Anmeldeseite ist eine URL (relativ zur Projekt-Root) anzugeben, die eine Loginseite zeigt. Im Feld Fehlerseite ist die URL der Seite einzutragen, die gezeigt wird, wenn der Benutzer nicht authentifiziert werden konnte.
Abb. 5-8-1 Sicherheitseinstellungen in RAD
216
5 Java-Web-Anwendungen @ Domino
Im EAR-Implementierungsdeskriptor •
Zuordnung von Benutzern und Gruppen zu Berechtigungsklassen In der Sektion im EAR-Implementierungsdeskriptor müssen zunächst über den Button „Erfassen“, bzw. „Details“ im Abschnitt „Sicherheitsberechtigungsklassen und „Erfassen“ für RAS die in den Web-Implementierungsdeskriptoren angelegten Berechtigungsklassen geladen werden. Dann können diesen Berechtigungsklassen Benutzer und Gruppen zugeordnet werden, wobei zunächst angegeben werden kann, ob alle Benutzer (also auch anonyme) zugreifen dürfen, oder ob nur solche zugreifen dürfen, die grundsätzlich angemeldet sind, oder ob von den angemeldeten nur ganz bestimmte berechtigt sind. Beachten Sie, dass diese Art der Zuordnung für WebSphere spezifisch ist und nur in diesem Application Server genutzt werden kann.
10 Loginseite und Servlet „j_security_check“ Durch die durchgeführten Sicherheitseinstellungen ist festgelegt, dass alle JSP-Seiten geschützt sind. Wenn Sie nun in der Perspektive „Web“ (im RAS ist diese Ansicht in der J2EEPerspektive unter dem Namen „Dynamische Webprojekte“ integriert) über einen Klick mit der rechten Maustaste auf die Seite „index.jsp“ im Ordner WebContent diese Seite im Server starten, so wird das JSP compiliert und auf den Application Server deployed und im Browser aufgerufen. Anstelle der Seite wird eine Login-Seite angezeigt. Sie können die Seite auch im Browser aufrufen (http://localhost:9080/SSOLoginWeb/index.jsp) Damit der J2EE-Server die Authentifizierung durchführen kann, müssen die Login-Daten des Benutzers an den Server übermittelt und programmatisch verarbeitet werden. Dies wird durch das Servlet „j_security_check“ übernommen, wobei es genügt auf der Loginseite ein HTML-Formular mit der Action „j_security_check“ zur Verfügung zu stellen. Das Servlet-Framework erkennt dieses Servlet automatisch. Es muss nicht weiter konfiguriert werden. Dieses Formular muss zusätzlich die Felder „j_username“ und „j_password“ für Benutzername und Passwort zur Verfügung stellen. Das Servlet arbeitet nur korrekt, wenn die Daten per POST übermittelt werden. Da angegeben wurde, dass alle authentifizierten Benutzer berechtigt sind, genügt es einen Benutzer aus dem Domino Directory anzugeben, der ein Internet-Passwort besitzt (s. Kap. 5.2.3) und der für den Test des SSO in der ACL Zugriff auf die Datenbank djbuch/djbuch.nsf hat. Bei der Eingabe des Benutzernamens ist auf die Einhaltung der LDAP-Konvention CN=..,O= zu achten. Für die Ausarbeitung dieser Anwendung ist es sicher eine gute Idee, hier die Benutzer nur ihren Namen eingeben zu lassen, durch eine entsprechende Hintergrundverarbeitung diesen in die passende Form zu bringen und die Organisation „ ,o=ORGANISATION“ zu ergänzen.
5.3.5.1 Setup von WebSphere und Domino für SSO via LtpaToken
217
Die Willkommenseite besitzt einen Link auf das Test Servlet MyServlet (Listing 5-6), das das Ltpa Token aus den Cookies des HttpServletRequest extrahiert und für ein Single Sign On gegen den Domino-Server verwendet. Wie der Code in Listing 5-6 zeigt, besitzt das Servlet keinerlei Code zur Überprüfung des Autorisierung. Dies wird komplett vom Application Server übernommen. Ist der Benutzer authentifiziert und autorisiert, erhält er vom Server ein Cookie mit dem LtpaToken. Dieses kann dann von der Anwendung extrahiert werden und wird direkt zur Anmeldung am Domino-Server verwendet . WebSphere stellt zusätzlich zum j_security_check Servlet noch eine weitere Funktion – das Servlet ibm_security_logout – zur Verfügung, mit dem der Benutzer package djbuch.kapitel_05; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import lotus.domino.*; public class MyServlet extends HttpServlet implements Servlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); resp.setContentType("text/html"); out.println ("\n" + "WebSphere SSO\n<style>\n" + "\tbody {font-family:Helvetica,Arial,sans-serif;}\n" + "\th1 {color:#FF00FF;}\n\n\n" + "\nWebSphere SSO\n"); out.println ("Gefundene Cookies: "+cookieToString(req)+"
"); Session otherSession = null; String token = getToken(req); out.println ("
ltpaToken im Cookie = " + token + "
"); try { String host = "localhost:63148"; otherSession = NotesFactory.createSession(host, token); Database db = otherSession .getDatabase("","djbuch/djbuch.nsf"); out.println ("
Domino-Datenbank geöffnet: " + db.getTitle() + "
"); printInfo(otherSession,out); } catch (Exception e) { e.printStackTrace(); } finally { recycleSession(otherSession); } out.println ("logout"); out.println (""); } private String getToken (HttpServletRequest req) { Cookie[] cookie = req.getCookies(); for (int i = 0; cookie != null && i < cookie.length; i++) { if (cookie [i].getName().equalsIgnoreCase("LtpaToken")) { return cookie [i].getValue(); } } return null; } ... }
Listing 5-6 Gesichertes Servlet mit SSO zwischen WAS und Domino
218
5 Java-Web-Anwendungen @ Domino
wieder abgemeldet werden kann. Prinzipiell kann dies natürlich auch durch eine eigene Programmierung erreicht werden, die einfach das Cookie mit dem LtpaToken löscht. 11 Vor- und Nachteile des SSO über Ltpa Token zwischen WebSphere und Domino Sollen Domino und WebSphere per SSO miteinander verbunden werden, ist das Ltpa Token sicher eine der sehr guten Möglichkeiten, dies einfach, sicher und schnell umzusetzen. Vorteile dieser Vorgehensweise sind: • Nahtlose und problemlose Kommunikation zwischen dem Domino-LDAP und WebSphere • Einfaches Setup über die WebSphere-Verwaltungskonsole • Weitreichende und granuläre Sicherheitskonfiguration • Zuordnung von Benutzern zu Berechtigungsklassen • Kein Programmierungsüberhang Nachteile dieser Vorgehensweise sind: • Nicht alle der von WebSphere angebotenen Einstellungen sind J2EE-Standard, daher nicht portierbar • Keine Autorisierung auf Methodenebene, wie bei JAAS • Im Wesentlichen optimiert für Web-Anwendungen (Login Servlet) • LDAP kann zum Performance-Engpass werden, wenn viele Anmeldeoperationen zu erwarten sind Zum vorletzten Punkt sei noch gesagt, dass natürlich auch eine vom Browser unabhängige Java-Applikation einen HTTP Request an den WebSphere Server zur Authentifizierung machen kann, um ein LTPA Token zu beziehen, sofern eine TCP/IP-Verbindung zwischen Anwendung und Server besteht. Da es zur Zeit keine Unterstützung von JAAS durch Domino gibt, kann alternativ noch das CORBA Security API über den LoginHelper genutzt werden, um eine Autorisierung auf Methoden-Ebene zu erreichen und gleichzeitig einen Security Context zur Verfügung zu haben, mit dem gegen Domino authentifiziert werden kann. Fazit: Sieht man von der fehlenden Portierbarkeit ab, ist das LtpaToken die Technik der Wahl, wenn Web-Anwendungen mit Domino per SSO verbunden werden sollen.
5.3.5.1 Setup von WebSphere und Domino für SSO via LtpaToken
219
12 Troubleshooting •
•
•
• •
• •
•
• •
Wenn Sie die Reihenfolge für den Setup der Sicherheit in WSAD nicht einhalten oder wenn die Verbindung zum Domino-LDAP nicht korrekt eingerichtet ist, kann es passieren, dass Sie sich aus der Verwaltungskonsole „aussperren“. In diesem Fall besteht die Möglichkeit, den Server und die Serverkonfiguration (genauer gesagt, das in WSAD angelegte Objekt) in der Perspektive „Server“ zu löschen. WSAD entfernt dann die zugehörigen XML-Konfigurationen und Sie können einen neuen unkonfigurierten Server in Ihrer IDE-Umgebung anlegen. Bei Kommunikationsproblemen mit dem LDAP-Server überprüfen Sie diesen mit einem LDAP-Browser, dies erleichtert auch das Verständnis für die von Domino angebotene Struktur der Directory-Daten. In der Datei notes.ini kann der Parameter LDAPDEBUG=n (n=1..7) gesetzt werden, um (nach einem Neustart) Debug-Informationen anzuzeigen. Die Werte werden wie folgt interpretiert: 1=Query-Informationen, 2=Result-Informationen, 3=1 und 2, 4=Authentifizierungsinformationen, 5=1 und 4, 6=2 und 4, 7=Alle Informationen (n=8..15 – Erweiterte Debug-Informationen). Bedenken Sie die unterschiedlichen Namenskonventionen in Domino und LDAP. Da Domino nicht nach Groß- und Kleinschreibung unterscheidet, ist es sinnvoll, diese für die Authentifizierung per LDAP in der entsprechenden Einstellung in der Verwaltungskonsole ebenfalls abzuschalten. Im Gegensatz zu anderen LDAP-Servern ist für Domino die Angabe einer BASE DN nicht erforderlich. Das SSO ist an die in der Konfiguration gewählte Domäne gebunden. Für erste Tests lassen Sie am besten dieses Feld leer und beziehen Sie sich in allen Einstellungen auf „localhost“. Alternativ können Sie in Ihrem Hostfile einen voll qualifizierten Domänen-Namen vergeben und müssen sich dann in allen Einstellungen auf diesen Namen beziehen. Wenn Sie dies konsequent für LDAP-Realm, Domino-Server-Host, DIIOP-Server-Host und WAS-Server-Host machen, vermeiden Sie Anmeldefehler. Versuchen Sie, die Anwendung über verschiedene URL, über die IP über „localhost“, über „127.0.0.1“ und optional über den Hostnamen anzusprechen, wenn Sie sich unsicher sind, ob Ihr Domänen-Setup korrekt ist. Vergessen Sie nicht, DIIOP für den Domino-Server zu aktivieren und für die gewünschte Domäne einzurichten. Gleiches gilt für den Domino-HTTP, wenn Sie createSession ohne Portnummer verwenden.
220
5 Java-Web-Anwendungen @ Domino
5.3.6
Connection Pooling – Object Request Broker
Bisher wurden in den Beispielen für jeden Life Cycle einer Anwendung oder eines Servlets eine DIIOP-Verbindung geöffnet und eine Domino-Session aufgebaut und wieder geschlossen. Dies erfordert für jeden Aufbau die Bereitstellung eines TCP Sockets, um die DIIOP-Verbindung aufzubauen. Einerseits entsteht hierdurch ein Überhang an (Zeit-) Kosten für den Aufbau der Verbindung, da diese Anforderung immerhin bis ganz auf Systemebene durchgreift und zum anderen werden hierdurch übermäßig Ressourcen für die einzelnen Verbindungen beansprucht. Gerade für Servlets ist es jedoch typisch, dass viele kurze Sitzungen geöffnet werden, da ja jede Benutzerinteraktion den erneuten Aufruf eines Servlets oder einer JSPSeite erfordert – im Gegensatz zu einer Standalone-Anwendung, die durchaus eine Session öffnen und dann für die gesamte Benutzungsdauer der Anwendung offen halten kann. Für das Servlet wäre es also vorteilhaft eine DIIOP-Verbindung über mehrere Aufrufe offen halten zu können, damit die Verbindungen nicht zu kurz (um noch effizient zu sein) werden, und für eine Anwendung ist zu berücksichtigen, dass die Verbindung nicht zu lange geöffnet bleibt, damit nicht eine Domino-Session serverseitig geschlossen wird (s. auch Kap. 5.3.4). Domino bietet die Möglichkeit über entsprechende CORBA API einen Object Request Broker zu beziehen, der über mehrere Domino-Sessions verwendet werden kann. Bezogen wird ein neuer ORB über die NotesFactory mit broker = NotesFactory.createORB(),
dieser kann dann zum Erzeugen einer Notes-Session z.B. über session = NotesFactory.createSession (String host, ORB orb, HttpServletRequest req);
oder session = NotesFactory.createSession (String host, ORB orb, String ltpaToken);
verwendet werden. Jede Session, die denselben ORB benutzt, nutzt somit dieselbe DIIOP-Verbindung. So einfach und geradeheraus dieser Ansatz ist, so komplex ist das eigentliche Geschehen im Hintergrund: •
Jede Notes-Session beinhaltet Autorisierungs- und Authentifizierungsinformationen. Daher kann eine Session über einen ORB nur mit einer eindeutigen Authentifizierung erstellt werden, wobei hierfür alle vier Signaturen, die im Kapitel über SSO beschrieben wurden, zulässig sind.
5.3.6 Connection Pooling – Object Request Broker •
•
•
• •
221
Für eine anonyme Session genügt es nicht, den HttpServletRequest eines „unangemeldeten“ Besuchers zu übermitteln. Hierfür muss ein gültiges LtpaToken einer anonymen Anmeldung aus createSession (String host, "","") verwendet werden. Wird ein HttpServletRequest ohne ein gültiges LtpaToken übermittelt, wird eine NotesException mit der Meldung „not implemented“ geworfen. Die Authentifizierungsinformationen werden benötigt, da Domino serverseitig die verschiedenen Domino-Objekte, wie Datenbanken, Views oder Dokumente, im Benutzerkontext behandelt. Über einen ORB kann ein Pool von Notes-Sessions aufgebaut werden. Je nach Anwendung und verwendeter Hardware wird die Anzahl der handhabbaren Sessions je Broker variieren. Überlasten Sie eine ORB nicht mit zu vielen Sessions. Der ORB wird statisch angelegt, um ihn über mehrere Sessions nutzen zu können. In der Multithreaded-Umgebung der Servlets muss unbedingt für eine korrekte Synchronisierung beim Erstellen neuer Sessions und ORBs geachtet werden.
Listing 5-7 zeigt ein Servlet mit einem einfachen Connection Pool, wobei berücksichtigt wird, dass nur angemeldete Benutzer dieses Servlet benutzen können und natürlich über einen statischen ORB mehrere Notes-Sessions gebündelt werden. Der erste wichtige Schritt im Servlet ist, dass überprüft wird , ob der Benutzer angemeldet ist, also ein LtpaToken im HttpServletRequest vorliegt, das einfach als Cookie aus dem Request extrahiert werden kann . Vorausgesetzt wird hier natürlich, dass SSO am Server aktiviert ist und über Cookies verwaltet wird (unter WebSphere wäre auch ein Rewriting von URLs zur Session-Verfolgung denkbar, dies ist aber für diesen Ansatz nachteilig). Ein Manko an dieser Vorgehensweise ist, dass im Beispiel nicht überprüft wird, ob das Token überhaupt noch am Server gültig ist. Dies soll in einem späteren Beispiel noch gelöst werden. Wird festgestellt, dass noch kein Token vorliegt, so muss dem Benutzer eine Login-Seite angezeigt werden. Im Beispiel wird davon ausgegangen, dass das Servlet auf einem Domino-Server zum Einsatz kommt, so dass ein Login über die URL „/ names.nsf?login“ angezeigt werden kann. Damit nach einem erfolgreichen Login das Servlet angezeigt werden kann, muss der Login-Seite noch über den URLParameter RedirectTo die gewünschte Seite übermittelt werden. Im Beispiel finden Sie allgemein gültigen Code , der auf jeder beliebigen Seite zum Einsatz kommen kann und nach einem Login zu dieser Seite zurückkehrt. Da die Seite names.nsf nach einem erfolgten Login nur ein GET ausführt, ist diese Technik nur für Seiten einsetzbar, die über GET erreicht werden sollen und können. URL-Parameter der ursprünglichen Seite werden aber mit übermittelt und durch den URLEncoder codiert, damit Sonderzeichen korrekt übermittelt werden. Selbstverständlich kann in der hier verwendeten Methode doRedirect auch ein Ansatz verwendet werden, der ein Verfahren verwendet, dass mit einem eingesetzten Application Server – unabhängig von Domino – ein Login durchführt, allerdings
222
5 Java-Web-Anwendungen @ Domino
... public class DominoServletConnectionPooling extends HttpServlet { static ORB broker = null; static int orbCount = 0; private static final int MAX_SESSION_IN_POOL = 10; private static final String host = "www.djbuch.de:63148"; public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { Session session = null; if (getToken (req) == null) { doRedirect (req, resp); return; } try { session = NotesFactory .createSession(host, getBroker(), req); ... } catch (Exception e) { e.printStackTrace(); } finally { recycleSession(session); } } private void doRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException { String uri = req.getRequestURI(); String thisUrl = URLEncoder .encode((uri==null||uri.equals("")?"":uri+"?") + req.getQueryString()); resp.sendRedirect("/names.nsf?login&redirectTo="+thisUrl); } synchronized private static ORB getBroker() { orbCount+=1; if ((broker == null) || ((orbCount % MAX_SESSION_IN_POOL) == 0)) { broker = NotesFactory.createORB(); System.out.println(broker.getClass() + " wurde erstellt."); } return broker; } private String getToken (HttpServletRequest req) { Cookie[] cookie = req.getCookies(); for (int i = 0; cookie != null && i < cookie.length; i++) { if (cookie [i].getName().equalsIgnoreCase("LtpaToken")) { return cookie [i].getValue(); } } return null; } ... }
Listing 5-7 Einfacher Connection Pool
muss dieses Verfahren mit der SSO-Technik LTPA von Domino-kompatibel sein, damit ein entsprechender HttpServletRequest bzw. ein LTPA Token zur Verfügung steht. Der im Beispiel aus Kapitel 5.3.5.1 vorgestellte Weg ist hierfür geeignet.
5.3.6 Connection Pooling – Object Request Broker
223
Für den Bezug des ORBs wurde zunächst eine statische Klassenvariable broker deklariert , damit diese allen Instanzen des Servlets zur Verfügung steht. In der Methode getBroker wird dann überprüft, ob der ORB bereits initialisiert wurde oder ob bereits die maximale Anzahl an Sessions, die über diesen Broker verwaltet werden sollen, erreicht ist . Ist der Broker noch null oder sind bereits (in unserem Fall) 10 Sessions in diesem Broker offen, so wird von der NotesFactory ein neuer ORB angefordert und von dieser erstellt35 . Beim Bezug des Brokers ist es extrem wichtig, dass immer nur ein Thread zur Zeit überprüft, ob ein Broker benötigt wird und diesen erstellt, sonst kann es in Race Conditions zu Problemen kommen. Hierfür muss die Java-Funktionalität „synchronized“ verwendet werden. Da es sich beim broker um eine statische Klassenvariable handelt, die auch null sein kann, ist es nicht möglich lediglich die Variable broker zu synchronisieren, sondern es muss die statische Klassen-Methode getBroker synchronisiert werden. Synchronisierung von statischen Methoden hat zur Folge, dass als zu synchronisierendes Objekt die gesamte Klasse angenommen wird. Dies ist aus Performance-Gesichtspunkten nicht optimal. Daher wird in einem späteren Beispiel eine Möglichkeit vorgestellt dieses Dilemma aufzulösen, indem eine eigene Klasse für den Connection Pool vorgestellt wird. Wird nun createSession mit Hostname, Broker und dem HttpServletRequest (mit dem gültigen Anmeldetoken) aufgerufen , wird innerhalb des Brokers eine Session für den aktuellen Benutzer erstellt. Mit dieser Notes-Session können Sie nun wie gewohnt arbeiten, Datenbanken öffnen und mit den Domino-Objekten arbeiten. Sollen alle Verbindungen mit den Rechten eines bestimmten Funktionsusers hergestellt werden, so steht die in diesem Beispiel verwendete Technik, die Benutzerauthentifizierung über einen Redirect auf eine Login-Seite herzustellen, nicht zur Verfügung. Insgesamt kann also festgestellt werden, dass noch einige offene Fragen im bisher gezeigten Ansatz bestehen: • • •
Ein eventuell ungültig gewordenes LTPA Token wird nicht erkannt. Es fehlt eine alternative Anmeldung für einen generellen Funktionsuser Die gewählte Art des synchronize ist nicht optimal
Die nächsten beiden Beispiele setzen sich mit diesen Fragestellungen auseinander, zunächst werden die ersten beiden Punkte näher untersucht. Um zu erreichen, dass eine
35
Die Architektur von Java und seiner Garbage Collection macht übrigens den Life Cycle der ORBs einfach zu handhaben. Wird beim Öffnen der elften Session ein neuer ORB erstellt, wird zwar die statische Variable broker nun auf das neu erstellte ORB-Objekt verweisen, aber der vorige ORB wird noch so lange „am Leben“ bleiben, wie offene Notes-Sessions, die diesen ORB als Argument im createSession übergeben haben, bestehen. Erst wenn keine Session mehr diesen ORB als Argument benutzt, wird es keinen Verweis mehr auf das alte ORB-Objekt geben und es kann von der GC aufgeräumt werden. Folgerichtig kann ohne Bedenken einfach die statische Variable neu zugewiesen werden, ohne dass die ORB-Objekte ausdrücklich verwaltet werden müssten.
224
5 Java-Web-Anwendungen @ Domino
Anmeldung generell für einen Funktionsuser erfolgt, kann initial einmalig mit den explizit verwendeten Daten für Benutzername und Passwort ein LtpaToken generiert werden und dieses für alle folgenden Authentifizierungen verwendet werden. Dieses Token wird, ähnlich wie im vorigen Beispiel schon der Broker, in einer statischen Variable vorgehalten und über eine Methode getToken geliefert, die bei jedem Aufruf überprüft, ob schon ein Token vorliegt, oder ob ein neues bezogen werden muss. Dem Token selbst ist nicht anzusehen, ob die dahinter geführte session noch gültig ist. Dies wird an einer anderen Stelle überprüft. Das Beispiel aus Listing 5-8 zeigt eine überarbeitete Version des Connection Pooling Servlets, das abgelaufene LtpaTokens handhaben kann und auf der Anmeldung eines Funktionsusers basiert. Um den Ablauf eines Tokens erkennen zu können, wurde der Bezug der eigentlichen Notes-Session in die Methode getSession ausgelagert. Dort wird die session zunächst wie im vorigen Beispiel mit Hostname, Broker und LtpaToken bezogen . Ist das über die private Methode getToken gelieferte LtpaToken ungültig, so wird Domino eine NotesException werfen, die abgefangen wird, damit erneut ein Verbindungsversuch unternommen werden kann . Hierfür wurde die Methode getToken mit einem boolean-Parameter enforce versehen, über den erzwungen werden kann, dass ein neues und gültiges Token erzeugt wird. Für die Erstellung der Session wird der Broker wie im vorigen Beispiel bezogen, die Methode getToken in diesem Beispiel extrahiert das Token jedoch nicht aus dem Request, sondern erzeugt eine Notes-Session auf dem klassischen Wege über DIIOP, aber ohne ORB . Diese session liefert ein gültiges LtpaToken , das in der statischen Variablen token vorgehalten wird, um für weitere Verbindungen verwendet zu werden. Auch diese Methode muss aus den oben genannten Gründen synchronisiert werden, um eindeutige Session Token beziehen zu können und in der Multithreaded-Umgebung des Servlets nicht in Racing Conditions zu geraten. Die Behandlung der Frage der Synchronisation bedarf etwas mehr Aufwand und erfordert eine eigene Klasse ConnectionPool, die als Objekt instanziert und synchronisiert werden kann. Die Idee ist, eine Klasse zu erstellen, die die komplette Abwicklung des Connection Pools übernimmt, den Broker verwaltet, auf Anfrage Notes-Sessions liefert und sich um die Synchronisation kümmert. Im Einsatz (Listing 5-9) wird diese über die eingebaute Factory-Methode getConnectionPool als Objekt bezogen und stellt damit die benötigten Methoden zur Verfügung, so kann z.B. über verschiedene getter-Methoden der Status über Token , die Anzahl der insgesamt geöffneten Sessions und der aktuellen Anzahl der Sessions im zur Zeit gültigen Broker abgefragt werden. Der eigentliche Pool wird über die Factory bezogen, die dafür verantwortlich ist, dass nur so viele Pools im Umlauf sind, wie nötig und sinnvoll sind, damit ein Pool auch tatsächlich mehreren Benutzern (jeder Benutzer, der das Servlet über den Browser öffnet, kommt ja als eigener Thread) zur Verfügung steht und liefert der Anwendung über getSession eine neue Notes-Session, die aber von der Anwendung selbst ausdrücklich recycled werden muss. Die Klasse ConnectionPool liefert hierfür die statische Utilities-Klasse recycleSession, die zur Vereinfachung das Exception Handling übernimmt.
5.3.6 Connection Pooling – Object Request Broker
225
... public class DominoServletConPoolOneUser extends HttpServlet { private static ORB broker = null; private static String token = null; ... public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { Session session = null; try { ... session = getSession(); ... } catch (Exception e) { e.printStackTrace(); } finally { recycleSession(session); } } private Session getSession() throws NotesException { Session session = null; try { session = NotesFactory .createSession(host, getBroker(), getToken(false)); } catch (NotesException e) { System.err.println ("TOKEN INVALID OR NO SESSION AVAILABLE " + orbCount); session = NotesFactory.createSession(host, getBroker(), getToken(true)); } return session; } synchronized private static String getToken (boolean enforce) throws NotesException { if (!enforce && token != null && !token.equals ("")) { return token; } else { Session tempSession = null; try { tempSession = NotesFactory.createSession( host, "Administrator","passwortString"); token = tempSession.getSessionToken(); return token; } finally { recycleSession(tempSession); } } } ... }
Listing 5-8 Connection Pool mit Funktionsuser für die Anmeldung
Ein kleiner Zusatzeffekt der Kapselung des Pools in einer Klasse ist, dass er sogar als final deklariert werden kann, da er nur ein einziges Mal zugewiesen werden muss und dann auf Dauer für alle Threads der gesamten Anwendung Gültigkeit behalten soll und behält (es sei denn er wird von der GC aufgeräumt, dann würde er aber bei der nächsten Benutzung neu instanziert) und somit außerdem vom Compiler noch weiter optimiert werden kann.
226
5 Java-Web-Anwendungen @ Domino
... private static final String host = "www.djbuch.de:63148"; private final ConnectionPool pool = ConnectionPool.getConnectionPool(); public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { Session session = null; try { ... out.println((pool.showToken()==null) ?"
New Session":"
Token = " + pool.showToken()); session = pool.getSession(); out.println("
Count = " + pool.getCount()); out.println("
concurrent Count = " + pool.getConcurrentCount()); ... } catch (Exception e) { e.printStackTrace(); } finally { ConnectionPool.recycleSession(session); } }
Listing 5-9 Verwendung des Connection Pools
Listing 5-10 zeigt den Entwurf der Klasse ConnectionPool. Der Pool selbst besitzt die Methoden getBroker und getToken als private Methoden, um intern diese Objekte beziehen zu können, und getSession als öffentliche Methode, über die die Anwendungen neue Sessions beziehen. Diese Methoden sind dem ursprünglichen Entwurf ähnlich, unterscheiden sich aber in wichtigen Punkten. Zunächst ist wichtig, dass die Felder für Broker und Token nicht mehr als static deklariert sind, sondern, da der Pool instanziert werden soll, als Instanzvariablen der Klasse deklariert werden (in Listing 5-10). Die Factory-Methode getConnectionPool ist dafür verantwortlich, so viele Pools wie nötig und sinnvoll in Umlauf zu bringen. Im Beispiel wurde diese Logik stark vereinfacht und durch einen Singleton ersetzt, so dass immer nur ein einziger Pool im Umlauf ist, was für viele Anwendungen sogar ausreichen wird. Die Klasse erhält nur einen privaten Konstruktor , da ein Pool nur über die Factory-Methode bezogen werden soll. Mit dem Parameter host wird bisher nur intern der Host festgelegt, zu dem der Pool die Verbindung aufbauen soll. Sollen verschiedene ConnectionPools zu verschiedenen Hosts ermöglicht werden, muss die Factory dies verwalten. Hier sind natürlich zusätzlich Varianten denkbar, die z.B. den anzumeldenden Benutzer einbeziehen. Die Methode getSession unterscheidet sich nur dadurch vom vorigen Beispiel, dass sie nicht mehr statisch deklariert ist. Dies wird notwendig, da broker und token keine Klassen- sondern Instanzvariablen sind. Der Bezug der Notes-Session als solches bleibt unverändert. Die Methoden getBroker und getToken sind aus demselben Grund nicht statisch deklariert, unterscheiden sich aber in der Synchronisation. Die Metho-
5.3.6 Connection Pooling – Object Request Broker
227
package djbuch.kapitel_05; import lotus.domino.*; import org.omg.CORBA.ORB; public class ConnectionPool { private static final int MAX_SESSION_IN_POOL = 10; private static final String DEFAULT_HOST="www.djbuch.de:63148"; private static final ConnectionPool singletonPool = new ConnectionPool (); private ORB broker = null; private int orbCount = 0; private String token = null; private String host = null; private ConnectionPool () { this.host = DEFAULT_HOST; } public static ConnectionPool getConnectionPool () { return singletonPool; } public Session getSession() throws NotesException { Session session = null; try { session = NotesFactory.createSession( host, this.getBroker(), this.getToken(false)); } catch (NotesException e) { session = NotesFactory.createSession( host, this.getBroker(), this.getToken(true)); } return session; } private ORB getBroker() { synchronized (this) { orbCount+=1; if ((broker == null) || ((orbCount % MAX_SESSION_IN_POOL) == 0)) { broker = NotesFactory.createORB(); } } return broker; } synchronized private String getToken (boolean enforce) throws NotesException { if (!enforce && token != null && !token.equals ("")) { return token; } else { Session tempSession = null; try { tempSession = NotesFactory.createSession( host, "Administrator","passwortString"); token = tempSession.getSessionToken(); return token; } finally { recycleSession(tempSession); } } } public String showToken () {return token;} public int getCount () {return orbCount;} public static void recycleSession (Session s) {...} }
Listing 5-10 ConnectionPool-Klasse für Connection Pooling mit verbesserter Synchronisation
228
5 Java-Web-Anwendungen @ Domino
de getBroker selbst muss nicht mehr synchronisiert werden, es genügt, den CodeTeil, der den Broker prüft und bei Bedarf neu bezieht, in einem synchronizedBlock zu kapseln . Synchronisiert wird das Objekt this, also die aktuelle Instanz der Klasse ConnectionPool. Der hierdurch erreichte Fortschritt ist, dass nicht mehr die gesamte Methode synchronisiert werden muss und insbesondere, dass nur noch der ConnectionPool durch die Synchronisation gelocked wird und nicht mehr die gesamte Klasse des Servlets, wie im Beispiel zuvor, denn dort war getBroker sowohl synchronisiert als auch statisch und synchronisierte Klassen-Methoden (statische Methoden) verwenden das Klassen-Objekt als zu synchronisierendes Objekt. Die Methode getToken ist weiterhin synchronisiert , verwendet aber als Instanz-Methode implizit ebenfalls das Objekt this für das Locking, mit den gleichen Vorteilen wie eben skizziert. Diese verbesserte Variante des Connection Pools bietet also einschließlich der zuvor gemachten Verbesserungen folgende Vorteile: • • • •
Ein eventuell ungültig gewordenes LTPA Token wird erkannt. Bequeme Nutzung als Objekt und einfacher Bezug einer Session durch getSession Optimierte Synchronisation Einsatz mehrerer Instanzen des Connection Pools denkbar
Selbstverständlich kann durch Kombination des ersten (Listing 5-7) und letzten Beispiels (Listing 5-10) ein ConnectionPool realisiert werden, der nicht mit einem grundsätzlichen Funktionsuser Verbindungen herstellt, sondern der die Verbindungen mit dem aktuell angemeldeten User der HTTP Session herstellt. Auch ist es möglich, die Anmeldung anstatt mit einem bestimmten Benutzer durch Übergabe leerer Strings als anonyme DIIOP- und Notes-Session herzustellen. Auch dies lässt sich wiederum kombinieren und ermöglicht, HTTP-Benutzer, die keine Anmeldung haben, mit einem Token einer derart anonym hergestellten Verbindung anzumelden und alle angemeldeten HTTP-User mit ihrem Token anzumelden, nachdem sie auf eine Login-Seite redirected wurden. Auch das zuletzt vorgelegte Beispiel lässt sich noch weiter verbessern. Bisher wird jeder ORB verworfen, wenn er die vorgegebene Anzahl von Sessions verarbeitet hat. Durch geschicktes Management der offenen Sessions, insbesondere durch eine Registratur, in der vermerkt ist, welche Sessions offen sind und welche bereits wieder geschlossen sind, lässt sich ein ORB weit öfter wiederverwenden, als dies im vorgegebenen Beispiel der Fall ist. Allerdings ist hierbei der Aufwand für das Management gegen die Kosten für das jeweils erneute Erstellen eines ORB abzuwägen. Connection Pooling für Domino-Verbindungen lässt sich also beim Einsatz von DIIOP gut realisieren, muss aber mit hoher Aufmerksamkeit verwendet werden. Bedenken Sie, dass beim Connection Pooling sowohl Ihre eingesetzte Hardware als auch das Betriebssystem bei der Bereitstellung des TCP Stacks wesentlich involviert sind. Überlasten Sie die TCP-Verbindungen nicht durch einen zu großen Pool. Am besten
5.3.7 Die NotesFactory – Überblick
229
ist es, wenn Sie die Größe des Pools für Ihre Anwendung durch geeignete Stresstests ermitteln und optimieren. Bedenken Sie auch, dass die Dauer der einzelnen NotesSessions eine wichtige Rolle bei der Ermittlung der optimalen Parameter spielt.
5.3.7
Die NotesFactory – Überblick
Die NotesFactory sieht verschiedene Kombinationen der vorgestellten Techniken vom einfachen Login über SSO und SSL bis hin zur Verwendung von ORBs vor. Da für einige Signaturen bestimmte Parameter als String oder Object deklariert sind und je nach Wert dieser Parameter ein unterschiedliches Verhalten an den Tag legen, sind im Folgenden die Signaturen als Aufruf mit den tatsächlichen Werten beschrieben. Die jeweiligen Parameter müssen wie folgt deklariert sein: String userString; String passwortString; String hostString; String ior; org.omg.CORBA.ORB orb; String [] args; String tokenString; org.omg.SecurityLevel2.Credentials credentials; HttpServletRequest httpServletRequest; java.util.Properties props;
Alle Methoden werfen eine NotesException im Fehlerfall. Die hierfür notwendigen Signaturen stehen wie folgt zur Verfügung. Lokale Verbindung bei Benutzung der lokalen Notes-User-ID session = NotesFactory.createSession(); session = NotesFactory.createSession ((String) null, (String) null, (String) null)); session = NotesFactory.createSession ((String) null, (String) null, passwortString);
Für diese Zugriffe muss eine lokale Notes-Installation vorliegen. Angemeldet wird der Benutzer der lokalen Notes-ID. Benutzt diese ID ein Passwort, muss die dritte Signatur verwendet werden. NotesThread wird verwendet. session = NotesFactory.createSessionWithFullAccess(); session = NotesFactory.createSessionWithFullAccess(passwortString);
Wie oben, jedoch unter Umgehung von Leser- oder Autorenfeldern.
230
5 Java-Web-Anwendungen @ Domino
Lokale Internet-Session session = NotesFactory.createSession((String) null, "", ""); session = NotesFactory.createSession((String) null, userString, passwortString);
Für diese Anmeldung muss ebenfalls eine lokale Notes-Installation vorliegen. NotesThread wird verwendet. Angemeldet wird entweder anonym (erste Signatur) oder der genannte Benutzer, der im Domino Directory bekannt sein muss. Erzeugt wird eine so genannte Internet-Session, für die besondere Anmelderechte in der ACL festgelegt werden. Nur im Kontext des Servers gültig. DIIOP-Verbindung über Benutzername und Passwort session = NotesFactory.createSession(hostString); session = NotesFactory.createSession(hostString,"",""); session = NotesFactory.createSession(hostString, userString, passwortString); session = NotesFactory.createSession(hostString, args, userString, passwortString); session = NotesFactory.createSession(hostString, orb, userString, passwortString);
DIIOP wird verwendet. NotesThread wird nicht verwendet. host darf nicht null oder leer sein und kann den DIIOP-Port enthalten, wenn kein HTTP-Task zur Verfügung steht.
DIIOP-Verbindung über IOR session = NotesFactory.createSessionWithIOR(ior); session = NotesFactory.createSessionWithIOR(ior,"",""); session = NotesFactory.createSessionWithIOR(ior, userString, passwortString); session = NotesFactory.createSessionWithIOR(ior, args, userString, passwortString); session = NotesFactory.createSessionWithIOR(ior, orb, userString, passwortString);
DIIOP wird verwendet. NotesThread wird nicht verwendet.
DIIOP-Verbindung per SSO session = NotesFactory.createSession(hostString, tokenString); session = NotesFactory.createSession(hostString, credentials);
5.3.7 Die NotesFactory – Überblick
231
session = NotesFactory.createSession(hostString, (Credentials) null); session = NotesFactory.createSession(hostString, httpServletRequest);
DIIOP wird verwendet. NotesThread wird nicht verwendet. Für die Verwendung von Credentials oder null als zweitem Parameter wird WebSphere benötigt. session = NotesFactory.createSession(hostString, orb, tokenString); session = NotesFactory.createSession(hostString, orb, credentials); session = NotesFactory.createSession(hostString, orb, (Credentials) null); session = NotesFactory.createSession(hostString, orb, httpServletRequest);
ebenso unter Verwendung eines Object Request Brokers, session args, session args, session args, session args,
= NotesFactory.createSession(hostString, tokenString); = NotesFactory.createSession(hostString, credentials); = NotesFactory.createSession(hostString, (Credentials) null); = NotesFactory.createSession(hostString, httpServletRequest);
ebenso unter Verwendung von SSL. DIIOP-Verbindung per SSL session args, session args, session args, session args, session args, session args,
= NotesFactory.createSession(host, userString, passwortString); = NotesFactory.createSessionWithIOR(ior, userString, passwortString); = NotesFactory.createSession(hostString, tokenString); = NotesFactory.createSession(hostString, credentials); = NotesFactory.createSession(hostString, (Credentials) null); = NotesFactory.createSession(hostString, httpServletRequest);
DIIOP-Verbindung über einen ORB session = NotesFactory.createSession(host,
232
5 Java-Web-Anwendungen @ Domino
orb, userString, passwortString); session = NotesFactory.createSessionWithIOR(IOR, orb, userString, passwortString); session = NotesFactory.createSession(hostString, orb, tokenString); session = NotesFactory.createSession(hostString, orb, credentials); session = NotesFactory.createSession(hostString, orb, (Credentials) null); session = NotesFactory.createSession(hostString, orb, httpServletRequest);
Bezug des IOR ior = NotesFactory.getIOR(hostString); ior = NotesFactory.getIOR(hostString, userString, passwortString); ior = NotesFactory.getIOR(hostString, args); ior = NotesFactory.getIOR(hostString, args, userString, passwortString);
Die Signaturen mit den Parametern userString, passwortString und args sind neu seit Domino 6.5. Bezug eines ORB orb = NotesFactory.createORB(); orb = NotesFactory.createORB(args); orb = NotesFactory.createORB(props);
5.4
Troubleshooting
Beim Setup von Java-basierten Domino-Anwendungen gibt es einige Fallstricke, auf die in den vorangegangenen Kapiteln hingewiesen wurden. Diese sollen an dieser Stelle nochmals kompakt zusammengefasst werden und als Kurzreferenz bei SetupProblemen dienen. Zusätzlich werden noch mögliche Exceptions und deren Bedeutung aufgelistet36. Netzwerk, Tasks • •
36
Netzwerkverbindung – Überprüfen Sie, ob der DIIOP-Server korrekt gestartet wurde und ob er über das Netzwerk zu erreichen ist. Falls Sie den IOR über den HTTP beziehen, gilt Gleiches für den HTTP-Task von Domino. Teilweise entnommen aus [Domino Exceptions]
5.4 Troubleshooting • • • • •
233
Ist der DIIOP-Task (und der HTTP-Task) an die Domain / IP gebunden, zu der Sie verbinden? Laufen DIIOP (und HTTP) unter den Ports, zu denen Sie verbinden (Achtung: Linux verwendet in der Regel andere Ports). Netstat (netstat -a) liefert den Zustand der offenen Verbindungen auf dem Server. Telnet kann genutzt werden, um vom Client die Verbindung auf einem bestimmten Port zu prüfen (telnet host port). Typische Exceptions für fehlerhafte Netzwerkverbindungen, nicht erhaltenes IOR oder nicht korrekt oder auf dem falschen Port gestartete Tasks oder ein Verbindungsversuch zu einem nicht erreichbaren Host sind: NotesException: Could not get IOR from Domino Server java.net.ConnectException: Connection refused java.net.ConnectException: Operation timed out java.net.UnknownHostException NotesException: Could not open Notes session: org.omg.CORBA.COMM_FAILURE
Sicherheit • •
•
Akzeptiert der HTTP-Task anonyme Verbindungen? Meldet ein Web-Agent „Agent done“? Dies ist ein Hinweis, auf eine eventuell nicht korrekt abgefangene Exception (untersuchen Sie die Datenbank log.nsf oder die Serverkonsole). Eventuell konnte der Agent nicht geladen werden, weil er entweder nicht korrekt compiliert wurde oder weil ihm Bibliotheken fehlen. Eventuell stimmen die Ausführungsberechtigungen für den Web-Agenten nicht. Überprüfen Sie Delegationen („run on behalf of“) und Signatur. Überprüfen Sie, ob – der ausführende Benutzer die benötigten Rechte in der ACL der Datenbank hat (Delegationen und Signaturen berücksichtigen oder bei Servlets Anmeldung überprüfen). – die ACL nicht den Internetzugriff einschränkt (erweiterte Einstellungen) – insbesondere bei DIIOP-Verbindungen und lokalen Internet-Sessions. – die benötigten Rechte im Serverdokument eingetragen sind (Abschnitt Security -> Server Access) – der Benutzer die benötigten Rechte für Delegationen hat (Abschnitt Security -> Programmability Restrictions) – der Benutzer auch im Adressbuch vorhanden ist und dort ein InternetPasswort vergeben ist. – der Benutzer nicht durch die programmatischen Rechte in der Ausführung bestimmter Operationen eingeschränkt ist (z.B. File I/O)
234 •
5 Java-Web-Anwendungen @ Domino Typische Fehlermeldungen sind: NotesException: Invalid user name/password (Falscher Benutzer
oder Benutzer nicht vorhanden oder falsches Passwort) NotesException: Server access denied (Benutzer hat keine Rechte im
Abschnitt Server Access oder anonyme Verbindungen sind nicht erlaubt.) Single Sign On • • • •
•
Netzwerkverbindung zwischen WAS und Domino prüfen LDAP-Task auf Domino auf korrekte Ausführung und Setup prüfen (DebugEinstellungen verwenden!) Wurde der Ltpa Key korrekt zwischen WAS und Domino ausgetauscht? Wurde die Anwendung, für die eine Authentifizierung stattfinden soll, unter derjenigen Domain im Browser aufgerufen, die im SSO Setup in WAS und Domino als LDAP-Realm eingetragen ist? Sind die verwendeten Benutzer im Domino-Adressbuch?
Setup •
• •
Das Setup der Classpath-Variablen in Domino ist nicht ganz einfach. An folgenden Stellen kann ein Classpath konfiguriert werden: – Für Servlet-Klassen gibt es im Serverdokument einen Eintrag. Trennung der Einträge durch Semikolon. – Für Klassen, die entweder von Servlets oder von Domino-Java-Agenten verwendet werden, müssen diese in der Datei notes.ini im Feld JavaUserClasses eingetragen werden. Einträge werden für Windows-Systeme mit Semikolon, für Unix-Systeme durch Doppelpunkt getrennt. Dieses Feld darf nicht mehr als 255 Zeichen haben. – JavaUserClasses kann alternativ (!) im der Datenbank names.nsf in einem Konfigurationsdokument in der Ansicht „Configuration“ --> „Configurations“ konfiguriert werden. Diese Einstellung überschreibt (!) Änderungen in der Datei notes.ini. – Jar-Dateien können in der Domino JVM, ebenso wie auch sonst z.B. für die Sun JVM üblich, als so genannte installed extention installiert werden. Hierfür werden die Jar-Dateien in den lib/ext-Pfad der JVM innerhalb des JavaHome-Verzeichnisses gelegt und von der JVM automatisch von dort geladen. Für eine Standardinstallation unter Windows ist dies z.B. C:\Lotus\Domino\jvm\lib\ext. Servlet-Klassen, die sich nicht im Default Package befinden, müssen in der Datei servlets.properties konfiguriert werden. Die für die lokale Verwendung vorgesehenen Signaturen müssen NotesThread oder die statischen Klassen sinitThread und stermThread verwenden. DIIOPVerbindungen dürfen NotesThread nicht verwenden.
5.4 Troubleshooting • • • •
235
Bei der Verwendung von NotesThread: Sind die Notes-Binaries im Path und die Notes.jar im Classpath? Bei der Verwendung von DIIOP-Verbindungen: Ist die NCSO.jar (R5.x, R6.x) / Notes.jar (R7.x) im Classpath? Einige Signaturen verlangen null-Parameter. Sind diese korrekt als (String) null gecasted? Typische Fehlermeldungen sind: NotesException: User username is not a server (Verwendung der lokalen Internet-Session nicht im Server-Kontext) Reference to createSession is ambiguous (Fehlender Cast auf String eines nullParameters) UnsatisfiedLinkError: NCreateSession (Fehlende Verwendung von Notes Thread) UnsatisfiedLinkError: Can't find library nlsxbe (nlsxbe.dll) in java.library.path (Binaries nicht im PATH)
Gültigkeitsbereiche Domino lädt mehrere Instanzen der JVM, bzw. des ClassLoaders, je nachdem, an welcher Stelle Java-Code verwendet wird. Dies kann zu unerwarteten Ergebnissen und zu SecurityExceptions führen, wenn JarDateien an den verschiedenen möglichen Stellen gleichzeitig angemeldet werden.
• • • • •
• •
Insbesondere sind folgende Eigenschaften zu beachten: Servlets werden in einem eigenen ClassLoader (im HTTP Thread von Domino) geladen. Klassen, die in JavaUserClasses aufgeführt werden, werden über den ClassLoader des Servers geladen. Klassen in einer Jar-Datei, die sowohl in JavaUserClasses, als auch im Servlet Classpath geführt werden, werden in einem gemeinsamen Kontext geladen. Java-basierte Agenten im Domino Designer werden in einem eigenen ClassLoader (Agent Manager) geladen. Klassen, die nur im Servlet Classpath geführt werden, können (während der Entwicklung im laufenden Betrieb auf Dateisystemebene) ausgetauscht werden und werden nach einem Neustart des HTTP neu geladen. Klassen, die in JavaUserClasses geführt werden, bedürfen eines Server-Neustarts, um neu geladen zu werden, nachdem sie ausgetauscht wurden. Klassen, die nur im Designer ausgetauscht werden, werden immer exklusiv neu geladen, wenn der Agent, der diese Klassen verwendet, gestartet wird. Hierfür ist also kein Neustart eines Tasks notwendig.
236 •
•
•
•
5.5
5 Java-Web-Anwendungen @ Domino Ist eine Jar-Datei im Domino Designer in einer Bibliothek (oder in einem Agenten) geladen und durch Java-Code im Designer referenziert und liegt sie gleichzeitig im Dateisystem und wird über die notes.ini-Einstellung JavaUserClasses geladen, so wird der JavaAgent auf die Jar-Datei aus dem File System zurückgreifen, auch wenn sich die Jar-Datei in einer Bibliothek ändert. Der Vorteil dieser Vorgehensweise ist, dass die Klassen nicht für jeden Agentenstart neu geladen werden müssen. Umgekehrt kann z.B. ein Servlet nicht auf eine Jar-Datei in einer Bibliothek im Domino Designer zugreifen. Wird eine Jar-Datei sowohl in JavaUserClasses, als auch im Servlet ClassPath geführt, sind diese Klassen von Agenten im Domino Designer und von Servlets erreichbar. Alternativ können Servlet-Klassen von vorne herein ausschließlich in JavaUserClasses geführt werden. Statische Variablen in Klassen, die lediglich in JavaCode, der im Domino Designer codiert wurde, geführt werden, stehen jeweils nur einer Instanz eines Agenten zur Verfügung und werden nach Beendigung des Agenten wieder aufgeräumt. Im Klartext bedeutet dies, dass sämtliche Variablen (insbesondere die statischen) bei einem Agentenstart erzeugt und danach wieder komplett entfernt werden. Für jeden Start eines Agenten wird also praktisch ein kompletter ClassLoader hochgefahren und wieder beendet. Dies ist bei Performanceüberlegungen zu berücksichtigen. Gleichzeitig stehen statische Variablen, die in Klassen erzeugt wurden, die in JavaUserClasses geführt werden, sowohl Servlets, als auch Java-Agenten aus dem Domino Designer persistent zur Verfügung. Empfehlung: Wenn Sie sowohl Servlets als auch Java-Agenten einsetzen, dann sollten Sie alle Jar-Dateien nur in JavaUserClasses anmelden. Kopieren Sie die Klassen zusätzlich in eine Bibliothek im Domino Designer, die Sie in allen Agenten referenzieren, damit Agenten bei einem erneuten Speichern des Designdokuments auch außerhalb des Serverkontextes compiliert werden können.
Zusammenfassung
Domino-Web-Anwendungen können auf vielfältige Weise realisiert werden. Im Mittelpunkt dieses Kapitels standen Domino-Web-Agenten und Servlets im Domino Servlet Manager, aber auch die Remote-Anbindung von Domino-Servern über DIIOP. Die Notes-Session wurde als Zentrum jeder Domino-Anwendung herauskristallisiert und es wurde aufgezeigt, wie sich die verschiedenen Arten der möglichen Verbindungen und Aufbau von Sessions verhalten und welche Besonderheiten jeweils zu beachten sind. Mit der Einführung ins Single Sign On wurde bereits aufgezeigt, wie Domino-Anwendungen extern auf Basis eines Application Servers wie WebSphere realisiert werden können und durch SSO dennoch mit einer gemeinsamen Anmelde-Realm arbeiten können.
5.5 Zusammenfassung
237
Mit dem Connection Pooling wurden fortgeschrittene Techniken vorgestellt, die es dem Java-Programmierer ermöglichen, Systemressourcen sparsam einzusetzen, indem mehrere Verbindungen über einen TCP Socket gebündelt werden. Im Bereich der Web-Anwendungen wurde noch nicht auf JSP eingegangen, was im Kapitel über die Domtags Tag Library nachgeholt werden wird. Nach diesen weitreichenden Grundlagen geht es nun daran, auf Basis der Notes-Session die Domino-Objekte genauer kennenzulernen, deren Einsatz und Objekt- und Speicherstruktur kennenzulernen.
6
6 Domino-Objekt- und Speichermodell
In diesem Kapitel: Die Hierarchie der Domino-Objekte Übersicht über die Domino-Java-Objekte Bezug von Objekten Basisklassen Sonderklassen AgentBase und AppletBase NotesException
240
6 Domino-Objekt- und Speichermodell
Domino unterscheidet sich wesentlich von vielen Datenbankservern – die zugrunde liegende Datenstuktur aus Dokument und Item, dargestellt durch Masken und Ansichten, wurde bereits ausführlich in den Kapiteln 2.2 bis 2.4 erläutert. Im folgenden Kapitel soll nun die Java-Sicht auf die Datenstruktur und das Objektmodell näher beleuchtet werden, aber auch auf erweiterte Techniken, die bisher noch nicht vertieft wurden, eingegangen werden.
6.1
Objekt- und Datenstruktur
Die Objektstruktur der Domino-Objekte und deren Speicherrepräsentation ist streng hierarchisch geordnet und ebenso sind es die Java-Repräsentationen. Rufen Sie sich den Überblick aus Abb. 2-14 in Kapitel 2.4 ins Gedächtnis. Dort wird die Hierarchisierung der Domino-Objekte dargestellt, wobei zwei wichtige Gruppen von Abhängigkeiten herauszustellen sind: die Beziehung zwischen der Session, der Datenbank, dem View und dem Dokument einerseits und dem Aufbau eines Dokuments als („lose“) Sammlung von Items andrerseits. Ein Document lässt sich auf einem der folgenden Wege beziehen: • • • • •
Session -> Database -> Document Session -> Database -> DocumentCollection -> Document Session -> Database -> View -> Document Session -> Database -> View -> DocumentCollection -> Document Session -> Database -> View -> ViewEntryCollection -> ViewEntry > Document
Die Datenbank selbst ist das Speicherobjekt für alle Domino-Elemente und wird in der Notes Storage Facility NSF, bzw. optional seit Domino R7 in einer DB2-Datenbank im NSFDB2-Format gespeichert. Dokumente können entweder direkt aus der Datenbank über getDocumentByID oder getDocumentByUNID (Kapitel 7.1.4.1) bezogen werden, oder indirekt über einen View (Kapitel 10), eine ViewEntryCollection oder eine DocumentCollection (Kapitel 10.7f.) referenziert werden, wobei DocumentCollection und ViewEntryCollection über verschiedene Mechanismen der Suche (Kapitel 15) generiert werden können. Das Document selbst besteht (neben diversen Eigenschaften, die ein Dokument haben kann) aus Items, RichTextItems oder Dateianhängen, so genannten EmbeddedObjects (Dateianhänge, Bilder, OLE-Objekte). • • •
Document->Item Document->RichtextItem Document->EmbeddedObject
6.1 Objekt- und Datenstruktur
241
Ein Item enthält die eigentlichen Informationen des Dokuments. Ein Dokument kann beliebig viele Items haben37. Ein Item wiederum enthält Daten unterschiedlichster DatenTypen, vom String bis hin zur Repräsentation von Objekten beliebiger Klassen über die Mechanismen von Serializable. Ein Item hat einen Namen und einen Wert, wobei es sowohl mehrere Items mit gleichem Namen38 geben kann, als auch ein Item mehrere Werte haben kann. • • • • •
Item -> Integer Item -> Double Item -> String Item -> DateTime / DateTimeRange Item -> Object (Objekt einer beliebigen Klasse, die Serializable imple-
•
Item -> Object (Objekt einer beliebigen Klasse, die Serializable imple-
•
Item -> Vector aus Integer, Double, String, DateTime, DateTimeRange oder Object
mentiert) mentiert) als Byte Array
Ein RichTextItem wiederum nimmt eine Sonderstellung im Dokument ein, da es wiederum, wie bereits in Kapitel 2.2.4.8 beschrieben, alle Elemente enthalten kann, die eine Maske enthalten kann. Programmatisch erreichen Sie jedoch nicht alle diese Objekte, sondern bis Version 5 nur Text, RichTextStyle (für die Manipulation von Fontattributen und Tabulatoren), Links und EmbeddedObjects und seit Version 6.5 auch RichTextNavigator, RichTextRange, und RichTextTable, die erweiterte Möglichkeiten zur programmatischen Gestaltung von RichText liefern.
• • • • • • • •
RichTextItem -> String RichTextItem -> RichTextStyle RichTextItem -> RichTextDoclink RichTextItem -> RichTextRange RichTextItem -> RichTextNavigator RichTextItem -> RichTextTable RichTextItem -> EmbeddedObject RichTextItem -> Andere, programmatisch nicht veränderbare Elemente, wie
z.B. Eingebettete Ansichten oder Applets. Basis und Grundlage für den Bezug aller Objekte ist die Notes-Session, die, wie in den vorigen beiden Kapiteln ausführlich besprochen, erzeugt wird. Für das Verständnis und die fehlerfreie Verwendung der Java-Klassen für Domino ist es wichtig, diese hierarchische Struktur einzuhalten.
37 38
Nur beschränkt durch die Speicherlimits (s. Kap. 20.1). Es wird dringend geraten, dies zu vermeiden, da sich solche Items recht schwer handhaben lassen.
242
6 Domino-Objekt- und Speichermodell
Ein Domino-Java-Objekt, also eine Datenbank oder ein Dokument, das über eine Session, die lokal bzw. auf dem Server an einen NotesThread gebunden ist, ist immer nur in diesem Thread und in dieser Session gültig, d.h. die sich hieraus ergebende Objektabhängigkeit ist immer beim Auf- und Abbau der Objekte zu beachten. Eine Datenbankreferenz, also ein Java-Objekt der Klasse Database, und alle über dieses bezogenen Domino-Java-Objekte, also z.B. ein über diese Database bezogenes Dokument oder Ansicht, ist immer nur dort (in der Session und dem Thread) und so lange (bis zum Recycling der Database) gültig, wie die Database gültig ist. In Multithreaded-Java-Anwendungen muss daher im Normalfall (s. jedoch Kap. 13.3.7) für jeden Java Thread eine eigene Notes-Session, bzw. und/oder NotesThread geöffnet werden. Beachten Sie, dass diese zwingende Hierarchie sich auf die Java-Objekte, aber nicht etwa auf die Notes Storage Facility, also die eigentliche Notes-Datenbank bezieht. Selbstverständlich kann ein Dokument, eine Ansicht oder eine Datenbank von verschiedenen Threads geöffnet werden, aber die jeweiligen Java-Objekte müssen jeweils innerhalb ihres NotesThreads unabhängig voneinander instanziert werden und können nicht (als Java-Objekt) zwischen den Threads geteilt werden. Umgekehrt spiegelt sich dieses Verhalten in den Methoden der Domino-Java-Objekte wieder. So hat das Item eine Methode getParent, die das Document liefert, über das das Java-Objekt Item bezogen wurde und in dem sich das Item befindet. Ein Document besitzt die Methode getParentDatabase, die das Java-Objekt Database liefert, über das das Dokument bezogen wurde und das die Datenbank referenziert, in der sich das Dokument befindet. Falls ein Dokument über einen View bezogen wurde, lierfert die Methode getParentView die Ansicht, über die das Dokument bezogen wurde. Folgerichtig liefert getParent in View ebenfalls das Domino-Java-Objekt der Datenbank, in der sich die Ansicht befindet. Schließlich wird über getParent in Database die Session geliefert, über die das Java-Objekt der Klasse Database instanziert wurde. Umgekehrt folgt automatisch, da eine Session immer an einen bestimmten, die Session aufbauenden Benutzer gebunden ist, dass auch alle abhängigen Objekte diese Benutzerinformationen tragen (über den Bezug zur Session). Anders formuliert: Jedes Java-Objekt für Domino ist benutzerabhängig! Dieser Hierarchie unterliegen alle Domino-Java-Klassen. Welche Klassen stehen nun zur Verfügung? Am wichtigsten sind sicherlich die Klassen Session, Database, View und Document. Ein Session-Objekt wird wie bereits besprochen bezogen. Aus der Session lassen sich nun (fast) alle benötigten Objekte direkt oder indirekt ableiten.
6.1.1 • •
Objekte, die über Session bezogen werden können
Database – über getDatabase Einfache unabhängige Stil- und Eigenschafts-Objekte
6.1.2 Objekte, die über Database bezogen werden können
•
•
•
Über die Session können Stil-, Zeit- und einige Objekte, die Eigenschaften repräsentieren, bezogen werden. Hierzu gehört zum Beispiel das Objekt Registration, das Eigenschaften und Methoden rund um die User-ID liefert. Oder auch Notes-DateTime-Objekte und andere Objekte, die alle unabhängig von anderen Objekten erzeugt werden können (s. Kap. 12.2). Objekte, die komplexe Service-Anwendungen repräsentieren Domino stellt auch im Java-Umfeld neben den einfachen Domino-Objekten, Objekte zur Verfügung, die Anwendungen repräsentieren, mit denen komplexe Aufgaben erfüllt werden können. Hierzu gehört zum Beispiel der DXLExporter und DXLImporter, mit denen alle Domino-Objekte in XML konvertiert werden können oder z.B. die Klasse Log, mit der spezielle Log-Datenbanken mit Logging-Informationen gefüllt werden können. Ausführen verschiedener Befehle Die Session stellt etliche Befehle zur Verfügung, die von keinen dritten Objekten abhängen. Hierzu gehört z.B. das Evaluieren von @Formeln oder auch das Ausführen von Serverkonsolenbefehlen. Verschiedene Eigenschaften In der Session werden verschiedenste Variablen, insbesondere zum Server (mit dem die Session verbunden ist) und zum Benutzer zur Verfügung gestellt. Hierzu gehört zum Beispiel der Name des angemeldeten Benutzers.
6.1.2 •
•
•
Objekte, die über Database bezogen werden können
Document und DocumentCollection Die Database kann Dokumente direkt über deren IDs oder indirekt über verschiedene Methoden zur Suche liefern. Außerdem stellt sie verschiedene DocumentCollections zur Verfügung, die z.B. alle oder alle unverarbeiteten Dokumente repräsentieren. Objekte, die aus Designelementen abgeleitet werden In einer Notes-Datenbank sind verschiedenste Designelemente definiert, deren Java-Repräsentation direkt aus dem Database-Objekt bezogen werden können. Hierzu gehören die Java-Objekte Agent, Form, Outline und View. Zusätzlich liefert NoteCollection alle Designelemente der Datenbank. Datenbankeigenschaften Über das Datenbankobjekt können Java-Objekte bezogen werden, die Eigenschaften wie Sicherheitseinstellungen in ACL oder Eigenschaften der Replikation in Replication repräsentieren.
6.1.3 •
243
Objekte, die über View bezogen werden können
Document und DocumentCollection Auch über den View können Dokumente direkt oder indirekt über Suchfunktionen bezogen werden. Ergebnis einer Suche kann ein einzelnes Document oder eine DocumentCollection sein.
244 •
6 Domino-Objekt- und Speichermodell Objekte des View Der View selbst liefert verschiedene Objekte, mit denen der View beschrieben oder durchsucht werden kann. ViewColumn liefert eine Spalte des Views, ein ViewEntry beschreibt eine Zeile eines Views. Eine ViewEntryCollection ist eine Collection von ViewEntry. Über einen ViewNavigator können insbesondere kategorisierte Views durchsucht werden.
6.1.4 • • •
Objekte in Document
Daten Item und RichTextItem Binär-Objekte EmbeddedObject und MimeEntity Eigenschaften DateTime-Informationen über Erstell- oder Änderungsdatum, aber auch über Ersteller und Revisionen des Dokuments.
6.1.5
Domino-Objektklassen
Tabelle 6-1 zeigt alle für den Programmierer zur Verfügung stehenden Klassen des Domino-Java-API in einer Übersicht. Ein vollständiges Methodenverzeichnis befindet sich im Index, jeweils unter dem Namen der zugehörigen Klasse. In der Tabelle wird zunächst zwischen zwei grundsätzlichen Arten von Klassen unterschieden und zwar zwischen Basisklassen und einigen besonderen Hilfklassen auf der einen Seite (Markierung in der ersten Spalte „B“ der Tabelle) und den eigentlichen Domino-Objekten auf der anderen Seite. Die Klassen der zweiten Gruppe erweitern alle die Basisklasse Base (Markierung in der zweiten Spalte „E“) und können alle direkt oder indirekt entsprechend dem oben beschriebenen Schema aus der Session bezogen werden. Die eigentlichen Domino-Objektklassen erweitern Base und tragen daher in der Tabelle in der zweiten Spalte „E“ eine entsprechende Markierung. Für diese Klassen bietet die Tabelle eine Übersicht, wie sie jeweils bezogen werden können und welche Objekte (auf direktem Wege) aus diesen Klassen erzeugt oder bezogen werden können. Trägt eine Klasse in der Tabelle eine Markierung in einer der Spalten mit den Titeln „S“, „V“, „DB“ oder „D“, wird hierdurch gekennzeichnet, dass Objekte dieser Klassen jeweils aus einer Session, einem View, einer Database oder einem Document erzeugt oder bezogen werden können. Hierdurch erhalten Sie einen guten Überblick über die Objekthierarchie der Domino-Objekte. In der Spalte „Zusätzlich zu beziehen über“ wird für jede der Klassen vermerkt, ob es noch weitere Klassen gibt, über die die Klasse bezogen oder erzeugt werden kann. In der letzten Spalte mit der Überschrift „Liefert Objekte der Klassen (auch optional)“ werden die Klassen von Objekten gelistet, die aus einer Klasse erzeugt oder bezogen werden können.
6.1.5 Domino-Objektklassen
245
Tabelle 6-1 Übersicht über die Domino-Klassen Domino-Klasse
B E
ACL
x
ACLEntry
x
AdministrationProcess Agent
x
AgentBase
S V D D Zusätzlich zu be- Liefert Objekte der KlasB ziehen über sen (auch optional) • ACL • •
x
AgentContext
Database, DateTime
x x
•
ColorObject
x
•
Database
x
•
DateRange
x
•
DateTime
x
DbDirectory
x
• • • • AgentContext, DateRange, DateTime • Database
Document
x
• •
DocumentCollection, Newsletter
DocumentCollection DxlExporter
x
• •
AgentContext
x
•
DxlImporter
x
•
EmbeddedObject
x
Form
x
International
x
AgentContext
AppletBase
x
Base
x
Item JAppletBase
RichTextSection, RichTextTable AgentContext, ACL, Agent, DateTime, DbDirectory Document, Form, DocumentCollection, Outline, Replication, View DateTime
DateTime, EmbeddedObject, Item, MimeEntity, RichTextItem Document
• RichTextItem • •
x
•
DateTime, MIMEEntity
• Item
MIMEHeader
x •
Log
x
MIMEEntity
x
MIMEHeader
x
Name
x
•
Newsletter
x
•
NoteCollection
x
NotesAppletContext NotesError
Agent, Database, DateTime, Document, DocumentCollection
x x
MIMEEntity AgentContext Document • AppletBase, JAppletBase
Database, Document
246
6 Domino-Objekt- und Speichermodell
Tabelle 6-1 Übersicht über die Domino-Klassen Domino-Klasse
B E
NotesException
x
NotesFactory
x
NotesThread
x
S V D D Zusätzlich zu be- Liefert Objekte der KlasB ziehen über sen (auch optional)
OutlineEntry
•
Outline
x
OutlineEntry
x
Registration
x
Replication
x
ReplicationEntry RichTextDoclink
x
Replication
x
RichTextItem, RichTextNavigator
RichTextItem (1) RichTextNavigator
Outline • •
• x
RichTextStyle
EmbeddedObject RichTextItem
RichTextDocLink, RichTextSection, RichTextTable RichTextTab
•
RichTextParagraphStyle RichTextRange
x x
RichTextItem
RichTextStyle
RichTextSection
x
ColorObject, RichTextStyle
RichTextStyle
x
RichTextNavigator RichTextItem
RichTextTab
x
RichTextTable
x
Session
x
Stream
x
View
x
ViewColumn
x
•
RichTextParagraphStyle RichTextItem, RichTextNavigator AgentBase, NotesFactory
ColorObject
AgentContext, Database, DateRange, DateTime, DbDirectory, Newsletter, Registration, Name, RichTextParagraphStyle, Stream, RichTextStyle, Log, International
• •
•
Document, DateTime, ViewColumn, ViewEntry, ViewEntryCollection, ViewNavigator
6.1.5 Domino-Objektklassen
247
Tabelle 6-1 Übersicht über die Domino-Klassen Domino-Klasse
B E
S V D D Zusätzlich zu be- Liefert Objekte der KlasB ziehen über sen (auch optional) ViewEntryCollection, ViewNavigator
Document
ViewEntry
x
•
ViewEntryCollection ViewNavigator
x
•
ViewEntry
x
•
ViewEntry
XSLTResultTarget
x
B E S V DB D
Basisklasse oder Sonderaufgabe Erweitert Base Enthalten in oder zu beziehen über Session Enthalten in oder zu beziehen über View Enthalten in oder zu beziehen über Database Enthalten in oder zu beziehen über Document
(1)
Erweitert Item
Die Tabelle wird für das Beispiel „Database“ wie folgt gelesen: In der Zeile „Database“ findet sich eine Markierung in der Spalte „E“, d.h. die Klasse Database erweitert die Basisklasse Base. Die Spalte „S“ trägt eine Markierung und kennzeichnet, dass ein Objekt der Klasse Database über eine Session bezogen werden kann (z.B. über getDatabase in Session). Die Spalte „Zusätzlich zu beziehen über“ enthält die Einträge „AgentContext“ und „DbDirectory“, d.h. ein Objekt der Klasse Database kann auch über eine dieser beiden Klassen erzeugt werden, z.B. über getCurrentDatabase in AgentContext oder openDatabaseByReplicaID oder openDatabase in DbDirectory. Die letzte Spalte liefert nun Informationen über die Objekte, die (direkt) über ein Database-Objekt erhalten werden können, dies sind ACL, Agent, DateTime, Document, DocumentCollection, Form, Outline, Replication und View, so kann z.B. ein Objekt der Klasse DocumentCollection über getAllDocuments bezogen werden, wobei eine derartige DocumentCollection alle Dokumente der Datenbank enthält. Session, Database, View und Dokument sind die wichtigen Objekte in der Domino-Objekthierarchie. Nachdem der Session (und dem eng mit der Session verbundenen NotesThread) bereits die Kapitel 4 und 5 gewidmet waren, erhalten nun die Klassen Database, View und Document in diesem Kapitel ihren eigenen Platz, wobei zunächst noch kurz auf die eingangs erwähnten und in der Tabelle mit „B“ markierten Base- und Sonderklassen eingegangen wird.
248
6 Domino-Objekt- und Speichermodell
6.2
Basis- und Sonderklassen
Die Basis- und Sonderklassen der Domino-Objektstruktur stehen ausserhalb der Domino-Objekthierarchie. Im Einzelnen sind dies folgende Klassen, mit den Aufgaben: •
•
•
•
•
•
•
AgentBase Die AgentBase ist die Basisklasse aller Java-Agenten, die als Agent im Domino Designer entwickelt werden. Sie stellt den Agenten den so genannten AgentContext zur Verfügung über den Sicherheits- und Kontextinformationen bezogen werden können. Hierzu gehört der Name des aktuellen Benutzers der Session, der effektive Benutzer unter dessen Rechten die Anwendung ausgeführt wird und der so genannte DocumentContext, der das Kontextdokument liefert. AppletBase und NotesAppletContext Die AppletBase ist die Basisklasse aller Domino-Java-Applets und erweitert java.applet.Applet. Jedes Applet, das Domino-Objekte verwendet, muss die AppletBase erweitern. Die AppletBase stellt Methoden zum Bezug einer Notes-Session zur Verfügung und kann einen NotesAppletContext zur Verfügung stellen. Der NotesAppletContext ist nur gültig, wenn das Applet innerhalb eines Domino-Designelements im NotesClient angezeigt wird und liefert Server, Datenbank und das Dokument, in dem sich das Applet befindet, jeweils sofern verfügbar. Nähere Informationen finden Sie in Kapitel 4.9. Base Die Klasse Base ist die Basisklasse aller regulären Domino-Objekte. Alle diese Objekte erweitern Base. Die Klasse Base selbst sollte nicht direkt benutzt werden. JAppletBase Die Klasse JAppletBase hat die gleiche Funktion wie AppletBase, erweitert selbst aber javax.swing.JApplet. Sie stellt die gleichen Methoden wie AppletBase zur Verfügung und wird bei der Entwicklung von Swing-basierten Domino-Applets verwendet. NotesError Die Klasse NotesError definiert Konstanten, die die Fehlernummern aus NotesException beschreiben. NotesException Die Klasse NotesException erweitert java.lang.Exception und wird für das Exception Handling in Notes verwendet. Notes-Methoden werfen NotesExceptions, sofern sie überhaupt eine Exception werfen. NotesFactory Über die Klasse NotesFactory kann die Notes-Session bezogen werden. Sie bietet zusätzlich unterstützende Methoden zum Bezug des IOR und für die Verwendung des Connection Poolings. Nähere Informationen finden Sie in Kapitel 5.2.3ff. und einen Überblick in Kapitel 5.3.7.
6.2.1 AgentBase •
249
NotesThread NotesThread erweitert java.lang.Thread und ist die Basis lokaler DominoSessions. NotesThread wird immer dann benötigt, wenn Notes-Sessions auf
•
Basis einer lokalen Notes- oder Domino-Installation aufgebaut werden sollen. Nähere Informationen finden Sie in Kapitel 4.3 ff. XSLTResultTarget Seit Version 5 bietet Domino reichhaltige Funktionalitäten zur Verarbeitung von XML und XSL. Mit transformXML in EmbeddedObject, Item, MIMEEntity kann XML über ein XSL Stylesheet transformiert werden. Das Ergebnis ist ein XSLTResultTarget. Diese Klasse ist ein Wrapper der Klasse com.lotus.xsl. XSLTResultTarget, die wiederum org.apache.xalan.xslt.XSLTResultTarget erweitert. Der Wrapper und das Extending sind absolut geradlinig ohne Nebenwirkungen implementiert, so dass die Konstruktoren und Methoden äquivalent zu denen aus org.apache.xalan.xslt.XSLTResultTarget verwendet werden können. Seit Domino R7 ist XSLTResultTarget eine eingenständige Klasse und basiert (intern) im Wesentlichen auf javax.xml.transform, da seit Java 1.4.x die XML- und XSL-Klassen Bestandteil des Standard-JRE sind39.
6.2.1
AgentBase
AgentBase ist deklariert als public class AgentBase extends NotesThread, wobei NotesThread seinerseits java.lang.Thread erweitert, hat den Constructor AgentBase()und enthält die Methoden: •
void dbgMsg(String s)
Wurde das Debugging für einen Agenten über setDebug (true) aktiviert, so können mit dbgMsg40 Nachrichten auf die Serverkonsole bzw. die JavaDebugKonsole im NotesClient geschrieben werden. dbgMsg gibt seine Meldungen auf System.out aus. •
•
• 39
40
void dbgMsg(String s, PrintStream printstream) dbgMsg (String, PrintStream) arbeitet wie dbgMsg (String), gibt jedoch seine Meldungen auf den PrintStream aus uns stellt den Meldungen den String "dbg: " voran. void dbgMsg(String s, PrintWriter printwriter) Wie dbgMsg (String, PrintStream), jedoch Ausgabe auf einen PrintWriter PrintWriter getAgentOutput() Dementsprechend wurden aus der Domino 7 Core-Installation auch die Bibliotheken xml4j.jar und lotusxsl.jar entfernt. Dies hat leider Auswirkungen auf bestehende Installationen für den Fall, dass Methoden aus xml4j direkt verwendet wurden, da nun die neuen Parser verwendet werden müssen. Die Methoden dbgMsg (String), dbgMsg (String, PrintStream), dbgMsg (String, PrintWriter) und setDebug (boolean) sind nicht von Lotus dokumentiert und können daher unangekündigten Änderungen unterliegen.
250
6 Domino-Objekt- und Speichermodell Jeder Agent hat einen Ausgabe-Stream. Für WebAgenten ist dies die Ausgabe auf den Browser, für Agenen im NotesClient die JavaDebugConsole, für Server-basierte Agenten die Serverkonsole.
•
OutputStream getAgentOutputStream()
Neu seit Domino R7. Liefert den Agenten-Ausgabe-Stream als java.io. OutputStream. Vgl. getAgentOutput(). •
•
Session getSession() Die Session, in der der Agent geöffnet wurde. Dies ist entweder die Session des aktiven Benutzers oder die Session des Servers. boolean isRestricted()
Domino-Java-Agenten, die im Domino Designer erstellt werden, haben (seit Version 6) im Kontext-Menü die Einstellung „Set runtime security level (1 = most secure)“. Hier kann für Benutzer mit uneingeschränkten Rechten festgelegt werden, ob der Agent dennoch mit eingeschränkten Rechten arbeitet. isRestricted ()41 liefert true, falls diese Einstellung den Wert 1 hat. Für die Werte 2 und 3 („allow restricted operations“ und „allow restricted operations with full administrative rights“) liefert die Methode false. Nähere Informationen finden Sie in Kapitel 4.16. •
void NotesMain()
Die Methode NotesMain ist der Einstiegspunkt eines Domino-Java-Agenten (s. Kap. 4.10). •
final void runNotes()
Interne Methode des Java-Agenten, die letztendlich NotesMain aufruft. Sollte nicht direkt verwendet werden. •
void setDebug(boolean flag)
Legt fest, ob dbgMsg seine Ausgaben tatsächlich auf System.out liefern soll. • •
void setTrace(boolean flag) Wrapper für Runtime.getRuntime().traceMethodCalls(boolean) final void startup(AgentInfo agentinfo) Interner Wrapper. Ruft letztendlich java.lang.Thread.start() auf. Sollte
nicht direkt verwendet werden.
6.2.2
AppletBase und JAppletBase
AppletBase ist deklariert als public class AppletBase extends java.applet.Applet implements DominoAppletBase und hat den Construktor public AppletBase(). JAppletBase ist definiert als public class JAppletBase extends JApplet implements DominoAppletBase und hat den Construktor public JAppletBase(). Das Interface DominoAppletBase ist Basis für AppletBase und JAppletBase und deklariert die in beiden Basisklassen für Domino-Applets benötigten Methoden. Sowohl AppletBase als auch JAppletBase implementieren alle
41
Die Methode isRestricted is nicht von Lotus dokumentiert.
6.2.2 AppletBase und JAppletBase
251
Methoden von DominoAppletBase und überschreiben zusätzlich die Methoden init, start, stop und destroy von java.applet.Applet, bzw. von javax.swing.JApplet AppletBase und JAppletBase haben folgende Methoden: • •
•
void closeSession(Session session) Schließt eine geöffnete Notes-Session. final void destroy() Überschreibt java.applet.Applet.destroy() und ruft (letztendlich) notesAppletDestroy() auf. Soll nicht direkt verwendet werden. Stattdessen soll in einer Anwendung notesAppletDestroy verwendet werden. NotesAppletContext getContext(Session session) Liefert den AppletContext, der Informationen über die Notes-Session liefert
(s.o.) • • •
•
Session getSession() Liefert die Notes-Session (s. Kap. 4.9). Session getSession(String s, String s1) Liefert die Notes-Session (s. Kap. 4.9). final void init() Überschreibt java.applet.Applet.init() und ruft notesAppletInit() auf. Soll nicht direkt verwendet werden. Stattdessen soll in einer Anwendung notesAppletInit verwendet werden. boolean isNotesLocal() Liefert die Information, ob die zugrunde liegende Notes-Session lokal aufge-
baut wurde. Dies ist (in der Regel) der Fall, wenn das Applet im NotesClient geöffnet wurde (s. Kap. 4.9). • •
Object newInstance(String s) void notesAppletDestroy()
Soll Aufräumarbeiten erledigen, wenn es vom eigentlichen Applet überschrieben wird. Die grundsätzliche Verwendungsweise ist entsprechend der Methode destroy in Applet vorgesehen. •
void notesAppletInit()
Soll Initialisierungsarbeiten erledigen, wenn es vom eigentlichen Applet überschrieben wird. Die grundsätzliche Verwendungsweise ist entsprechend der Methode init in Applet vorgesehen. •
void notesAppletStart()
Soll Arbeiten bei jedem Start des Applets erledigen, wenn es vom eigentlichen Applet überschrieben wird. Die grundsätzliche Verwendungsweise ist entsprechend der Methode start in Applet vorgesehen. •
void notesAppletStop()
Soll Arbeiten bei jedem Anhalten des Applets erledigen, wenn es vom eigentlichen Applet überschrieben wird. Die grundsätzliche Verwendungsweise ist entsprechend der Methode stop in Applet vorgesehen. • •
Session openSession() Siehe getSession (). Session openSession(String s, String s1)
252
•
•
6 Domino-Objekt- und Speichermodell Siehe getSession (String, String). final void start() Überschreibt java.applet.Applet.start() und ruft notesAppletStart() auf. Soll nicht direkt verwendet werden. Stattdessen soll in einer Anwendung notesAppletStart verwendet werden. final void stop() Überschreibt java.applet.Applet.stop() und ruft notesAppletStop() auf. Soll nicht direkt verwendet werden. Stattdessen soll in einer Anwendung notesAppletStop verwendet werden.
6.2.3
NotesException und NotesError
Die NotesException ist deklariert als public final class NotesException extends org.omg.CORBA.UserException. Sie ist die einzige Exception, die innerhalb der Domino-Java-Objekte verwendet wird und speichert daher zusätzlich zu den Fehlertexten spezielle für Notes-spezifische Fehlernummern, die im Feld id in NotesException gespeichert werden. Mögliche Werte für id werden über Konstanten in NotesError definiert. Dort findet sich auch die Methode String getErrorString(int i) mit der sich die Fehler IDs in den zugehörigen Text umwandeln lassen. In Version 5 von Lotus Domino wurde über printStackTrace in NotesException lediglich der Trace, jedoch nicht die Fehlernummer ausgegeben. Daher musste zusätzlich immer noch die ID in (z.B.) den catch Clauses mit ausgegeben werden, um eine vollständige Anzeige des Fehlers zu erhalten. Seit Version 6 wird die Notes-Fehlernummer automatisch bei Verwendung von printStackTrace ausgegeben. Zusätzlich steht seit Version 6 ein Feld mit einer internen Exception zur Verfügung, falls eine solche vorliegt. Ansonsten ist dieses Feld null. NotesException hat drei Construktoren, die vom Programmierer normal verwendet und mit throw new NotesException geworfen werden können: •
NotesException()
NotesException, bei der die internen Felder id, internal und text (für die Message aus toString) null bzw. 0 sind. •
NotesException(int i, String s)
Neue NotesException mit Fehlernummer i und Text s. •
NotesException(int i, String s, Throwable throwable)
Neue NotesException mit Fehlernummer i, Text s und interner Exception throwable. Ungewöhnlich ist, dass die internen Felder id für die Fehlernummer, text für die Exception Message und internal für die interne Exception als Public fields deklariert sind und direkt ohne getter angesprochen werden können. Ist e eine NotesException, werden diese Parameter über e.id als int, e.text als String und e.internal als Throwable angesprochen. Die Methode to-
6.3 Zusammenfassung
253
String liefert den Messagetext text oder die id, falls keine Message vorliegt und
optional zusätzlich dieselben Informationen über die interne Fehlermeldung, sofern eine vorliegt.
6.3
Zusammenfassung
Domino-Java-Objekte sind streng hierarchisch angelegt. Einerseits erweitern sämtliche Domino-Java-Objekte die Basisklasse Base. Andrerseits sind alle Objekte von einer Domino-Session abhängig. Bei der Erzeugung von Objekten wird daher immer direkt oder indirekt von einer Session ausgegangen. Hieraus hervorgehend können dann Datenbanken, Ansichten, aber auch Masken und Agenten geöffnet werden. Die Dokumente der Datenbanken stehen entweder als Container von Item-Objekten zur Verfügung oder werden selbst in DocumentCollections gesammelt, während Resultsets gleichermaßen in ViewEntryCollections abgebildet werden können. In den folgenden Kapiteln werden die Zusammenhänge zwischen diesen Objekten und die Arbeit mit ihnen und den Daten- und Speicherobjekten ausführlich diskutiert. Weitere wichtige Objekte und Methoden von Ansichtenindizes über Agenten, von Datenbanken und Designelementen bis hin zum Document- und Designlocking und der komplexen Verarbeitung von RichText werden dann in jeweils eigenen Kapiteln die beteiligten Objekte und Methoden auch anhand von Beispielen analysiert. Während dieses Kapitel nur eine Einführung und Übersicht bietet, wird mit den folgenden Kapiteln das gesamte Rüstzeug zur Verfügung stehen, das als Basis benötigt wird, um komplexe Domino- und Notes-Anwendungen in Java zu programmieren.
7
7 Document
In diesem Kapitel: Document und Item als Abbild der Datenstruktur von Domino Dokumenten-Lifecycle Profil- und Antwortdokumente Dokumenthierarchien Encryption und Signing Attachments und OLE-Objekte Document Locking
256
7 Document
7.1
Document und Item
Immer wieder zeigt sich, dass Datenbankprogrammierer, kommen sie das erste Mal mit Domino-Java-Objekten in Berührung, Einstiegshürden überwinden müssen, die die Objektstruktur mit sich bringt. Dies liegt an der Struktur der losen Schemata, auf denen das lotus.domino. Document basiert. Letzendlich ist das Document eine leere, flexible Hülle, in der Werte oder gar Objekte beliebig in Anzahl und Art gespeichert werden können. Daten sind nicht, wie z.B. in SQL-Datenbanken, daran gebunden, über Ansichten oder Tabellenabfragen bezogen zu werden (obwohl dies auch möglich ist) und unterliegen auch nicht der Datenstruktur des zwingenden Schemas z.B. einer Tabelle. Andrerseits wird (in der Regel) dennoch ein konkretes und konsistentes Schema erreicht, indem Dokumente, sofern sie durch einen Benutzer interaktiv angelegt werden, auf der Basis von Masken erstellt werden, die ein erweiterbares Schema erzeugen. Am ehesten lassen sich Dokumente noch mit JavaBeans vergleichen. Statt der getter- und setter-Methoden verwendet das Document die Methoden getItemValue und replaceItemValue (und andere), über die Name-Value-Paare, ähnlich einem Hashtable, in den Items (also den Speichereinheiten für einzelne Werte) gespeichert werden können. Items sind am ehesten mit den Feldern einer JavaBean vergleichbar. Der Unterschied zwischen einem Dokument und einer JavaBean besteht darin, dass ein Document nicht programmatisch erweitert werden muss, wenn neue Werte oder Werte eines bisher noch nicht verwendeten Datentyps gespeichert werden sollen, sondern es können jederzeit einem Dokument weitere Items hinzugefügt werden. Interessant ist in diesem Zusammenhang eine Entwicklung an einer anderen Stelle der Java-Programmierung. In der Projektentwicklung für die Datenbankschicht relationaler Datenbanken erfreuen sich im Java-Umfeld O/R Frameworks zum Mapping zwischen der relationalen Datenbankschicht und der objektorientierten JavaSchicht zunehmender Verbreitung42. Auch wenn sich die Konzepte von Database, View und Document nicht direkt mit solchen Frameworks vergleichen lassen, so ist doch die Idee der objektorientierten Datenbankverarbeitung sehr ähnlich, so dass sich ein Programmierer, der im O/R Mapping Erfahrungen mitbringt, sicher leicht in die Domino-Java-Klassen einarbeiten wird. Ein Document ist aber mehr als nur ein Container einfacher Datentypen. Das Konzept der Embedded Objects und des RichTextes, in denen Dateianhänge bis hin zu OLE-Objekten gespeichert werden können, geht weit über das der JavaBean hinaus und erweitert das Document zu einem universellen Container für fast jeden denkbaren Datentyp. Das Konzept der Items ist übrigens dem Konzept des Node in XML-Dokumenten sehr ähnlich, daher können Notes-Dokumente nahtlos in XML überführt werden (s. Kap. 17.7). Es bestehen folgende Möglichkeiten ein Dokument zu beziehen oder zu erzeugen: 42
Das zur Zeit verbreitetste Framework in diesem Bereich aus der OpenSource-Gemeinde ist sicherlich Hibernate [Hibernate, URL]. Als einführende Literatur sei das Hibernate „In Action“ Buch aus dem Mannings Verlag empfohlen. [Hibernate, In Action]
7.1.1 Mit Items arbeiten
• •
• •
257
Direkt über die Datenbank über die Universal ID oder NoteID des Dokuments, bzw. durch Neuerstellen eines Dokuments (s. Kap. 9) Über einen View der Datenbank, indem entweder über eine Suche ein einzelnes Dokument oder mehrere über eine DocumentCollection oder eine ViewEntryCollection bezogen werden (s. Kap. 10 und 15). Über ein Newsletter-Objekt, das Verweise zu Dokumenten sammelt, um diese per E-Mail versenden zu können. Für einige Sonderfälle direkt aus Objekten (z.B. das Kontextdokument in DocumentContext)
Grundsätzlich gibt es vier Arten von Dokumenten, die aber alle über das Javaobjekt Document angesprochen werden: „Normale“ Dokumente, die als Datencontainer in der Datenbank dienen, Profildokumente, die eine spezielle Speicherbehandlung erfahren und dort zeitsparend (aber „sticky“) verwaltet werden (s. Kap. 7.2), Antwortdokumente, die sich dadurch auszeichnen, dass sie immer von einem übergeordneten Dokument abhängen und einen Querverweis zu diesem Parent-Document tragen (s. Kap. 7.3)und schließlich Konfliktdokumente, die beim Speichern oder bei der Replikation entstehen, wenn entweder zwei Benutzer gleichzeitig ein Dokument öffnen, verändern und dann zu speichern versuchen, oder bei der Replikation, wenn ein Dokument auf zwei verschiedenen Repliken verändert wurde und diese beiden Repliken nun synchronisiert werden müssen (s. Kap. 2.3 ff). Um Speicherkonflikte zu vermeiden gibt es seit Domino 6 den Mechanismus des Document Locking, durch den Dokumente exklusiv für einen Benutzer oder Prozess gesperrt werden können (s. Kap. 7.7).
7.1.1
Mit Items arbeiten
Items sind die Container innerhalb eines Document, in denen die eigentlichen Daten gespeichert werden. Sie sind vergleichbar mit Nodes in einem XML-Dokument. Während im Notes Client durch den Benutzer die Daten in Felder eingegeben und dann in Items gespeichert werden, sind die Items als solche unabhängig von einer Maske und können über die Java-Backend-Programmierung immer gelesen oder geschrieben werden. Für Items wird einerseits zwischen verschiedenen Datentypen unterschieden und andrerseits, ob ein Item einen diskreten Wert enthält oder ob es sich um ein MultiValue Item handelt, das mehrere Werte eines solchen Datentyps enthält. Bei MultiValue Items wird in Domino auch von Listen gesprochen. Multi-Value Items werden als Vector (der entsprechenden Datentypen) ausgelesen. Items des Datentyps DateTime machen hier eine Ausnahme, da sie immer als Vector gespeichert und gelesen werden. Items können entweder als Java-Objekt behandelt werden, indem ein einzelnes Item über getFirstItem (String name) in Document bezogen wird oder alle Items eines Document über getItems () bezogen werden, oder ihr Wert kann direkt über verschiedene Methoden getItemValue... () in Document gelesen
258
7 Document
werden. Sollte es in einem Document mehrere Items gleichen Namens geben, liefert getFirstItem nur das erste Item und getItemValue... nur den Wert des ersten Items diesen Namens. Ein Item kann nicht ohne das Document, in dem es sich befindet, existieren, d.h. die Klasse Item kann auch instanziert werden und innerhalb eines Dokuments neu erstellt werden. Folgerichtig hat Item die Methode getParent (), die das beinhaltende Document liefert. Neu erstellt wird ein Item innerhalb eines Document durch Item replaceItemValue(String name, Object value) in Document oder durch Item appendItemValue(String name, Object value), Item appendItemValue(String name, int value) oder Item appendItemValue(String name, double value), wobei alle ein neues Item im Document anlegen und als Item-Objekt zurückgeben, sofern es noch nicht existiert, replaceItemValue jedoch ein möglicherweise vorhandenes Item gleichen Namens ersetzt, wogegen appendItemValue ein weiteres Item gleichen Namens anlegt. Dies wird nicht empfohlen, da von mehreren Items gleichen Namens, wie eingangs erwähnt, nur über getFirstItem das erste aus einem Dokument gelesen werden kann und folglich zusätzliche Items nur schwer handhabbar sind. Ein anderer Weg, ein Item neu zu erstellen, ist die Methode Item copyItemToDocument (Document targetDoc) in Item, wodurch ein Item in ein Zieldokument kopiert wird und Item copyItem(Item sourceItem) in Document, wodurch ein Item in das aufrufende Document kopiert wird. Beide Methoden können einen zusätzlichen String-Parameter haben, durch den der Name des neuen Items spezifiziert wird. Die Methode void copyAllItems(Document targetDocument, boolean replaceItemIfExists) in Document kopiert alle Items des Documents, über das diese Methode aufgerufen wird, in das im Parameter targetDocument spezifizierte Zieldokument. Hat replaceItemIfExists den Wert true, werden bereits vorhandene Items im Zieldokument überschrieben, andernfalls werden die Werte an die vorhandenen Items angehängt. Wird mit der Methode copyItemtoDocument ein RichTextItem kopiert, werden EmbeddedObjects oder Anhänge im Item nicht mitkopiert. Soll der Wert eines einzelnen Items für viele Dokumente gleichzeitig geändert werden, so sei hier als Vorgriff auf Kapitel 10.7 darauf hingewiesen, dass mit der Methode void stampAll(String name, Object value) in DocumentCollection und ViewEntryCollection der Wert des Items mit dem Namen name für alle Dokumente der Collection durch den Wert value ersetzt wird, wie es durch replaceItemValue erreicht würde. Diese Methode ist einem Loop über alle Dokumente aus Performancegründen vorzuziehen. Die so veränderten Dokumente müssen nach der Operation nicht gespeichert werden, dies geschieht implizit in stampAll. Änderungen in bereits instanzierten Document-Objekten müssen vor der Verwendung von stampAll bereits gespeichert sein, um Versions- und Speicherkonflikte zu vermeiden. Umgekehrt werden solche bereits geladenen Java-Objekte durch stampAll nicht aktualisiert und müssen neu geladen werden.
7.1.2 Datentypen handhaben 7.1.2
259
Datentypen handhaben
Der einfachste und direkteste Weg, Daten, Items und Dokumente zu handhaben sind die Methoden getItemValue... (String name) in Document. Diese Methoden sind äquivalent zu entsprechenden getValue... () Methoden in Item, die in Tabelle 7-1 dargestellt werden. Beim Beziehen von Werten von Items, egal ob aus dem Document oder dem Item, ist es wichtig, den Datentyp der zu lesenden Daten zu kennen, sonst werden unerwartete Ergebnisse geliefert oder Exceptions geworfen (s.u.). Wird mit Items gearbeitet, steht die Methode getType () zur Verfügung, die die Art der Daten liefert. Die wichtigsten Typen sind TEXT, NUMBERS, DATETIMES, NAMES, READERS, AUTHORS, RICHTEXT, NOTEREFS, NOTELINKS, ATTACHMENT, OTHEROBJECT, EMBEDDEDOBJECT (s. Tabelle 2-3 in Kapitel 2.2.3), wobei diese Typen durch
gleichnamige Konstanten in Item repräsentiert werden. Beim Lesen (s. Tabelle 7-1) und Schreiben (s. Tabelle 7-2) von Item-Werten sind einige Besonderheiten zu beachten (die Nummern beziehen sich auf die beiden Tabellen). 1
2
3
4
5
Für Multi-Value Items können mehrere Werte als Vector bezogen werden. Enhält ein Item nur einen Wert, liefern die Methoden getItemValue und getValues einen Vector mit nur einem Element. Rückgabewerte im Vector values können Elemente der Typen Double, String oder DateTime sein. Das Konzept der CustomData Items erlaubt es, Objekte einer beliebigen Klasse, die Serializable implementiert, abzuspeichern. Die Handhabung ist einfach, es können solche Objekte direkt mit replaceItemValueCustomData (21) geschrieben und mit getItemValueCustomData wieder gelesen werden. Es empfiehlt sich beim Schreiben von CustomData-Elementen einen Typnamen zu spezifizieren (22), am besten wird hier der Name der Klasse des CustomDataElements verwendet. Wird versucht, ein Element über getItemValueCustomData (String, String) zu laden, das nicht dem spezifizierten Typ entspricht, wird eine entsprechende NotesException („Supplied Data type name does not match stored CustomData type“) geworfen. Zur Verwendung über externe APIs können solche Daten mit getItemValueCustomDataBytes (String name, String className) auch als Byte Array geschrieben werden. Datumsangaben können in Notes entweder als diskrete Datums- (und Uhrzeit-) Angaben gespeichert werden oder als DateTimeArray (getItemValueDateTimeArray (String name)) zur Definition von Zeitspannen. Üblich ist Letzteres. Datentypen kontrollieren Dringend überwachen müssen Sie die Datentypen bei der Verwendung der Methoden getItemValueInteger, getItemValueDouble und getItemValueString, denn diese werfen keine (!) Exception, wenn versucht wird, ein Item eines anderen Datentyps zu lesen.
260
7 Document
Tabelle 7-1 Methoden zum Lesen von Item-Werten in Item und Document
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 6 7
8 9
in Document
in Item
Vector getItemValue(String name) throws NotesException Object getItemValueCustomData (String name) throws IOException, ClassNotFoundException, NotesException Object getItemValueCustomData (String name, String className) throws IOException, ClassNotFoundException, NotesException byte[] getItemValueCustomDataBytes (String name, String className) throws IOException, NotesException Vector getItemValueDateTimeArray (String name) throws NotesException double getItemValueDouble(String name) throws NotesException int getItemValueInteger(String name) throws NotesException String getItemValueString(String name) throws NotesException
Vector getValues() throws NotesException Object getValueCustomData() throws IOException, ClassNotFoundException, NotesException Object getValueCustomData (String className) throws IOException, ClassNotFoundException, NotesException byte[] getValueCustomDataBytes (String className) throws IOException, NotesException Vector getValueDateTimeArray() throws NotesException double getValueDouble() throws NotesException int getValueInteger() throws NotesException String getValueString() throws NotesException
Item getFirstItem (String name) throws NotesException Vector getItems () throws NotesException int getSize() throws NotesException DateTime getDateTimeValue() throws NotesException String getText() throws NotesException String getText(int maxLen) throws NotesException String abstractText (int maxlen, boolean dropvowels,boolean userdict) throws NotesException int getValueLength() throws NotesException int getType() throws NotesException boolean containsValue (Object value) throws NotesException String toString ()
Zahlen werden in der Regel als Double gespeichert, beim Lesen über getItemValueDouble werden so auch Integer-Werte in Double umgewandelt. Grundsätzlich können Zahlen auch als Integer gespeichert werden, beim Lesen von Double-Werten über getItemValueInteger werden sie buchhalterisch gerundet. Der in Notes am einfachsten zu handhabende (und am häufigsten verwendete) Datentyp ist String und wird über getItemValueString gelesen. getFirstItem liefert nur ein Item, auch wenn es mehrere gleichen Namens gibt. Für getFirstItem gibt es kein Äquivalent in Item.
7.1.2 Datentypen handhaben
261
Tabelle 7-2 Methoden zum Schreiben von Item-Werten in Item und Document
20
21 22 23
24 25 26 27
28 29 30 31
in Document
in Item
Item replaceItemValue (String name, Object value) throws NotesException (1)
void setDateTimeValue() throws NotesException void setValueInteger(int value) throws NotesException void setValueDouble(double value) throws NotesException void setValueString(String value) throws NotesException void setValues(Vector values) throws NotesException (2)
(1) mit value instanceof Integer, Double, String, DateTime oder Item oder Vector aus Intger, Double, String oder DateTime
(2) mit Vector aus String, Integer, Double, DateTime
Item replaceItemValueCustomData (String name, Object value) throws IOException, NotesException Item replaceItemValueCustomData (String name, String className, Object value) throws IOException, NotesException Item replaceItemValueCustomDataBytes(String name, String className, byte[] value) throws IOException, NotesException
void setValueCustomData (Object value) throws NotesException void setValueCustomData (String className, Object value) throws NotesException
Item appendItemValue(String name) throws NotesException Item appendItemValue(String name, double value) throws NotesException Item appendItemValue (String name, int value) throws NotesException Item appendItemValue (String name, Object value) throws NotesException (1)
void setValueCustomDataBytes (String className, byte[] value) throws NotesException
void appendToTextList (String value) throws NotesException void appendToTextList (Vector /*of String*/ values) throws NotesException
boolean hasItem (String name) throws NotesException void removeItem(String name) throws NotesException void remove() throws NotesException recycle() throws NotesException
10 getItems liefert alle Items eines Dokuments, auch eventuell solche mit gleichem Namen. 11 getSize in Document liefert die Gesamtgröße des Dokuments, die Größe von Items kann über getValueLength in Item berechnet werden. 12 Im Gegensatz zum Dokument kann im Item auch ein diskreter DateTime-Wert gehandhabt werden.
262
7 Document
13 getText liefert den Wert des Items als String, sofern er sich in einen solchen umwandeln lässt. RichTextItems können auch über getText angezeigt werden, werden jedoch um sämtliche Nicht-Text-Elemente reduziert. 14 getText (int max) arbeitet wie getText (), jedoch wird das Ergebnis auf maximal max Zeichen limitiert. 15 abstractText (int maxlen, boolean dropvowels,boolean userdict) ist ursprünglich dazu gedacht, RichText Items in Text umzuwandeln, kann aber auch auf normale (Text-) Items angewendet werden. maxlen gibt die maximale Anzahl von zurückgegebenen Zeichen an, dropvowels spezifiziert, ob Vokale aus dem Text entfernt werden sollen und userdict spezifiziert, ob Synonyme aus der Datei NOTEABBR.TXT angewendet werden sollen. Diese Datei enthält ein Replacement pro Zeile, wobei jede Zeile zunächst das neu einzufügende Wort, dann ein Leerzeichen, dann das zu ersetzende Wort enthalten muss. Es gelten spezielle Regeln für die Umwandlung in Uppercase, die Sie der Notes-Hilfe unter den Stichworten @Abstract und abstractText entnehmen können. Neu einzufügende Wörter müssen kürzer sein als zu ersetzende Wörter. 16 getValueLength liefert den internen Speicherbedarf für das Item in Bytes, einschließlich des von Notes benötigten Überhangs. 17 getType liefert eine int-Konstante (s.o.), die den Datentyp beschreibt. 18 containsValue ist für die Auswertung von Multi-Value Items gedacht und überprüft, ob eine Text-, oder Zahl- oder DateTime Liste den spezifizierten Wert enthält. 19 toString liefert (wider Erwarten) den Namen des Items, der auch über getName () erhalten werden kann. 20 Das Schreiben von Werten ist unterschiedlich in Document und Item realisiert. In Document wird der Methode replaceitemValue ein Parameterpaar aus name und value übergeben, wobei value ein Object ist, für das Werte instanceof Integer, Double, String, DateTime oder Item oder Vector aus Intger, Double, String oder DateTime übergeben werden können. In Item müssen diese Werte diskret über setValueDouble, setValueString usw. bzw., falls mehrere Werte gesetzt werden sollen, über setValues gesetzt werden. 21, 22, 23 – siehe 2,3,4 24 In Document können mit appendItemValue(String name) leere Items erzeugt werden, hasItem (28) liefert dann true, getItemValue liefert dann jedoch einen leeren Vector. getItemValueString liefert allerdings nicht etwa null, sondern einen leeren String. 25, 26, 27 Werte vom Typ Integer, Double, int, double, String oder DateTime können zu einem Multi-Value Item ergänzt werden, indem Werte hinzugefügt werden. Über Vektoren (27) können zeitgleich mehrere Werte angehängt werden. In Item gibt es hierfür nur Entsprechungen für Text und Textlisten (27). 28 Die Methode hasItem (String name) zeigt an, ob es ein Item (oder mehrere) dieses Namens im Document gibt. 29 removeItem in Document entfernt ALLE Items des angegebenen Namens. Doppelte Items mit gleichem Namen sind alle betroffen. 30 remove in Item entfernt nur das aktuelle Item. Doppelte Items mit gleichem Namen werden nicht gelöscht.
7.1.2 Datentypen handhaben
263
31 recycle räumt die C++ Backend-Repräsentation des Items auf. Für Version 5.x ist es wichtig, auch Items zu recyclen. In Version 6 gibt es verschiedene Versionen, in denen ein Recycle auch Fehler verursachen kann. In Version 6.5.2 z.B. recycelt, das recycle eines Items auch das gesamte Parent Document (s. Kap. 14). Es ist wichtig, sich immer ins Gedächtnis zu rufen, dass das Item nicht ohne „sein“ Document existieren kann. Wenn also z.B. ein Item über remove in Item entfernt wird, bezieht sich dies nicht etwa auf ein „alleinstehendes“ Item-Objekt, sondern entfernt endgültig das Item aus dem Document-Objekt, über das das Item bezogen wurde. Allerdings wird dieses Löschen des Items erst in der Datenbank manifestiert, wenn das Document über save gespeichert wird. Hier zeigt sich eine weitere wichtige Technik für die Verwendung von Items. Wird nämlich ein Document über eine der oben beschriebenen Methoden manipuliert, sind diese Änderungen so lange „virtuell“, bis ein save aufgerufen wird. Dies kann man sich zu Nutze machen, wenn Operationen auf einem Dokument ausgeführt werden müssen, die nicht in der Datenbank manifestiert werden dürfen, aber für einen bestimmten Zweck benötigt werden. Hierzu gehört die Verarbeitung von Items, von denen es mehrere gleichen Namens in einem Document gibt: doc.replaceItemValue(itemName,"Wert 1"); doc.appendItemValue(itemName, "Wert 2"); System.out.println ("Liefert \"Wert 1\": " + doc.getItemValue (itemName)); Item item = doc.getFirstItem(itemName); item.remove(); System.out.println ("Liefert \"Wert 2\": " + doc.getItemValue (itemName));
Mit diesem Code können also nach und nach Items (gleichen Namens) aus dem Document entfernt und weiterverarbeitet werden. Die Items müssen zwar über remove entfernt werden, um referenziert werden zu können, dies bleibt aber ohne Auswirkungen auf die Datenbank, solange kein save folgt. In jedem Fall gilt, dass die Verarbeitung am leichtesten (und die Anwendung am robustesten) ist, wenn jederzeit sauber kontrolliert wird, welche Items welchen Datentyp haben. Werden Daten über Benutzer und den Notes Client eingegeben, muss in den dort verwendeten Masken kontrolliert werden, welche Felder welchen Datentyp erzeugen. Einen Sonderfall nehemen Items mit dem Namen „Received“ ein, die in Dokumenten zu finden sind, die empfangene Internet-Mail-Nachrichten (in der eigenen Mail-In Datenbank) repräsentieren und deren Werte seit Domino R6.5 durch getReceivedItemText als Vector gelesen werden können. Dringend überwachen müssen Sie die Datentypen bei der Verwendung der Methoden getItemValueInteger, getItemValueDouble und getItemValueString, denn diese werfen keine (!) Exception, wenn versucht wird, ein Item eines anderen Datentyps zu lesen. getItemValueInteger liefert zum Beispiel immer 0, wenn kein Item des spezifizierten Namens gefunden wird oder das gefundene Item
264
7 Document
Best Practice Kontrollieren Sie, welche der Items welchen Datentyp haben und verwenden Sie nur die passenden Methoden. Ziehen Sie die Entwicklung eigener Methoden in Erwägung, die z.B. bei der Rückgabe von 0, 0.0 oder null über instanceof in getItemValue feststellen, um welchen Datentyp es sich tatsächlich handelt. Verwenden Sie getItemValue zum Bezug der Vectoren, wenn Sie keine Informationen über die zu lesenden Items haben. Verwenden Sie alternativ getFirstitem, getType und getValues, getValueString etc., wenn Sie keine Informationen über die zu lesenden Items haben.
einen anderen Datentyp hat. Dies stellt natürlich ein großes Risiko dar, da nie entschieden werden kann, ob tatsächlich ein Item mit dem diskreten Wert 0, oder aber ein Fehler vorliegt. Tabelle 7-3 zeigt das Verhalten der verschiedenen Methoden getItemValue..., falls versucht wird, Items zu lesen, deren Datentyp nicht zur Methode passt, und zeigt klar die Risiken bei der Rückgabe von 0, 0.0 oder null auf. Wenn keine Informationen vorliegen, welchen Datentyp die Items haben, ist es hilfreich, mit getItemValue immer nur die Vectoren zu beziehen. Dies erfordert aber, dann im Nachhinein entweder auftretende Exceptions in Kauf zu nehmen oder ein Exception Handling zu implementieren (z.B. falls Double erwartet wurde und bei dem dann notwendigen Cast Exceptions auftreten, da z.B. ein String geliefert wurde). Eine Alternative kann sein, eine Hilfsklasse zu implementieren, die zwar äquivalente Methoden für getItemValueString etc. vorhält, aber für den Fall der Rückgabe von 0, 0.0 oder null über getItemValue den Datentyp überprüft und falls dieser nicht zur aufgerufenen Methode passt, eine Exception wirft. Ein dritter Weg ist die konsequente Verwendung von Items und der Ermittlung des Datentyps über getType.
Bedenken Sie Dokumente können von Benutzern im Notes Client in Masken geöffnet werden. Enthalten Masken Berechnungen, so können im Backend über Java gesetzte Werte (unerwartet) verändert werden. Notes-Masken führen eine automatische Typen-Konvertierung durch, die zu unerwarteten Ergebnissen führen kann, wenn über Java in einem Item Daten eines anderen Datentyps als in der Maske vorgesehen gespeichert werden.
265
7.1.2 Datentypen handhaben
Tabelle 7-3 Verhalten der getItemValue-Methoden beim Lesen nicht erwarteter Datentypen (lokale Session) Datentyp des Items Integer
Methode getItemValueInteger
Integer
Double
gerundeter 0 Wert als Integer als Double 0.0
getItemValueDouble Wert Double getItemValueString null getItemValue Vector mit Wert als Double (!!) getItemValue(6) DateTimeArray getItemValue(1) CustomData
getItemValueCustomDataBytes
(2)
String
DateTime CustomData
Custom- Item Datanicht Bytes vorhanden
0
0
0
0
0.0
0.0
0.0
0.0
null Vector mit Wert als Double
String Vector aus String
null null Vector aus (7) DateTime
null (7)
null Leerer Vector
(6)
(6)
(6)
(4)
(1)
(1)
(3)
(5)
(2)
String (!!)
Vector aus (6) DateTime (1) Objekt der abgelegten Custom Klasse (2) ObjektStream als Byte Array
Objekt null als Byte Array
(1) NotesException: Supplied Data type name does not match stored CustomData type oder java.io.StreamCorruptedException: InputStream does not contain a serialized object (2) NotesException: Supplied Data type name does not match stored CustomData type oder sinnloses Ergebnis (3) java.io.StreamCorruptedException: InputStream does not contain a serialized object (4) NotesException: Item value is not a date type (5) java.lang.NullPointerException (6) NotesException: Item value is not a date type (7) NotesException: Unknown value type to convert Eine weniger elegante, aber dennoch des Öfteren (in der gelebten Praxis) anzutreffende Strategie ist es, grundsätzlich alle Werte in Strings oder Vectoren aus Strings abzuspeichern (bei der Eingabe über Notes-Masken) und dann im Einzelfall über entsprechende Parser etc. die Werte auszulesen, natürlich mit der gesamten Problematik der Konvertierung und des unnötigen Speicherüberhangs. Die Methoden verhalten sich übrigens äquivalent, sofern es sich um Multi-Value Items handelt, wobei die Methoden getItemValueInteger, getItemValueDouble und getItemValueString nur jeweils den ersten Wert des Items zurückgeben.
266
7 Document
Tabelle 7-4 Verhalten der getItemValue-Methoden beim Lesen nicht erwarteter Datentypen (DIIOP-Session) Datentyp des Items Integer
Methode getItemValueInteger
Integer
Double
0 Integer Anteil des Wertes als Double 0.0
getItemValueDouble Wert Double getItemValueString "" getItemValue Vector mit Wert als Double (!!) getItemValueDateTi- (6) meArray getItemValueCus(1) tomData getItemValueCustomDataBytes
(2)
String
DateTime CustomData
Custom266 Item Dataicht Bytes vorhanden
0
(8)
(8)
0
0.0
(8)
(8)
0.0
"" Vector mit Wert als Double
String Vector aus String
"" (8) Vector aus (8) DateTime
(8) (8)
null Leerer Vector
(6)
(6)
(8)
(1)
(1)
Leerer Vector Undefiniertes Object
(2)
String (!!)
Vector aus (8) DateTime (1) Objekt der abgelegten Custom Klasse (2) ObjektStream als Byte Array
(3)
Objekt null als Byte Array
(1) NotesException: Supplied Data type name does not match stored CustomData type oder java.io.StreamCorruptedException: InputStream does not contain a serialized object (2) NotesException: Supplied Data type name does not match stored CustomData type oder sinnloses Ergebnis (3) java.io.StreamCorruptedException: InputStream does not contain a serialized object (4) NotesException: Item value is not a date type (5) java.lang.NullPointerException (6) NotesException: Item value is not a date type (8) NotesException: Invalid object type for method argument Die Unterschiede zu Tabelle Tabelle 7-3 sind fettgedruckt. Fazit: Planen Sie Ihre Datentypen gründlich und verhindern Sie Fehler, die aus der Rückgabe von Null-Werten entstehen können. Zur weiteren Verdeutlichung des Verhaltens wurde die Klasse GetItemReturnValues im package djbuch.kapitel_07 beigelegt, die insgesamt vier Demonstrationen beinhaltet. Demo 1 erzeugt ein leeres Dokument und wendet die Methoden getItemValue... auf nicht vorhandene Items an.
7.1.2 Datentypen handhaben
267
Unterschiedliche Rückgabewerte für lokale und DIIOP-Sessions Je nachdem, ob die getItemValue Methoden in einer lokalen oder einer IIOP Session verwendet werden, geben sie unterschiedliche Werte zurück, wenn sie auf unerwartete Datentypen angewendet werden. Tabelle 7-3 und Tabelle 7-4 zeigen diese Werte für lokale und IIOP-Sessions. Insbesondere die unterschiedliche Rundung von getItemValueInteger und die Rückgabe von leeren Strings statt null bei getItemValueString sind bemerkenswert. Sind mehrere Items gleichen Namens in einem Document gespeichert, ist nicht gewährleistet, dass Domino diese Werte für lokale und DIIOP-Sessions in der gleichen Reihenfolge behandelt. Dies kann bedeuten, dass je nach Session-Typ verschiedene (!) Werte (mal das eine, mal das andere Item des doppelt verwendeten Namens) zurückgegeben werden.
Demo 2 erzeugt für jeden Datentyp ein Item und wendet auf jedes Item alle getItemValue Methoden an.
Demo 3 arbeitet wie Demo 2, erzeugt jedoch Vectoren aller Datentypen. Diese drei Demonstrationen zeigen das Verhalten der Methoden in showBehaviorGetItem an. Dort wird versucht ein Item mit allen zur Verfügung stehenden Methoden zu lesen und anzuzeigen. Demo 4 zeigt den obigen Code zum Lesen von mehreren Items mit gleichem Namen. Ein letzter wichtiger Punkt ist noch erwähnenswert. Items, die über die beschriebenen Java-Methoden manipuliert werden, unterliegen (selbstverständlich aber nur zunächst) ausschließlich diesen vom Programmierer erwünschten Veränderungen. Da Items im Notes Client in Notes-Masken geladen werden können, muss immer bedacht werden, dass ursprünglich sauber gesetzte Datentypen in den Masken verändert werden können. Speichern Sie zum Beispiel ein Number Item (Integer oder Double) in einem Dokument und ein Benutzer öffnet ein solches Dokument in einer Maske, die ein Feld gleichen Namens besitzt, das aber den Datentyp Text hat, so wird der NumberWert automatisch (durch die Maske) in Text umgewandelt. Speichert der Benutzer das Dokument, so wird aus Ihrem Number Item ein Text Item. Dies kann unangenehme Nebenwirkungen haben und sollte immer bedacht werden. Gleiches gilt für automatische Berechnungen, die in Masken durchgeführt werden könn(t)en.
268
7 Document
Im umgekehrten Fall kann es erwünscht sein, die in einer Maske hinterlegten automatischen Berechnungen, Programmierungen oder Validierungen und Eingabeumsetzungen auf ein im Backend durch Java manipuliertes Dokument anzuwenden. Dies kann über die Methode computeWithForm in Document erreicht werden.
7.1.3
Item Properties und weitere Methoden in Item
Neben den beschriebenen Methoden stehen noch verschiedene setter- und getter-Methoden in Item zur Verfügung, mit denen Eigenschaften der Items verändert werden, die nicht direkt über Methoden im Document gesetzt werden können. Soll für Items im Document einer dieser Werte gesetzt oder gelesen werden muss natürlich immer der Weg über das Item eingeschlagen werden und die Verwendung der getItemValue-Methoden in Document erübrigt sich. •
void setAuthors(boolean flag) und boolean isAuthors() throws NotesException boolean isAuthors() throws NotesException
Wird ein Text-Item mit dieser Eigenschaft versehen und das Dokument gespeichert, so können das Dokument nur noch Benutzer verändern, deren Benutzername(n) in diesem Feld auftauchen. Alternativ kann im so erzeugten Autorenfeld auch eine auf dem Server bekannte Gruppe, in der sich der Benutzer befindet, eingetragen werden. Eine weitere Möglichkeit besteht darin, in der ACL eine Rolle anzulegen und dort den gewünschten Benutzern diese Rolle zuzuweisen. Werden diesen Benutzern dann lediglich Autoren- (nicht Editoren-) Rechte in der ACL zugewiesen, jedoch erzwungen, dass jedes Dokument, das von solchen Autoren bearbeitet werden soll, ein Autorenfeld hat, in dem diese Rolle eingetragen ist, dann können diese Autoren solche Dokumente verändern, auch wenn sie nicht von ihnen selbst angelegt wurden. Wenn Rollen in Autoren-, Namens- oder Leserfelder eingetragen werden, müssen diese mit eckigen Klammern umschlossen werden, z.B.: doc.replaceItemValue ("allowedAuthors", "[authorRole]"); Item item = doc.getFirstItem ("allowedAuthors"); item.setAuthors (true); doc.save ();
Sollen mehrere Rollen oder Benutzer gleichzeitig eingetragen werden, müssen Sie einen Vector verwenden: Vector authors = new Vector(); authors.add("CN=Vorname Nachname/O=DJBUCH"); authors.add("[Sachbearbeiter]"); Item i = doc.replaceItemValue("Authors",authors); i.setAuthors(true); doc.save();
7.1.3 Item Properties und weitere Methoden in Item
269
Leser- und Autorenfelder richtig einsetzen Verwenden Sie immer die Langform für Einträge in Leser- und Autorenfeldern. Auch wenn Domino es erlaubt, an einigen Stellen die Bezeichner „CN=“ oder „O=“ usw. wegzulassen, ist es zwingend erforderlich, dass Sie Benutzer, die Sie in Leser- oder Autorenfelder eintragen, dort in der ausführlichen Form listen. Andernfalls werden die Benutzer die erwarteten Rechte nicht erhalten. Autorenfelder greifen nur für Benutzer bis maximal Autor Level. Alle Benutzer mit EditorRechten oder höher dürfen ohnehin die Dokumente anderer Benutzer verändern, können also nicht über ein Autorenfeld eingeschränkt werden.
•
void setReaders(boolean flag) throws NotesException boolean isReaders() throws NotesException
Analog zu setAuthors und isAuthors zu verwenden. Liegt ein derartiges ReaderItem (auch Leserfeld) in einem Document vor, dann können nur noch Benutzer dieses Document lesen, die im Leserfeld eingetragen sind. Beachten Sie, dass durch unaufmerksame Verarbeitung von Reader Items Dokumente unerreichbar werden können, wenn es keinen Benutzer gibt, der im Leserfeld gelistet ist. Erst seit Version 6 gibt es mit der Methode createSessionWithFullAcces in NotesFactory eine Möglichkeit solche Dokumente wiederzuerlangen, denn für einen Benutzer einer durch createSessionWithFullAcces geöffneten Session gelten keine Leser- oder Autorenfelder. •
void setNames(boolean flag) throws NotesException boolean isNames() throws NotesException Durch setNames wird ein Item zu einem Namensfeld. Dies hat keine besondere
Bedeutung, sondern kennzeichnet lediglich, dass in einem solchen Item nur Notes-Namen gespeichert werden sollen. •
void setEncrypted(boolean flag) throws NotesException boolean isEncrypted() throws NotesException
Notes-Dokumente können durch symmetrische Schlüssel verschlüsselt werden. Bei der Verschlüsselung auf Dokumentenebene werden nur Items verschlüsselt, die das Flag isEncrypted () == true tragen (s. Kap. 7.5). •
void setProtected(boolean flag) throws NotesException boolean isProtected() throws NotesException Items, für die isProtected () == true gilt, können auch bei der Neuanlage
von Dokumenten nur von Benutzern bearbeitet werden, die als Editor in der ACL eingetragen sind. Diese Eigenschaft kommt in der Regel nur in der Verwendung von Notes-Masken über die Eigenschaft „Must have at least Editor access to use“ zum Einsatz. •
void setSaveToDisk(boolean flag) throws NotesException boolean isSaveToDisk() throws NotesException
270
•
7 Document Items, für die isSaveToDisk () == false gilt, werden bei einem save in Document nicht mit in die Datenbank gespeichert. Default ist isSaveToDisk() == true. void setSigned(boolean flag) throws NotesException boolean isSigned() throws NotesException Wird diese Eigenschaft gesetzt, werden Dokumente, wenn sie über die Notes-eigenen Mail-Methoden (send in Document) verschickt werden und das Dokument signiert wird, signiert (s. Kap. 9.7).
•
•
void setSummary(boolean flag) throws NotesException boolean isSummary() throws NotesException Items, für die isSummary() == false gilt, können nicht in Ansichten-Spalten angezeigt werden. Default ist true. DateTime getLastModified() throws NotesException
Liefert das letzte Änderungsdatum des Items. •
String getName() throws NotesException getName liefert den Namen des Items, der z.B. beim Erstellen über replaceItemValue (String name, Object value) im Parameter name verwendet
•
Document getParent() throws NotesException getParent liefert das Document, über das das Item geladen oder erzeugt wur-
wurde.
de. Folgende Methoden stehen zusätzlich in Item zur Verfügung: •
org.xml.sax.InputSource getInputSource() throws NotesException Liefert den Inhalt eines Items als InputSource für die weitere Verarbeitung.
Diese Methode erzeugt eine temporäre Datei, die (erst) beim Recycling wieder gelöscht wird. •
java.io.InputStream getInputStream() throws NotesException Liefert den Inhalt des Items als java.io.InputStream-Repräsentation. Diese
•
java.io.Reader getReader() throws NotesException Liefert den Inhalt des Items als java.io.Reader. Diese Methode erzeugt eine
•
lotus.domino.MIMEEntity getMIMEEntity() throws NotesException
Methode erzeugt eine temporäre Datei, die beim Recycling wieder gelöscht wird.
temporäre Datei, die (erst) beim Recycling wieder gelöscht wird.
Seit Version 5 kann Domino Multipart Mime Messages verarbeiten, die auch ihre Repräsentation in den Java-Methoden gefunden haben. Typischerweise enthält eine Mail, die über Domino in die Mail-Datenbank des Benutzers geschickt wurde, ein Body-Feld, in dem sich MIME Entities befinden. MIME Entities können auch dazu genutzt werden, um HTML-E-Mails über Java in Domino zu versenden.
7.1.4 Mit Document arbeiten – Lifecycle eines Dokuments •
271
org.w3c.dom.Document parseXML(boolean validate) throws IOException, SAXException, NotesException org.w3c.dom.Document parseXML(boolean validate) throws IOException, NotesException43
Enthält ein Item gültiges XML, so kann es mit dieser Methode in ein XML dom Document umgewandelt werden. Ist der Parameter validate true, so wird die Überprüfung der DTD erzwungen. •
transformXML(Object xslStyleSheet, XSLTResultTarget44 xsltResult) throws IOException, SAXException, NotesException transformXML(Object xslStyleSheet, XSLTResultTarget xsltResult) throws NotesException45
Wendet das XSL StyleSheet xslStyleSheet auf das XML an, das in dem Item gespeichert ist. xslStyleSheet darf vom Typ org.xml.sax.InputSource, lotus.domino.Item, lotus.domino.EmbeddedObject oder lotus.domino.MIMEEntity sein.
7.1.4
Mit Document arbeiten – Lifecycle eines Dokuments
Dokumente in Domino und das Document als Repräsentation in Java sind der Basiscontainer für alle in Domino gehandhabten Objekte. Sie haben bereits in Kapitel 2.2.1 das Konzept der Domino-Note kennengelernt. Das Document ist die DominoNote, wobei sich nicht jede Note über ein Document handhaben lässt. Ein Document hat in der Regel einen typischen Lifcycle: Es wird erstellt oder z.B. über einen Suchmechanismus selektiert, es wird verändert oder kopiert, es wird gespeichert oder gelöscht und natürlich zum Schluss recycelt. Ohne eine Ansicht oder eine Suchanfrage lassen sich Dokumente direkt aus der Datenbank oder dem AgentContext beziehen, entweder einzeln über die UNID oder als vordefinierte Collection, z.B. aller Dokumente.
7.1.4.1 •
Dokumente selektieren über Methoden in Database
Document getDocumentByID(String noteID) throws NotesException
Ist die Note ID eines Dokuments bekannt, so kann in Database mit getDocumentByID das Dokument mit dieser ID geöffnet werden. Gibt es das spezifizierte Dokument nicht, wird null zurückgegeben. 43 44
45
Neue Signatur: Ab Domino Version 7 wirft diese Methode nur noch eine NotesException. Seit Domino R7 ist XSLTResultTarget eine eingenständige Klasse und basiert im Wesentlichen auf javax.xml.transform, da seit Java 1.4.x die XML- und XSL-Klassen Bestandteil des Standard-JRE sind. Dementsprechend wurden aus der Domino 7 Core Version auch die Bibliotheken xml4j.jar und lotusxsl.jar entfernt. Dies hat leider Auswirkungen auf bestehende Installationen, da nun die neuen Parser verwendet werden müssen. Neue Signatur: Ab Domino Version 7 wirft diese Methode nur noch eine NotesException.
272
7 Document Ist noteID keine Hexadezimalzahl oder "" oder null, wird eine NotesException geworfen. Die NoteID kann in Document über String getNoteID() bezogen werden. Sie ist nur pro Replik eindeutig. Ein primary Key, der über alle Repliken einer Datenbank eindeutig ist, liefert String getUniversalID(). Die NoteID ist eine achtstellige Hexadezimalzahl, die als String übergeben wird. Führende Nullen müssen nicht zwingend mit angegeben werden. Manchmal, z.B. innerhalb der @Formelsprache, wird der Note ID die Buchstabenfolge „NT“ vorangestellt. Für getDocumentByID muss diese entfernt werden.
•
Document getDocumentByUNID(String universalID) throws NotesException
Ist die Universal ID eines Dokuments bekannt, so kann in Database mit getDocumentByUNID ein Dokument direkt geöffnet werden. Gibt es das spezifizierte Dokument nicht oder ist der Parameter universalID entweder keine gültige 32stellige Hexadezimalzahl (als String) oder leer oder null, wird eine NotesException geworfen. •
Document getDocumentByURL(String url, boolean enforceReload) throws NotesException
Der Name dieser Methode ist leicht irreführend oder zumindest nicht selbsterklärend. getDocumentByURL lädt kein Notes-Dokument, sondern diejenige Web-Seite, die in url spezifiziert ist. Das Ergebnis dieser Methode ist recht unflexibel zu handhaben und wird von Domino eigentlich nur vorgehalten, um im Notes Client Webseiten anzeigen zu können. Der geladene HTML-Code wird in einem neuen Notes-Document im Item Body, das als RichText Item angelegt wird, abgespeichert. Zusätzlich werden in diesem Document Items für Header und Metainformationen angelegt. Das Item Form erhält den Wert „HTMLForm“, d.h. die Default-Maske für dieses Dokument heißt „HTMLForm“. Dies so erzeugte Notes-Document wird als Ergebnis der Methode zurückgegeben. Wird ein solches Dokument gespeichert, so bleibt zunächst neben den Metainformationen lediglich der unveränderte HTML-Code im Body Item. Wird dieses Dokument aber im Notes Client geöffnet, so versucht Domino den HTML-Code auszuwerten und zu rendern. Images werden vom ursprünglichen Server (erst jetzt) downgeloaded. Falls dies nicht mehr möglich ist, wird ein entsprechender Platzhalter hinterlegt. Wird das Dokument nun vom Anwender erneut gespeichert, so wird der HTMLCode in RichText umgewandelt, Links in Hotspots konvertiert und die geladenen Bilder als PICT-Ressourcen in den RichText eingebettet. Dies funktioniert natürlich nur, wenn im Designer der Datenbank, in dem das Dokument gespeichert wurde, eine Maske mit Namen HTMLForm vorliegt, die ein RichText-Feld mit Namen HTMLForm hat. Kann eine URL nicht geöffnet werden, wird keine Exception geworfen, sondern ein Document zurückgegeben, mit dem entsprechenden Status im Item „Status“, z.B. 503, wenn eine URL nicht aufgelöst werden kann.
7.1.4.2 Dokumente selektieren über Methoden in Document
273
Wird ein nicht unterstütztes Protokoll oder eine nicht gültige URL übergeben, so wird ebenso verfahren. Je nach Fall wird im Feld Title und/oder Body eine Fehlermeldung hinterlegt. Übrigens kann diesem RichText-Feld die Eigenschaft „Storage - Store contents as HTML and Mime“ mitgegeben werden (im Domino Designer in den erweiterten Feldeigenschaften). Dann findet aber ebenfalls eine Umwandlung statt (wie dies immer der Fall ist, ist diese Eigenschaft gesetzt). Domino wandelt dann den RichText-Inhalt in gültiges MIME um, erzeugt die benötigten MIME Parts und erzeugt in optional auch mehreren Body Items eine Multipart MIME Message. Bilder werden hierdurch z.B. in jeweils einem eigenen MIME Part gespeichert. •
Document getDocumentByURL(String url, boolean enforceReload, boolean reloadOnlyIfModified, boolean saveUrlsInListItem, String charset, String httpUserName, String httpUserPassword, String proxyUserName, String proxyUserPassword, boolean returnImmediately) throws NotesException Diese Signatur der Methode getDocumentByURL ermöglicht die Angabe zusätz-
licher Parameter: reloadOnlyIfModified – falls enforceReload gesetzt ist, wird nur dann
neu geladen, falls der Webserver meldet, dass die Seite verändert wurde. saveUrlsInListItem – falls dieser Parameter auf true gesetzt wird, speichert Domino alle gefundenen Links im Item URLLinks[n], wobei n mit 1 beginnt
und immer jeweils ein neues Item mit aufsteigender Endziffer angelegt wird, wenn ein Item droht, größer als 64 KByte zu werden. Die Verwendung dieses Parameters kann die Verarbeitung verlangsamen. charset – spezifiziert das für die Konvertierung zu verwendende Character Set. httpUserName und httpUserPassword erlauben das Login mit einer HTTP Base Authentication. proxyUserName und proxyuserPassword erlauben die Authentifizierung an einem Proxyserver. returnImmediately – für interne Verwendung. Wird dieser Parameter auf true gesetzt, wird kein Document Object zurückgegeben, sondern die Seite im Hintergrund geladen.
7.1.4.2 •
Dokumente selektieren über Methoden in Document
String getURL() throws NotesException String getNotesURL() throws NotesException (Neu seit Domino R6.5)
Diese beiden Methoden liefern die so genannte Notes-URL (Abb. 7-1) eines Notes-Objekts. Sie wird wie jede URL aufgebaut und besteht aus Protokollbezeichnung („notes“), einem Eintrag für den Server und einer URL mit der Datenbank und Objekt spezifiziert werden. z.B.: a) notes:///__C1256EEE002C549F.nsf/0 /9BE8F2EAD563EC66C1256FC70044B02C?OpenDocument.
274
7 Document b) notes://www.djbuch.de/djbuch/djbuch.nsf/$all /9BE8F2EAD563EC66C1256FC70044B02C?OpenDocument
c) notes://java@djbuch/djbuch/djbuch.nsf/V_alleNachVorname /Thomas?OpenDocument
Für ein Dokument besteht diese URL aus der Datenbank-ReplikID, der zwei Unterstriche „_“ vorangestellt und die Endung „.nsf“ nachgestellt werden, dem Namen des zu verwendenden Views, wobei als Default-View das Zeichen Null „0“ verwendet werden kann, und entweder dem Index des Dokuments innerhalb des View, wie er sich in der ersten Spalte des Views wiederfindet, oder, wie in diesem Beispiel, die Universal ID des Dokuments, gefolgt von einer Aktion, die für dieses Beispiel „OpenDocument“ oder „EditDocument“ heißen kann. Beachten Sie, dass die Methode getURL() je nach Kontext unterschiedliche Ergebnisse liefert. In einer lokalen Session liefert diese Methode die Notes-URL, in einer DIIOP-Session die HTTP URL. Beachten Sie, dass diese Art von URLs nicht (!) kompatibel mit der Methode getDocumentByURL in Database ist. •
String getHttpURL() throws NotesException Neu seit Domino R6.5. Die Methode getHttpURL liefert eine URL, mit der ein
Domino-Objekt im Browser angezeigt werden kann. Für ein Dokument ist dies eine URL in der Art des Beispiels b aus dem vorigen Absatz, wobei das Protokoll von notes auf http geändert wird. Allerdings liefert getHttpURL nur die URL, wenn diese Methode im HTTP-Kontext aufgerufen wird, sonst liefert sie nur einen leeren String. Der HTTP-Kontext steht z.B für periodische Agenten auf einem Server zur Verfügung, ebenfalls bei der Verwendung von DIIOP oder beim Einsatz von Webagenten. Es genügt nicht, einen Agenten zwar in einer Datenbank auf einem Server zu starten, die Session aber lokal (z.B. über den Notes Client oder sinitThread) aufzubauen.
Abb. 7-1 Notes URL
7.1.4.3 Dokumente selektieren über vordefinierte DocumentCollections
275
Anmerkung: Die Methoden getURL, getNotesURL und getHttpURL stehen für die Objekte Agent, Database, Document, Form, View und Session zur Verfügung. getNotesURL ist für Session nicht vorhanden. Die beiden übrigen Methoden liefern für eine lokale Session den leeren String und für eine DIIOP-Session die HTTP URL des Servers in der Form „http://www.djbuch.de?OpenServer“ Ein ausführliches Beispiel zur Anzeige aller Methoden finden Sie in den Beispielen im Package djbuch.kapitel_07 in der Klasse ShowURLMethods.
7.1.4.3
Dokumente selektieren über vordefinierte DocumentCollections
Steht die Universal ID eines Dokuments nicht zur Verfügung, so werden Dokumente über eine DocumentCollection, ViewEntryCollection oder direkt über einen View selektiert. In Database und AgentContext gibt es vordefinierte DocumentCollections, eigene Collections können über Suchmechanismen (s. Kap. 15) erhalten werden. Alle Dokumente einer Datenbank werden über getAllDocuments() throws NotesException in Database ausgewählt. Das Ergebnis ist eine DocumentCollection, die z.B. über getFirstDocument() throws NotesException und getNextDocument() throws NotesException durchschritten werden kann (s. Kap. 10.8). Seit Domino R7 steht in Database die Methode getModifiedDocuments zur Verfügung, mit der bestimmte Daten- aber auch Designdokumente ausgewählt werden können. Vordefinierte DocumentCollections in Database: •
DocumentCollection getAllDocuments() throws NotesException
Liefert alle (Daten-) Dokumente einer Datenbank. •
DocumentCollection getModifiedDocuments() throws NotesException DocumentCollection getModifiedDocuments (DateTime notOlderThan) throws NotesException DocumentCollection getModifiedDocuments (DateTime notOlderThan, int typeOfNote)throws NotesException Neu seit Domino R7. Die Methode getModifiedDocuments stellt eine Beson-
derheit dar, die typisch für Domino R7 ist. Es wird nicht mehr nach Design- oder Datendokumenten unterschieden, sondern alle Note-Objekte werden, wie im ursprünglichen Konzept und in der Datenstruktur vorgesehen, gleich behandelt.
276
7 Document Erst über das zusätzliche Attribut typeofNote wird dann genauer spezifiziert, welche Art von Note-Objekten geladen werden soll. Zulässige Werte für typeOfNote sind vordefinierte Konstanten in Database, die durch bitweises OR („|“) miteinander verknüpft werden können: DBMOD_DOC_FORM, DBMOD_DOC_ VIEW, DBMOD_DOC_ICON, DBMOD_DOC_ACL, DBMOD_DOC_DATA, DBMOD_ DOC_HELP, DBMOD_DOC_AGENT, DBMOD_DOC_SHAREDFIELD, DBMOD_ DOC_REPLFORMULA, DBMOD_DOC_ALL. Zurückgegeben werden alle seit dem Zeitpunkt notOlderThan modifizierten Dokumente (Note-Objekte). Für getModifiedDocuments() werden alle Dokumente der Datenbank zurückgegeben. Dies entspricht db.getModifiedDocuments (db.getCreated()). Zum Zeitpunkt der Ausführung von getModifiedDocuments wird die aktuelle Datenbankzeit in der DocumentCollection gespeichert und kann dort über getUntilTime wieder bezogen werden, um als Parameter für einen erneuten Aufruf von getModifiedDocuments (DateTime) zu dienen, so dass mit je-
dem Aufruf die neuen Dokumente seit dem jeweils letzten Aufruf geladen werden können. Wird ein Java-Agent im Domino Designer erstellt und steht ein AgentContext-Objekt über getDocumentContext() throws NotesException zur Verfügung, liefert getUnprocessedDocuments() throws NotesException (beide in AgentContext) alle von diesem Agenten bisher unverarbeiteten Dokumente, wobei der Verarbeitungsstand über die Methode updateProcessedDoc() throws NotesException nachgehalten werden muss. Die Collection der unverarbeiteten Dokumente kann über verschiedene Suchfunktionen und Signaturen der Methode unprocessedFTSearch und unprocessedSearch eingeschränkt werden, wobei diese ebenso arbeiten wie die in Kapitel 15.5 beschriebenen Suchmechanismen. Der Mechanismus der unverarbeiteten Dokumente wurde bereits in Kapitel 3.4.2 ausführlich erläutert.
7.1.4.4
Neue Dokumente erstellen
Neue Dokumente erstellen Sie entweder über createDocument in Database oder über copyToDatabase(Database targetDatabase) in Document. In beiden Fällen erhalten Sie ein neues Document-Objekt eines noch nicht im Speicher der Datenbank (des NSF) manifestierten Notes-Dokuments. Wird ein Dokument kopiert, entsteht ein exakter Klon des Originals, lediglich die entsprechenden Angaben für Autor, Erstellzeitpunkt etc. werden angepasst. Wird ein Dokument neu erstellt, wird über isNewNote() in Document signalisiert, dass es sich hier um ein noch nicht in der Datenbank vorhandenes Dokument handelt. Nach dem ersten save liefert dieses Flag nur noch false. Hilfreich ist dies, wenn bestimmte Operationen nur beim ersten Erstellen des Dokuments ausgeführt werden sollen. Daher liefert isNewNote() nach einem copyToDatabase auch false, um zu signalisieren, dass es sich hier um ein Dokument handelt, das bereits programmatisch einen Inhalt erhalten hat.
7.1.4.4 Neue Dokumente erstellen
277
Neue Dokumente erhalten bereits mit der Instanzierung über createDocument eine Universal ID, aber noch keine Note ID. Selbstverständlich kann weder über getDocumentByUNID noch über getDocumentByID ein solches Dokument selektiert werden, denn es existiert nur als Speicher-Objekt und kann daher nicht aus der Datenbank ausgelesen werden. Die Universal ID (und die Notes-ID) wird von Domino vergeben und dient als primärer Schlüssel des Dokuments. Die Universal ID kann jedoch auch vom Benutzer nachträglich verändert werden, um eigenen Bedürfnissen Rechnung zu tragen. Mit setUniversalID kann die Universal ID eines Dokuments verändert werden. Geschieht dies bei einem neuen Dokument, wird dieses neue Dokument lediglich mit dieser Universal ID versehen und später unter dieser ID gespeichert. Wird setUniversalID auf ein vorhandenes Dokument angewendet, entsteht ein neues Dokument, d.h. das bereits in der Datenbank gespeicherte Dokument mit der „alten“ Universal ID bleibt bestehen. Bedingung ist, dass die neu gesetzte Universal ID eine 32stellige Hexadezimalzahl ist. Wird eine ID doppelt vergeben, so wird (erst) beim Aufruf von save eine NotesException „Database already contains a document with this ID (UNID)“ geworfen. Bedenken Sie, dass in der Regel Notes-Dokumente auch von Notes-Benutzern, also letztendlich im Notes Client verarbeitet werden. Daher ist es zwingend erforderlich zu klären, welche Maske einem neuen Dokument zugeordnet werden soll und ob es unter Umständen Items gibt, die von dieser Maske erwartet werden. Außerdem ist es eine Überlegung wert, zu prüfen, ob Leser- oder Autorenfelder im Dokument benötigt werden, um die Sicherheit und die konsistenten Zugriffsrechte zu gewährleisten. Wurden durch die Notes-Programmierer bereits Masken und Felder vorgegeben, müssen deren Datentypen korrekt eingehalten werden. Falls es notwendig sein sollte, dass jedes neu erstellte Dokument durch bestimmte Berechnungen, die in einer Notes-Maske festgelegt sind, überarbeitet werden muss, dann könnnen Sie mit der Methode computeWithForm(boolean unused, boolean throwExceptionOnError) throws NotesException eine Anwendung aller Maskenformeln und -berechnungen auf Ihr Dokument erzwingen. Allerdings sollte computeWithForm nur auf bereits gespeicherte Dokumente angewendet werden, da hier teilweise unerwartete Ergebnisse beobachtet wurden, insbesondere scheinen nicht immer alle Items mit in die Berechnung einbezogen zu werden, wenn das Dokument nicht zuvor gespeichert wurde. computeWithForm kann auf zwei Arten benutzt werden. Wird der Parameter throwExceptionOnError als true übergeben, so wirft diese Methode eine NotesException, falls die Berechnungen nicht durchgeführt werden konnten oder, und dies ist der wichtigere Fall, falls eine Feldvalidierungsformel (s. Kap. 3.2) nicht validieren konnte. Ist der Parameter throwExceptionOnError false, dann gibt die Methode true oder false zurück, je nachdem, ob die Berechnung und Validierung erfolgreich war oder nicht. Der Parameter unused wird von der Methode nicht verwendet und scheint nur aus Gründen der Abwärtskompatibilität noch vorzuliegen. Wenn geklärt ist, welche Maske ein Dokument verwendet, so müssen Sie natürlich noch über replaceItemValue ("Form", yourFormName) den Maskenna-
278
7 Document
men im Dokument speichern. Dies sollte auf keinen Fall vergessen werden, auch wenn es nicht zwingend vorgeschrieben ist, dass ein Dokument ein Item Form beinhaltet. Sieht man von der Problematik des Recyclings im Document ab, so kann übrigens ein Document auch ein sehr praktischer (temporärer) Container oder Übergabeparameter in Ihrer Java-Umgebung sein, ohne dass das Document jemals gespeichert werden muss. Die verschiedenen in Kapitel 7.1.1 ff beschriebenen Methoden und Datentypen, mit denen Items ausgestattet sind, bieten einen reichen Werkzeugkasten und machen das Document zu einem universellen Container. Bedenken Sie, dass selbst Dateianhänge (s. Kap. 7.4) in einem Document gespeichert werden können.
7.1.4.5
Speichern von Dokumenten
Um ein Document permanent in der Datenbank zu speichern, genügt der einfache Aufruf der Methode save(), die über einen booleschen Rückgabewert signalisiert, ob der Speichervorgang erfolgreich war. Wie so oft, haben einfache Dinge ihre Tücken und so will auch dieser einfache Vorgang wohlüberlegt sein. Domino ist nicht nur ein Mehr-Benutzer-System, sondern existiert dank der Replikation in verteilten Umgebungen, so dass mehrere Repliken (die man auch als Ausprägungen oder Instanzen bezeichnen könnte) eines einzigen Dokuments auf mehreren Servern vorliegen können (und werden), ohne dass irgendeine Garantie darüber besteht, dass diese Server dauerhaft miteinander verbunden sind. Das beste Beispiel ist die lokale Replik einer Datenbank auf einem Notebook, die vom Benutzer nur ab und zu, wenn er denn Zugang zum Internet (oder seinem Einwähl-Notesserver) hat, mit der Version auf dem Server abgeglichen wird. Hierin ist der Grund zu sehen, dass erst seit Version 6 für Domino ein Document Locking zur Verfügung steht, das aber natürlich immer nur dann aktiv funktionieren kann, wenn zwei Zugriffe, die gegeneinander gelocked werden sollen, auf dem gleichen oder zumindest auf zwei dauerhaft verbundenen Servern stattfinden. Daher wird auch nach Locking und provisorischem Locking unterschieden. Näheres hierzu finden Sie in Kapitel 7.7. Aus diesen Überlegungen folgt, dass beim Speichern eines Dokuments bedacht werden muss, dass möglicherweise ein anderer Benutzer dieses Dokument ebenfalls geöffnet und ebenfalls bereits geändert und gespeichert hat oder noch ändern und speichern wird. Tritt solch ein Konfliktfall auf, sind verschiedene Szenarien denkbar. Der einfachste Fall ist, dass der konkurrierende Benutzer am Notes Client arbeitet und (zeitlich) nach Ihnen speichern will, nachdem Ihre Operationen bereits abgeschlossen sind. Dann wird er auf den Konfliktfall hingewiesen und gefragt, ob er seine Änderungen in einem so genannten Konfliktdokument speichern möchte. Sein Problem :-). Weitere Optionen werden dem Benutzer interaktiv nicht angeboten. Im umgekehrten Fall gibt es in Ihrem Java-Programm natürlich keine Interaktion, daher können Sie der Methode save verschiedene Parameter mitgeben, mit denen Sie das Verhalten im Konfliktfall steuern können und zwar in den Signaturen
7.1.4.5 Speichern von Dokumenten
279
save (boolean enforceSave) und save (boolean enforceSave, boolean makeResponseOnConflict). Der Parameter enforceSave legt fest, ob Ihr
Dokument auch dann gespeichert werden soll, wenn jemand anders das Dokument ebenfalls geöffnet und geändert hat. Im Gegensatz zum vorigen Beispiel kann also programmatisch ein Speichern erzwungen werden, ohne dass ein Konfliktdokument erzeugt wird. Falls enforceSave false ist, kann in der zweiten Signatur mit dem Parameter makeResponseOnConflict festgelegt werden, ob im Konfliktfall ein Konfliktdokument gespeichert werden soll. Ist dieser Parameter false, wird im Konfliktfall Ihr Dokument nicht gespeichert. Konfliktdokumente (s. Abb. 7-2) sind Klone des Originaldokuments, die jedoch zwei zusätzliche Items tragen. Zum einen wird einem solchen Dokument das Item mit Namen $Conflict und Wert "" hinzugefügt, über den ein Konfliktdokument als solches identifiziert wird, und das Item mit Namen $Ref, das einen speziellen Datentyp hat, der einen Link auf ein übergeordnetes Mutterdokument spezifiziert. Der Wert kann jedoch einfach mit getItemValueString gelesen werden, wobei als Ergebnis die Universal ID des Originaldokuments geliefert wird. Ein Konfliktdokument ist also ein Antwortdokument auf das Original. Je nach Bedürfnissen kann es sinnvoll sein, in Ansichten Dokumente, für die gilt „@isavailable ($Conflict)“, in der SELECTFormel auszuschließen, damit der Benutzer nicht mit Speicherkonflikten konfrontiert wird oder im umgekehrten Fall diese bewusst einzuschließen, damit der Benutzer nachträglich entscheiden kann, welche Daten für ihn relevant sind. Keine (!) Kontrolle über Speicherkonflikte haben Sie, wenn der zweite Benutzer und Bearbeiter des betroffenen Dokuments an einem entfernten Server arbeitet. Beide werden zunächst nicht bemerken, dass eine Konfliktsituation vorliegt. Erst bei einer Replikation, bei der die geänderten Daten zwischen den Servern ausgetauscht und die notwendigen Updates auf beiden Seiten durchgeführt werden, fällt diese Situation auf. Es wird dann in jedem Fall ein Konfliktdokument erstellt, das in diesem Fall auch als Replikationskonflikt bezeichnet wird. Domino wird dann entscheiden, welches der Dokumente als Hauptdokument und welches als Antwortdokument gespeichert wird (s. Kap. 2.3.2).
Abb. 7-2 Konfliktdokument (Speicherkonflikt)
280
7 Document
Als Empfehlung und Anhaltspunkt kann Folgendes gesagt werden: Solange in Java im Backend Batchprozesse programmiert werden, hat sich gezeigt, dass es für viele Fälle sinnvoll ist, save (true, false) zu verwenden. Hierbei wird davon ausgegangen, dass der Batchvorgang unerlässliche Wartungsarbeiten vornimmt. Daher ist es sinnvoll, die Wartung in jedem Fall durchzusetzen (enforceSave==true) und keine Datendubletten (makeResponseOnConflict == false) anzulegen, damit nicht später Probleme auftreten, wenn Datensätze doppelt auftauchen. Wenn zusätzlich davon ausgegangen werden kann, dass die Batchvorgänge zu Zeiten durchgeführt werden, die außerhalb der Bürozeiten liegen, werden Sie mit dieser Einstellung gut fahren. Zusätzlich kann die Methode save (boolean enforceSave, boolean makeResponseOnConflict, boolean markAsRead) noch einen dritten boolschen Parameter haben, mit dem festgelegt wird, ob ein durch save gespeichertes Dokument als (für den Benutzer der Session) gelesen markiert werden soll. Wie eingangs erläutert wurde, ist jedes Domino-Objekt auch in Java mit den Rechten des jeweiligen Sessionbenutzers behaftet. Folglich können nur Benutzer Dokumente verändern, die hierfür die entsprechenden Berechtigungen haben. Das erste Mal werden die Berechtigungen überprüft, wenn ein Benutzer versucht, ein Dokument aus der Datenbank in ein Java-Objekt zu laden, also z.B. über getDocumentByUNID. Hierfür muss der Benutzer mindestens Leserechte in der ACL der Datenbank eingeräumt bekommen, wobei nicht vergessen werden darf, dass ein Benutzer, der nicht in einem eventuell vorhandenen Leserfeld gelistet ist, das Dokument nicht sehen darf, d.h. die Methode getDocumentByUNID wird eine NotesException „Invalid universal id“ werfen. Wurde das Dokument erfolgreich in das Java-Document geladen, so kann auch ein Benutzer mit lediglich Leserechten dieses Dokument verändern. Dies geschieht unabhängig von der Datenbank und nur im Java-Objekt. Versucht nun allerdings ein Benutzer ohne Schreibrechte, das Dokument mit save zu speichern, so wird dies mit einer NotesException „Notes error: You are not authorized to perform that operation“ quittiert. Abschließend sei noch darauf hingewiesen, dass save eine teure, also zeitaufwändige Operation ist und nur wohlüberlegt eingesetzt werden sollte. Problemlos kann ein Document während allen Operationen (Ausnahme: computeWithForm) ungespeichert bleiben und erst nach Fertigstellung save aufgerufen werden. Hierdurch werden auch Inkonsistenzen vermieden, die durch Systemausfälle während einer Transaktion aus mehreren Veränderungen an einem Dokument entstehen könnten, würde zwischenzeitlich save mehrfach ausgeführt.
7.1.4.6
Dokumente löschen
Dokumente können ebenso einfach gelöscht werden und – wie könnte es anders sein – dies ist mit ähnlichen Überlegungen behaftet, wie sie beim Speichern eine Rolle spielen.
7.1.4.6 Dokumente löschen
281
Wie beim Speichern kann es auch beim Löschen zu Speicher- oder Replikationskonflikten kommen, wenn der eine Benutzer (oder Batchprozess) ein Dokument löscht und der andere Benutzer ein Dokument verändert und speichert. Folgerichtig kennt die Methode remove (boolean enforceDelete) throws NotesException den Parameter enforceDelete, in dem festgelegt wird, ob die Löschung, die durch remove ausgelöst wird, Vorrang vor anderen Operationen haben soll. Für Batchprozesse gilt hier Ähnliches wie das bereits für save Gesagte. Allerdings müssen Sie sich darüber im Klaren sein, dass, falls ein Benutzer ein Dokument im NotesClient geöffnet hat und dieses Dokument durch remove (true) im Backend gelöscht wird und nun nachträglich der Benutzer dieses Dokument speichert, dieses Dokument neu angelegt wird. Hier gibt es also keine absolute Sicherheit. Vielleicht fragen Sie sich bereits, wie ein gelöschtes Dokument überhaupt noch repliziert werden und folglich Replikationskonflikte verursachen kann, genauer gesagt, kann es vorkommen, dass in bestimmten Replikationssituationen (Löschung und Änderung auf verschiedenen Repliken) ein gelöschtes Dokument wieder sichtbar wird (s. Kap. 2.3ff.). Um auch hier einen sauberen Datenabgleich zu ermöglichen, wurden für Domino-Datenbanken so genannte „Deletionstubs“ eingeführt. Dies sind Dokumente, die bereits gelöscht wurden. Sie sind für alle Anwendungen und Benutzer unsichtbar und können weder über Ansichten noch über datenbankweite Suchfunktionen angesprochen werden, auch wenn sie natürlich über das C++ API angesprochen werden können. Deletionstubs werden dazu verwendet, über alle Repliken einer Datenbank die Information zu übermitteln, dass ein Dokument gelöscht werden muss. Eine Einschränkung ist hier allerdings zu machen. Damit nicht Datenbanken ins Unendliche wachsen, ist in den Datenbankeinstellungen (genauer gesagt, in den Replikationseinstellungen der Datenbank) ein Parameter vorgesehen, der festlegt, wann die Deletionstubs verfallen. In der Regel ist dieser Parameter auf 90 Tage gesetzt, danach dürfen die „untoten“ Dokumente endgültig sterben, natürlich mit reichlich Nebenwirkungen, wenn es eine Replik geben sollte, die länger als diese Zeit nicht repliziert wurde. Wenn Sie das Java-Objekt Document neu instanzieren, dann wird zunächst initial festgestellt, ob es sich bei dem Document um ein Deletionstub handelt. Dies wird nur in absoluten Ausnahmefällen der Fall sein. Die Methode isValid in Document oder ViewEntry zeigt true, falls es sich um kein Deletionstub handelt. Sollen mehrere Dokumente gleichzeitig gelöscht werden, können Sie mit removeAll (boolean enforceDelete) in DocumentCollection und ViewEntryCollection alle Dokumente, die über diese Collections referenziert werden, löschen. Diese Methode ist aus Performancegründen einem einzelnen Löschen der Dokumente über einen Loop vorzuziehen. Sollte nachdem ein Document instanziert wurde ein Dokument durch einen anderen Prozess gelöscht werden, so zeigt zwar isValid weiterhin true (das Dokument war ja ursprünglich nicht gelöscht und gültig, aber dieser Wert wird nicht aktualisiert), aber die Methode isDeleted() spiegelt den aktuellen Stand wider und liefert dann den korrekten Wert true.
282
7 Document
Neben remove existiert seit Version 6 auch die Methode removePermanently(boolean flag) die zum Einsatz kommt, wenn für die Datenbank die so genannte Softdeletion aktiviert war, die Sie mit setOption(Database. DBOPT_SOFTDELETE,true) in Database einschalten können und deren Status mit getOption(Database.DBOPT_SOFTDELETE) überprüft wird. Bei aktiver SoftDeletion verbleiben gelöschte Dokumente nach der Anwendung von remove für einige Stunden (Konfiguration über getUndeleteExpireTime() und setUndeleteExpireTime(int hours)) in der Datenbank, ehe sie endgültig gelöscht werden, können aber in diesem Zustand bereits nicht mehr über Views und Suchen gefunden werden. Um per SoftDeletion gelöschte Dokumente anzuzeigen und dem Benutzer die Möglichkeit zu geben, diese Dokumente wieder „zurückzuholen“, muss in der Datenbank ein spezieller gemeinsamer View eingerichtet werden, der die Eigenschaft „Shared, contains deleted documents“ erhält. Dort muss ein Aktionsbutton zur Verfügung gestellt werden, der z.B. für das Wiederherstellen eines Dokuments die @Formel „@Command([EditRestoreDocument])“ ausführt. Soll vor Ablauf der UndeleteExpireTime ein Dokument endgültig gelöscht werden, kann dies mit removePermanently erreicht werden.
7.2
Profildokumente
Profildokumente wurden in Domino als privilegierte Dokumente konzipiert. Privilegiert bedeutet insbesondere, dass Profildokumente im Hauptspeicher gecached werden und dadurch bei der Verwendung von Profildokumenten erhebliche Zeitgewinne erreicht werden können. Gleichzeitig können Profildokumente nicht wie gewöhnliche Dokumente angelegt und selektiert werden, sondern müssen in Database über getProfileDocument(String baseForm, String key) selektiert oder als DocumentCollection über getProfileDocCollection(String baseForm) geladen werden. Sie können nicht in Views angezeigt oder über die Suchfunktionen (s. Kap. 15) gefunden werden. Ist die Universal ID bekannt, können Profildokumente über getDocumentByUNID geladen werden, über isProfile () wird angezeigt, ob es sich um ein Profildokument handelt. Profildokumente werden ansonsten über das Java-Objekt Document behandelt und erfahren hier keine Sonderbehandlung. Das Konzept der Profildokumente sieht vor, dass diese kategorisiert werden, wobei diese Gruppenaufteilung schlicht über die diesen Dokumenten zugrunde liegende Basismaske erreicht wird, die über den Parameter baseForm beim Erstellen eines Profildokuments gesetzt wird. Diese Maske wird dann auch beim Öffnen im Notes Client verwendet, wenn ein Benutzer das Profildokument ändern oder einsehen möchte. Innerhalb des Clusters der Profildokumente einer bestimmten Maske kann noch ein Schlüssel mit dem zweiten Parameter key hinzugefügt werden, wobei dieser auch null sein darf. Ursprünglich war dieser Key zur Personalisierung vorgesehen und es wurde hier ausschließlich der Benutzername des aktiven Benutzers eingesetzt. Profile ohne diesen zweiten Parameter (mit Wert null) werden daher als allgemeingültig behandelt und auch als Serverprofile bezeichnet. Profildokumente mit diesem Parameter werden auch als Personenprofile bezeichnet. Grundsätzlich steht es
7.2 Profildokumente
283
dem Programmierer natürlich frei, für den Parameter key auch andere Werte anstelle des aktuellen Benutzernamens zu verwenden, gelesen wird dieser Wert über getKey(). Erstellt werden Profildokumente implizit über die Methode getProfileDocument, d.h. es gibt keine eigene Methode zum Erstellen von Profildokumenten und diese Methode hat die Nebenwirkung, dass immer ein neues Profildokument erstellt (und gespeichert) wird, bereits wenn versucht wird, es zu laden, auch wenn es das Profildokument (für diese base-Maske und den Key) noch nicht gibt. Es ist ein häufiger Fehler, zu überprüfen, ob es ein Profildokument mit einem bestimmten Paar von Parametern gibt. Die Anwort bei Verwendung der Methode getProfileDocument ist immer true und erstellt als Nebenwirkung auch noch ein neues Dokument. Um dieses Problem zu umgehen, hat Lotus die Methode getProfileDocCollection eingeführt, mit der der gesamte Cluster von Profilen, die dieselbe Maske verwenden, geladen werden kann. Die zurückgegebene DocumentCollection kann auch leer sein und wird in jedem Fall keine neuen Profildokumente erzeugen. Umgekehrt darf aus der Tatsache, dass Profildokumente automatisch erstellt werden, nicht geschlossen werden, dass Veränderungen, die Sie am Profildokument vorgenommen haben, nicht gespeichert werden müssten. Verwenden Sie auch hier save, um Ihre Änderungen zu manifestieren. Die dem Profil zugrunde liegende Maske wird auch schlicht als Name des Profils bezeichnet und kann über die Methode getNameOfProfile() geladen werden. Der Name der Basismaske wird übrigens nicht automatisch ins Item Form übernommen. Das sollte explizit nachgeholt werden. Verwenden Sie Aliasnamen für den Namen Ihrer Masken, so ist zu bedenken, dass grundsätzlich entweder der Klarname oder der Aliasname für den Parameter baseForm verwendet wird. Entscheiden Sie sich grundsätzlich für eines von beidem, am besten für den Aliasnamen. Falls Sie beides gemischt verwenden, werden beide Profile als voneinander getrennt betrachtet und behandelt und die Verwendung des Aliasnamens als Parameter für die Maske öffnet nicht ein eventuell vorhandenes Profil mit dem Klarnamen als Maske und umgekehrt. Um die Profildokumente schnell und verfügbar zu halten, werden sie im Hauptspeicher gecached. Grundsätzlich können Sie davon ausgehen, dass Veränderungen in der Regel schnell durchgesetzt werden. Allerdings kann ein gelöschtes Profildokument durchaus noch eine Weile im Hauptspeicher verfügbar sein und in der Praxis wurde auch schon beobachtet, dass durch den Aufruf von getProfileDocument ein bereits gelöschtes Profildokument geladen und (mit allen ehemals vorhandenen Items) gespeichert und somit die Löschung aufgehoben wurde. Hier ist also erhöhte Vorsicht geboten. Ein weiterer Unterschied zu „normalen“ Dokumenten liegt in der Replikation. Zu Profildokumenten können keine Replikations- oder Speicherkonflikte entstehen. Es setzt sich in der Regel die letzte Änderung durch, wobei sinngemäß nach den Regeln aus Kapitel 2.3.2 vorgegangen wird, aber keine Konfliktdokumente angelegt werden. Letztendlich ist es sinnvoll, Profildokumente so einzusetzen, wie der Name es bereits nahelegt: Als Profile für Personen oder Server mit häufig benutzten, aber selten geänderten Einstellungen oder Auswahllisten, die lange im Hauptspeicher verbleiben können und sollen.
284
7 Document
7.3
Antwortdokumente
Antwortdokumente haben Sie bereits mehrfach kennengelernt, z.B. als Sonderfall der Speicherkonflikte. Sie zeichnen sich durch die Eigenschaft aus, dass sie nicht alleine bestehen können, sondern immer an ein übergeordnetes Dokument geknüpft sind, dessen Universal ID als Link-Item $Ref im Antwortdokument referenziert wird. Antwortdokumente wurden ursprünglich als das konzipiert, was der Name nahelegt: Als Antwort auf eine Anfrage in einem Diskussionsforum. Grundsätzlich werden Antwortdokumente nach wie vor häufig in Notes-Anwendungen eingesetzt, insbesondere dann, wenn kenntlich gemacht werden soll, dass ein Dokument (Antwortdokument) abhängig von einem übergeordneten Dokument ist. Zum Beispiel könnten für eine Adressdatenbank Firmenanschriften als Hauptdokumente angelegt werden und zur Firma gehörige Kontakte von Mitarbeitern als Antwortdokumente. Hierdurch können zwei Dinge in der Anwendung erreicht werden. Zum einen lassen sich Kombinationen aus Haupt- und Antwortdokumenten leicht in Ansichten durch so genannte Kategorisierung übersichtlich gestaffelt anzeigen und zum anderen wird automatisch erreicht, dass die Antwort immer eine Referenz auf das Hauptdokument hat. Ein Beispiel für die Verwendung von Antwortdokumenten sind die durch einige der Tools der Beispieldatenbank erzeugten Dokumente. Markieren Sie z.B. ein Dokument in einer der Ansichten in djbuch.nsf und starten Sie im Menu „Actions“ -> „Tools“ die Aktion „2. Items anzeigen“ und es wird ein Antwortdokument auf das markierte Dokument erstellt, das eine Kurzbeschreibung aller im markierten Dokument gefundenen Items enthält (s. Abb. 7-3). Da der Ansicht die Eigenschaft „Show response documents in a hierarchy“ mitgegeben wurde, tauchen diese Dokumente automatisch hierarchisch unterhalb des Hauptdokuments auf. Dies ist recht praktisch, jedoch hat die Verwendung von Antwortdokumenten auch einige Nachteile: •
•
•
Antwortdokumente können in einer Ansicht nur zusammen mit ihren Hauptdokumenten angezeigt werden. Enthält die Ansichtsauswahl das Hauptdokument nicht, so wird in keinem Fall das Antwortdokument angezeigt. Was ursprünglich als Funktion gedacht war – schließlich machen Antworten nur Sinn, wenn es auch eine Frage (Hauptdokument) gab –, kann je nachdem, welchen Inhalt die Antwortdokumente haben, auch zum Problem werden, wenn Inhalte der Anwortdokumente auch alleine benötigt werden. Aus dem ersten Punkt folgt direkt, dass große Schwierigkeiten zu erwarten sind, falls ein Hauptdokument gelöscht wird, ohne dass zuvor die Antwortdokumente gelöscht wurden. Hier muss immer für eine saubere Reihenfolge beim Löschen gesorgt werden. Noch problematischer wird es, wenn ein Benutzer ein Dokument ausschneidet und sich dann doch entscheidet dieses Dokument wieder einzufügen. Da Dokumenten, die per Copy-Paste eingefügt werden, eine neue Universal ID von Domino zugeordnet wird, geht die Relation zwischen Hauptdokument und Antwort verloren.
7.3 Antwortdokumente
285
Abb. 7-3 Antwortdokument (Beispiel)
•
Wird ein Antwortdokument per Copy-Paste kopiert und ist beim Einfügen ein Hauptdokument markiert, wird das eingefügte Dokument automatisch zur Antwort dieses Dokuments. Dies kann sowohl ein Vor- als auch ein Nachteil sein, je nachdem, ob dieses Konzept der gewünschten Businesslogik entspricht.
Um Antwortdokumente handzuhaben, gibt es verschiedene Methoden in Document und View, wobei auf letztere in Kapitel 10 ff. eingegangen werden soll. getResponses() in Document liefert eine unsortierte DocumentCollection, mit allen direkt vom Document abhängigen Antwortdokumenten. Sollten Antwortdokumente ihrerseits wieder Antwortdokumente besitzen, so werden diese nicht eingeschlossen. Ob ein Document eine Antwort ist, können Sie mit isResponse() prüfen. Ein Antwortdokument erstellen Sie, indem Sie zunächst ein Dokument myDoc erstellen und für dieses Dokument dann die Methode myDoc.makeResponse(parentDoc) aufrufen, wobei als Parameter das Dokument übergeben werden muss, das zum Hauptdokument für diese Antwort werden soll und hierfür auch wiederum ein Antwortdokument zum Einsatz kommen darf, wodurch so genannte Antworten auf Antworten entstehen. Einen Rückgabewert hat diese Methode nicht, wirft aber eine NotesException, falls Fehler auftreten. Um für ein Antwortdokument die Universal ID des (direkt) übergeordneten Hauptdokuments zu ermitteln, kann die Methode getParentDocumentUNID verwendet werden, die diese UNID als String zurückgibt.
286
7 Document
7.3.1
Antwortreferenzen über Self ID und Parent ID
Antwortdokumente in Domino sind so etwas wie eine rudimentäre Funktion relationaler Verknüpfungen in der Welt nicht-struktierter Daten von Domino. Mit den oben erwähnten Nebenwirkungen lässt sich nicht immer leben und es gibt eine Alternative, die hier kurz angerissen werden soll. Auch wenn es sich hier um kein ausdrücklich Java-spezifisches Problem handelt, werden Sie im Domino-Umfeld früher oder später mit dieser Fragestellung konfrontiert werden. Hierfür müssen folgende Aufgaben gelöst werden: 1 2 3 4 5 6
Relational verknüpfte Dokumente müssen eine Referenz auf das Hauptdokument erhalten. Relational verknüpfte Dokumente müssen zusammen mit ihren Hauptdokumenten selektiert werden können. Relational verknüpfte Dokumente sollen in Ansichten hierarchisch dargestellt werden können. Beim Löschen von Hauptdokumenten sollen Antworten nicht im unreferenzierten Niemandsland verschwinden. Beim Ausschneiden und Einfügen von Hauptdokumenten sollen die Referenzen von Antworten nicht zerstört werden. Hauptdokumente sollen alle (in der gesamten Hierarchie) relational verknüpften Antwortdokumente kennen.
Werden diese Aufgaben gelöst, können Sie einerseits das Konzept der Antwortdokumente auf eine echte Relationalität erweitern ohne andrerseits die Funktionalität der Antwortdokumente zu verlieren. Der erste Schritt für dieses Vorhaben ist zunächst, dass jedes Dokument eine eindeutige ID erhalten soll, die unabhängig von der Universal ID ist und für die Referenzierungen genutzt werden soll: Die SelfID. Erstellen Sie für jedes neue Dokument ein Item F_selfID, in das Sie die Universal ID beim ersten Erstellen des Dokuments als String kopieren. Danach wird dieses Feld unabhängig von der UNID und kann durch Domino nicht mehr (unerwartet) verändert werden. Dokumente, die Hauptdokumente referenzieren sollen, benötigen eine Referenz auf dieses Hauptdokument: Die ParentID. Sie erhalten ein Feld F_parentID, in das die SelfID des gewünschten Hauptdokuments eingetragen wird. Denkbar ist hier sogar eine „n zu m“-Relation, wenn das Antwortdokument im Feld F_parentID eine Liste von Strings enthält. Konzipieren Sie für diesen Fall dieses Feld als Vector. Die Einführung von F_parentID erfüllt Forderung 1. Forderung 2 wird erfüllt, indem dafür gesorgt wird, dass es eine konsistente Eigenschaft von Haupt- und Antwortdokumenten gibt, die es erlaubt, beide mit denselben Auswahlkriterien zu selektieren. In der Regel werden dies die Maskennamen der beiden sein, z.B.: SELECT Form="FO_Haupt":"FO_Antwort" & F_meineBe-
7.3.1 Antwortreferenzen über Self ID und Parent ID
287
dingung=@true. (Beachten Sie, dass der Listenvergleich Form= "FO_Haupt":"FO_Antwort" gleichbedeutend ist mit Form="FO_Haupt" | Form="FO_Antwort"). Der Vollständigkeit halber sei hier darauf hingewiesen, dass
diese Ansichten-Auswahl-Formel auch Antwortdokumente auswählt, die entweder keinen Eintrag in der ParentID haben, oder deren ParentID auf ein nicht vorhandenes Dokument oder ein solches mit Form != "FO_Haupt" verweist. Dies ist entsprechend Forderung 4 erwünscht, insbesondere, wenn die Dokumente im Notes Client angezeigt werden. Sollen für jedes Hauptdokument die zugehörigen Antwortdokumente selektiert werden, können diese über eine Suche mit db.search gefunden werden oder mit dem für Forderung 6 beschriebenen Mechanismus die Referenzen auf die Antwortdokumente im Hauptdokument gehalten werden. Für die Selektion der Haupt- und deren Antwortdokumente im Java Backend ist diese Forderung noch leichter zu erfüllen. In Database gibt es die Methode search, mit der Sie mit einer entsprechenden @Formel die Antwortdokumente für ein bekanntes Hauptdokument hauptDoc auswählen können, wobei für dieses Beispiel als zusätzliche Bedingung angenommen wird, dass nur Dokumente gewünscht werden, die die Maske „FO_dokument_k2“ haben: DocumentCollection dc = db.search( "SELECT Form=\"FO_dokument_k2\" & F_parentID = \"" + hauptDoc.getUniversalID() + "\"");
Forderung 3 erreichen Sie, wenn Sie in jedem Antwortdokument in einem Kategorisierungsitem ein (textliches) Merkmal des Hauptdokumentes (redundant) zusätzlich speichern, z.B. den Titel des Dokuments. Für Hauptdokumente wird dann im Kategorisierungsitem der Titel gespeichert, für Antwortdokumente die Kategorie des Hauptdokuments und der eigene Titel zusammen, durch Backslash „\“ getrennt und für Antwort-von-Antwort-Dokumente wird die wiederum durch den eigenen Titel erweiterte Kategorie des übergeordneten Dokuments gespeichert. In der Ansicht kann dann in einer (üblicherweise der ersten) Spalte das Kategorisierungsitem angezeigt werden. Domino hierarchisiert diese Spalte automatisch am Backslash-Zeichen, wenn für die Spalte die Kategorisierung aktiviert ist. Forderung 4 ergibt sich von allein, da aus Notes-Sicht nur noch Hauptdokumente vorliegen, so dass keine unreferenzierten Antwortdokumente entstehen können. Sollte es Dokumente geben, für die es keine Referenz für das Feld F_selfID gibt, können diese Dokumente problemlos über Suchen etc. selektiert werden. Forderung 5 ergibt sich aus der SelfID, die nachträglich nicht verändert wird. Solange es zu einer ParentID im Antwortdokument ein Dokument mit dieser SelfID gibt, besteht die Referenzialität. Um Forderung 6 zu erfüllen, müssen Sie beim Anlegen von Antworten im zugehörigen Hauptdokument ein Feld F_responses pflegen, in dem Sie einen Vektor mit allen Antworten führen. Dies erfordert aber, dass Sie alle Ansichten mit so genannten Paste- und Deletion-Handlern ausstatten, die überprüfen, wenn Lösch- oder Einfügeoperationen durch den Benutzer stattfinden, so dass diese Referenzen up-to-date gehalten werden können. Zusätzlich müssen alle von Ihnen programmierten Methoden
288
7 Document
beim Erstellen oder Löschen von Dokumenten diese Relationalität berücksichtigen und ebenfalls die Referenzlisten im Hauptdokument pflegen. Dies gilt auch für Operationen, die Sie dem Benutzer über Agenten, Aktionsbuttons oder Aktionen zur Verfügung stellen.
7.4
Attachments
Attachments werden in Domino über das EmbeddedObject abgebildet und können entweder an ein RichTextItem oder an ein Document angehängt werden, wobei letztere Möglichkeit die Ausnahme ist und im Wesentlichen nur aus historischen Gründen noch zur Verfügung steht. Ein EmbeddedObject kann ein Dateianhang, ein OLE-Objekt oder ein OLE-Objectlink sein, wobei die letzteren beiden in ihrer Repräsentation als EmbeddedObject auf OS/2, Unix und Macintosh nicht unterstützt werden. Die verschiedenen Möglichkeiten, Anhänge und OLE-Objekte im Dokument oder im RichText zu finden, führen gerne zu Verwirrung. Eine Übersicht gibt Tabelle 7-5. Ist ein Feld mit „JA“ markiert, bedeutet dies, dass die Methode in der linken Spalte in der Lage ist, Objekte der markierten Spalte zu referenzieren (bearbeiten, lesen, in eine Auswertung einzubeziehen). Entscheidend sind die Spalten zwei und drei oder anders formuliert: EmbeddedObjects sollen aktuell nur noch in RichTextItems gespeichert werden und sollten auch durch die Methoden in RichTextItem bearbeitet bzw. geladen werden, wobei einige Methoden in Document für die Bearbeitung von Attachments in RichTextItems sehr hilfreich sind. Es stehen entsprechende Methoden in RichTextItem und RichTextNavigator (seit Version 6.5) zur Verfügung. Für diejenigen Methoden, die aktuell sind und die Sie auch nutzen sollten, wurden die Einträge durch Fettdruck in der Tabelle hervorgehoben. Für die Bearbeitung von EmbeddedObjects sollen zunächst Dateianhänge untersucht werden. Seit Version 6.5 steht in Domino der so genannte RichTextNavigator zur Verfügung, der mit einem RichTextItem über createNavigator() erstellt werden kann. Im RichTextNavigator können die verschiedenen Objekte, die ein RichTextItem enthalten kann, komfortabel angesprochen werden, Dateianhänge werden über die Konstante RichTextItem.RTELEM_TYPE_FILEATTACHMENT referenziert. Der erste Schritt, um einen in einem RichTextItem vorliegenden Dateianhang zu erhalten, ist, ein entsprechendes EmbeddedObject als Referenz auf diesen Dateianhang zu erhalten: rt = (RichTextItem) doc.getFirstItem("F_yourRichTextItem"); rtn = rt.createNavigator(); eo = (EmbeddedObject) rtn.getFirstElement(RichTextItem.RTELEM_TYPE_FILEATTACHMENT);
7.4 Attachments
289
RichTextNavigator bietet etliche weitere Methoden, mit denen die Objekte eines RichTextItems geladen werden können und mit denen zwischen den einzelnen Objekten navigiert werden kann (s. Kap. 11.1). Um einen Dateianhang an ein RichTextItem anhzuhängen, nutzen Sie embedObject: rt = (RichTextItem) doc.getFirstItem("F_yourRichTextItem"); EmbeddedObject eo = rt.embedObject(EmbeddedObject.EMBED_ATTACHMENT, "", filePath, "yourObjectName");
Das EmbeddedObject ist die Basis für die weiteren Operationen. Um eine Datei ins Dateisystem abzulösen, können sie die Methode extractFile verwenden, die keinen Rückgabewert hat, aber eine NotesException wirft, falls die Operation nicht ausgeführt werden konnte. Denkbare Exceptions sind zum Beispiel „NotesException: Tabelle 7-5 Methoden und Properties mit Zugriff auf EmbeddedObject OLE im Doc
Anhänge aus R3
OLE/ Anmerkung 1 aus R3
JA
Anhänge im Doc -
-
-
-
(3)
JA
-
-
-
-
-
RichTextNavigator über RichTextItem.create Navigator() erstellen (4)
-
JA
-
-
-
-
RichTextItem. getEmbeddedObjects()
JA
JA
-
-
JA
JA
RichTextItem .getEmbeddedObject (String objectName) Document .hasEmbedded()
JA
JA
-
-
-
-
RichTextNavigator über RichTextItem.create Navigator() erstellen (4) Nur die EmbeddedObjects des aktuellen RT (5)
JA
JA
JA
JA
JA
JA
Document .getAttachment (String objectName) Document. getEmbeddedObjects() Document.getItems()
JA
-
JA
-
JA
-
-
JA
-
JA
-
-
Nur OLE
JA JA JA
JA JA JA
JA JA(1)
JA JA (2)
JA
JA
(7)
Anhänge im RT
OLE im RT
RichTextItem .embedObject (int type, String class, String fileName, String objectName) RichTextNavigator .getFirstElement (RichTextItem .RTELEM_TYPE_FILEATTACHMENT) RichTextNavigator .getFirstElement (RichTextItem. RTELEM_TYPE_OLE)
JA
Erstellung Programmatisch Erstellung durch Benutzer
Ist true, wenn ein beliebiger Dateianhang oder ein beliebiges EmbeddedObject vorhanden ist. (6). Nur Attachments.
290
7 Document
Tabelle 7-5 Methoden und Properties mit Zugriff auf EmbeddedObject (1) Dateianhänge in Domino R2 wurden direkt im Dokument gespeichert. (2) Falls eine Maske ein eingebettetes OLE-Objekt enthält und mit Hilfe dieser Maske ein Dokument erstellt wird, hat der Benutzer die Möglichkeit dieses OLE-Objekt zu bearbeiten. Bearbeitet UND speichert der Benutzer dieses Objekt, dann wird die bearbeitete Fassung direkt im Dokument als EmbeddedObject gespeichert. (3) type kann den Wert EmbeddedObject.EMBED_ATTACHMENT oder EmbeddedObject.EMBED_OBJECT haben. Objectlinks mit Type EmbeddedObject. EMBED_OBJECTLINK können nicht in Java erstellt werden. objectName ist optional und wird als interner Name des Objekts verwendet. Class muss immer der leere String "" sein; OLE-Objekte aus einem Speicherobjekt über die Angabe von class statt über die Angabe einer Datei in fileName zu laden, ist in Java nicht möglich. (4) Neben getFirstElement stehen noch weitere Methoden zur Selektion und Navigation zur Verfügung. (5) Der intern verwendete Dateiname muss bekannt sein. Kann z.B. über getName() in EmbeddedObject bezogen werden. Das EmbeddedObject kann wiederum z.B. über getEmbeddedObjects in RichTextItem bezogen werden. (6) Der intern verwendete Dateiname muss bekannt sein. Kann z.B. über getName() in EmbeddedObject bezogen werden. Das EmbeddedObject kann wiederum z.B. über getEmbeddedObjects in Document bezogen werden. (7) Aus allen Items müssen diejenigen mit type == Item.ATTACHMENT oder Item.EMBEDDEDOBJECT herausgesucht werden.
Notes error: Access to data denied“, falls für den Zielpfad der abzulösenden Datei keine Schreibrechte vorliegen oder „NotesException: Notes error: File cannot be created“, falls ein erwartetes Verzeichnis nicht vorliegt. Ein EmbeddedObject hat immer einen type, der mit getType () bezogen werden kann, wobei eine int-Konstante zurückgegeben wird, die die Werte EmbeddedObject.EMBED_OBJECTLINK (1452), EmbeddedObject.EMBED_OBJECT (1453) oder EmbeddedObject.EMBED_ATTACHMENT (1454) annehmen kann. Wie Tabelle 7-5 bereits andeutet, kann eine Referenz auf ein EmbeddedObject auch darüber bezogen werden, dass entweder in RichTextItem alle EmbeddedObjects mit getEmbeddedObjects als Vector geladen werden oder in Document alle OLE EmbeddedObjects über getEmbeddedObjects ebenfalls als Vector bezogen werden. Einzelne EmbeddedObjects können in RichTextItem über getEmbeddedObject geladen werden, sofern der interne (!) Name bekannt ist. In Document besteht zumindest auf Dateianhänge über getAttachment Zugriff, wobei auch hier der interne Name bekannt sein muss. Der interne Name wurde entweder durch embedObject im letzten Parameter gesetzt, oder von Domino vergeben. Um festzustellen, ob es überhaupt irgendwelche EmbeddedObjects im Dokument gibt, kann die Methode hasEmbedded() verwendet werden, die imme true liefert, falls es ein solches Objekt gibt, egal wo es innerhalb des Dokuments gespeichert ist, oder ob es womöglich über eine alte Notes-Version erstellt wurde und nur über Umwege erreichbar ist.
7.4 Attachments
291
Die Liste aller Dateianhänge zu berechnen, ist etwas schwieriger. Leider bietet Domino hierfür keine Java-Methode an, aber es gibt die @Formel @attachmentNames, die genau dieses Ergebnis liefert. In der Session können @Formeln über public Vector evaluate (String atFormula [, Document doc]) evaluiert werden. Rückgabewert ist ein Vector, der nur einen einzelnen Wert enthält, falls der Rückgabewert der @Formel ein diskreter Wert ist; falls der Rückgabewert eine Liste ist (eine anderer Rückgabewert ist nicht zu erwarten), wird der Vector entsprechend mit den Werten gefüllt. Listing 7-1 zeigt einen kleinen Agenten, der die verschiedenen Möglichkeiten, mit Attachments umzugehen, zeigt. Der Agent ist der Beispieldatenbank beigefügt und kann über „Actions“ -> „Kapitel 7“ -> „1. Doc mit Attachment erstellen / Attachment anhängen“ gestartet werden. Sie müssen ein Dokument markieren, bevor Sie den Agenten starten. Falls Sie den Agenten zum ersten Mal aufrufen, so erstellt er ein neues Dokument mit der Maske „FO_dokument_k2“, das in der Ansicht „Kapitel 2“ -> „Dokument“ erscheint. Der Agent versucht in Ihrem lokalen Lotus-Dataverzeichnis die Dateien „image.gif“ und „binary.gif“, die sich immer dort befinden (sollten), zu
import java.util.Vector; import lotus.domino.*; public class AttachmentAgent extends AgentBase { private static final String PATH = "C:\\temp\\"; public void NotesMain() { Document doc = null; RichTextNavigator rtn = null; Database db = null; EmbeddedObject eo = null; RichTextItem rt = null, hiddenRT = null; try { //Vorbereitung Session session = getSession(); AgentContext agentContext = session.getAgentContext(); String base = System.getProperty("java.home"); base = base.substring(0,base.length()-4); db = agentContext.getCurrentDatabase(); doc = agentContext.getDocumentContext(); //Neues Dokument erstellen oder Context Document laden... if (doc != null && !(doc.getItemValueString("F_Titel")) .equals("Demo Dokument mit Attachment")) { doc = db.createDocument ();} doc.replaceItemValue("Form", "FO_dokument_k2"); doc.replaceItemValue("F_Titel", "Demo Dok. mit Attachment"); //RichText Feld 1 -> nicht sichtbar in der Maske rt = (RichTextItem) doc.getFirstItem ("F_attachment"); if (rt == null) { rt = doc.createRichTextItem("F_attachment");} rt.embedObject(EmbeddedObject.EMBED_ATTACHMENT, "", base + "\\data\\image.gif", "demoGif"); //RichText Feld 2 -> sichtbar in der Maske rt = (RichTextItem) doc.getFirstItem("F_richText"); if (rt == null) { rt = doc.createRichTextItem("F_richText");} rt.embedObject(EmbeddedObject.EMBED_ATTACHMENT, "", base + "\\data\\binary.gif", "demoGifVisible");
Listing 7-1 Demonstration verschiedener Attachment-Operationen
292
7 Document //Auswertung ... rt.appendText("Vor dem Speichern: " + rt.getEmbeddedObjects().size() + "/" + doc.getEmbeddedObjects().size()); rt.addNewLine(); doc.save(true, false); rt.appendText("Nach dem Speichern: " + rt.getEmbeddedObjects().size() + "/" + doc.hasEmbedded()); rt.addNewLine(); //Bsp 1: Anhang via RichTextNavigator ablösen und speichern... rtn = rt.createNavigator(); eo = (EmbeddedObject) rtn .getLastElement(RichTextItem.RTELEM_TYPE_FILEATTACHMENT); eo.extractFile(PATH + eo.getName()); rt.appendText("Datei erfolgreich nach " + PATH + eo.getName() + " abgelöst."); rt.addNewLine(); eo.recycle(); //Bsp 2: Anhang aus RichText Item extrahieren und speichern... hiddenRT = (RichTextItem) doc.getFirstItem("F_attachment"); Vector v = hiddenRT.getEmbeddedObjects(); eo = (EmbeddedObject) v.elementAt(v.size()-1); //letzt. Eintr. String attachFileName = eo.getSource(); String attachName = eo.getName(); eo.extractFile(PATH + attachFileName); rt.appendText("Datei erfolgreich nach " + PATH + attachFileName + " (interner Name: " + attachName + ")" + " abgelöst."); rt.addNewLine(); eo.recycle(); //Bsp 3: Anhand des Namens aus Doc extrahieren und speichern... eo = doc.getAttachment (attachName); eo.extractFile(PATH + "_erneut_" + attachName); rt.appendText("Datei erneut erfolgreich nach " + PATH + "_erneut_" + attachName + " abgelöst."); rt.addNewLine(); //Bsp 4: Alle Namen aller Anhänge berechnen... v = session.evaluate("@attachmentNames", doc); rt.appendText("Alle Anhänge: " + v.toString()); rt.addNewLine(); doc.save (true,false); } catch(Exception e) { e.printStackTrace(); } finally { try { if (rt != null) {rt.recycle();} if (rtn != null) {rtn.recycle();} if (hiddenRT != null) {hiddenRT.recycle();} if (eo != null) {eo.recycle();} if (doc != null) {doc.recycle();} if (db != null) {db.recycle();} } catch (NotesException e1) { e1.printStackTrace(); } }
} }
Listing 7-1 Fortsetzung Demonstration verschiedener Attachment-Operationen
laden und dem RichText des Dokuments anzuhängen. Der Agent geht von einer Standardinstallation unter Windows aus. Mögliche Dateipfadfehler müssten Sie direkt im Desiger im Agenten anpassen.
7.4 Attachments
293
Wird der Agent nun ein zweites Mal gestartet und dabei ein zuvor durch den Agenten erstelltes Dokument markiert, erstellt er kein neues Dokument, sondern versucht nun das vorhandene Dokument um weitere Anhänge zu ergänzen. Ein derartiges Dokument sehen Sie in Abb. 7-4. Dabei werden folgende Techniken angewendet (Listing 7-1): Nach den üblichen Vorbereitungen eines Domino-Java-Agenten, muss er zunächst unterscheiden, ob der Agent zum ersten Mal gestartet wird, oder ob ein bereits vorhandenes Dokument manipuliert werden soll, was für das Beispiel schlicht am Titel erkannt wird und wobei bei Bedarf in der aktuellen Datenbank ein neues Dokument angelegt wird. Im nächsten Schritt wird eine Referenz auf ein RichTextItem benötigt . Der Agent wird zwei RichTextItems mit Anhängen füllen. Eines, für das es ein korrespon-
Abb. 7-4 Dateianhänge in Richtext und Dokument
294
7 Document
dierendes Feld in der Maske gibt, und eines, für das es kein Feld in der Maske gibt und das daher gewissermaßen unsichtbar bleibt. Hieran können Unterschiede in der Verarbeitung aufgezeigt werden. Mit Hilfe von getFirstItem wird versucht das „versteckte“ RichTextItem zu laden. Ob es neu angelegt werden muss, kann daran erkannt werden, ob getFirstItem null zurückgegeben hat . Vergessen Sie nicht den Cast auf (RichTextItem). Der Dateianhang wird wie eingangs beschrieben mit embedObject angehängt. Diese Methode hat vier Parameter. Der Erste ist eine int-Konstante, die den oben bereits beschriebenen Typ angibt. Der zweite Parameter ist theoretisch dafür vorgesehen, OLE-Objekte über deren Klassennamen zu erzeugen, was aber in Java nicht unterstütz wird und muss für die Erstellung von Dateianhängen immer als leerer String übergeben werden. Der dritte Parameter ist ein String, der den absoluten Pfad zu der anzuhängenden Datei enthält, und schließlich ist der vierte Parameter ein String, der optional den intern zu verwendenden Namen bestimmt. Dieser Parameter ist optional und darf auch als leerer String übergeben werden. Dieser Vorgang wird für das Item F_richText, für das es ein gleichnamiges Feld in der Maske gibt, wiederholt . In den Agenten wurde ein kleines Logging eingebaut, mit dem eine wichtige Tatsache demonstriert wird: RichTextItems sind erst dann Bestandteil des DocumentObjekts, sobald das Document gespeichert wurde. Folgerichtig ergibt der Aufruf von getEmbeddedObjects() in RichTextItem einen, um ein Element zu kleinen, Vector – das aktuell angefügte Item wird nicht angezeigt. Dieser Vector wird erst nach dem save korrekt geladen. Außerdem lässt sich hier deutlich erkennen, dass nur die EmbeddedObjects des RichtTextItems gezählt werden. Das im zweiten RichTextItem angefügte Attachment wird hier nicht geladen. Zusätzlich wird in dieser Logging-Ausgabe noch getEmbeddedObjects() in Document angezeigt, genauer gesagt die Größe dieses Vector. Dieser bleibt so lange leer, wie Sie nicht manuell ein OLE-Objekt in das RichText Feld einfügen, da diese Methode in Document nur OLE-Objekte, jedoch keine Dateianhänge anzeigt. Nachdem der Agent das erste Mal gelaufen ist, sollte im RichText Feld F_richText die Datei binary.gif und folgender Text vorliegen: Vor dem Speichern: 0/0 Nach dem Speichern: 1/true Datei erfolgreich nach C:\temp\binary.gif abgelöst. Datei erfolgreich nach C:\temp\image.gif (interner Name: image.gif) abgelöst. Datei erneut erfolgreich nach C:\temp\_erneut_image.gif abgelöst. Alle Anhänge: [image.gif, binary.gif]
Die Ausgabe zeigt wie erwartet an, dass vor dem Speichern des RichTextItems, obwohl bereits Dateien angehängt wurden, noch keine EmbeddedObjects über getEmbeddedObjects geladen werden können („0/0“) und dass nach dem Speichern das
7.4 Attachments
295
eine Attachment in diesem RichTextItem erkannt wird, nicht jedoch das zweite Attachment im anderen RichTextItem. Has EmbeddedObjects liefert den korrekten Wert true („1/true“). Die folgenden Zeilen geben über das erfolgreiche Ablösen der Anhänge (s.u.) Auskunft. Die letzte Zeile zeigt das Ergebnis des Tricks mit der @Formel zum Bestimmen aller Namen von Dateianhängen. Nach dem zweiten Lauf des Agenten werden erneut die Bilder binary.gif an das sichtbare und image.gif an das „unsichtbare“ RichTextItem angehängt. Auffällig sind zwei Dinge: Eigentlich wäre zu erwarten gewesen, dass die Elemente des RichTextItems F_attachment nicht in der Ansicht des Dokuments hätten auftauchen dürfen, schließlich gilt dies auch für jedes andere Item, das kein korrespondierendes Feld in der Maske hat. Hier macht Domino eine Ausnahme. Dateianhänge werden am unteren Rand der Maske angezeigt (Abb. 7-4), sofern sie nicht in einem RichTextItem sichtbar dargestellt werden können. Der zweite wichtige Unterschied ist die Bezeichnung der jeweils im zweiten Durchlauf angehängten Datei. Während im sichtbaren RichTextFeld die Datei korrekt zweimal als binary.gif angezeigt wird, taucht für den zweiten Eintrag von image.gif ein automatisch generierter Name auf. Dies ist der interne Name des Dateianhangs. Wenn Sie sich Abb. 7-4 ansehen, können Sie in der Liste aller vorhandenen Dateianhänge erkennen, dass auch das zweite binary.gif einen automatisch generierten internen Namen hat. Domino zeigt für „versteckte“ Items den internen und im sichtbaren RichText-Feld den ursprünglichen Dateinamen an. Den internen Namen können Sie immer aus dem referenzierten EmbeddedObject als getName() beziehen und den ursprünglichen Dateinamen als getSource(). Nach dem ersten Upload sind diese Werte übrigens noch identisch, da Domino keine automatischen Namen wegen der Namensdoppelung generieren musste. Für das Ablösen der Anhänge macht dies für Sie übrigens keinen Unterschied. Entweder Sie kennen den Dateinamen und können sie über getEmbeddedObject in RichTextItem oder über getAttachment in Document ablösen oder Sie referenzieren das Objekt auf andere Weise (s.u.), wodurch sich die Kenntnis der Namen erübrigt. Im Folgenden geht es nun darum, die Attachments wieder vom Dokument abzulösen: Im ersten Beispiel wird der bereits skizzierte Weg beschritten. Ein RichTextNavigator liefert diesmal über getLastElement(RichTextItem.RTELEM_TYPE_FILEATTACHMENT) das letzte File-Attachment-Objekt. Rückgabewert von getLastElement (und den anderen get...Element-Methoden) ist übrigens ein Objekt der Klasse Base. Dieser einmalige Ausnahmefall für die explizite Verwendung von Base ist hier unerlässlich, da viele verschiedene Domino-Objekte zurückgegeben werden können, die aber alle Base erweitern. extractFile verwendet in dem Beispiel getName(), das den internen Namen des Objekts liefert, in extractFile als Dateiname. Sollte diese Datei schon im Dateisystem vorliegen, wird sie ohne Fehlermeldung überschrieben. Also Vorsicht!
296
7 Document
Das zweite Beispiel extrahiert zur Abwechslung den Anhang aus dem zweiten RichTextItem. Beachten Sie, dass die beschriebenen Methoden unabhängig davon sind, ob wie in unserem Fall ein Item in der Maske durch ein Feld referenziert und somit bearbeitbar ist oder nicht. Um die Datei direkt aus dem RichTextItem ablösen zu können, werden zunächst alle im Item gefundenen EmbeddedObjects als Vector über getEmbeddedObjects geladen und dann das gewünschte über die Elemente des Vector referenziert (hier das letzte Element im Vector). Um beim Speichern im Dateisystem den ursprünglichen Dateinamen wieder herzustellen wird hier getSource verwendet, der interne Name aus getName wird zur Verwendung im nächsten Beispiel zwischengespeichert. Da nun ein Name eines Attachments bekannt ist, kann im dritten Beispiel über getAttachment in Document der Anhang erneut abgelöst werden. Wichtig ist hier die Verwendung des internen Namens. Alle Ablösevorgänge werden als Text-Log im RichTextItem selbst hinterlegt und dabei noch die verschiedenen Varianten von internem und ursprünglichem Dateinamen angezeigt. Zu guter Letzt wird dann noch die Liste der Namen aller Anhänge ausgegeben. Auch hier werden die internen Namen angezeigt. Da Sie nun die Behandlung von Dateianhängen perfekt beherrschen :-), folgt die Beschreibung des Handlings von OLE-Objekten, die in Dokumenten eingebunden sein können. Beachten Sie, dass diese Art von eingebetteten Objekten nur für Benutzerinteraktionen interessant ist. OLE-Objekte können vom Benutzer durch einen Doppelklick auf das Objekt in den Bearbeitenmodus versetzt werden und innerhalb des Notes Clients aber mit den originären Methoden des Ursprungsprogramms bearbeitet werden. Wenn Sie den Cursor versuchshalber in ein RichText-Feld im Notes Client platzieren und dann den Menübefehl „Create“ -> „Object“ wählen, können Sie manuell ein OLE-Objekt erstellen und das Verhalten überprüfen. Der Bezug eines OLE EmbeddedObject erfolgt auf dem gleichen Weg wie für Attachments beschrieben, wobei Sie Tabelle 7-5 beachten müssen. Allerdings scheint es so, dass die Java-Implementierung von RichTextNavigator für die Verarbeitung von OLE-Objekten in der Client-Version 6.5.3 noch nicht perfekt ist, da in der Praxis eine gewisse Unzuverlässigkeit beobachtet wurde und nicht immer alle Objekte gefunden wurden. Die äquivalenten LotusScript-Befehle unterliegen nicht dieser Einschränkung. Hilfreich ist es, wenn Sie RichTextItems nicht innerhalb von Tabellen platzieren, da offenbar hiervon abhängt, wie gut der RichTextNavigator über getFirstElement und findFirstElement usw. die Objekte referenzieren kann. Der Beispieldatenbank wurde die Aktion „Actions“ -> „Kapitel 7“ -> „3. Embedded Objects via Java RTN anzeigen“ beigefügt. Er versucht in einem markierten Dokument das RichTextItem „F_richText“ zu laden und sucht dort nach verschiedenen RichText-Elementen, wie OLE-Objekten, Dateianhängen, Abschnitten oder Tabellen und Link-Verknüpfungen, wobei die Mechanismen von RichTextNavigator genutzt werden. Zu den Objekten zeigt er verschiedene Werte an, z.B. die unterschiedlichen Ergebnisse von findFirstItem (gibt einen boolean-Wert zurück und zeigt an, ob es ein Objekt des gewünschten Typs gibt) und getFirstItem (zeigt an, ob das Objekt geladen werden konnte).
7.4 Attachments
297
Abb. 7-5 Eigenschaften eines OLE EmbeddedObject im Java-Agenten
getEmbeddedObjects() in RichTextItem arbeitet zuverlässig auch in Java. Hierfür wurde der Beispielagent „Actions“ -> „Kapitel 7“ -> „2. Embedded Objects via Java anzeigen“ beigefügt. Eine typische Ausgabe sehen Sie in Abb. 7-5. Ist ein EmbeddedObject vom Typ EmbeddedObject.EMBED_OBJECT so kann über die Methode getVerbs() ein Vector aus String bezogen werden, der die so genannten Verbs des OLE-Objekts, also die vom Objekt akzeptierten interaktiven Methoden, anzeigt. Ein OLE-Objekt kann ebenfalls einem RichTextItem über embedObject hinzugefügt werden. Verwenden Sie den Typ EmbeddedObject.EMBED_OBJECT: rt = (RichTextItem) doc.getFirstItem("F_richText"); rt.embedObject(EmbeddedObject.EMBED_OBJECT,"","/your/full/filePath","internalName"); doc.save(true,false);
In LotusScript kann im zweiten Parameter eine Klasse statt im dritten Parameter ein Dateipfad, aus dem das Objekt gebildet werden soll, angegeben werden. Dies ist in Java nicht möglich. Auch hier wurden leider Unzuverlässigkeiten beobachtet. Einen Beispielagenten finden Sie unter „Actions“ -> „Kapitel 7“ -> „4. Embedded Object via Java anhängen“. Zum Vergleich der Funktionalität wurde der Agent „Kapitel 7\2.“ (die Namen wurden abegekürzt) zusätzlich in LotusScript als Agent „Kapitel 7\5.“ und der Agent „Kapitel 7\4.“ als Agent „Kapitel 7\6.“ ebenfalls in LotusScript implementiert. Der Agent „Kapitel 7\5.“ analysiert ein markiertes Dokument und gibt Informationen zu eventuell im RichTextItem „F_richText“ enthaltenen OLE-Objekten aus und zeigt sie in der Informationsleiste an, die angeklickt werden muss, um vollständig angezeigt zu werden (s. Abb. 7-6). Den Java-Sourcecode finden Sie im defaultPackage von „01 Examples“ und natürlich in den beigefügten Agenten der Beispieldatenbank „djbuch.nsf“, den LotusScript-Code finden Sie ebenfalls direkt über den Domino Designer in der Beispieldatenbank unter den erwähnten Namen.
298
7 Document
Abb. 7-6 Eigenschaften eines OLE EmbeddedObject im LotusScriptAgenten
Sollen größere Projekte mit OLE umgesetzt werden, empfiehlt es sich zur Zeit, die Verwendung von LotusScript zu erwägen, zumal LotusScript als VisualBasic-Derivat ohnehin technisch näher an den OLE-Objekten ist. Dateianhänge dagegen können perfekt über Java in die Dokumente eingebettet werden, zumal Ihnen für das eigentliche Dateihandling viele nützliche Methoden in Java bereitstehen.
7.4.1
Übersicht über die Methoden zur Attachmentbearbeitung
Attachments können in Domino ähnlich wie diskrete Datentypen in Items eines Notes-Document gespeichert werden. Verwendung findet hier das RichTextItem, in dem sowohl Dateianhänge als auch OLE-Objekte und OLE-Objectlinks gespeichert werden können. Es sind verschiedene Einschränkungen zu beachten, welche Objekttypen auf welchen Plattformen und durch welche Methoden gehandhabt werden können (Tabelle 7-5). Referenziert werden Attachments und OLE-Objekte durch EmbeddedObject, das über verschiedene Methoden in RichTextItem, RichTextNavigator und Document bezogen werden kann. In RichTextItem: • public EmbeddedObject embedObject(int type, String class, String fileName, String objectName) throws NotesException
Für type == EmbeddedObject.EMBED_ATTACHMENT: Fügt dem aufrufenden RichTextItem das Attachment aus der Datei fileName hinzu. fileName ist ein absoluter Dateipfad. class muss den Wert "" haben und objectName spezifiziert den Namen, unter dem das Objekt intern referenziert wird. Dieser Wert darf auch leer "" sein.
7.4.1 Übersicht über die Methoden zur Attachmentbearbeitung
299
Für type == EmbeddedObject.EMBED_OBJECT: Fügt dem aufrufenden RichTextItem ein OLE-Objekt, das aus der Datei in fileName erzeugt wird, hinzu. Verwendung von class und objectName wie oben. Für type == EmbeddedObject.EMBED_OBJECTLINK: Dieser Typ ist hier nicht zulässig. •
public Vector getEmbeddedObjects() throws NotesException
Liefert einen Vector aus EmbeddedObject mit allen Dateianhängen und OLEObjekten des RichTextItems. •
public EmbeddedObject getEmbeddedObject(String objectName) throws NotesException
Liefert ein einzelnes EmbeddedObject innerhalb des aufrufenden RichTextItem mit Namen objectName. In RichTextNavigator: • public Base getFirstElement(int type) throws NotesException RichTextNavigator ermöglicht es, die verschiedenen Elemente und Objekte eines RichTextItems zu referenzieren. getFirstItem liefert das erste im RichTextItem gefundene Element vom Typ type. Für type == RichTextItem.RTELEM_TYPE_FILEATTACHMENT oder RTELEM_TYPE_OLE liefert die Methode den ersten Dateianhang oder das erste OLE-Objekt. • public boolean findFirstElement(int type) throws NotesException
Arbeitet wie getFirstElement, anstatt des eigentlichen Objekts wird jedoch nur über den boolschen Rückgabewert angezeigt, ob es ein Element des Typs type gibt. In Document: • public boolean hasEmbedded() throws NotesException Liefert true, falls es ein wie auch immer geartetes EmbeddedObject im Document bzw. in einem RichTextItem des Documents gibt. • public Vector getEmbeddedObjects() throws NotesException Liefert alle OLE-Objekte des Documents, egal ob im Document oder im RichTextItem gespeichert. • public EmbeddedObject getAttachment(String objectName) throws NotesException
Liefert den Dateianhang mit (internem) Namen objectName. Zusätzlich zu diesen Methoden stehen Ihnen in EmbeddedObject folgende Methoden zur Verfügung.
300
7 Document
In EmbeddedObject: • int activate(boolean showApplication) throws NotesException Aktiviert ein OLE-Objekt in seiner Anwendung. Im Backend muss showApplication auf false gesetzt werden. Liefert je nach Anwendung, die zu dem OLE-Objekt gehört, einen gültigen Handle auf das OLE-Objekt. Gültig für EmbeddedObject.EMBED_OBJECTLINK und EmbeddedObject.EMBED_OBJECT. Für EmbeddedObject. EMBED_ATTACHMENT wird eine NotesException geworfen. • void doVerb(String nameOfVerb) throws NotesException Führt für ein OLE-Objekt die Interaktion mit Namen nameOfVerb aus. • void extractFile(String fullFilePath) throws NotesException
Speichert •
ein
Attachment
im
Dateipfad
fullFilePath.
Gültig
für
EmbeddedObject.EMBED_ATTACHMENT String getClassName() throws NotesException
Name der Application, zu der ein OLE-Objekt gehört. • •
int getFileSize() throws NotesException Dateigröße für EmbeddedObject.EMBED_ATTACHMENT String getName() throws NotesException
Interner Name für die Referenzierung des Objekts oder Attachments. •
int getObject() throws NotesException
Liefert je nach Anwendung, die zu dem OLE-Objekt gehört, einen gültigen Handle auf das OLE-Objekt, sofern dies bereits im Hauptspeicher geladen ist. •
RichTextItem getParent() throws NotesException
Liefert das übergeordnete RichTextItem-Objekt. Wurde ein EmbeddedObjekt direkt aus einem Attachment aus dem Document generiert, liefert getParent null, da es kein übergeordnetes RichTextItem gibt. •
String getSource() throws NotesException
Ursprünglicher Dateiname des Objekts. •
•
int getType() throws NotesException Typ des Objekts: EmbeddedObject.EMBED_OBJECTLINK (1452), EmbeddedObject.EMBED_OBJECT (1453) oder EmbeddedObject.EMBED_ATTACHMENT (1454) Vector getVerbs() throws NotesException
Vector der möglichen interaktiven Methoden (verbs) des OLE-Objekts •
void remove() throws NotesException
Entfernt ein EmbeddedObject oder Attachment aus dem RichTextItem (oder Document) •
java.io.Reader getReader() throws NotesException
Inhalt des Objects als Reader-Objekt. Erzeugt eine temporäre Datei, die (erst) beim recycle des Objekts aufgeräumt wird. •
org.xml.sax.InputSource getInputSource() throws NotesException
7.4.1 Übersicht über die Methoden zur Attachmentbearbeitung
301
Inhalt des Objekts als org.xml.sax.InputSource. Um die zugehörigen Transformationsmethoden benutzen zu können, müssen alleinstehende Javaanwendungen XML4J.jar46 im Classpath einbinden und Applets diese als ClassPath-Parameter verwenden. Diese Methode erzeugt eine temporäre Datei, die beim Recycling wieder gelöscht wird. •
java.io.InputStream getInputStream() throws NotesException
Inhalt als java.io.InputStream. Diese Methode erzeugt eine temporäre Datei, die beim Recycling wieder gelöscht wird. •
•
org.w3c.dom.Document parseXML(boolean validateDTD) throws IOException, SAXException, NotesException org.w3c.dom.Document parseXML(boolean validateDTD) throws IOException, NotesException47
Parsed den Inhalt eines Attachments mit Type EmbeddedObject.EMBED_ATTACHMENT in ein XML DOM Document. Um die zugehörigen Transformationsmethoden benutzen zu können, müssen alleinstehende Javaanwendungen XML4J.jar48 im Classpath einbinden und Applets diese als ClassPath-Parameter verwenden. void transformXML (Object xslStyleSheet, XSLTResultTarget xsltresulttarget) throws IOException, SAXException, NotesException void transformXML(Object xslStyleSheet, XSLTResultTarget xsltresulttarget) throws NotesException49
Transformiert den Inhalt eines Attachments mit Type EmbeddedObject.EMBED_ATTACHMENT durch ein XSL Stylesheet. Um die zugehörigen Transformationsmethoden benutzen zu können, müssen alleinstehende Javaanwendungen XML4J.jar50 im Classpath einbinden und Applets diese als ClassPathParameter verwenden. xslStyleSheet darf vom Typ org.xml.sax.InputSource, lotus.domino.Item, lotus.domino.EmbeddedObject oder lotus.domino. MIMEEntity sein.
46
47 48 49 50
Seit Domino R7 ist XSLTResultTarget eine eingenständige Klasse und basiert im Wesentlichen auf javax.xml.transform, da seit Java 1.4.x die XML und XSL Klassen Bestandteil des standard JRE sind. Dementsprechend wurden aus der Domino 7 Core Version auch die Bibliotheken xml4j.jar und lotusxsl.jar entfernt. Dies hat leider Auswirkungen auf bestehende Installationen, da nun die neuen Parser verwendet werden müssen. Neue Signatur: Ab Domino Version 7 wirft diese Methode nur eine NotesException. Siehe 46. Neue Signatur: Ab Domino Version 7 wirft diese Methode nur eine NotesException. Siehe 46.
302
7 Document
7.5
Encryption und Signing
Daten in Domino-Datenbanken und -Dokumenten und deren Übertragung können auf verschiedene Weise verschlüsselt oder signiert werden. Verschlüsselung ist nicht zu verwechseln mit den verschiedenen bereits behandelten Methoden der Zugriffskontrolle über Zugriffskontrolllisten, serverbasierter programmatischer Kontrolle oder Autoren- und Leserfelder. Verschlüsselt werden können sowohl der Transport der Daten als auch die Daten selbst, und zwar entweder auf Basis der gesamten Datenbank oder auf Basis einzelner Items eines Dokuments. Die Verschlüsselung eines gesamten Dokuments als Gesamtheit ist nicht möglich, wird aber durch die Verschlüsselung jedes einzelnen Items erreicht. Verschlüsselung kann entweder auf Basis der Notes-Public-/Private Key-Infrastruktur erfolgen, also über eine Verschlüsselung über den öffentlichen Schlüssel eines Benutzers auch über S/MIME jeweils beim Speichern von Daten oder beim Versenden als E-Mail oder auf Basis von geheimen symmetrischen Schlüsseln, die für die Verschlüsselung auf Item-Basis Verwendung finden. Signiert werden können im Prinzip alle Domino-Objekte, die auf der Domino„Note“ basieren, also im Wesentlichen Gestaltungselemente und Dokumente, aber auch z.B. die ACL und unter bestimmten Bedingungen auch einzelne Items, beim Speichern von Items innerhalb von Abschnitten in Notes-Masken und beim Versenden über Notes-Mail oder S/MIME. Domino kennt viele Arten der Verschlüsselung und auch die gesamte Sicherheitsinfrastruktur basiert auf in der Regel asymmetrischen, persönlichen oder serverbasierten Schlüsselpaaren, die nach dem Prinzip von PGP arbeiten. Hierzu gehört die gesamte Notes-Infrastruktur, basierend auf dem Notes-Adressbuch, in dem die öffentlichen Schlüssel der bekannten Benutzer verwaltet werden. Zusätzlich können entfernte Verzeichnisse, hauptsächlich auf der Basis von LDAP, mit in diese Infrastruktur eingebunden werden. Das gesamte Thema „Sicherheit in Domino“ sprengt den Rahmen dieses Buches bei weitem, so dass dieses Kapitel nur einen groben Überblick über die Thematik liefern kann, zumal in diesem Zusammenhang ein großer Teil des benötigten Wissens mit der Infrastruktur und Administration des DominoServers verbunden ist, unabhängig von der Javaprogrammierung. Dieses Kapitel wird ihnen die Werkzeuge an die Hand geben, die Sie befähigen, mit einfachen Mitteln Daten zu verschlüsseln, zu speichern und zu versenden und dabei nur so tief in die Administration einzusteigen, wie es für ein erfolgreiches und einfaches Umsetzen der Methoden notwendig ist. Einen kleinen Überblick über die wichtigsten in Domino zur Verfügung stehenden Verschlüsselungs- und Signaturtechniken bietet folgende Übersicht:
7.5 Encryption und Signing
303
•
Verschlüsselung von Items durch geheime Verschlüsselungsschlüssel Die für die Java-Programmierung wichtigste Art der Verschlüsselung ist die Verschlüsselung von Items in einem Dokument mit einem geheimen symmetrischen Schlüssel. Dieser Schlüssel muss einmalig manuell generiert werden und wird an die berechtigten Benutzer verteilt, die ihn in ihrer Notes-ID speichern. Für die serverseitige Verwendung wird der Schlüssel in der Server-ID gespeichert. Mit Verschlüsselungsschlüsseln werden Daten für die Speicherung in NotesDatenbanken verschlüsselt, nicht für den Versand per E-Mail. Die Behandlung dieser Mechanismen erfolgt ausschließlich auf Dokumentenebene.
•
Verschlüsselung und Signatur von Dokumenten mit Notes-Schlüsselpaaren beim Speichern oder Versenden als Notes-Mail Das gesamte Sicherheitssystem für Encryption und Signing beruht in Domino auf asymmetrischen RSA-Schlüsselpaaren, die für jeden Benutzer und Server in deren User-ID bzw. Server-ID ausgegeben werden und für die RC2- und RC4Verschlüssselungsalgorithmen zum Einsatz kommen. Ähnlich wie durch den zuvor beschriebenen geheimen Schlüssel können Items mit dem öffentlichen Schlüssel aus dem Schlüsselpaar des Benutzers, für den die Daten lesbar bleiben sollen, verschlüsselt werden, wobei dieser über das NotesAdressbuch bekannt ist, und durch diesen Benutzer mit seinem privaten Schlüssel wieder entschlüsselt werden. Innerhalb der Notes-Infrastruktur ist es darüber hinaus möglich, jede Art von Note mit der Notes-Benutzer-ID zu signieren, wobei hierfür nun der private Schlüssel aus diesem Schlüsselpaar verwendet wird, zusätzlich wird der Signatur der öffentliche Schlüssel des Signierers beigelegt, anhand dessen überprüft werden kann, ob die Integrität der Daten erhalten ist. Eine wichtige Verwendung dieser Signaturen haben Sie bereits in Kapitel 4.16 kennen gelernt. Dort werden die Notes-Signaturen verwendet, um das Designelement „Agent“ zu signieren. Hierdurch kann bei der Ausführung eines Agenten sicher ermittelt werden, welcher Benutzer einen Agenten zuletzt gespeichert hat und ob diese Speicherung nicht zwischenzeitlich kompromittiert wurde.
•
Verschlüsselung und Signatur von Dokumenten mit S/MIME-Schlüsselpaaren beim Speichern oder Versenden als E-Mail Parallel zu den Notes-Verschlüsselungs- und Signaturmechanismen unterstützt Domino S/MIME. Die Grundprinzipien sind dieselben, jedoch kommen hier X.509-Zertifikate zum Einsatz.
•
Verschlüsselung gesamter Datenbanken Notes-Datenbanken können als Gesamtheit entweder anhand der Server-ID des Servers, auf dem sie sich befinden, oder anhand der ID eines Benutzers verschlüsselt werden, indem die entsprechende Datenbankeigenschaft über das Menu „File“ -> „Database“ -> „Properties“ und dort über den Button „Encryption settings“ aktiviert wird.
304
7 Document Für diese Art der Verschlüsselung wird ein zufälliger geheimer (symmetrischer) Schlüssel neu erstellt, der mit dem öffentlichen Schlüssel der Benutzer- bzw. Server-ID verschlüsselt und in der Datenbank gespeichert wird. Nur ein Benutzer, der Zugriff auf den entsprechenden privaten Schlüssel hat (also das Passwort der Benutzer- oder Server-ID kennt), kann diesen DatenbankVerschlüsselungsschlüssel entschlüsseln und erhält somit Zugriff auf die Daten der Datenbank. Datenbankverschlüsselung ist hauptsächlich für die Verschlüsselung lokaler Repliken von Datenbanken gedacht, da hier die ACL-Sicherheit nicht greift. Derartig verschlüsselte Datenbanken bleiben sicher, auch wenn sie auf Dateisystem-Ebene kopiert werden. Sollen Datenbanken nachträglich verschlüsselt werden, muss zuvor eine Datenbankwartung mit „compact“ vorgenommen werden.
•
Verschlüsselung des generischen Notes-Protokolls (NRPC) bei der Datenübertragung Die Verschlüsselung ist nahtlos in den Domino-Server und Notesclient integriert und wird über geheime, symmetrische Schlüssel abgewickelt. Um die Encryption für einen Server zu aktivieren, muss im Domino Administrator unter Configuration das Serverdokument markiert und die Aktion im Menü „Configuration“ -> „Server“ -> „Setup ports“ gewählt werden. Auf einem Notes Client kann ebenfalls der ausgehende Traffic verschlüsselt werden, indem im Menü „File“ -> „Preferences“ -> „User Preferences“ im Abschnitt „Ports“ die Eigenschaft „Encrypt network data“ aktiviert wird. Ist die NRPC-Protokoll-Verschlüsselung aktiviert, wird automatisch jede Datenübertragung, die über dieses Protokoll abgewickelt wird, zwischen Server und Client verschlüsselt.
•
Verschlüsselung von InternetProtokollen bei der Datenübertragung Für sämtliche von Domino unterstützten InternetProtokolle kann (sofern für das Protokoll verfügbar) für die Kommunikation mit dem Domino-Server SSL aktiviert werden. Ausführliche Informationen hierzu finden Sie im Kapitel 5.3.4. Dort wurde die SSL-Verschlüsselung anhand des DIIOP-Protokolls näher erläutert.
Für die Verwendung im Kontext mit Java soll auf die ersten beiden Punkte näher eingegangen werden.
7.5.1
Items mit geheimen Verschlüsselungsschlüsseln verschlüsseln
Vorbereitend für die Verwendung von Verschlüsselungsschlüsseln auf Item-Ebene – zum Teil wird auch von Feldverschlüsselung gesprochen – wird zunächst der Schlüssel benötigt. Öffnen Sie im Notes Client das Menü „File“ -> „Security“ -> „User security“ und dort die Sektion „Notes data“ -> „Documents“ (Abb. 7-7). Ein neuer Schlüssel wird mit dem Button „New secret key“ erstellt (Abb. 7-8), wobei hierbei noch ein Kommentar und die Einstellung, ob der Schlüssel bei der Verwendung älte-
7.5.1 Items mit geheimen Verschlüsselungsschlüsseln verschlüsseln
305
rer Notes-Versionen (vor 5.04) die in Domino so genannte „internationale Verschlüsselung“ verwenden soll, vorgenommen wird, was eine gut gemeinte Umschreibung der Verwendung einer weniger sicheren Verschlüsselung mit kürzeren Schlüsseln ist, die zu Zeiten von Domino 5.04 und früher bei der Verwendung außerhalb der Abb. 7-7 Verschlüsselungsschlüssel USA verwendet werden mussten, da die US-amerikanischen Exportvorschriften dies vorschrieben. Seit Version 5.04 existiert zwar weiterhin diese Unterscheidung, um vor möglichen neuerlichen Änderungen in den Exportvorschriften gewappnet zu sein, aber technisch sind nach dieser Zeit erstellte Schlüssel unabhängig von dieser Einstellung identisch. Kompatibel sind ohnehin alle Versionen untereinander. Beim Erstellen wird der Schlüssel automatisch in der User-ID des aktuellen Benutzers gespeichert. Um den Schlüssel zu verteilen, kann er entweder über den Button „Mail Secret Key“ per Notes-Mail versendet oder über „Other Actions“ als Datei exportiert werden. Im letzten Fall sollte immer ein nicht zu kurzes Passwort vergeben werden und der Schlüssel kann für die Benutzung durch bestimmte Empfänger geschützt werden, wobei zusätzlich noch festgelegt werden kann, ob solche Benutzer die Erlaubnis erhalten, den Schlüssel ihrerseits weiterzuversenden, so dass sehr genau kontrolliert werden kann, wer Zugriff auf den Schlüssel erhält. Es muss wohl nicht darauf hingewiesen werden, dass sämtliche Daten, die mit diesen Schlüsseln verschlüsselt wurden, verloren sind, wenn der Schlüssel verloren wird. Administrativ sind dies schon alle Schritte, die durchgeführt werden müssen, um mit Verschlüsselungsschlüsseln zu arbeiten.
Abb. 7-8 Neuen Verschlüsselungsschlüssel erstellen
306
7 Document
7.5.2
Verwendung der Verschlüsselungsschlüssel
Die eigentliche Verschlüsselung wird in drei Schritten erledigt. Erstens müssen Sie dem zu verschlüsselnden Item über void setEncrypted (boolean encrypt) die Eigenschaft boolean isEncrypted() mitgeben, wobei natürlich der Parameter encrypt auf true gesetzt werden muss. Diese Eigenschaft können Sie für jedes Item eines Dokuments einstellen, allerdings muss dies für jedes Item einzeln erledigt werden. Es genügt, diese Einstellung einmalig vorzunehmen, da diese Eigenschaft bis auf Widerruf im Item bleibt. Im zweiten Schritt müssen Sie – ebenfalls einmalig – dem Dokument den Namen des zu verwendenden Verschlüsselungsschlüssels mitgeben. Da es grundsätzlich erlaubt ist, mehrere Verschlüsselungsschlüssel auf ein Dokument anzuwenden, wird für diesen Zweck ein Vector, bestehend aus den zu verwendenden Schlüsselnamen, mittels void setEncryptionKeys (Vector keys) übergeben, der über Vector getEncryptionKeys() wieder gelesen werden kann. Die Verwendung mehrerer Verschlüsselungsschlüssel bedeutet, dass das verschlüsselte Dokument mit einem einzigen der verwendeten Schlüssel, der für die Verschlüsselung verwendet worden war, wieder entschlüselt werden kann. Schließlich muss durch encrypt() in Document die eigentliche Verschlüsselung durchgeführt und durch save() gespeichert werden. Im package djbuch.kapitel_07 finden Sie die Beispielklasse EncryptItems, mit den beiden Methoden beispiel1 (Listing 7-2) und beispiel2 (Listing 7-3), die eine erfolgreiche und fehlerhafte Verwendung der Encryption zeigen. Das erste Beispiel erzeugt zunächst ein Dokument und belegt die Items Form, F_Titel und F_secret mit Werten . Das Item F_secret soll verschlüsselt werden, private static Document beispiel1 (Database db) throws NotesException { System.out.println (LINE + "\nBeispiel 1" + LINE); Document docOne=db.createDocument(); docOne.replaceItemValue("Form", "FO_Dokument_k6"); docOne.replaceItemValue("F_Titel","Automatisch (1) erstellt am " + Calendar.getInstance().getTime()); Item item = docOne.replaceItemValue("F_secret","my Secret Value"); item.setEncrypted(true); Vector keys = new Vector (); keys.add("encryptionKey"); docOne.setEncryptionKeys(keys); docOne.encrypt(); boolean ok = docOne.save (true,false); String unid = docOne.getUniversalID(); if (docOne != null) {docOne.recycle();docOne=null;} Document result = db.getDocumentByUNID(unid); System.out.println ("Dokument ist verschlüsselt: " + result.isEncrypted()); System.out.println ("Geheimer Wert: " + result.getItemValueString("F_secret")); return result; }
Listing 7-2 Beispiel für Feldverschlüsselung
7.5.2 Verwendung der Verschlüsselungsschlüssel
307
also muss die Verschlüsselung für dieses Item mit setEncrypted (true) aktiviert werden . Der Vector keys enthält für dieses Beispiel lediglich einen einzelnen Namen. Dem Dokument wird der zu verwendende Schlüssel über setEncryptionKeys mitgeteilt. Alle derart für das Dokument definierten Schlüssel müssen sämtlich für die Verschlüsselung in der aktuellen User-ID vorliegen. Für die Entschlüsselung genügt es, mindestens einen dieser Keys zu besitzen. Nachdem die Verschlüsselung angestoßen wurde, muss das Dokument noch gespeichert werden . Um das Verhalten beim Entschlüsseln zu demonstrieren, wird das zuvor erstellte Dokument anhand der Universal ID komplett neu in ein neues Document-Objekt geladen . Beachten Sie, dass für diese Demonstration die Universal ID zwischengespeichert wird, aber das ursprüngliche Document-Objekt recycelt wird, um es endgültig aufzuräumen. Folgerichtig liefert die Methode isEncrypted für dieses neu geladene Document true. Allerdings ist darauf zu achten, dass isEncrypted keine Auskunft darüber liefert, ob die Daten zur Zeit der Abfrage tatsächlich verschlüsselt sind, sondern diese Methode gibt lediglich an, ob eine Verschlüsselung vorgesehen ist, also zuvor über setEncrypted (true) aktiviert wurde. Das Entschlüsseln ist transparent, d.h. es muss nicht ausdrücklich durchgeführt werden, sondern es genügt, wie gewohnt lesend oder schreibend auf das Dokument zuzugreifen . Am Rande sei noch darauf hingewiesen, dass diese Methode das von ihr erzeugte Dokument als Rückgabewert zurückgibt, es also nicht in der Methode selbst recycelt werden kann (und darf), sondern außerhalb der Methode der aufrufende Code sich darum kümmern muss, dass das über diese Methode erzeugte Dokument recycelt wird .
Bedenken Sie Sie können nur Verschlüsselungsschlüssel verwenden, die auch in Ihrer User- (oder Server) ID gespeichert sind. Die Übereinstimmung des Namens genügt nicht, sondern es können nur Items mit ein und demselben Schlüssel entschlüsselt werden, mit dem sie verschlüsselt wurden. Die Verwendung mehrerer Schlüssel bei der Verschlüsselung bedeutet, dass ein Item mit lediglich einem dieser Schlüssel entschlüsselt werden kann. Für die Verschlüsselung werden alle Schlüssel benötigt. Verschlüsselte Felder werden nicht in Ansichten angezeigt. Ansichten können nicht über Spalten mit verschlüsselten Feldern durchsucht werden.
308
7 Document
7.5.3
Fehlerquellen bei der Verwendung von Verschlüsselungsschlüsseln
In der Transparenz der Entschlüsselung liegt gleichzeitig eine Fehlerquelle. Folgt auf einen schreibenden Zugriff ein save, so wird das implizit entschlüsselte Dokument nicht automatisch neu verschlüsselt. Um hier keine Sicherheitslücke zu riskieren, gilt also bei der Verwendung der Feldverschlüsselung, dass immer vor jedem save auch ein encrypt erfolgen muss. Das zweite Beispiel (Listing 7-3) zeigt diese Problematik und noch einige andere Nebenwirkungen recht plastisch. Zunächst wird wie zuvor ein neues Dokument angelegt, verschlüsselt und gespeichert (Listing 7-3). In diesem Zustand zeigt sich über die Ausgabe des Items F_secret die erste Nebenwirkung. Wider Erwarten ist hier die Entschlüsselung weder automatisch noch transparent – die Ausgabe zeigt hier den Wert null, anstatt das Item implizit zu entschlüsseln. Dieses Verhalten gilt für den Zustand direkt nach dem Aufruf von encrypt. Wird ein (beliebiges anderes) Item schreibend verändert , wird die Encryption komplett aufgehoben, der zuvor als null angezeigte verschlüsselte Wert wird wieder korrekt ausgegeben . Auch wenn die Eigenschaft isEncrypted im Document
private static Document beispiel2(Database db) throws NotesException { System.out.println (LINE + "\nBeispiel 2" + LINE); Document result=db.createDocument(); result.replaceItemValue("Form", "FO_Dokument_k6"); result.replaceItemValue("F_Titel", "Automatisch (2) erstellt am " + Calendar.getInstance().getTime()); Item item = result.replaceItemValue("F_secret","my Secret Value"); item.setEncrypted(true); Vector keys = new Vector (); keys.add("encryptionKey"); result.setEncryptionKeys(keys); System.out.println ("Dokument ist verschlüsselt: " + result.isEncrypted()); result.encrypt(); boolean ok = result.save (true,false); System.out.println ("Dokument gespeichert: " + ok); System.out.println ("Dokument ist verschlüsselt: " + result.isEncrypted()); System.out.println ("Geheimer Wert: " + result.getItemValueString("F_secret")); result.replaceItemValue ("F_sonst", "val"); System.out.println ("Geheimer Wert: " + result.getItemValueString("F_secret")); result.save(true,false); System.out.println ("Dokument ist verschlüsselt: " + result.isEncrypted()); return result; }
Listing 7-3 Fehlerhafte Feldverschlüsselung
7.5.3 Fehlerquellen bei der Verwendung von Verschlüsselungsschlüsseln
309
Abb. 7-9 Verschlüsselte und unverschlüsselte Items
weiterhin über den Rückgabewert true suggeriert, es handele sich um ein verschlüsseltes Dokument , ist diese Information hier nicht mehr korrekt, das Dokument war unverschlüsselt gespeichert worden. Leicht können Sie dies überprüfen, wenn Sie den Notes Client öffnen und dort die Ansicht „kapitel 7\Dokumente“ öffnen und sich die Eigenschaften der beiden neuen Dokumente ansehen, die durch die Ausführung des Java-Programms Encrypt-Items aus den Beispielen erzeugt wurden (Abb. 7-9). Im rechten Eigenschaftenfenster der Abbildung erkennen Sie, dass der Wert des Items F_secret im Klartext lesbar ist und somit unverschlüsselt geblieben (genauer: geworden) ist. Hieraus kann es nur eine Konsequenz geben: Die beiden Methoden encrypt und save sind ein unzertrennliches Paar. Außerdem ergibt sich eine klare Reihenfolge, die bei der Verarbeitung einzuhalten ist: Führen Sie alle notwendigen Veränderun-
Der Verarbeitungszyklus für die Encryption Schritt 1 Vorbereitung der Encryption durch Setzen von setEncrypted in Item und setEncryptionKeys in Document Schritt 2 Durchführen aller benötigten Veränderungen auf dem Document Schritt 3 Aufruf von encrypt und save. Diese beiden Methoden sind ein unzertrennliches Paar bei der Verwendung der Feldverschlüsselung. Schritt 4 Keine weitere Verwendung des Documents, sonst muss der Zyklus erneut durchlaufen werden (im Einzelfall auch ohne Schritt 1).
310
7 Document
gen auf Ihrem Dokument durch, rufen Sie encrypt und save auf. Danach darf das Document nicht mehr verwendet werden, oder es muss der komplette Zyklus erneut durchlaufen werden. Wenn Sie nun im Notes Client das Dokument öffnen, werden in jedem Fall alle Werte korrekt angezeigt, sofern in Ihrer User-ID die korrekten Schlüssel gespeichert sind, denn beim Öffnen von Dokumenten im Notes Client wird Notes ebenfalls implizit eine Entschlüsselung durchführen. Beim Speichern des Dokuments würde übrigens dieser Encryption-Fehler korrigiert, aber nur, da in der Beispieldatenbank das korrespondierende Feld F_secret in der Maske mit der Eigenschaft „Enable Encryption for this Field“ aktiviert und für die Maske der „Default Encryption key“ auf den Namen des verwendeten Schlüssels („encryptionKey“) gesetzt wurde, so dass der Notes Client die Verschlüsselung implizit beim Speichern durchführt. Umgekehrt sei hier nochmals der Hinweis erlaubt, dass bei der Java-Programmierung immer bedacht werden muss, dass die verwendeten oder erzeugten Daten unerwartete Veränderung über die Verwendung von Masken im Notes Client erfahren können, auch wenn das Unerwartete in diesem Fall einen Fehler wieder wettmacht (Nachholen der Verschlüsselung). Im NotesClient können Sie in der Ansicht „kapitel 7\Dokumente“ eine weitere Auswirkung der Feldverschlüsselung erkennen. Derart verschlüsselte Items können nicht in Ansichtenspalten angezeigt werden und ebensowenig können verschlüsselte Items über Ansichtenspalten gefunden werden.
7.5.4
Verwendung von Verschlüsselungsschlüsseln über DIIOP
Verschlüsselungsschlüssel sind an eine User- oder Server-ID gebunden. Wie können also solche Keys über DIIOP, also über Internet-Sessions verwendet werden? Domino bietet hier den Ausweg über die Server-ID, in die lediglich der Key importiert51 wird. Liegt der Schlüssel in der Server-ID vor, so können alle Benutzer, die Zugriffsrechte auf Server, Datenbank und Dokument haben, mit diesem Key das Dokument (Item) ver- und entschlüsseln. Eingeschränkt wird dieses Recht aber über die programmatischen Agentenberechtigungen (s. Kap. 4.16). Nur Benutzer, die über volle Agentenberechtigungen („Run unrestricted methods and operations“) verfügen52, können dieses Verfahren anwenden, andernfalls wird eine NotesException „You must have permission to encrypt documents for server based agents“ geworfen. Liegt der benötigte Key nicht in der Server-ID vor, so wird dies mit der NotesException „You don't have any of the specified encryption keys“ quittiert.
51
52
Ein Verschlüsselungsschlüssel wird in die Server-ID importiert, indem diese ID im Notes Client über das Menü „File“ -> „Security“ -> „Switch ID“ geladen und der Schlüssel importiert wird. Beachten Sie, dass sich Änderungen am Serverdokument nicht immer sofort durchsetzen.
7.5.5 Verschlüsselung und Signatur von Dokumenten 7.5.5
311
Verschlüsselung und Signatur von Dokumenten
Neben der Verwendung geheimer Verschlüsselungsschlüssel besteht unter Domino die Möglichkeit, die vorhandene Infrastruktur zu nutzen und die Schlüsselpaare der User-IDs für die Verschlüsselung zu verwenden. Gleichzeitig eröffnet dies die Möglichkeit, Daten zu signieren und bei der Verwendung von Notes-Mail auch Mail-Inhalte verschlüsselt und signiert zu versenden. Dies kann direkt aus den Domino-JavaObjekten heraus erfolgen, ohne zusätzlichen Setup53.
7.5.6
Verwendung öffentlicher Verschlüsselungsschlüssel
Bei den bisher gezeigten Beispielen spricht man von geheimen Verschlüsselungsschlüsseln. Bei öffentlichen Verschlüsselungsschlüsseln kommt der öffentliche Schlüssel aus dem asymmetrischen Schlüsselpaar aus einer User-ID zum Einsatz, d.h. Items können ausdrücklich für bestimmte Personen verschlüsselt werden und können dann nur von dieser Person gelesen werden54. Die Verschlüsselung von Items über öffentliche Schlüssel erfolgt sehr ähnlich wie bei der Verwendung geheimer Schlüssel, lediglich die Methode setEncryptionKeys steht hier nicht zur Verfügung. Stattdessen wird einfach mit replaceItemValue das Feld „PublicEncryptionKeys“ mit dem kanonischen Namen des gewünschten Benutzers in der einfachen Form (ohne CN= etc.) gefüllt. Auch hier kann ein Vector eingesetzt werden, um die Daten für mehrere Zielpersonen lesbar zu machen. Damit Domino die Verschlüsselung durchführen kann, wird der öffentliche Schlüssel dieses Benutzers benötigt, den Domino entweder aus dem Adressbuch des Home-Servers des aktuellen Benutzers, bei einer lokalen Notes-Session, oder aus dem Adressbuch des Servers, mit dem die DIIOP-Session verbunden ist, ausliest. Insbesondere im ersten Fall muss eine Verbindung zu diesem Server möglich sein, sonst wird die Verschlüsselung scheitern und mit der NotesException „There is no certificate in the Address Book“ quittiert.
53
54
Zusätzlicher Setup ist erforderlich, falls keine Standardinstallation des Domino-Servers vorliegt, der per Default über die benötigten Einstellungen und Dienste für das NotesMail-Routing verfügt. Sollen Mails an entfernte Domino-Server übermittelt werden, müssen entsprechende Notes-Netze und Verbindungsdokumente eingerichtet werden. In der asymmetrischen Verschlüsselung wird mit einem öffentlichen Schlüssel verschlüsselt. Derart verschlüsselte Daten können nur mit dem zu diesem Schlüsselpaar gehörigen privaten Schlüssel entschlüsselt werden. Der für die Verschlüsselung verwendete Schlüssel ist für die Entschlüsselung nicht geeignet. Hierin liegt das Prinzip der asymmetrischen Verschlüsselung, da zwar ein Schlüssel öffentlich verfügbar, aber für die Entschlüsselung ungeeignet ist. Der private Schlüssel liegt in der User-ID des Benutzers, ist durch ein Passwort geschützt und sollte von dem Benutzer vertraulich behandelt werden.
312
7 Document
7.5.7
Signieren von Daten
Während die Verschlüsselung von Daten den Zugriff auf die Daten an sich durch Unkenntlichmachung der Daten erreichen will, ist das Ziel der Signatur, feststellen zu können, ob Daten zum einen von einer bestimmten Person stammen und ob diese Daten seit dem Speichern oder Senden durch diese Person nicht möglicherweise nachträglich (durch Dritte) verändert wurden. Der Vorgang des Signierens wird (nicht nur) in Notes wie folgt durchgeführt: Von den Daten wird ein Hashwert55 erzeugt. Der Hashwert wird mit dem privaten Schlüssel des Absenders (oder des Originators der Daten) verschlüsselt. Im Gegensatz zur oben beschriebenen Verschlüsselung wird hier der private Schlüssel des Absenders verwendet. • Die Daten werden zusammen mit dem Hashwert, dem öffentlichen Schlüssel des Originators und dem so genannten Zertifizierer des Originators gespeichert oder versendet. • Hashwert, Daten und öffentlicher Schlüssel bilden eine konsistente Einheit. Nur wenn die Daten unversehrt sind, kann ihr Hashwert mit dem öffentlichen Schlüssel des Originators entschlüsselt werden. Hieran kann erkannt werden, dass der Originator den Hash verschlüsselt hat, da nur er Zugriff auf seinen privaten Schlüssel hat, mit dem seine Daten verschlüsselt wurden. Wer der Originator ist, kann anhand seines Zertifizierers aus dem öffentlichen Schlüssel erkannt werden, der auf Basis der Notes-Zertifizierungsstelle erzeugt wurde. Dass die Daten unversehrt sind, kann daran erkannt werden, dass von den erhaltenen Daten erneut durch den Empfänger ein Hashwert gebildet wird. Da der mathematische Algorithmus eindeutig ist, wird aus denselben Daten derselbe Hashwert errechnet, der mit dem Hashwert aus der Signatur verglichen werden kann. • Anhand des Zertifizierers kann erkannt werden, ob dem Signierer vertraut wird. Dies ist der Fall, wenn der Signierer entweder zum selben Zertifizierer gehört, oder wenn über so genannte Gegenzertifikate (Cross Certificate) vom prüfenden Benutzer festgelegt wurde, dass er dem überprüften Zertifikat vertraut. Das Ausstellen von Gegenzertifikaten ist immer eine ausdrücklich manuelle Tätigkeit, bei der der Leser der Daten festlegt, welchen Organisationen (Herausgebern von Zertifikaten) er vertraut. • Nur wenn die Entschlüsselung des Hashwertes, dessen Vergleich mit dem aus den erhaltenen Daten errechneten Hashwert, positiv ausfällt und ein vertrauenswürdiges Zertifikat gefunden wurde, wird die Signatur als echt anerkannt, d.h. es wird anerkannt, dass die Daten vom angegebenen Originator stammen und unversehrt sind. Das Signieren ist eng in die Infrastruktur von Domino integriert und kann von außen nicht beeinflusst werden. Dokumente werden signiert, indem einzelne Items • •
55
Ein Hash ist ein nicht umkehrbarer, aber eindeutiger mathematischer Algorithmus, d.h. identische Daten liefern immer den identischen Hashwert. Umgekehrt lassen sich aber die Daten aus dem Hashwert nicht wieder herstellen. Selbst die kleinste Veränderung in den Daten führt zu einem völlig anderen Hashwert. Daher wird der Hashwert auch fingerprint (Fingerabdruck) genannt, da er einen Datensatz eindeutig identifiziert.
7.5.7 Signieren von Daten
313
durch void setSigned(true) in Item für die Signatur aktiviert und das Dokument über void sign() in Document signiert werden. Auch hier sollten Sie ähnlich wie für die Encryption das Dokument direkt nach dem Signieren speichern und nicht weiter verändern (sonst würde ja die Signatur zerbrechen). Alle für die Signatur notwendigen Schritte werden von Domino dann automatisch ausgeführt und zwar bezogen auf die ID des aktuellen Benutzers, dem die Notes-Session gehört. Hieraus folgt übrigens, dass niemand im Namen eines Dritten signieren kann. Die Methode boolean isSigned() in Item zeigt an, ob ein Item in die Signatur einbezogen ist, aber nicht etwa, ob die Signatur für dieses Item gültig ist. Gleiches gilt für die gleichnamige Methode void isSigned () in Document. Diese Methode liefert keine (!) Auskunft über den Zustand der Signatur, sondern zeigt lediglich an, ob es ein Item mit Namen „$Signature“ im Dokument gibt. Normalerweise sind sämtliche Mechanismen für die Erstellung und Überprüfung von Signaturen eng in den Notes Client verwoben und recht schwer von außen verwendbar. Beim Versuch, eine Verifizierung der Signatur vorzunehmen, ist ein erster Schritt, zu überprüfen, ob das Feld $Signature nicht nur vorhanden ist, sondern ob der Typ des Items – getFirstItem ("$Signature").getType() – den int-Wert der Konstante Item.SIGNATURE (8) liefert. Aber auch dieser Wert liegt nicht immer zuverlässig vor, da signierte Mails die Signatur in einem verschlüsselten Feld tragen. Notes unterstützt nur im geöffneten (LotusScript-) UI-Document ein Item mit Namen $SignatureStatus, das den Wert "1" hat, sofern eine Signatur vorhanden und gültig ist. Dieses Item nimmt den Wert "0" an, falls keine Signatur vorliegt und den Wert "2", falls eine zerbrochene Signatur vorliegt. Da eine Überprüfung dieses Items wegen der fehlenden UI Klassen in Java nicht möglich ist, müssen Sie sich anderer Mittel bedienen. Die beiden Methoden String getSigner() und String getVerifier() in Document liefern nur dann sinnvolle Werte (nicht ""), falls die Signatur gültig ist, denn beide Werte werden aus dieser extrahiert. Diese beiden Methoden liefern Ihnen immer sicher Auskunft, ob eine Signatur vorliegt und diese gültig ist. Bedenken Sie auch, dass die Überprüfung von Signaturen immer benutzerabhängig ist, da eine gültige Signatur immer nur dann vorliegt, wenn zwei Bedingungen erfüllt sind: Einerseits kann der Hash entschlüsselt werden und passt zu den Daten und andrerseits muss ein Benutzer die Zertifizierungsstelle des Absenders anerkennen, d.h. der aktuelle Benutzer der Notes-Session bürgt z.B. über Gegenzertifikate, dass er den Zertifizierer (den Aussteller) der Signatur anerkennt. Bedenken Sie, dass bei einer DIIOP-Verbindung, ebenfalls wegen der Problematik der fehlenden User-ID, wie zuvor bei der Verschlüsselung die Server-ID diesmal für die Signatur herangezogen wird. Wenn also z.B. eine DIIOP-Verbindung zu einem Server aufgebaut wird, der die ID CN=meinServer/O=meineOrganisation besitzt – wobei die DIIOP-Verbindung selbst durch einen beliebigen Benutzer (mit den nötigen Rechten) geöffnet wird – so liefert die Methode getSigner() für alle Dokumente, die über die beschriebenen Mechanismen mit setSigned() und sign() signiert wurden, den Wert CN=meinServer/O=meineOrganisation. Die Methode getVerifier() liefert analog den Wert O=meine Organisation. Auch für den Vor-
314
7 Document
gang des Signierens werden daher volle Agentenrechte im Serverdokument benötigt, sonst wird eine NotesException „You must have permission to sign documents for server based agents“ geworfen. Neben der hier beschriebenen Signatur über den Hashwert aller für die Signierung markierten Items kennt Domino auch noch die Abschnitt-weite Signatur. Diese entsteht, wenn Sie für Felder innerhalb eines Abschnittes einer Notes-Maske im Domino Designer die Eigenschaft „Sign if mailed or saved in section“ aktivieren. Dann erstellt Domino den Hash nicht über alle zu signierenden Items, sondern jeweils eine Signatur für die zu signierenden Items eines Abschnitts.
7.5.8
Versenden von signierten und verschlüsselten Daten
Die bisher beschriebenen Verfahren ermöglichen es, Daten zu verschlüsseln und zu signieren, wenn sie in einer Datenbank gespeichert werden. Sehr wichtig sind diese Verfahren jedoch ebenfalls, wenn Daten per Mail versendet werden sollen. Für den Versand per Notes-Mail (über die Notes-eigenen Routing-Mechanismen) stehen hierfür einfach anzuwendende Methoden zur Verfügung. Listing 7-4 zeigt ein Beispiel aus der Klasse EncryptItems im package djbuch.kapitel_07 für den Versand einer verschlüsselten und signierten Notes-Mail. Notes-Dokumente können direkt über die Methode void send (), in Document per Notes-Mail versendet werden. Vorbereitend müssen aber einige PflichtItems im Dokument vorliegen. Wichtig ist, dass die Maske auf „Memo“ lautet und die eigentliche Nachricht im Item „Body“ liegt, wobei dieses Item in der Regel ein RichTextItem ist. Im Item „Subject“ kann die Betreffzeile der Mail be-
private static void doMailSend (Database db) throws NotesException { Document docOne = null; try { System.out.println (LINE + "\nBeispiel 3 - Mail send" + LINE); docOne=db.createDocument(); String subject = "Automatisch (1) erstellt am " + Calendar.getInstance().getTime(); docOne.replaceItemValue("Form", "Memo"); docOne.replaceItemValue ("Subject", subject); Item i = docOne.replaceItemValue ("Body","hallo hallo"); docOne.replaceItemValue ("SendTo", "Administrator/DJBUCH"); i.setEncrypted(true); i.setSigned(true); docOne.setEncryptOnSend(true); docOne.setSignOnSend(true); docOne.send(); System.out.println ("Send done."); } finally { if (docOne != null) {docOne.recycle(); docOne=null;} } }
Listing 7-4 Verschlüsselte und signierte Mail versenden
7.6 Document Properties und weitere Methoden
315
stimmt werden und schließlich bestimmt SendTo den Adressaten. Bis auf SendTo sind diese Items unerlässlich. Wird SendTo weggelassen, können über zusätzliche Signaturen von send ein oder mehrere Empfänger übermittelt werden: void send(String recipient) throws NotesException void send(Vector recipients) throws NotesException void send(boolean attachForm) throws NotesException void send(boolean attachForm, String recipient) throws NotesException void send(boolean attachForm, Vector recipients) throws NotesException
Der Parameter attachForm ist eine Domino-Besonderheit und sorgt dafür, dass die Maske mit der ein Dokument erstellt wurde zusammen (im Domino-Routing) mit dieser Mail versendet wird. Eine über send verschickt mail trägt ein spezielles Feld, so dass über isSentByAgent angezeigt werden kann, ob die Mail automatisiert erstellt wurde. isSaveMessageOnSend und setSaveMessageOnSend ermöglichen es, das Senden mit einem automatisierten Speichern zu verbinden und Document createReplyMessage(boolean replyToAll) throws NotesException erzeugt ein Dokument, das eine Antwortmail darstellt, mit (optional allen) Absendern als Empfänger. Zusätzlich können Sie noch im Feld „From“ den anzuzeigenden Absender und im Feld „Principal“ den tatsächlichen Absender festlegen, wobei Domino intern immer den Benutzer der Session als Absender führt, um Absenderfälschungen zu unterbinden. Die zu verschlüsselnden oder zu signierenden Items werden wie oben durch setEncrypted und setSigned markiert. Anstatt jedoch das Verschlüsseln oder Signieren durch encrypt oder sign ausdrücklich anzustoßen werden zwei Properties im Document gesetzt. setEncryptOnSend (true) und setSignOnSend(true) erreichen, dass Verschlüsselung und Signatur direkt vor dem Versenden angestoßen werden, so dass das Document nur noch verschickt werden muss . Ob diese Eigenschaften im Document gesetzt sind, kann durch boolean isEncryptOnSend() und boolean isSignOnSend() in Document überprüft werden. Auch hier wird automatisch die Benuzter-ID des die Session führenden Benutzers verwendet, um das Dokument zu signieren. Auch für die Verschlüsselung kann kein Schlüssel angegeben werden, sondern es wird automatisch der öffentliche Schlüssel des Empfängers verwendet, was natürlich auch hier voraussetzt, dass dieser über das Domino Directory (auch Notes-Adressbuch genannt) bekannt ist und dort sein öffentlicher Schlüssel zur Verfügung steht.
7.6
Document Properties und weitere Methoden
Neben den beschriebenen Methoden zur Arbeit mit Dokumenten finden sich noch einige Methoden und Eigenschaften im Document, die nicht unerwähnt bleiben sollen. Properties in Document
316 •
7 Document Vector getAuthors() throws NotesException
Liefert eine Liste der Benutzer, die das Document zuletzt geändert haben (nicht zu verwechseln mit Autorenfeldern). Solange ein Document nicht zum ersten Mal durch save gespeichert wurde, liefert diese Methode null. •
DateTime getCreated() throws NotesException
Liefert das Erstelldatum des Document. Steht bereits vor dem ersten save zur Verfügung. •
DateTime getLastAccessed() throws NotesException
Liefert den Zeitpunkt des letzten Zugriffs auf das Document. Diese Methode liefert nur den exakten Tag. Wird ein Document innerhalb eines Tages mehrmals gelesen, so wird jeweils nur der Zeitpunkt des ersten Lesezugriffs dieses Tages zurückgegeben. Der Wert wird nur dann sofort aktualisiert, wenn ein Dokument verändert und gespeichert wird. •
DateTime getLastModified() throws NotesException
Liefert den letzten Änderungszeitpunkt des Document. •
int getSize() throws NotesException
Liefert die Gesamtgröße des Dokuments einschließlich des durch die Note benötigten Überhangs. •
Database getParentDatabase() throws NotesException
Liefert das Datenbankobjekt, über das das Document geladen wurde. XML • String generateXML() throws NotesException Anhand der Domino DTD wird XML-Code für das aktuelle Dokument (Abb. 710) erzeugt und als String zurückgegeben. • void generateXML(Writer writer) throws NotesException, IOException;
Statt den XML Code als String zurückzugeben, kann das Ergebnis auf einen Writer umgeleitet werden. •
void generateXML(Object xslStyleSheet, XSLTResultTarget xsltResult) throws IOException, SAXException, NotesException void generateXML(Object xslStyleSheet, XSLTResultTarget xsltResult) throws IOException, NotesException56
Erzeugt das XML des Document und transformiert es mit einem XSL Stylesheet. Das Ergebnis wird als XSLTResultTarget zurückgegeben. Der Parameter xslStyleSheet darf vom Typ org.xml.sax.InputSource, lotus.domino. Item, lotus.domino.EmbeddedObject oder lotus.domino.MIMEEntity sein. Ein typisches Code-Beispiel sieht wie folgt aus: String xsl = "//your XSL StyleSheet"; Item xslItem = styleDoc.replaceItemValue("F_xsl",xsl); XSLTResultTarget xsltResult = new XSLTResultTarget(); StringWriter wr = new StringWriter (); 56
Neue Signatur: Ab Domino Version 7 wird keine SaxException mehr geworfen.
7.6 Document Properties und weitere Methoden
317
Abb. 7-10 Dokument als XML
xsltResult.setCharacterStream(wr); sourceDoc.generateXML(xslItem,xsltResult); System.out.println (wr.toString());
RichText-Methoden in Document Im Zusammenhang mit der RichText-Verarbeitung befinden sich einige Methoden in Document, die hier als Vorgriff auf Kapitel 11.1 erläutert werden. •
RichTextItem createRichTextItem(String nameOfRTItem) throws NotesException
318
7 Document Um in einem Document ein neues RichTextItem zu erstellen, muss diese Methode verwendet werden.
•
boolean renderToRTItem(RichTextItem richtextitem) throws NotesException
Domino bietet die Möglichkeit, ein komplettes (!) Document als RichText in ein RichTextItem zu rendern. Damit dies sinnvoll möglich ist, wird natürlich eine Maske benötigt, damit Domino weiß, auf welcher Basis die Darstellung des Documents erzeugt werden soll. Verwendet wird hierfür die im Document angegebene Maske im Item Form. Verwendet wird die Methode, indem im zu rendernden Quelldokument die Methode createRichTextItem aufgerufen wird. Ziel ist ein RichTextItem (eines anderen Documents), das als Parameter übergeben wird. View- und Folder-Methoden in Document Im Zusammenhang mit Ansichten (View) befinden sich einige Methoden in Document, die hier als Vorgriff auf Kapitel 10.4 erläutert werden. •
Vector getColumnValues() throws NotesException
Falls ein Document über eine Ansicht selektiert wurde, so können die Spaltenwerte für diese Ansicht über getColumnValues() als Vector geladen werden. Einige Werte des Vector können daher auch leer sein. Jedes Element des Vector repräsentiert die Berechnung einer Spalte für die Ansicht, die wiederum (in der Regel) auf den Items des Dokuments basiert. Falls eine Spalte einer Ansicht ohne weitere Berechnung Items eines Dokuments anzeigt, so ist es effizienter, die Item Values über diese Methode zu beziehen, als über den im Kapitel über Items beschriebenen Weg. Spaltenberechnungen, die sich auf UI-Funktionen beziehen, werden nicht zurückgegeben. •
int getFTSearchScore() throws NotesException
Dokumente können in Domino über Volltextsuchen über die Datenbank oder eine Ansicht selektiert werden. Falls dies der Fall ist, liefert diese Methode eine Trefferbewertung für dieses Dokument anhand von Häufigkeit der Suchwörter aber auch Vergleichsoperationen und Wertungen in der Volltextsuche. Gültig ist immer die zuletzt durchgeführte Suche. Falls eine Datenbank keinen Volltextindex hat, liefert diese Methode zufällige Ergebnisse. Falls das Document nicht über eine Volltextsuche selektiert wurde, liefert diese Methode 0. •
View getParentView() throws NotesException
Falls das Dokument über eine Ansicht selektiert wurde, liefert diese Methode eine Referenz auf das zugehörige Objekt. •
Vector getFolderReferences() throws NotesException
Domino kennt so genannte Ordner (s. Kap. 3.3), die zwar den Ansichten recht ähnlich sind, aber anstatt Dokumente über Ansichtenauswahlformeln zu selektieren, beliebige Referenzlisten auf Dokumente enthalten, so dass Dokumente frei in Ordner gelegt oder auch wieder entfernt werden können, und zwar unabhängig vom eigentlichen Dokument.
7.7 Document Locking
319
Normalerweise wird aber keine umgekehrte Referenzliste von Domino gepflegt, in der erkenntlich wäre, in welchen Ordnern sich ein Dokument befindet. Wird diese Funktion benötigt, muss in der Datenbank diese Eigenschaft mit void setFolderReferencesEnabled(true) aktiviert werden. Dies beeinflusst aber die Gesamtperformance der Datenbank. Zusätzlich werden noch die beiden Ansichten „($FolderInfo)“ und „($FolderRefInfo)“ in der Datenbank benötigt. Andernfalls wirft setFolderReferenceEnabled eine NotesException „Database does not support folder references“. •
void putInFolder(String targetFolder) throws NotesException void putInFolder(String targetFolder, boolean createIfNotExists) throws NotesException
Um für ein Dokument eine Referenz in einen Ordner zu legen, wird die Methode putInFolder mit dem Namen des Zielordners als Parameter aufgerufen. Per Default wird ein nicht existierender Ordner automatisch neu angelegt. Optional kann dies mit dem Wert false für den optionalen Parameter createIfNotExists unterbunden werden. Ordner können inneinander verschachtelt werden, indem die Ordnernamen durch einen Backslash „\“ getrennt werden. So erzeugt z.B. „Äusserer Ordner\Innerer Ordner“ zwei ineinander geschachtelte Ordner „Äusserer Ordner“, der den Ordner „Innerer Ordner“ enthält. •
void removeFromFolder(String targetFolder) throws NotesException
Entfernt eine Dokumentenreferenz wieder aus einem Ordner.
7.7
Document Locking
Seit Version 6 verfügt Domino über die Möglichkeit des Document Locking. Dokumente können entweder durch den Benutzer manuell im Notes Client gesperrt werden oder über die API-Funktionen. Letztere stehen für Java allerdings erst seit Version 6.5 zur Verfügung. Die Idee des Lockings ist einfach. Anstatt grundsätzlich auf allen Repliken zuzulassen, dass mehrere Benutzer gleichzeitig ein Dokument ändern und speichern, und diese unterschiedlichen Änderungen in Speicher- und Konfliktdokumenten zu behandeln, wird an einem zentralen Dominoserver das Locking verwaltet und den einzelnen Benutzern und Prozessen die Möglichkeit gegeben, einzelne Dockumente exklusiv für die eigene Bearbeitung zu sperren. Dadurch wird der Zugriff durch Dritte verhindert, bis das Locking wieder aufgehoben ist. Hieraus folgen zwei Bedingungen, die für das Locking notwendig werden: Zum einen muss ein zentraler Locking-Server zur Verfügung stehen. Bei Domino ist dies der für eine Datenbank in deren ACL eingestellte Administrationsserver, der auch sonst für zentrale Verwaltungsaufgaben für die Datenbanken zuständig ist. Die zweite
320
7 Document
Voraussetzungen Ein zentraler Locking-Server ist verfügbar. Der Locking Server ist als Administrationsserver in der Datenbank eingetragen: db.getACL().getAdministrationServer() ist nicht der leere String "" In der Datenbank, für die Locking aktiviert werden soll, ist isLockingEnabled() == true
Folge ist, dass das Locking ausdrücklich von den jeweiligen Benutzern vor einem gewünschten exklusiven Zugriff aktiviert und danach natürlich wieder deaktiviert werden muss, damit das Dokument nicht dauerhaft gesperrt bleibt. Um zu überprüfen, ob ein Administrationsserver für eine Datenbank eingetragen ist, kann über ACL getACL () in Database die ACL bezogen werden und in ACL über String getAdministrationServer() der eingetragene Administrationsserver in der abgekürzten Form (z.B. „Java/DJBUCH“) geladen werden und über void setAdministrationServer (String name) wieder gesetzt werden. Beachten Sie, dass Veränderungen in der ACL Administratorenrechte voraussetzen57. Falls in der Datenbank kein Administrationsserver eingetragen ist, liefert getAdministrationServer den leeren String "". Damit für eine Datenbank das Locking verwendet werden kann, muss für diese Datenbank in den Eigenschaften das Document Locking aktiviert werden. Dies kann programmatisch in Database auch über void setDocumentLockingEnabled(boolean state) mit state==true erreicht und natürlich mit boolean isDocumentLockingEnabled() wieder abgefragt werden. Wenn nun ein Benutzer ein Dokument sperrt, so wird am zentralen Locking Server diese Sperrung hinterlegt und zwar unabhängig davon, auf welcher Replik der Benutzer gerade arbeitet. Einzige Voraussetzung ist, dass der Locking Server (direkt, in der Regel im LAN) erreichbar ist. Jeder andere Benutzer, Server oder Agentenprozess, der jetzt versucht, auf einer beliebigen (!) Replik das Dokument oder eine Replik davon zu öffnen, wird eine NotesException erhalten, da das Dokument für den Benutzer, der die Sperrung eingerichtet hat, den so genannten Lock Holder, exklusiv gesperrt ist. Um dies zu gewährleisten, wird Domino, sobald das Locking für eine Datenbank aktiviert ist, für jedes Öffnen eines Dokuments am Locking Server nach eventuell vorliegenden Sperrungen nachfragen. Das Locking sollte also immer nur in performanten lokalen Netzen für eine Datenbank aktiviert werden, in der der Locking Server immer erreichbar ist, sonst muss mit erheblichen Wartezeiten durch den erhöhten Traffic durch die Locking-Anfragen und mit eventuellen Timeouts, bei Nichtverfügbarkeit, gerechnet werden. 57
Eine häufige Fehlerquelle bei der Verwendung von DIIOP ist an dieser Stelle, dass die maximalen Internet-Zugriffsrechte per Default auf „Editor“ eingestellt sind, so dass Veränderungen an der ACL per DIIOP nicht vorgenommen werden können und in einer NotesException resultieren.
7.7 Document Locking
321
Document Locking Kann für mehrere Lock Holder gleichzeitig eingerichtet werden. Locking kann für den aktuellen Benutzer oder für Dritte eingerichtet werden. Kann nur von einem eingetragenen Lock Holder wieder aufgehoben werden. Sollte nur in schnellen lokalen Netzen eingesetzt werden. Erfordert mindestens Autoren-Rechte. Provisorisches Locking erfolgt, falls der zentrale Locking-Server nicht erreichbar ist und provisorisches Locking akzeptiert wurde. Locking kann nur für Dokumente eingesetzt werden, die bereits in der Datenbank gespeichert wurden. Benutzer mit Manager-Rechten können ein Lock aufheben, ohne selbst Lock Holder zu sein. Kann in Document, View, Form oder Agent verwendet werden.
Aktiviert wird das Locking für ein Dokument durch die Methode boolean lock (boolean acceptProvisional) in Document. Rückgabewert dieser Methode ist true, falls das Locking erfolgreich war. Der Parameter acceptProvisional zeigt an, ob beim Locking akzeptiert werden soll, dass das Locking nur provisorisch eingerichtet wird. Provisorisches Locking findet nur auf der aktuellen Replik statt, auf der der aktuelle Benutzer gerade arbeitet, und wenn der zentrale Locking-Server nicht erreichbar ist. Domino versucht dann, sobald der Locking-Server erreichbar ist, das Locking endgültig auf diesem Server zu platzieren. Die Durchführung des provisorischen Lockings erfolgt transparent und es kann lediglich gesteuert werden, ob es grundsätzlich akzeptiert wird. Anstelle von lock (true) kann auch boolean lockProvisional() verwendet werden. Das provisorische Locking ist nicht unproblematisch, wenn mehrere Benutzer gleichzeitig provisorisch locken oder ein Benutzer provisorisch und einer endgültig lockt. Es wird sich dann der Benutzer durchsetzen, der zuerst den endgültigen Lock auf dem Administrationsserver platzieren kann. Änderungen eines weiteren Benutzers, der versucht hat provisorisches Locking für ein Dokument einzurichten, werden verworfen (s. Abb. 7-11)! Hiermit wird der Tatsache Rechnung getragen, dass im Falle eines endgültigen Lockings der zweite Benutzer ohnehin keine Änderungen hätte durchführen können. Falls derartige Daten verworfen wurden, wird der Benutzer automatisch per Notes-Mail informiert, dass seine Änderungen verfallen sind. lock und lockProvisional zeigen folgendes Verhalten:
•
Per Default wird provisorisches Locking nicht akzeptiert (boolean lock()).
322
7 Document 1
Benutzer A sperrt ein Dokument auf dem Administrationsserver A
1
Benutzer A Replik A
2
B
AdministrationsServer
2
Benutzer B erhält keinen Zugriff auf das Dokument, da es gesperrt ist. Die Sperrung wird auf dem Administrationsserver erkannt.
Replik B
Benutzer B
3
C 3
Benutzer C Replik C
Benutzer C hat temporär keine Verbindung zum Administrationsserver. Sobald die Verbindung wieder hergestellt ist, gehen seine Änderungen verloren, da ein Locking erkannt wird und er keine Änderungen hätte vornehmen dürfen.
Abb. 7-11 Document Locking in verteilter Umgebung
•
Falls provisorisches Locking nicht akzeptiert wird (lock (false)) und der Locking-Server nicht erreichbar ist, wird eine NotesException „Unable to lock cannot connect to Master Lock Database“ geworfen. Falls provisorisches Locking nicht akzeptiert wird und der Locking-Server erreichbar ist und noch kein endgültiges Locking vorliegt, bzw. der aktuelle Benutzer bereits als Lock Holder eingetragen ist, wird true zurückgegeben und das Locking etabliert bzw. beibehalten. Falls das endgültige Locking nur für andere Benutzer eingetragen ist, wird die NotesException „Note is already locked by someone else“ geworfen.
7.7 Document Locking •
•
323
Falls provisorisches Locking akzeptiert wird (lock (true) oder lockProvisional()), wird ein provisorisches Locking eingetragen, falls der Locking-Server nicht erreichbar ist, aber noch kein (provisorisches) Locking vorliegt, bzw. der aktuelle Benutzer bereits (provisorischer) Lock Holder ist. Provisorisches Locking wird nicht eingetragen, falls ein Locking bereits für andere Benutzer vorliegt. Dann wird eine NotesException „Note is already locked by someone else“ geworfen. Falls der Locking-Server erreichbar ist und noch kein Locking vorliegt, bzw. der aktuelle Benutzer bereits Lock Holder ist, wird endgültiges Locking eingetragen und true zurückgegeben, andernfalls eine NotesException „Note is already locked by someone else“ geworfen. Provisorisches Locking kann nicht erzwungen werden, sondern wird implizit verwendet, falls der Locking-Server nicht verfügbar ist. In jedem anderen Fall wird endgültig gelockt.
Zusätzlich kann den Methoden lock und lockProvisional als erster Parameter ein String oder ein Vector aus Strings übergeben werden. Hierdurch kann das Locking für einen bestimmten Benutzer bzw. für mehrere bestimmte Benutzer eingetragen werden. Hieraus ergeben sich die Signaturen: boolean lock() throws NotesException boolean lock(boolean acceptProvisional) throws NotesException boolean lock(String lockHolder) throws NotesException boolean lock(String lockHolder, boolean acceptProvisional) throws NotesException boolean lock(Vector lockHolders) throws NotesException boolean lock(Vector lockHolders, boolean acceptProvisional) throws NotesException boolean lockProvisional() throws NotesException boolean lockProvisional(String lockHolder) throws NotesException boolean lockProvisional(Vector lockHolders) throws NotesException
Wird durch die Verwendung eines der Parameter lockHolder oder lockHolders ein Lock für einen Dritten eingerichtet, kann das Locking nicht mehr durch den aktuellen Benutzer aufgehoben werden, es sei denn er war bereits selbst Lock Holder. Einzige Ausnahme ist ein Benutzer, der als Manager in der ACL einer Datenbank eingetragen ist. Manager dürfen vorhandene Lockings aufheben, auch wenn sie nicht selbst Lock Holder sind. Dies ist der einzige Ausnahmefall. So kann zum Beispiel ein Dokument, das für einen anderen Benutzer gelockt ist, nicht durch den Administrator erneut gelockt werden. Aufgehoben wird das Locking durch die Methode void unlock(). Berechtigt sind hierfür nur die als Lock Holder (im gelockten Document oder Designelement) eingetragenen Benutzer. Falls ein nicht berechtigter Benutzer versucht, das Locking aufzuheben, wird eine NotesException „Notes error: The document is not locked by you“ geworfen.
324
7 Document
private static Document beispiel2 (Database db) throws NotesException { System.out.println (LINE + "\nBeispiel 2" + LINE); String adminServer = db.getACL().getAdministrationServer(); boolean activated = db.isDocumentLockingEnabled(); if (activated && adminServer != null && !adminServer.equals("")){ Document result = db.createDocument(); result.replaceItemValue("Form", "FO_Dokument_k2"); result.replaceItemValue("F_Titel", "Automatisch (2) erstellt am " + Calendar.getInstance().getTime()); result.save(); result.lock("Someone Else/DJBUCH",true); boolean locked=false; try { locked = result.lock(false); } catch (NotesException e) { System.out.println ("Achtung: Endgültiges Locking nicht möglich."); try { locked = result.lock(true); } catch (NotesException e1) { System.out.println ("Achtung: Provisorisches Locking nicht möglich."); System.out.println ("Für " + result.getLockHolders() + " gesperrt."); } } String holders = result.getLockHolders().toString(); System.out.println ("Gesperrt durch: " + holders); try { result.unlock(); System.out.println ("Sperre für " + holders + " wurde aufgehoben."); } catch (NotesException e) { System.out.println ("Sperre für " + holders + " wurde nicht aufgehoben."); } System.out.println ("Gesperrt durch: " + result.getLockHolders()); return result; } else { System.out.println ("Locking für Datenbank aktiviert: " + activated); System.out.println ("Locking Server: "); } return null; }
Listing 7-5 Beispiel für Document Locking
Über die Methode getLockHolders() kann ein Vector aus String mit allen Lock Holdern abgerufen werden. Falls das Dokument nicht gelockt ist, wird ein leerer Vector zurückgegeben. Neben Dokumenten können auch Ansichten, Masken oder Agenten mit einem Locking gesperrt werden. Sämtliche Methoden entsprechen den zuvor beschriebenen.
7.8 Zusammenfassung
325
Design Locking muss zuvor in der Datenbank über void setDesignLockingEnabled(boolean allow) erlaubt werden. Ob das Design Locking aktiviert ist, kann über boolean isDesignLockingEnabled() geprüft werden. In dem Package djbuch.kapitel_07 findet sich die Klasse DocumentLocking mit zwei Beispielen für das Document Locking. Listing 7-5 zeigt das zweite Beispiel, in dem einige Ausnahmesituationen aufgezeigt werden. Bevor in einer Java-Anwendung Locking verwendet wird, sollte überprüft werden, ob es für die aktuelle Datenbank überhaupt zulässig ist . Um das Locking demonstrieren zu können, wird in diesem Beispiel ein Dokument neu angelegt. Damit es gelockt werden kann, muss es gespeichert werden , denn ein Dokument, das nur als Java-Objekt existiert, wie dies hier für das Dokument result vor dem save der Fall ist, wird vom Locking-Mechanismus nicht gefunden und kann nicht gelockt werden. Das Dokument wird für einen anderen Benutzer gelockt und danch wird versucht für den aktuellen Benutzer ein endgültiges Lock durchzuführen . Zur Verdeutlichung wurde hier der Parameter acceptProvisional ausdrücklich auf false gesetzt. Erwartungsgemäß wird hier eine NotesException geworfen, da das Dokument ja bereits einen anderen Lock Holder hat. Entsprechend wird auch der Versuch, ein provisorisches Locking durchzusetzen, scheitern . Der weitere Verlauf hängt davon ab, welche Rechte Ihr lokaler Benutzer auf der Datenbank hat. Sollte er keine Manager-Rechte für die Datenbank haben, wird auch beim unlock eine NotesException anzeigen, dass das Locking des vorhandenen Lock Holders nicht aufgehoben werden darf, was auch gut an den vor und nach dem unlock angezeigten Lock Holdern erkannt werden kann.
7.8
Zusammenfassung
Das Domino-Document und sein Item sind die zentrale Speicherstruktur, in der alle Domino-Daten abgelegt werden. Ein Document stellt die Klammer für sämtliche enthaltenen Items, RichTextItems, Anhänge oder OLE-Objekte dar, die im Document gespeichert werden können. Das Document ist das wichtigste Objekt innerhalb der Domino-Datenstruktur und mit Sicherheit auch das komplexeste. Daher wird sich das Kapitel 13 erneut intensiv mit dem Document auseinandersetzen. Es kann auf die verschiedensten Arten erzeugt werden, indem es direkt aus einer Datenbank geladen, über eine Suche oder Ansicht und die hieraus resultierenden Collections selektiert oder indem es über Volltextsuchen gefunden wird. In diesem Kapitel wurden wichtige Techniken anhand und für das Document erläutert. Hierzu gehören die Verschlüsselung und Signatur von Daten, aber auch das seit Domino R6 neue Document Locking.
8
8 Session
In diesem Kapitel: Session als zentrale Basis von Domino-Java-Anwendungen Bezug von DBDirectory als Abbild der Datenbankstruktur eines Domino-Servers oder eines Notes Clients Bezug des AgentContext Administrative Anwendungen im API – AdministrationProcess DateTime, Name oder Stil-Objekte beziehen Convenience-Methoden
328
8 Session
Die Verwendung und der Bezug einer Notes-Session wurde in den Kapiteln 4 und 5 ausführlich diskutiert. Einen schnellen Überblick über die NotesFactory zum Bezug einer Session bietet Kapitel 5.3.7. Die Session ist die Basis aller Domino-Java-Anwendungen. Neben dieser grundlegenden Aufgabe liefert sie aber noch einige nützliche Objekte, wie Streams, Datums- oder Farbobjekte und einige Properties, die für die aktuelle Verbindung hilfreich und wichtig sind. Darüber hinaus werden über die Session verschiedener Anwendungen instanziert, wie z.B. der Administrationsprozess. Im Folgenden werden daher nochmals alle Methoden und Properties aus Session zusammengefasst und kurz erläutert.
8.1 •
Bezug von Datenbanken Database getDatabase(String serverName, String databaseName) throws NotesException Database getDatabase(String serverName, String databaseName, boolean createOnFail) throws NotesException Öffnet die Datenbank mit Namen databaseName auf dem Server mit dem Namen serverName. Falls serverName null ist, wird der aktuelle Server ange-
nommen. Falls es sich nicht um eine lokale Notes-Session handelt, also z.B. um einen periodischen Agenten oder eine DIIOP-Session, so können nur Datenbanken auf dem aktuellen Server der Session geöffnet werden, es sei denn, es wurde im Serverdokument eine entsprechende Vertrauensstellung zwischen den Servern eingerichtet. Der Name der Datenbank muss den vollen Pfad einschließlich eventueller Ordner enthalten, in denen sich die Datenbank befindet. Es ist zulässig (auch unter Windows), in jedem Fall den Vorwärtsslash „/“ als Pfadtrenner zu verwenden. Falls der optionale Parameter createOnFail den Wert true hat, so wird die Datenbank neu angelegt, falls sie nicht geöffnet werden konnte und der aktuelle Benutzer entsprechende Rechte hat. Wird diese Methode mit zwei null-Parametern aufgerufen, liefert sie ein leeres, neu instanziertes Database-Objekt. •
Database getURLDatabase() throws NotesException
Liefert die Default-Web-Navigator-Datenbank des aktuellen Benutzers. Steht nur für lokale Notes-Sessions auf einer Client-Installation zur Verfügung. In der Regel ist die Web-Navigator-Datenbank die Datenbank mit dem Namen perweb50.nsf (auch für R6.x).
8.2 Einfache unabhängige Stil- und Eigenschafts-Objekte
8.2 •
329
Einfache unabhängige Stil- und Eigenschafts-Objekte ColorObject createColorObject() throws NotesException
Domino kennt eigene Farb-Objekte, die für die unterschiedlichsten Zwecke verwendet werden. Diese reichen von Textstilen in RichText Items bis hin zu Farbeigenschaften von Ansichtsspalten oder Maskenhintergründen. Ein ColorObject kennt die Darstellung von Farbräumen über RGB- oder HSLWerte, d.h. über Werte für Rot, Grün und Blau (jeweils zw. 0 und 255) oder für Farbton (0..240: 0=Rot, 80=Grün, 160=Blau), Sättigung (Anteil des Grauwertes; 0..240: 240 entspricht der reinen Farbe; der Wert 0 hat immer einen Grauton als Ergebnis) und Helligkeit (0..240: 0 ergibt immer Schwarz, 240 ergibt immer Weiß). Notes weicht mit den möglichen Werten für HSL leider vom Standard ab, der jeweils Werte im Bereich 0..360 für den Farbton (Gradangabe im Farbkreis) und 0..100 für Sättigung und Helligkeit (in Prozent) vorsieht. Bevor für ein Notes-Designelement oder einen RichTextStyle eine Farbe gesetzt werden kann, muss über createColorObject ein Farbobjekt erzeugt werden. Für dieses kann dann über die setter int setRGB(int red, int green, int blue) oder int setHSL(int hue, int saturation, int luminance) der gewünschte Farbwert gesetzt werden, wobei diese Methoden gleichzeitig als Ergebnis den nächst möglichen Wert der so genannten Notes-Farbe zurückgeben, der lediglich aus einem int besteht und die Werte von 0 bis 240 annehmen kann. Die ersten 16 Notes-Farben sind als Konstanten in RichTextStyle definiert und lauten: public public public public public public public public public public public public public public public public
static static static static static static static static static static static static static static static static
final final final final final final final final final final final final final final final final
int int int int int int int int int int int int int int int int
COLOR_BLACK = 0; COLOR_WHITE = 1; COLOR_RED = 2; COLOR_GREEN = 3; COLOR_BLUE = 4; COLOR_MAGENTA = 5; COLOR_YELLOW = 6; COLOR_CYAN = 7; COLOR_DARK_RED = 8; COLOR_DARK_GREEN = 9; COLOR_DARK_BLUE = 10; COLOR_DARK_MAGENTA = 11; COLOR_DARK_YELLOW = 12; COLOR_DARK_CYAN = 13; COLOR_GRAY = 14; COLOR_LIGHT_GRAY = 15;
Für ColorObject bestehen auch die getter-Methoden getRed(), getGreen(), getBlue(), getHue(), getSaturation() und getLuminance(), die die int Werte für die einzelnen Komponenten der beiden Farbmodelle liefern.
330 •
•
8 Session DateRange createDateRange() throws NotesException DateRange createDateRange(DateTime begin, DateTime end) throws NotesException DateRange createDateRange(Date begin, Date end) throws NotesException Ein DateRange Objekt ist ein Zeitraum, der aus zwei lotus.domino. DateTime- oder java.util.Date-Objekten besteht, die jeweils Anfang und Ende des Zeitraumes angeben. Sie können über die Methoden getStartDateTime(), setStartDateTime(DateTime datetime), getEndDateTime() und setEndDateTime(DateTime datetime) gesetzt und gelesen werden. DateTime createDateTime(Date date) throws NotesException DateTime createDateTime(Calendar calendar) throws NotesException DateTime createDateTime(String s) throws NotesException Über einen String, ein java.util.Date- oder ein java.util.Calendar (seit Version 6)-Objekt kann ein neues lotus.domino.DateTime-Objekt erstellt werden. Ein DateTime-Objekt ist die Domino-Repräsentation eines Zeit-
Objektes. Zulässige Formate für die Verwendung des String finden sich in Kapitel 12.2. •
International getInternational() throws NotesException Liefert ein lotus.domino.International-Objekt, das ähnlich wie der Ca-
lender vielfältige lokalisierte Date/Time Parameter zugänglich macht (s. Kap. 12.3). •
Name createName(String name) throws NotesException Name createName(String name, String language) throws NotesException
Domino kann einen hierarchischen Domino-Namen in einem Name-Objekt kapseln. Dieses stellt über seine Properties verschiedene Teile des hierarchischen Namens zur Verfügung und erlaubt zusätzlich auch RFC E-Mail-Adressen aufzunehmen. Zum Beispiel kann der angemeldete User über getUserNameObject() als Name-Objekt geladen werden (s. Kap. 12.1). •
RichTextParagraphStyle createRichTextParagraphStyle() throws NotesException Ein RichTextItem ist, wie bereits in Kapitel 2.2.4.8 eingeführt, in Paragraphen aufgeteilt. Jeder Paragraph kann über ein RichTextParagraphStyle-Objekt
•
RichTextStyle createRichTextStyle() throws NotesException Ein RichText Item kann über ein RichTextStyle-Objekt mit Schriftstilen ver-
mit Layout-Eigenschaften versehen werden (s. Kap. 11.1ff).
sehen werden (s. Kap. 11.10).
8.3 Service-Anwendungen
8.3 •
331
Service-Anwendungen AdministrationProcess createAdministrationProcess (String serverName) throws NotesException
Der Administrationsprozess ist die zentrale Verwaltungsengine in Domino. Über ihn werden viele der periodischen und unternehmensübergreifenden Aufgaben abgewickelt. Hierzu gehört die Erzeugung und Löschung von Repliken, Servern, Gruppen oder Benutzern, die Rezertifizierung von Benutzern oder Servern, domänenübergreifende Suchen nach Benutzern und Gruppen oder die Verwaltung von Gruppen und Serverclustern. Die meisten dieser Aufgaben werden abgewickelt, indem Aufträge als Notes-Dokumente in der Datenbank „Administration Requests“ (in der Regel admin4.nsf) hinterlegt und je nach Art der Aufgabe vom Administrationsprozess in entsprechend vordefinierten Intervallen abgearbeitet werden. Um dieses Objekt erzeugen und die Methoden nutzen zu können, werden volle Zugriffsrechte auf den Server und Zugriffsberechtigung für das Domino Directory benötigt. Seit Domino R6.5 kann der Administrationsprozess über createAdministrationProcess als Objekt instanziert und somit auch über Java gesteuert werden. •
AgentContext getAgentContext() throws NotesException Der AgentContext ist ein Objekt, das den Kontext eines im Domino Designer
erstellten und in Domino betriebenen Agenten darstellt. Er wird über getAgentContext geladen, sofern die Session im Kontext mit einem Agenten steht. Nähere Informationen finden Sie in Kapitel 3.4 ff. im Zusammenhang mit Agenten und in Kapitel 4.10. Die Dokumentenbasis, auf der ein Agent arbeitet, kann über Volltextsuchen eingeschränkt werden, wie in Kapitel 3.4.2 und 7.1.4 („Vordefinierte DocumentCollections“) beschrieben. •
DbDirectory getDbDirectory(String serverName) throws NotesException Liefert ein DbDirectory-Objekt, das die Datenbanken auf einem Domino-Server oder einer lokalen Arbeitsstation repräsentiert. Wurde über getDbDirectory ein DbDirectory-Objekt auf dem Server serverName bezogen, kann dieses mit getFirstDatabase() und getNextDatabase durch alle gefundenen Da-
tenbanken navigiert werden, ohne zuvor den Namen einer Datenbank zu kennen. Für serverName == null wird der aktuelle Server der Session verwendet. Für DIIOP-Sessions muss dieser Parameter mit null angegeben werden. DbDirectory kennt folgende Methoden: – –
Database createDatabase(String dbNameAndPath) throws NotesException Database createDatabase(String dbNameAndPath, boolean alreadyOpenIt) throws NotesException Erstellt eine Datenbank mit Namen dbNameAndPath und öffnet sie, falls alreadyOpenIt==true. Die neue Datenbank ist leer und enthält keine
Gestaltungselemente.
332
8 Session –
Database getFirstDatabase(int databaseType) throws NotesException
Öffnet die erste gefundene Datenbank eines bestimmten Typs. databaseType kann die Werte DbDirectory.DATABASE (eine Domino-Datenbank „.nsf“), DbDirectory.TEMPLATE (ein Datenbanktemplate „.ntf“), DbDirectory.REPLICA_CANDIDATE (eine Datenbank oder ein Template, für das die Replikation nicht abgeschaltet ist) oder DbDirectory. TEMPLATE_CANDIDATE (eine beliebige Datenbank oder Template) annehmen. Beachten Sie, dass bei der Verwendung von DIIOP die Eigenschaft „Allow HTTP clients to browse databases:“ im Serverdokument unter „Internet Protocolls“ -> „Http“ auf „Yes“ gestellt sein muss, da die DIIOP-Session als Internetsession behandelt wird. Die Datenbank wird nicht automatisch geöffnet (siehe isOpen() und open() in Database). –
Database getNextDatabase() throws NotesException
Die nächste Datenbank des zuvor durch getFirstDatabase definierten Typs. Die Datenbank wird nicht automatisch geöffnet (siehe isOpen() und open() in Database). –
String getName() throws NotesException
Der Name des aktuellen Servers, auf dan sich das DbDirectory-Objekt bezieht. –
Session getParent() throws NotesException
Die aktuelle Domino-Session. – –
Database openDatabase(String dbNameAndPath) throws NotesException Database openDatabase(String dbNameAndPath, boolean failOverInCluster) throws NotesException Liefert die geöffnete Datenbank mit dem relativen Pfad dbNameAndPath,
wobei der Ordner und Dateiname innerhalb des Domino-Dataverzeichnisses angegeben werden kann. Ausser in DIIOP-Sessions kann mit failOverInCluster==true erlaubt werden, bei Nichtverfügbarkeit einer Datenbank innerhalb eines Clusters auf andere Cluster-Server auszuweichen. –
–
Database openDatabaseIfModified(String dbNameAndPath, DateTime datetime) throws NotesException Siehe openDatabase. Die Datenbank wird nur geöffnet, falls sie Dokumente enthält, die nach dem Zeitpunkt datetime geändert wurden. Andernfalls wird null zurückgegeben. Database openMailDatabase() throws NotesException
Über das Personendokument eines Benutzers im Domino-Verzeichnis wird festgelegt, wie die Mail-Datenbank eines Benutzers heißt. Über openMailDatabase() kann diese geöffnet werden und zwar auf demjenigen Server, auf dem sich die Maildatenbank des Benutzers befindet. Dies ist insbesondere bei lokalen Sessions interessant, da so ein direkter Bezug zwischen der lokalen Session und dem „Heim“-Server des Benutzers hergestellt werden kann.
8.3 Service-Anwendungen –
333
Database openDatabaseByReplicaID(String replicaID) throws NotesException
Öffnet eine Datenbank anhand ihrer ReplikaID (s. Kap. 9.1). •
DxlExporter createDxlExporter() throws NotesException DxlImporter createDxlImporter() throws NotesException
Erzeugt einen Dxl Importer bzw. Exporter. Dxl steht für Domino-XML. Diese Methoden sind neu seit R6.5 (s. Kap. 17.7.3). •
Log createLog(String s) throws NotesException
Erzeugt ein Log-Objekt, mit dem innerhalb von Domino ein einfaches Logging ermöglicht wird. Log Informationen können in eine Datei, eine Notes-Datenbank, im File System oder in der Log Property eines Agenten gespeichert werden (s. Kap. 19.6.1). •
Newsletter createNewsletter(DocumentCollection docsToBeMailed) throws NotesException
Ein Newsletter in Notes ist eine einfache Möglichkeit eine Sammlung von Informationen (Dokumente) an einen Mail-Empfänger zu senden. Sie wird automatisiert anhand der übergebenen DocumentCollection erzeugt. Über Document formatMsgWithDoclinks(Database database) wird ein Document erzeugt, das Links auf die Dokumente der DocumentCollection enthält. Dieses kann wie gewohnt über save gespeichert und mit send versendet werden. Document formatDocument(Database database, int indexOfDocumentInCollection) kann verwendet werden, um komplette Abbilder der ein-
zelnen Dokumente im Zieldokument zu erzeugen. Diese Methode muss für jedes einzelne Dokument der Collection angewendet werden. •
Registration createRegistration() throws NotesException Erzeugt ein Registration-Objekt. Ein Registration-Objekt bildet einer-
seits sämtliche Eigenschaften und Methoden ab, die benötigt werden, wenn neue Benutzer registriert werden sollen. Über dieses Objekt können folglich Batchprozesse für die automatisierte Benutzerregistrierung erstellt werden. Gleichzeitig liefert diese Klasse Methoden und Eigenschaften, die sich entweder auf ein ID File oder auf den Eintrag eines Benutzers im Domino-Verzeichnis beziehen. So kann z.B. mit String switchToID(String idFullFilePath, String password) in einer aktiven lokalen Session der aktuelle Benutzer gewechselt werden, indem auf ein neues ID File gewechselt wird. Dies hebt die in Kapitel 4.3 diskutierte Beschränkung auf lediglich den aktuellen Benutzer auf. •
Stream createStream() throws NotesException Seit Version 6 kennt Domino ein eigenes lotus.domino.Stream-Objekt, das allerdings auf den aktuellen NotesThread beschränkt ist und über createStream bezogen werden kann. Es handelt sich hierbei um einen Character-
(readText, writeText) oder Byte-Stream (read, write) und die Klasse kennt folgerichtig ein Characterset, das über getCharset() überprüft werden kann. Anders als z.B. java.io.Reader oder java.io.Writer wird der Stream automatisch intern gepuffert, wie es in java.io.Reader durch mark(int readAheadLimit) erreicht werden könnte.
334
8 Session Der lotus.domino.Stream kann sowohl als Input- als auch als OutputStream genutzt werden. Über void setContents(Reader reader), void setContents(InputStream inputstream), void getContents(Writer writer), void getContents(OutputStream outputstream) können klassische Java Streams mit dem Domino Stream gekoppelt werden. boolean isEOS() zeigt an, ob der Pointer sich am Ende des Streams befindet. setPosition (int) und getPosition können den Pointer innerhalb des Streams
neu positionieren bzw. dessen Position anzeigen. String readText() liest Text als Character Stream, String readText(int readLine) liest Text zeilenweise, wenn readLine==Stream.STMREAD_LINE. String readText(int readLine, int endOfLineChar) liest Text zei-
lenweise mit einem steuerbaren Zeilentrenner. endOfLineChar kann die Werte Stream.EOL_CRLF (ASCII 13 und ASCII 10), Stream.EOL_LF (ASCII 10), Stream.EOL_CR (ASCII 13), Stream.EOL_PLATFORM (plattformspezifisch), Stream.EOL_ANY (ASCII 10, 13 oder 13+10), Stream.EOL_NONE (per Default
wird kein Zeichen als Zeilenende definiert) annehmen. Dementsprechend existieren analog die Methoden writeText. Ein Domino Stream kann als Datei-Stream über boolean open(String filePath) oder boolean open(String filePath, String characterSet) geöffnet werden. Diese Methoden liefern false, falls die Datei nicht gefunden wurde, nicht geöffnet werden konnte, oder das spezifizierte CharacterSet nicht gültig ist, andernfalls true.
8.4 •
Ausführen verschiedener Befehle String sendConsoleCommand(String serverName, String command) throws NotesException
Die Befehle der Dominokonsole können seit Version 6.5 mit dieser Methode programmatisch ausgeführt werden. Der Parameter serverName gibt den Server an, auf dem der Befehl ausgeführt werden soll. Falls der Parameter null ist, wird der aktuelle Computer / Server der Session verwendet. Rückgabewert ist die Rückmeldung des Befehls, wie er in der Konsole erscheinen würde. Wird der Befehl in einem Domino-Agenten eingesetzt, der auf dem Server ausgeführt wird, muss der Signierer im Serverdokument unter „Security“ mit den vollen Rechten „Run unrestricted methods and operations“ ausgestattet sein. Wird der Befehl in einer lokalen Session auf einem Client eingesetzt, muss der Benutzer im Serverdokument mit vollen Konsolen-Rechten ausgestattet, also im Feld „Full Remote Console Administrators“ eingetragen sein. Da Konsolenbefehle eine Verbindung abbrechen können („Drop >“), muss in diesem Fall die Session neu aufgebaut werden. •
Vector evaluate(String atFormula) throws NotesException Vector evaluate(String atFormula, Document workingDocument) throws NotesException
8.4 Ausführen verschiedener Befehle
335
Formeln der @Formelsprache können über die Session evaluiert werden. Wird der optionale Parameter workingDocument angegeben, so können auch @Formeln, die sich auf ein Dokument beziehen, verwendet werden. Da @Formeln Listen zurückgeben können, wird ein Vector zurückgegeben, der optional alle Listenelemente liefert. Da einige @Formeln Bezug auf die UIKlassen (LotusScript) nehmen, können einige der @Formeln nicht angewendet werden. Hierzu gehören: @Command, @DialogBox, @PickList, @PostedCommand oder @Prompt. •
Vector freeTimeSearch(DateRange fromTo, int durationInMinutes, Object nameAsStringOrVector, boolean firstMatchOnly) throws NotesException
Da Domino ein Collaboration Tool ist, bietet es natürlich vorgefertigte Kalenderfunktionen. Über freeTimeSearch kann für einen oder für mehrere Benutzer ein freier Zeitraum in den Kalendern nachgeschlagen werden, der für alle gewünschten Teilnehmer gilt. Der Parameter fromTo gibt den gewünschten Zeitraum an, durationInMinutes die Länge des Zeitfensters, nach dem gesucht werden soll, in Minuten, nameAsStrinOrVector kann ein String oder ein Vector aus Strings sein und gibt einen oder mehrere Benutzer oder Gruppennamen an und firstMachtOnly bestimmt mit dem Wert true, ob lediglich eine oder mehrere Möglichkeiten zurückgegeben werden sollen. •
Base resolve(String uri) throws NotesException String getURL() throws NotesException String getHttpURL() throws NotesException Öffnet ein Domino-Objekt anhand seiner URI, die über getURL bezogen wer-
•
String hashPassword(String stringToBeHashed) throws NotesException boolean verifyPassword(String password, String hashValue) throws NotesException
den kann (s. Kap. 7.1.4 – „Dokumente selektieren“).
Domino bietet über die @Formel die Möglichkeit Hashwerte auf Strings anzuwenden. So werden z.B. Internetpasswörter in Domino in dieser gehashten Form im Personendokument abgespeichert. Der Hashmechanismus kann in zwei verschiedenen Ausführungen verwendet werden. Dies hängt von der Einstellung im Serverdokument unter „Security“ -> „Internet Access“ -> „Internet Authentication“ ab. Seit Version 6.5 kann der Hashmechanismus direkt über hashPassword ausgeführt und über verifyPassword überprüft werden, wobei die Einstellungen im Serverdokument automatisch berücksichtigt werden. •
void recycle() throws NotesException void recycle(java.util.Vector objects) throws NotesException
Wie bereits verschiedentlich herausgestellt, ist es unerlässlich Domino-Java-Objekte nach Gebrauch dem Recycling zuzuführen, damit die dahinter liegenden C++ Objekte aufgeräumt werden. recycle() räumt die aktuelle Session auf, während recycle (Vector) alle über den Vector referenzierten Objekte aufräumt (s. Kap. 14.3.1).
336
8 Session
8.5
Verschiedene Eigenschaften
•
void setEnvironmentVar(String name, Object value) throws NotesException void setEnvironmentVar(String name, Object value, boolean isDominoParameter) throws NotesException String getEnvironmentString(String name) throws NotesException String getEnvironmentString(String name, boolean isDominoParameter) throws NotesException Object getEnvironmentValue(String name) throws NotesException Object getEnvironmentValue(String name, boolean isDominoParameter) throws NotesException
Wenn in Domino von Environment-Variablen die Rede ist, sind Parameter-Einträge in der Datei notes.ini gemeint. Domino unterscheidet zwischen DominoSystemparametern und Anwenderparametern, wobei letztere per Konvention mit einem Dollarzeichen „$“ beginnen. Diese Unterscheidung ist nicht technisch zwingend, aber dringend empfohlen. Per Default werden alle Parameter als Anwenderparameter mit einem führenden Dollarzeichen interpretiert. Über den Parameter isDominoParameter==true kann erzwungen werden, dass dieser weggelassen wird. Gesetzt werden kann eine Environment-Variable als Objekt, wobei die Klassen String, DateTime und Integer zulässig sind. Double oder Float sind ebenfalls zugelassen, werden aber gerundet und in Integer umgewandelt. Parameter können in jedem Fall über getEnvironmentString als String geladen werden. Gültige Einträge vom Typ DateTime oder Integer können über getEnvironmentValue als DateTime- oder Integer-Objekt geladen werden. Die Zuordnung und der Cast erfolgen automatisch. Parameter, die mit isDominoParameter==true gespeichert wurden, können auch nur mit isDominoParameter==true geladen werden und umgekehrt. •
String getSessionToken() throws NotesException String getSessionToken(String s) throws NotesException
Bezug des Anmeldetokens für SSO (s. Kap. 5.3.5f). •
Document getUserPolicySettings(String server, String user, int type) throws NotesException Document getUserPolicySettings(String server, String user, int type, String nameSpace) throws NotesException
8.5 Verschiedene Eigenschaften
337
Seit Version 6 können in Domino weitreichende und differenzierte Policies definiert werden, durch die für verschiedene Bereiche und Prozesse Defaultwerte definiert werden können, die zum Teil entweder einmalig z.B. bei einem Setup zur Anwendung kommen oder als dynamische Grundlage für bestimmte Einstellungen dienen. Policies können vom Typ Session.POLICYSETTINGS_ARCHIVE, Session.POLICYSETTINGS_DESKTOP, Session.POLICYSETTINGS_REGISTRATION, Session.POLICYSETTINGS_SECURITY oder Session. POLICYSETTINGS_SETUP sein. Über getUserPolicySettings können entweder die effektiven Policy Settings für einen bestimmten Benutzer geladen werden oder, falls der Parameter nameSpace zum Einsatz kommt, für einen bestimmten Namespace, wobei in diesem Fall der Parameter user ignoriert wird. Rückgabewert ist ein Document mit der entsprechenden Policy für den spezifizierten type. •
Vector getAddressBooks() throws NotesException Bezug aller für eine aktuelle Session gültigen Adressbücher. Die im Vector zurückgegebenen Database-Objekte sind geschlossen und müssen über open in Database geöffnet werden. Ist die Session lokal, werden alle privaten und öf-
fentlichen Adressbücher des aktuellen Benutzers zurückgegeben, beim Einsatz von DIIOP nur die öffentlichen Adressbücher des Servers. •
String getCommonUserName() throws NotesException
Bezug des Common Name (CN) des Users, der die Session geöffnet hat. •
String getUserName() throws NotesException String toString() Sowohl getUserName als auch toString in Session liefern den vollen Na-
men in der langen kanonischen Form des Users (z.B. CN=User Name/OU=Abteilung/O=Firma/C=Land), der die Session geöffnet hat. •
Name getUserNameObject() throws NotesException
Bezug des Namens des Users, der die Session geöffnet hat als lotus.domino. Name-Objekt. • •
Vector getUserNameList() throws NotesException Benutzername einschließlich alternativer Namen als Vector. Vector getUserGroupNameList() throws NotesException
Neu seit Domino R6. Liste der Gruppen, zu denen der aktuelle Benutzer gehört, einschließlich zusätzlicher, automatisch generierter Einträge für die hierarchischen Organisationen und Unterorganisationen, zu denen ein Benutzer gehört. •
boolean isConvertMIME() throws NotesException void setConvertMIME(boolean flag) throws NotesException
Information, ob RichTextItems für diese Client Session in Mime konvertiert werden. Sollte, nachdem dieser Parameter verändert wurde, nach Gebrauch wieder auf den ursprünglichen Wert zurückgesetzt werden. Default ist true. •
boolean isOnServer() throws NotesException
Information, ob die Session auf einem Server läuft. •
boolean isValid()
Information, ob eine DIIOP-Session noch gültig ist (s. Kap. 5.3 ff), insbesondere 5.3.6.
338 •
8 Session String getNotesVersion() throws NotesException
Benutzte Domino-Version, z.B. „Release 7.0|August 18, 2005“. •
String getPlatform() throws NotesException
Betriebssystem, unter dem die Session betrieben wird, z.B. „UNIX“ oder „Windows/32“. •
String getServerName() throws NotesException
Servername, zu dem die Session eine Verbindung aufgenommen hat. Liefert den leeren String "" für lokale Sessions. •
String getOrgDirectoryPath() throws NotesException
Seit Version 6 ist es in Domino möglich, einen Server in gehostete Organisationen aufzuteilen. Wenn eine Session zu einer gehosteten Organisation gehört, liefert diese Methode den Default-Dateipfad für diese gehostete Umgebung, allerdings ohne führende Dateipfadzeichen, wie den Slash oder Backslash. Ansonsten liefert diese Methode den leeren String.
8.6
Zusammenfassung
Während Kapitel 4 und 5 ausführlich auf die NotesFactory eingingen und die Erzeugung einer Session näher beleuchteten, wurde in diesem Kapitel nun das eigentliche Java-Ojekt Session näher erläutert. Neben der zentralen Funktion, die Verbindung zum Domino-Server herzustellen und als Basis für alle Domino-Anwendungsfälle zu dienen, stellt die Session einige allgemeingültige Objekte bzw. Anwendungen bereit, die außerhalb des Objektkontextes und der üblichen Hierarchie aus Session, Database oder View und Document stehen. Hierzu gehören Datumsobjekte in Form der Klassen DateTime und DateRange, Namensobjekte in Form des Name-Objektes und Stilobjekte als RichTextStyle, RichTextParagraphStyle und Color und nicht zuletzt der Bezug eines International-Objektes. Das Öffnen von Datenbanken gehört ebenso zu den Aufgaben einer Session wie die Bereitstellung eines DbDirectory-Objektes als Abbild des Serververzeichnisses. Verschiedene administrative Anwendungen und Objekte, wie der Bezug eines AdministrationProcess, eines Log oder einer Registration ermöglichen es, über das JavaAPI Prozesse programmatisch zu steuern, während Methoden wie evaluate, zum Evaluieren von @Formeln, oder verifyPassword und freeTimeSearch unmittelbaren Zugriff auf das Collaboration Environment von Domino geben. Die Session ist also zuständig für Prozesse und Eigenschaften und die Steuerung bzw. Bereitstellung der Domino-Infrastruktur.
9
9 Database
In diesem Kapitel: Database API Database-Lifecycle Templating Replikation Service – Compact und Fixup
340
9 Database
Das Database-Objekt in Java kapselt eine Domino-Datenbank, bzw. ein DominoDatenbanktemplate, also eine nsf- oder ntf-Datei. Es dient nicht nur dazu, Datenbanken zu öffnen und handzuhaben, sondern über verschiedene Methoden und Properties können Datebanken geöffnet, durchsucht, erstellt, repliziert, kopiert, gelöscht, verschiedene Parameter und Einstellungen eingestellt und gelesen und schließlich Wartungsarbeiten durchgeführt werden. Geöffnet und erstellt werden können Datenbanken über die Klassen Session, DbDirectory, AgentContext, Database und über die getParent- bzw. getParentDatabase-Methoden der meisten Domino-Java-Objekte. Alle übrigen Funktionen werden direkt über Database ermöglicht (s. Tabelle 6-1). In Session können Datenbanken über getDatabase oder resolve geöffnet werden und spezielle Datenbanken über getURLDatabase und getAddressBooks geladen werden (s. Kap. 8). In DbDirectory können neue Datenbanken über createDatabase erstellt werden. Außerdem ist diese Klasse geeignet, Datenbanken über getFirstDatabase und getNextDatabase zu öffnen, für die zwar der Server, aber nicht deren Name bekannt ist. openDatabase, openDatabaseByReplicaID und openDatabaseIfModified ermöglichen das Öffnen von Datenbanken, deren Name, bzw. deren Replik ID bekannt ist, und openMailDatabase öffnet schließlich als spezielles Datenbankobjekt die Mail-Datenbank des Benutzers. Schließlich gibt es noch die Möglichkeit, im Falle, dass ein AgentContext zur Verfügung steht, die aktuelle Datenbank, in der sich der Agent befindet, zu dem der AgentContext gehört, über getCurrentDatabase in AgentContext zu öffnen.
9.1
Datenbanken öffnen, schliessen, replizieren und löschen
In Database selbst stehen folgende Methoden zum Öffnen, Erstellen, Kopieren, Löschen und zur Replikation zur Verfügung: •
boolean open() throws NotesException boolean isOpen() throws NotesException
Beim Öffnen von Datenbanken muss zwischen zwei Vorgängen unterschieden werden. Zum einen kann über verschiedene Methoden (z.B. über openByReplicaID) ein Datenbankobjekt bezogen werden. Um die Datenbank für die Verwendung zu öffnen, um den Zugriff auf die enthaltenen Dokumente und Designelemente zu ermöglichen, muss diese über open geöffnet werden. Die meisten Methoden zum Bezug des Datenbankobjekts öffnen die Datenbank implizit. Über isOpen kann überprüft werden, ob die Datenbank geöffnet ist. Ist sie das nicht, können nur einige wenige Eigenschaften abgefragt werden, wie z.B. der Datenbanktitel. getFirstDatabase und getNextDatabase in DbDirecotry sind Methoden, die zwar das Datenbankobjekt zurückliefern, aber die Datenbank nicht öffnen.
9.1 Datenbanken öffnen, schliessen, replizieren und löschen
341
Best Practice: Überprüfen Sie beim Öffnen von Datenbanken Replik IDs und Datenbanknamen auf Gültigkeit und verhindern Sie den Einsatz von Null-Werten oder leeren Strings. Je nach Konstellation zeigen die verschiedenen Methoden Performance-Vor- und Nachteile. Haben Sie hier den Verdacht eines Performance Leaks, überprüfen Sie die Zeiten für das Öffnen von Datenbanken und halten Sie geöffnete Datenbanken möglichst zentral vor.
Hat ein Benutzer zum Beispiel über die ACL keine Zugriffsberechtigung auf eine Datenbank, so kann er zwar mit getFirstDatabase und getNextDatabase eine solche Datenbank als (geschlossenes) Datenbankobjekt referenzieren und z.B. über getTitle() den Titel der Datenbank anzeigen, erhält jedoch eine NotesException 4060 „User CN=UserName/O=Organisation cannot open database relative\path\to\myDatabase.nsf“, wenn er versucht über open() die Datenbank zu öffnen. Dies ermöglicht Anwendungen, die zwar das Directory eines Domino-Servers anzeigen und die vorhandenen Datenbanken auflistet, ohne dabei die ACL zu unterlaufen. •
boolean openByReplicaID(String serverName, String replicaID) throws NotesException58
Neu seit Domino R6.5. Öffnet die Datenbank mit der Replik ID replicaID auf dem Server serverName. Wird für den Parameter serverName null übergeben, wird die Replik auf dem lokalen Server gesucht. Der Rückgabewert true zeigt an, dass die Datenbank gefunden und geöffnet werden konnte. •
boolean openIfModified(String serverName, String dbNameAndPath, DateTime modifiedSince) throws NotesException Neu seit Domino R6.5. Öffnet eine Datenbank mit Namen dbNameAndPath (einschließlich Pfadangabe) auf dem Server serverName, sofern diese Datenbank Dokumente enthält, die nach dem Zeitpunkt modifiedSince verändert wurden. Liefert true, wenn die Datenbank solche geänderten Dokumente ent-
•
boolean openWithFailover(String serverName, String dbNameAndPath) throws NotesException
hält und erfolgreich geöffnet werden konnte.
58
Bei allen Methoden, die die Angabe eines Servers ermöglichen, gilt, dass entweder ein expliziter Servername genannt werden kann oder der leere String oder der String null angegeben werden kann, um den aktuellen Server zu spezifizieren. Bei DIIOP-Verbindungen ist zu beachten, dass diese Verbindungen keine Verbindung zu dritten Servern aufnehmen dürfen, solange keine Vertrauensstellungen zwischen diesen Servern (seit Version 6) hergestellt wurden. In diesem Fall muss entweder für den Parameter serverName der exakte Name des aktuellen Servers (z.B. über getServerName() in Session) angegeben werden, oder (String) null oder der leere String "".
342
9 Database
Warnung Vermeiden Sie es, Methoden auf Datenbankobjekte anzuwenden, die z.B. auf eine nicht existente Datenbank verweisen, oder Methoden, die eine geöffnete Datenbank erfordern, auf ein geschlossenes Datenbankobjekt anzuwenden. In der Regel werden NotesExceptions auftreten, unerwartete Ergebnisse bis hin zu Abstürzen im Einzelfall sind nicht ausgeschlossen und wurden vereinzelt beobachtet.
Neu seit Domino R6.5. Öffnet eine Datenbank auf dem Server serverName mit Name und Pfad dbNameAndPath. Befindet sich die Datenbank in einem Cluster und konnte die Datenbank auf dem bezeichneten Server nicht geöffnet werden, versucht Domino eine Replik der Datenbank auf einem anderen Server des Clusters zu öffnen. Liegt ein Cluster vor, wurde eine solche Datenbank gefunden, und konnte diese erfolgreich geöffnet werden, wird true zurückgegeben. Gleichzeitig wird die Änderung des Servernamens im zugrunde liegenden Database-Objekt adäquat angepasst. Befindet sich die Datenbank nicht in einem Cluster und konnte sie auf dem durch serverName spezifizierten Server geöffnet werden, wird diese Datenbank im Database-Objekt geöffnet. Da zur Verwendung dieser Methode ein Datenbankobjekt benötigt wird, bevor eine Datenbank geöffnet wird, kann hierfür über getDatabase (null, null) in Session ein leeres Datenbankobjekt instanziert werden. Dies gilt nur für lokale Sessions, die über NotesThread erzeugt wurden, nicht für DIIOPSessions. •
Database createCopy(String serverName, String dbNameAndPath) throws NotesException Database createCopy(String serverName, String dbNameAndPath, int maxSizeGig) throws NotesException
Erstellt eine Kopie der durch das aktuelle Datenbankobjekt repräsentierten Datenbank auf dem Server serverName mit (Datei-) Namen dbNameAndPath. Beim Kopieren werden keine Dokumente, sondern lediglich das Design der Datenbank kopiert, einschließlich der ACL der Datenbank, die als exakte Kopie angelegt wird. Der Parameter maxSizeGig gibt für Datenbanken, die noch im Dateiformat von Domino Version 4 gespeichert sind, die maximale Größe in Gigabyte an. Dieser Parameter kann die Werte 1..4 annehmen. Eine Kopie einer Datenbank ist keine Replik, sondern eine neue unabhängige Datenbank. Eine nachträgliche Replikation der Daten zwischen dem Original und der Kopie ist nicht möglich. Hierfür muss eine Replik mit createReplica erstellt werden. •
Database createFromTemplate(String serverName, String newDbNameAndPath, boolean inheritInFuture) throws NotesException Database createFromTemplate(String serverName, String newDbNameAndPath, boolean inheritInFuture, int maxSizeGig) throws NotesException
9.1 Datenbanken öffnen, schliessen, replizieren und löschen
343
String getDesignTemplateName() throws NotesException
Erzeugt aus einer vorhandenen Datenbankschablone eine neue Datenbank. Basis ist das aktuelle Datenbank-Objekt, das createFromTemplate aufruft. Erzeugt wird die Datenbank auf dem spezifizierten Server und Dateinamen. Der Templatingmechanismus in Domino sieht vor, dass eine Datenbank auf Basis einer Vorlage erzeugt wird. Beim ersten Erzeugen gelten einige Regeln für die Übernahme der ACL. Ist der Parameter inheritInFuture auf true gesetzt, wird der Name des Templates (ein vom Datenbankname unabhängiger Name des Templates) hinterlegt und es kann zukünftig für eine Datenbank eine Aktualisierung der Gestaltung von einem Template dieses Templatenamens angefordert werden59. Handelt es sich bei dem Datenbankobjekt um ein Template, so liefert getDesignTemplateName diesen Templatenamen. Für die Übernahme der ACL gilt: Handelt es sich bei dem Template, das von dem aktuellen Datenbankobjekt repräsentiert wird, um eine NTF-Datei und ist die Eigenschaft „Database file is a Master Template“ gesetzt, so gilt für die ACL: Alle in eckige Klammern gesetzten Einträge werden als gültige Einträge ohne Klammern aufgenommen (z.B. [Benutzer/Organisation] wird als Benutzer/Organisation in die ACL aufgenommen). Dieser Mechanismus dient dazu, bestimmte Einträge in der ACL als Templates zu hinterlegen. Ist das Template eine NSF-Datenbank oder ist die Eigenschaft nicht gesetzt, so wird die ACL kopiert. In jedem Fall gilt: Wird die neue Datenbank auf einem Server neu angelegt, so wird dieser Server als Manager und als Administrationsserver in der ACL eingetragen. Der aktuelle Benutzer wird als Manager in der ACL eingetragen. Der Parameter maxSizeGig gibt für Datenbanken, die noch im Dateiformat von Domino Version 4 gespeichert sind, die maximale Größe in Gigabyte an. Dieser Parameter kann die Werte 1..4 annehmen. •
Database createReplica(String serverName, String newDbNameAndPath) throws NotesException
Erzeugt eine neue Replik der Datenbank auf dem spezifizierten Server und mit dem spezifizierten Namen. •
boolean replicate(String serverName) throws NotesException Stößt den Replikationsvorgang mit dem Server serverName an unter Beibehaltung der Replikationsregeln, die unter getReplicationInfo gespeichert sind.
59
Über den Menubefehl „File“ -> „Database“ -> „Refresh Design“ kann dieses Update der Gestaltung angefordert werden. Hierfür muss ein Server angegeben werden, von dem das Template dieses Namens angefordert werden soll. Domino kenn zusätzlich den so genannten Design Task, der diese Updates automatisch und regelmäßig durchführt. Hierbei ist zu beachten, dass dieser Automatismus unter Umständen für einzelne Datenbanken nicht erwünscht ist, bzw. zu unerwarteten Nebenwirkungen führen kann, wenn auf dem aktuellen Server ein zugehöriges Template zur Verfügung steht. Unerwünschte Effekte treten vor allem auf, wenn die Gestaltung einer Datenbank manuell aktualisiert wird und auf dem Server ein veraltetes Template vorliegt, so dass dann bei der Ausführung des Design Tasks die alte Gestaltung wiederhergestellt wird. Der Design Task kann über die Datei notes.ini im Feld TASKS durch Entfernen des Eintrags DESIGN abgeschaltet werden.
344 •
9 Database Replication getReplicationInfo() throws NotesException
Liefert die Replikationsregeln für die aktuelle Datenbank. Diese können über Re-
•
plicationEntry getEntry(String sourceComputer, String targetComputer) throws NotesException und ReplicationEntry getEntry(String sourceComputer, String targetComputer, boolean create) throws NotesException erzeugt bzw. gelesen werden. void remove() throws NotesException
Löscht eine Datenbank endgültig aus dem Dateisystem. Nicht zu verwechseln mit recycle. Eine Datenbank kann nicht gelöscht werden, wenn sie in Benutzung ist. •
void markForDelete() throws NotesException boolean isPendingDelete() throws NotesException
Neu seit Domino R6.5. Löscht eine Replik innerhalb eines Clusters. Die Löschung wird vom Cluster Manager durchgeführt. Wurde einmal die Eigenschaft markForDelete gesetzt, kann dies nicht rückgängig gemacht werden. Bevor der Cluster Manager die Datenbank löscht, wird dieser noch eine Replikation mit einer anderen Replik im Cluster durchführen, sofern eine vorliegt. isPendingDelete zeigt an, ob eine Markierung durch markForDelete vorliegt.
9.1.1
Vergleich – Methoden zum Öffnen einer Datenbank
Die häufigsten Methoden zum Öffnen einer Datenbank sind in der Praxis die Methode openDatabaseByReplicaID in DbDirectory und getDatabase in Session. Der entscheidende Unterschied zwischen diesen beiden Methoden ist die Verwendung des (vollständigen, relativen) Dateipfades im einen und der Replik ID im anderen Fall. Beide Methoden haben ihre Vor- und Nachteile. Während für getDatabase immer der relative Dateipfad innerhalb des Domino-Dataverzeichnisses vollständig bekannt sein muss, es aber möglich ist, sofern serverübergreifend dieselbe Ordnerstruktur beibehalten wird, einmalig einen Schlüssel (den Dateinamen) für eine Datenbank zu vergeben, der über verschiedene Installationen beibehalten werden kann, muss openDatabaseByReplicaID die Replik ID bekannt sein, die sich mit jeder Installation ändern wird. Stellt man Performance-Untersuchungen an, stellt man fest, dass getDatabase bei der Verwendung von NotesThread nicht sehr schnell arbeitet. openDatabaseByReplikID ist hier viel schneller. Allerdings wird für die Instanzierung von DbDirectory so viel Zeit benötigt, dass der Vorteil verloren geht und erst beim Öffnen von sehr vielen Datenbanken auf Basis von einem DbDirectory-Objekt zum Tragen kommt. Bei der Verwendung von DIIOP dreht sich das Verhältnis zu Gunsten von getDatabase (s. Tabelle 9-1). Beachten Sie bei Verwendung aller Methoden zum Öffnen von Datenbanken, dass in keinem Fall null-Parameter als Replik ID oder Datenbankname übergeben werden. In vielen Versionen von Domino wurden in unterschiedlichen Konstallationen bei der Verwendung von null-Parametern leider immer wieder Probleme bis hin zu Serverabstürzen beobachtet. Hier ist es sinnvoll für jedes Öffnen einer Daten-
9.2 Datenbanken, Dokumente und Document Collection
345
Tabelle 9-1 openDatabaseByReplicaID und getDatabase
Schlüssel, anhand dessen die Datenbank geöffnet wird Performance bei Verwendung von NotesThread
Performance bei Verwendung von DIIOP
Robustheit
Recycling (s. Kap. 14.3.2)
openDatabaseByReplicaID
getDatabase
Replik ID – Diese muss bekannt sein, wenn nicht das gesamte DbDirectory durchsucht werden soll. Ist die Replik ID bekannt, erheblich schneller als getDatabase. Allerdings wird der Vorteil beim Öffnen weniger Datenbanken oder nur einer durch den Zeitverbrauch für die Instanzierung von DbDirectory zu Gunsten von getDatabase umgekehrt. Deutlich langsamer als bei der Verwendung von NotesThread.
Vollständiger Dateipfad relativ zum Data Directory.
Reagiert in einigen Domino-Versionen empfindlich auf null- bzw. ""Parameter für die ReplikID. Jedes Objekt muss recycelt werden
Insgesamt gute Performance, da zwar getDatabase langsamer als openDatabaseByReplicaID ist, aber kein DbDirectory instanziert werden muss. Erheblich schneller als bei der Verwendung von NotesThread. Schneller als openDatabaseByReplicaID. Unkritisch.
Identische Datenbanken werden durch identische Objekte repräsentiert und müssen nur einmal recycelt werden.
bank, egal für welche Methode Sie sich entscheiden, sicherzustellen, dass null-Werte und leere Strings als Parameter vermieden werden. Es kann sinnvoll sein, ein eigenes Wrapper-Objekt einzusetzen, das sich ums Öffnen und Bereitstellen von Datenbanken kümmert und dabei gleich unzulässige Replik IDs oder Dateinamen identifiziert.
9.2
Datenbanken, Dokumente und Document Collection
Die Methoden in Database zur Behandlung von Dokumenten und DocumentCollections wurden bereits in vorherigen Kapiteln behandelt. getAllDocuments() liefert alle Dokumente einer Datenbank (s. Kap. 7.1.4.3), createDocument, getDocumentByID, getDocumentByUNID und getDocumentByURL wurden in Kapitel 7.1.4.1 erläutert. Informationen zu getProfileDocCollection und getProfileDocument finden Sie im Kapitel 7.2 über Profildokumente. Bleibt in diesem Zusammenhang noch die Methode NoteCollection createNoteCollection(boolean selectAll) zu erwähnen, mit der Sie eine NoteCollection (s. Kap. 17.7.1) aus der Datenbank erstellen können. Dieses Objekt wird dazu verwendet aus einer Datenbank alle oder einzelne Domino-Notes, also z.B. Gestaltungselemente oder Dokumente, zu selektieren. Üblicherweise wird eine leere NoteCollection mit createNoteCollection (false) erstellt, dann wird über setSelectDocuments, setSelectForms, setSelectFiews usw. festgelegt, wel-
346
9 Database
che Elemente zur Collection gehören sollen und dann wird diese NoteCollection mit buildCollection in NoteCollection aufgebaut. Die Methode createNoteCollection und die Klasse NoteCollection sind neu seit Version 6.5.
9.3
Allgemeine Datenbankeigenschaften
Im Folgenden finden sich setter- und getter-Methoden in Database für verschiedene Datenbankeigenschaften. • •
DateTime getCreated() throws NotesException DateTime-Ojekt mit dem Erstelldatum und -uhrzeit der Datenbank. DateTime getLastModified() throws NotesException DateTime-Ojekt mit dem letzten Änderungsdatum und -uhrzeit der Daten-
bank. •
String getReplicaID() throws NotesException
Die Replik ID der Datenbank. Diese 16-stellige Hexadezimalzahl wird als String zurückgegeben. Die Datenbank muss nicht geöffnet sein, um die Replik ID auszulesen. •
String getServer() throws NotesException
Der Servername des Servers, auf dem sich die Datenbank befindet, in der hierarchischen Langform (z.B. CN=Java/O=DJBUCH) aus. Die Datenbank muss nicht geöffnet sein, um den Servernamen auszulesen. •
String getTemplateName() throws NotesException
Der Templatename, unter dem eine Datenbank als Template geführt wird, wenn die Eigenschaft „Database file is a master template“ gesetzt ist, andernfalls der leere String. Die Datenbank muss nicht geöffnet sein, um den Templatenamen auszulesen. •
String getFilePath() throws NotesException
Liefert den vollen oder relativen Dateipfad einschließlich Dateiname der Datenbank. Liegt die Datenbank lokal, wird der vollständige Dateipfad (z.B. c:\lotus\ notes\data\djbuch\djbuch.nsf) ausgegeben, liegt sie auf einem Server oder wird sie über eine Remote-Session referenziert, wird der relative Pfad zum Dataverzeichnis (z.B. djbuch\djbuch.nsf) ausgegeben. Wurde die Datenbank auf Basis eines Verzeichnis- oder Datenbanklinks60 geöffnet, wird, im Falle einer lokalen Session, diese Methode den tatsächlichen Dateipfad der Datenbank liefern, wenn die Datenbank, auf die der Link verweist, ebenfalls lokal liegt und den Link-Pfad, wenn die Datenbank auf einem Server liegt. 60
Im Domino Administrator können für ganze Verzeichnisse oder für einzelne Ordner virtuelle Namen eingerichtet werden, die dann auf tatsächliche Ordner oder Dateien im Filesystem verweisen. Solche Ordner- oder Dateinamen können dann über die Methoden, die den Pfad einer Datenbank verlangen, verwendet werden. Solche Links können im Domino Administrator im Bereich „Files“ über den Menübefehl „Files“->„New“->„Link“ erstellt werden.
9.3 Allgemeine Datenbankeigenschaften
347
Falls die Session auf dem Server ausgeführt wird (DIIOP), so wird in jedem Fall der volle tatsächliche Dateipfad der Datenbank zurückgegeben, wenn es sich bei der Datenbank um einen Link handelt. Die Datenbank muss nicht geöffnet sein, um den FilePath auszulesen. Allerdings ist zu beachten, dass ein Database-Objekt einer nicht existenten Datenbank durchaus mit getDatabase (>, ) geöffnet werden kann. Dann liefert getFilePath diesen im Parameter übergebenen, nicht existenten DateiPfad, ohne eine Exception zu werfen. Anhand von isOpen () lässt sich aber feststellen, dass das Öffnen nicht erfolgreich war. •
String getFileName() throws NotesException
Liefert den Dateinamen einer Datenbank, jedoch ohne Pfadangabe. Wird die Methode in einer lokalen Session aufgerufen und der FileName einer Datenbank, die über einen Datenbanklink geöffnet wurde, erfragt, so liefert sie den Namen des Links, wenn die Datenbank auf dem Server liegt und den tatsächlichen Dateinamen, wenn die verlinkte Datei lokal liegt. Die Datenbank muss nicht geöffnet sein, um den FileName auszulesen. Auch hier gilt, dass auch der FileName einer nicht existierenden Datenbank angezeigt werden kann. Eine ausführliche Auflistung befindet sich in Tabelle 9-2 •
boolean isClusterReplication() throws NotesException
Neu seit Domino R6. Zeigt an, ob die Cluster-Replikation für diese Datenbank aktiviert ist. •
Session getParent() throws NotesException
Liefert die Session, unter der die Datenbank geöffnet wurde. •
void setOption(int option, boolean switch) throws NotesException
Neu seit Domino R6.5. Für jede Datenbank können über das Kontextmenü erweiterte Eigenschaften eingestellt werden. Diese können seit Version 6.5 mit setOption programmatisch geändert werden. Über den Parameter option können die Eigenschaften anhand der Konstanten in Database wie folgt über switch ein- und ausgeschaltet werden. Für switch == true bedeutet dies im Einzelnen: Database.DBOPT_NOUNREAD – Es werden keine Ungelesenkennzeichen in
der Datenbank verwaltet. Diese Option kann sinnvoll sein, um die Geschwindigkeit zu optimieren. Wenn diese Option verändert wird, muss die Datenbank komprimiert werden. Siehe Database.compact(). Database.DBOPT_OPTIMIZATION – Durch diese Option werden interne Tabellen, in denen Referenzen zwischen Dokumenten, Ansichten und Masken geführt werden optimiert. Vorteile hat dies nur für Ansichten mit Select-Formeln, die „Form=...“ enthalten, und insbesondere für kleine Ansichten in großen Datenbanken. Wenn diese Option verändert wird, muss die Datenbank komprimiert werden. Siehe Database. compact().
9 Database
348
Tabelle 9-2 getFileName und getFilePath Session
Datenbank
Anzeige durch getFilePath
lokal lokal lokal lokal remote remote
lokal, Link lokal auf Server, Link auf Server auf Server, Link auf Server
vollständiger tatsächlicher Dateipfad vollständiger tatsächlicher Dateipfad Linkbezeichnung als relative Pfadangabe relative Pfadangabe vollständiger tatsächlicher Dateipfad relative Pfadangabe
Session
Datenbank
Anzeige durch getFileName
lokal lokal lokal lokal remote remote
lokal, Link lokal auf Server, Link auf Server auf Server, Link auf Server
tatsächlicher Name der Datei tatsächlicher Name der Datei Name des Dateilinks Name des Datei tatsächlicher Name der Datei tatsächlicher Name der Datei
Database.DBOPT_NOOVERWRITE – Verhindert das Überschreiben von lee-
rem Speicherplatz innerhalb einer Datenbank. Database.DBOPT_MAINTAINLASTACCESSED – Aktiviert die Verwendung
der LastAccessed-Eigenschaft von Dokumenten (s. Kap. 7.6). Database.DBOPT_NOTRANSACTIONLOGGING – Deaktiviert das Transakti-
onslog. Database.DBOPT_SOFTDELETE – Aktiviert die Möglichkeit der Softdele-
tion (s. Kap. 7.1.4.6). Database.DBOPT_NORESPONSEINFO – Verhindert die Speicherung interner Informationen, die von den @Funktionen @AllChildren und @AllDescendants benötigt werden. Werden diese Funktionen nicht benötigt (sie finden z.B. in SELECT-Formeln von Ansichten Verwen-
dung), so kann durch Setzen dieses Flags eine deutliche Performanceverbesserung erreicht werden. Wenn diese Option verändert wird, muss die Datenbank komprimiert werden. Siehe Database.compact(). Database.DBOPT_NOHEADLINEMONITORS – Verhindert die Verwendung von Datenbankabonnements, mit deren Hilfe Benutzer bestimmte Datenbanken auf neue Einträge überwachen lassen können. Database.DBOPT_MOREFIELDS – Erlaubt die Verwendung von mehr Feldern pro Datenbank. Statt des sonst gültigen Limits von 64 KByte für die Gesamtlänge aller Feldnamen wird durch Setzen dieser Option ermöglicht, bis zu 23.000 Feldnamen in einer Datenbank zu speichern. Database.DBOPT_LZCOMPRESSION – Aktiviert die Verwendung von LZ1Kompression von Anhängen für die Datenbank.
9.3 Allgemeine Datenbankeigenschaften
349
Database.DBOPT_LZ1 – Aktiviert die Verwendung von LZ1-Kompression
von Anhängen für die Datenbank. Database.DBOPT_REPLICATEUNREADMARKSTOCLUSTER – Aktiviert die
Replikation von Ungelesenkennzeichen innerhalb der Cluster-Replikation. Wird diese Option auf true oder false gesetzt, wird DBOPT_REPLICATEUNREADMARKSTOANY auf false gesetzt. Database.DBOPT_REPLICATEUNREADMARKSTOANY – Aktiviert die Replikation von Ungelesenkennzeichen auf alle Server. Wird diese Option auf true gesetzt, wird DBOPT_REPLICATEUNREADMARKSTOCLUSTER auf false gesetzt. •
boolean getOption(int option) throws NotesException
Liest die durch option spezifizierte Eigenschaft. •
double getPercentUsed() throws NotesException
Liefert den Prozentteil, der innerhalb des Datenbankfiles genutzt wird. Durch die Methode compact in Database (s. Kap. 9.8) oder den Konsolenbefehl load compact kann eine Kompression der Datenbank durchgeführt werden. Durch die Option -B kann eine Dateigrößenreduktion erreicht werden. •
double getSize() throws NotesException
Dateigröße der Datenbank im Dateisystem in Byte. •
int getSizeQuota() throws NotesException
Für Datenbanken können Quota-Angaben gemacht werden, durch die die Maximalgröße limitiert wird. Die Datenbank muss nicht geöffnet sein, um diesen Wert abzufragen. •
void setSizeQuota(int quotaInKB) throws NotesException
Setzt das Datenbank-Quota. Angegeben wird das Quota in KiloByte. Wird das Quota überschritten, wird beim Schreiben von Dokumenten eine NotesException „NotesException: Notes error: Unable to write to database because database would exceed its disk quota.“ mit ID=4000 geworfen. Wird ein Quota neu gesetzt, so setzt sich dieses nicht sofort am Server durch. Da Domino die Daten beim Speichern innerhalb des NSF Files optimiert und zum Teil auch komprimiert, besteht immer eine gewisse Diskrepanz zwischen dem gesetzten Wert und dem Moment, zu dem die Exception geworfen wird. •
long getSizeWarning() throws NotesException void setSizeWarning(int i) throws NotesException Der Parameter sizeWarning korrespondiert mit dem Datei-Quota. Sie sollte
niedriger als das Quota gesetzt werden. Ein Benutzer wird gewarnt, wenn er diesen Wert überschreitet. Speichern ist jedoch weiterhin möglich. Für die Benutzung von getSizeWarning muss die Datenbank nicht geöffnet sein. •
long getMaxSize() throws NotesException
Das Datei-Quota ist nicht zu verwechseln mit der Maximalgröße einer Datenbank. Diese Größe kann nicht überschritten werden. Sie ist nur für Datenbanken, die noch im Dateiformat von Domino R4 gespeichert wurden, gültig und kann nur über den Domino Administrator geändert werden. •
void setTitle(String s) throws NotesException String getTitle() throws NotesException
350
9 Database Setzt und liest den Titel der Datenbank. Nicht zu verwechseln mit dem Dateinamen. Die Datenbank muss für die Benutzung von getTitle nicht geöffnet sein.
•
int getType() throws NotesException
Neu seit Domino R 6.5. Liefert den Typus einer Datenbank. Hier wird angezeigt, ob es sich um einen besonderen Datenbanktypus handelt. Zurückgegeben wird eine der Konstanten aus Database:
•
DBTYPE_WEB_APP, DBTYPE_MAILFILE, DBTYPE_MAILBOX, DBTYPE_SUBSCRIPTIONS, DBTYPE_NEWS_SVR_PROXY, DBTYPE_IMAP_SVR_PROXY, DBTYPE_PORTFOLIO, DBTYPE_MULTIDB_SRCH, DBTYPE_LIGHT_ADDR_BOOK, DBTYPE_ADDR_BOOK, DBTYPE_PERS_JOURNAL, DBTYPE_LIBRARY oder DBTYPE_STANDARD. int getFileFormat() throws NotesException
Liefert das Format der OnDiskStructure der NSF-Dateien. So wird z.B. für eine R6.x Datenbank 43 zurückgegeben. •
boolean isLink() throws NotesException Liefert true, sofern die Datenbank auf Basis eines Verzeichnis- oder Datenbank-
•
boolean isPublicAddressBook() throws NotesException Liefert true, sofern es sich bei der Datenbank um das Domino-Verzeichnis des
links geöffnet wurde.
Servers handelt (auch Domino-Adressbuch oder Domino Directory genannt). In der Regel ist dies die Datenbank names.nsf auf einem Server. •
boolean isPrivateAddressBook() throws NotesException Liefert true, sofern es sich bei der Datenbank um ein persönliches Adressbuch
handelt. In der Regel ist dies die Datenbank names.nsf auf einem Client-Computer. •
boolean isInService() throws NotesException void setInService(boolean flag) throws NotesException
Neu seit Domino 6.5. Zeigt an, ob eine Datenbank für einen Cluster zur Verfügung steht. •
String getURL() throws NotesException String getNotesURL() throws NotesException String getHttpURL() throws NotesException
Diese Methoden arbeiten analog zu den gleichnamigen Methoden in Document (s. Kap. 7.1.4.2).
9.3.1 •
Weitere Datenbankeigenschaften
boolean isDB2() throws NotesException
Neu seit Domino R7. Zeigt an, ob eine Datenbank im NSFDB2-Format vorliegt61 (s. Kap. 13.4). 61
In der zum Zeitpunkt der Drucklegung vorliegenden Version R7.0 waren für diese Methode bisher nur die lokalen Methoden aus lotus.domino.local.Database implementiert und eine Benutzung über DIIOP nicht möglich.
9.3.1 Weitere Datenbankeigenschaften
351
Falls eine Datenbank in diesem Format vorliegt, jedoch DB2 nicht erreichbar oder aus sonstigen Gründen nicht verfügbar ist, liefert diese Methode ebenfalls false. Nur wenn die Datenbank über NSFDB2 erfolgreich geöffnet werden kann, liefert isDB2 true. •
String getDB2Schema() throws NotesException
Neu seit Domino R7. Gibt das DB2-Schema62 einer Datenbank im NSFDB2-Format zurück. Dies ist der im Domino Designer als „db2 groups“ bezeichnete Schemaname (z.B. „GRP1“). •
double getLimitRevisions() throws NotesException void setLimitRevisions(double maxEntries) throws NotesException
In Domino-Dokumenten werden im Item $Revisions Datumsangaben der Änderungszeitpunkte gespeichert. Durch Setzen dieses Parameters kann seit Domino R6.5 die Anzahl auf 0 bis 2147483647 (Integer.MAX_VALUE) Werte limitiert werden. •
double getLimitUpdatedBy() throws NotesException void setLimitUpdatedBy(double maxEntries) throws NotesException
In Domino-Dokumenten werden im Item $UpdatedBy die Namen der Personen, die ein Dokument geändert haben, in der hierarchischen Langform (CN=Vorname Nachname/O=DJBUCH) gespeichert. Dieses Item wird erst beim zweiten Speichern zum ersten Mal gesetzt. Während $Revisions bei jedem Speichern gesetzt wird, wird $UpdatedBy nur gesetzt, wenn zwischenzeitlich ein anderer Benutzer das Dokument speichert. Durch Setzen dieses Parameters kann seit Domino R6.5 die Anzahl auf 0 bis 2147483647 (Integer.MAX_VALUE) Werte limitiert werden. •
int getUndeleteExpireTime() throws NotesException void setUndeleteExpireTime(int timeInHours) throws NotesException
Werden in einer Datenbank SoftDeletions zugelassen (s. Kap. 7.1.4.6), so kann über diesen Parameter die Zeit in Stunden angegeben werden, nach der die Löschung eines Dokuments nicht mehr rückgängig gemacht werden kann. •
•
String getURLHeaderInfo(String url, String headerName, String userName, String userPassword, String proxyUser, String proxyPassword) throws NotesException Liest einen HTTP-Header aus der unter der URL url gelieferten Webadresse. Vergleiche getDocumentByURL in Kapitel 7.1.4.2. boolean isDelayUpdates() throws NotesException void setDelayUpdates(boolean flag) throws NotesException
Wird diese Eigenschaft für eine Datenbank gesetzt, so werden Speicher- und Löschoperationen für Dokumente nicht sofort, sondern mit einem Delay, sofern Domino dies günstig erscheint, durchgeführt. Dies kann zu Performanceverbesserungen führen, birgt jedoch das Risiko, dass im Falle eines Crashs Daten verlorengehen. 62
dito
352
9 Database
9.4
Suche und Catalog
In Domino stehen verschiedene Suchkonzepte zur Verfügung. Diese werden in Kapitel 15 ausführlich vorgestellt. Neben der Suche in Voltextindizes oder Ansichten in Datenbanken stellt Domino verschiedene Verzeichnisdienste und Volltextindizes über mehrere Datenbanken zur globalen Suche (DomainSearch, seit Domino R5) zur Verfügung. Hierfür werden verschiedene Methoden in Database zur Verfügung gestellt, die im Folgenden kurz vorgestellt werden. Insbesondere die Verzeichnisdienste und die Domainsearch erfordern insbesondere administrative Tätigkeiten, deren Beschreibung nicht Ziel dieses Buches ist. Daher sind diese Methoden aus Database hier lediglich kurz erläutert. •
DocumentCollection FTSearch(String searchQuery) throws NotesException DocumentCollection FTSearch(String searchQuery, int max) throws NotesException DocumentCollection FTSearch(String searchQuery, int max, int options, int otherOptions) throws NotesException DocumentCollection FTSearchRange(String searchQuery, int max, int options, int otherOptions, int start) throws NotesException Mit der Methode FTSearch können Datenbanken mit einer Volltextsuche
durchsucht werden. Die Suche findet über alle Dokumente, unabhängig von einzelnen Items statt. Sie kann über eine bestimmte Syntax der searchQuery und verschiedene Suchparameter gesteuert werden. Näheres hierzu in Kapitel 15. FTSearchRange ist neu seit Domino R6. •
DocumentCollection search(String searchQuery) throws NotesException DocumentCollection search(String searchQuery, DateTime cuttOfDateTime) throws NotesException DocumentCollection search(String searchQuery, DateTime cuttOfDateTime, int maxResults) throws NotesException Neben Unterschieden in der Syntax für die searchQuery unterscheiden sich FTSearch und search in der Herangehensweise. Während FTSearch eine Volltextsuche über den Datenbankindex durchführt, ist search vergleichbar mit den SELECT-Formeln, die in Views verwendet werden. FTSearch kennt typi-
sche Funktionen, die aus verschiedenen Suchmaschinen bekannt sind, wie Wortbeugungen oder Fuzzysearch, während für search die @Formelsprache zum Einsatz kommt. Näheres hierzu in Kapitel 15. •
void createFTIndex(int options, boolean createFromScratch) throws NotesException void removeFTIndex() throws NotesException void updateFTIndex(boolean createOnFail) throws NotesException
9.4 Suche und Catalog
353
Damit eine Volltextsuche effizient arbeiten kann, muss ein Datenbankindex erzeugt werden. Dies kann mit der Methode createFTIndex erreicht werden. Mögliche Optionen werden durch folgende Konstanten in Database gesteuert. Mehrere Optionen können gleichzeitig gesetzt werden, indem sie addiert werden. FTINDEX_ENCRYPTED_FIELDS – Verschlüsselte Felder werden ebenfalls in-
diziert. Beachten Sie, dass dies bis zu einem gewissen Grad die Verschlüsselung aushebelt. Werden z.B. bei einer Suche nach einem Stichwort Dokumente gefunden, die das Stichwort in verschlüsselten Feldern enthalten, kann zwar ein Benutzer die Daten als solche in ihrer Gesamtheit nicht lesen, erhält aber zumindest die Information, dass Daten zu diesen Stichworten vorliegen. Bei sensiblen Daten kann allein diese Information ausreichen, um die Datensicherheit zu korrumpieren. FTINDEX_ALL_BREAKS – Sätze und Absätze werden ebenfalls indiziert. Dies ermöglicht der FTSearch-Suchoperationen derartig durchzuführen, dass die Trefferwertung abhängig davon gemacht wird, wie nahe zwei Worte innerhalb eines Absatzes zueinander stehen. Die Suchsyntax hierzu ist Wort1 PARAGRAPH Wort2. FTINDEX_CASE_SENSITIVE – Der Index unterscheidet nach Groß- und Kleinschreibung. Die Suche kann dann mit dem Schlüsselwort EXACTCASE nach Wörtern in exakter Schreibweise suchen. Beispielsweise findet die Suchquery „EXACTCASE DOMINO“ nur Dokumente, in denen Domino komplett groß geschrieben ist. „Domino“ oder „dominO“ würde nicht gefunden. FTINDEX_ATTACHED_FILES – Dateianhänge in Dokumenten werden ebenfalls indiziert. FTINDEX_ATTACHED_BIN_FILES – Kann zusätzlich (und nur zusätzlich) zu FTINDEX_ATTACHED_FILES verwendet werden. In diesem Fall werden Anhänge, sofern unterstützt, nicht nur anhand der Rohdaten, sondern soweit möglich anhand der generischen Anhangsformate indiziert, so dass Anhänge wie Word- oder PDF-Dokumente deutlich besser durchsucht werden können. Diese Option entspricht der Indizierungseigenschaft „using conversion filters on supported files“.
• •
removeFTIndex () löscht einen Volltextindex und updateFTIndex aktualisiert ihn, wobei er nur neu erstellt wird, falls createOnFail true ist. boolean isFTIndexed() throws NotesException Liefert true, sofern eine Datenbank volltextindiziert ist. DateTime getLastFTIndexed() throws NotesException void setFTIndexFrequency(int option) throws NotesException int getFTIndexFrequency() throws NotesException
Diese Methoden steuern die Updatehäufigkeit für einen Volltextindex. getLstFTIndexed liefert Datum und Uhrzeit der letzten Indizierung. Der Rhythmus der Indizierung kann anhand der folgenden Konstanten aus Database gesetzt werden:
354
9 Database
Best Practice Verwenden Sie die Methoden queryAccess, queryAccessPrivileges und queryAccessRoles, wenn es darum geht, die aktuellen detaillierten Rechte des Benutzers aufzu-
lösen. Domino verwendet hierfür dieselben Prozesse wie bei der Ermittlung der Berechtigungen beim Zugriff auf Domino-Objekte. Verwenden Sie die Methoden in ACL, wenn Sie einzelne Einträge der ACL manipulieren möchten, also wenn es darum geht, Rechte für Benutzer, Server oder Gruppen hinzuzufügen, zu verändern oder zu löschen.
FTINDEX_DAILY – Indizierung einmal täglich. FTINDEX_HOURLY – einmal stündlich. FTINDEX_IMMEDIATE – sofort nach der Änderung eines Dokuments. FTINDEX_SCHEDULED – entsprechend dem Schedule des Servers.
•
void setInMultiDbIndexing(boolean flag) throws NotesException boolean isInMultiDbIndexing() throws NotesException
Nimmt die Datenbank in die datenbankübergreifende Domainsearch auf oder entfernt sie daraus. •
boolean isMultiDbSearch() throws NotesException
Zeigt an, ob es sich bei der Datenbank um eine Domainsearch-Datenbank (Domain Catalog) handelt. •
Document FTDomainSearch(String searchQuery, int max, int options, int otherOptions, int start, int pageCount, String searchForm) throws NotesException Kann nur angewendet werden, falls isMultiDbSearch() == true. Liefert ein
Dokument mit den Ergebnissen der Domainsearch. Für die Abfrage in searchQuery gelten die gleichen Regeln wie für FTSearch, wobei noch einige zusätzliche Parameter zugelassen sind. pageCount zeigt an, wieviele Trefferseiten zurückgegeben werden sollen. searchForm gibt die Maske an, mit denen die
Treffer angezeigt werden sollen. •
boolean isDirectoryCatalog() throws NotesException
Zeigt an, ob es sich bei der Datenbank um einn Domino Directory Catalog (basierend auf dircat5.ntf) handelt. Funktioniert nur, wenn das Datenbankobjekt über getAddressBooks in Session bezogen wurde. •
boolean isConfigurationDirectory() throws NotesException
Neu seit Domino R6.5. Zeigt an, ob es sich bei der Datenbank um ein Domino Configuration Directory handelt. Funktioniert nur, wenn das Datenbankobjekt über getAddressBooks in Session bezogen wurde. •
boolean getListInDbCatalog() throws NotesException void setListInDbCatalog(boolean flag) throws NotesException
9.5 Sicherheit
355
Neu seit Domino R6.5. Zeigt an, ob die Datenbank im Standard-Domino-Datenbankkatalog (catalog.nsf bzw. basierend auf catalog.ntf) aufgelistet wird. •
String getCategories() throws NotesException void setCategories(String s) throws NotesException
Zeigt an, unter welchen Kategorien die Datenbank im Datenbankkatalog (catalog.nsf) erscheinen soll (s. Abb. 9-1).
9.5
Sicherheit
In Kapitel 4.16 und Abbildung Abb. 4-9 wurde die Hierarchie der in Domino zum Tragen kommenden Sicherheitsmechanismen beschrieben. Dort wurde insbesondere auf die Ausführungsberechtigungen und die programmatische Sicherheit eingegangen. Die wichtigste Sicherheitsfunktion in Domino ist die Datenbank ACL (Access
Abb. 9-1 Datenbankeigenschaften
356
9 Database
Control List – Zugriffskontrollliste – s. Abb. 9-2)63. Über sie wird gesteuert, wer grundsätzlich Zugriff auf eine Datenbank erhält und mit welchen Rechten. Die ACL wird in einer eigenen Note gespeichert und im Java-Objekt ACL abgebildet. Die Berechtigungen sind wie folgt eingeteilt. Zunächst gibt es eine grundsätzliche Einteilung in sieben Standardrollen (s. Tabelle 9-3), wie z.B. Leser oder Manager. Diesen Standardrollen sind Maximalrechte zugeordnet (ein Leser darf keinerlei Dokumente
Abb. 9-2 ACL
erstellen) und ihnen können optionale Berechtigungen (s. Tabelle 9-4) zugeordnet oder entzogen werden (einem Autor kann z.B. das Recht, Dokumente zu löschen, entzogen werden). Jedem Eintrag in der ACL werden diese Standardrollen und solche optionalen Berechtigungen zugeordnet. Hierbei ist zu beachten, dass für einen Eintrag in der ACL festgelegt werden kann, für welche Arten von Benutzern, Servern oder Gruppen er gelten soll. Ist ein Eintrag z.B. einer Person zugeordnet, so würden die durch diesen Eintrag spezifizierten Rechte nicht für einen Server Anwendung finden, der zufällig den gleichen Namen hätte, wie in diesem Eintrag festgelegt. Soll ein Eintrag unabhängig von der Art des Benutzers gelten, muss er als „Unspecified“ markiert werden. Neben diesen optionalen Rechten und den Standardrollen können in der ACL entweder im ACL-Dialog des Notes Clients oder über renameRole(String s, String s1), addRole(String s), deleteRole(String s) und getRoles() in ACL eigene benutzerdefinierte Rollen erstellt werden. Diese werden in den einzelnen ACLEntry-Einträgen über enableRole(String roleName) und disableRole(String roleName) gesetzt bzw. über isRoleEnabled(String roleNa63
Die ACL wird im Notes Client über „File“ -> „Database“ -> „Access Control“ oder mit rechtem Mausklick auf das Symbol der Datenbank geöffnet.
9.5 Sicherheit
357
Tabelle 9-3 Zugriffslevel in der ACL MANAGER
DESIGNER EDITOR
AUTHOR READER
DEPOSITOR
NOACCESS
Ein Manager der Datenbank darf Dokumente der Datenbank lesen und ändern und optional löschen. Dies gilt nicht für Dokumente, für die ein Manager nicht in einem eventuell vorhandenen Leserfeld eingetragen ist. Nur im Serverdokument als Full-Access Administrators geführte Manager können auch solche Dokumente lesen und ändern. Nur Manager dürfen die ACL der Datenbank ändern. Designer haben Editor-Rechte auf der Datenbank. Nur Designer dürfen eine Datenbank im Domino Designer öffnen. Leser- und Autorenfelder finden Anwendung. Editoren haben Autorenrechte und dürfen neben den Dokumenten, die sie selbst erstellt haben, auch Dokumente anderer Autoren ändern. Leser- und Autorenfelder finden Anwendung, d.h. wenn z.B. beim Speichern eines Dokuments (durch einen Autor) ein Autorenfeld mit bestimmten Benutzern gefüllt wird, dürfen nur diese Benutzer und nicht grundsätzlich alle Editoren dieses Dokument ändern. Falls in einer Datenbank die Dokumente in verschiedene Klassen aufgeteilt werden können, ist es Best Practice, für diese Dokumente ein Autorenfeld mit einer Rolle (also mit einem Wert in der Art „[AutorFuerDokumentenArt1]“ zu füllen. Dann dürfen alle Benutzer, denen diese Rolle zugeordnet wurde diese Dokumente ändern, auch wenn sie nur als Autor in der ACL eingetragen sind. Auf diese Art lassen sich einerseits die Zugriffsberechtigungen auf den Autorlevel begrenzen und andernseits die Dokumente bzw. Autorenzugriffe klassifizieren. Autoren dürfen neue Dokumente erstellen, aber nur eigene Dokumente ändern oder optional löschen. Leser- und Autorenfelder finden Anwendung. Leser dürfen nur lesend auf eine Datenbank zugreifen. Leserfelder finden Anwendung. Autorenfelder spielen für diese Benutzer insofern eine Rolle, dass sie durch ein Autorenfeld zum Lesen berechtigt werden können. Benutzer des Levels Depositor dürfen nur Dokumente erstellen, aber nicht lesen. Dies ist z.B. für Datenbanken interessant, die als Wahlurne dienen. Leser- und Autorenfelder spielen für diese Benutzer keine Rolle, da sie nur neue Dokumente erstellen dürfen. Benutzer ohne Zugriff. Diesen Benutzernn kann die Berechtigung „Read / Write public documents“ zugeordnet werden. Hierdurch können Benutzer einerseits grundsätzlich vom Zugriff ausgeschlossen werden aber für einzelne ausdrücklich hierfür bestimmte öffentliche Dokumente berechtigt werden.
me) abgefragt. Im ACL-Dialog tauchen die benutzerdefinierten Rollen rechts unten im „Basiscs“-Fenster auf. Benutzerdefinierte Rollen werden in der Regel in Autoren-
358
9 Database
Tabelle 9-4 Optionale Berechtigungen in der ACL Level
NOAC- DEPO- REACESS SITOR DER
AUTH OR
EDITOR
DESIG- MANANER GER
Berechtigung Create documents Delete documents Create private agents Create personal folders/views Create shared folders/views Create LotusScript/ Java Agents Read public documents Write public documents Replicate or copy documents
x
o
x
x
x
o
o
o
o
o
o
o
x
x
o
o
o
x
x
o
x
x
o
o
x
x
x
x
o
o
o
d
o
d
d
d
x
x
x
d
d
d
d
o
d
x – Option ist fest voreingestellt aktiviert und kann nicht geändert werden. o – Option ist deaktiviert und kann optional aktiviert werden. d – Option ist aktiviert und kann optional deaktiviert werden. (kein Eintrag) – Option kann nicht aktiviert werden. oder Leserfeldern eingesetzt und immer in eckigen Klammern angegeben (z.B. „[Sachbearbeiter]“). Ist ein Domino-Objekt für eine bestimmte Rolle berechtigt, so kann ein Benutzer, dem diese Rolle zugeordnet ist, dieses Recht wahrnehmen. Die Einträge in ACL werden in Objekten der Klasse ACLEntry abgebildet, die über getFirstEntry(), getNextEntry(ACLEntry actualEntry), getNextEntry() und getEntry(String entryName) in ACL abgefragt werden können. Beachten Sie, dass die ACL nach einer Änderung über save() in ACL gespeichert werden muss, damit die Änderungen wirksam werden. Die einzelnen Eigenschaften werden in ACLEntry Einträgen innerhalb von ACL entsprechen Tabelle 9-4 abgebildet und über Methoden in ACLEntry, z.B. durch isCanCreateDocuments(), setCanCreateDocuments(boolean flag), isCanDeleteDocuments() oder setCanDeleteDocuments(boolean flag) usw. gelesen oder gesetzt. Zur Überprüfung der Rechte des aktuellen Benutzers geht Domino wie folgt vor: Zunächst wird überprüft, ob es einen Eintrag in der ACL gibt, der mit dem aktuellen Benutzernamen übereinstimmt und vom entsprechenden Typ (Benutzer, Server, etc.)
9.5 Sicherheit
359
ist. Wird ein solcher Eintrag gefunden, kommen diese Berechtigungen zum Einsatz. Einzige Einschränkung hierbei ist für Internet-Sessions (Browserzugriff, DIIOP) der maximale Internet-Zugriff, der unter „Advanced“ limitiert werden kann. Der maximale Internet-Zugriff kann über getInternetLevel() und setInternetLevel(int level) in ACL gelesen oder gesetzt werden. Der Parameter level beschreibt die grundsätzlichen Berechtigungsrollen und wird über die Konstanten LEVEL_NOACCESS, LEVEL_DEPOSITOR, LEVEL_READER, LEVEL_AUTHOR, LEVEL_EDITOR, LEVEL_DESIGNER oder LEVEL_MANAGER in ACL gesetzt. Wurde kein Eintrag für den Benutzer gefunden, so wird überprüft, ob der Benutzer sich in einer Gruppe befindet, die in der ACL gelistet ist. Wird der Benutzer in mehreren Gruppen gefunden, für die Einträge in der ACL vorliegen, wird der höchste (!) Zugriffslevel zugeordnet. Rechte, die sich aus benutzerdefinierten Rollen bzw. aus optionalen Rechten (Dokumente löschen, etc.) ergeben, werden gemischt. Allerdings muss das Recht, Agenten zu erstellen, in der Gruppe mit dem höchsten Level eingetragen sein, damit es zugeteilt wird. Wird der Benutzer auch in keiner Gruppe gefunden, so wird der Level des Eintrags „-Default-“ zurückgegeben. Das ACL-Objekt ist gut geeignet, um die ACL zu ändern. Um jedoch die Berechtigungen des aktuellen Benutzers abzufragen, ist es recht umständlich hierüber einen Eintrag für den aktuellen Benutzer auszulesen. Insbesondere bei Berechtigungen, die über Gruppen erhalten werden, ist die Auflösung der Rechte über ACL sogar unmöglich. Daher gibt es in Database einige Methoden, die dies erleichtern. Insbesondere queryAccess, queryAccessPrivileges und queryAccessRoles sind wichtige Methoden, da sie es erlauben für einen beliebigen Benutzernamen zu ermitteln, welche Rechte er auf einer Datenbank hat. Hierbei wird intern derselbe Prozess ausgeführt, den Domino anwendet, um beim Zugriff auf Objekte die Rechte des Benutzers zu ermitteln. Anders formuliert sind queryAccess, queryAccessPrivileges und queryAccessRoles die Methoden der Wahl, wenn es darum geht, die aktuellen detaillierten Rechte des Benutzers aufzulösen. In Database stehen folgende Methoden bezüglich der ACL zur Verfügung: • •
ACL getACL() throws NotesException Liefert das ACL-Objekt. Vector getACLActivityLog() throws NotesException Neu seit Domino R6.5. Liefert einen Vector mit Änderungsereignissen für die
ACL. •
•
int getCurrentAccessLevel() throws NotesException Liefert einen int-Wert entsprechend den Konstanten LEVEL_NOACCESS, LEVEL_DEPOSITOR, LEVEL_READER, LEVEL_AUTHOR, LEVEL_EDITOR, LEVEL_DESIGNER oder LEVEL_MANAGER aus ACL, der den Zugriffslevel angibt. Vector getManagers() throws NotesException
360
9 Database Liefert einen Vector von Namen als String mit ACL-Einträgen, für die Manager-Zugriff aktiviert ist. Die Einträge werden in der hierarchischen Langform (CN=Name/O=Organisation/C=Land) angegeben, sofern sie hierarchisch eingetragen wurden. Gruppennamen, die nicht hierarchisch angelegt sind (z.B. LocalDomainServers) werden in der Kurzform angegeben.
•
void grantAccess(String aclEntryString, int accessLevel) throws NotesException Setzt den Zugriffslevel des Eintrags aclEntryString auf den Level accessLevel. Hierbei wird nur der Zugriffslevel angepasst. Eventuell vergebene beson-
dere Berechtigungen (Löschen von Dokumenten, etc.) oder benutzerdefinierte Rollen bleiben erhalten, sofern sie für den neu zu setzenden Level zulässig sind. accessLevel muss einer der Konstanten entsprechen, die von getCurrentAccessLevel zurückgegeben werden können. •
int queryAccess(String aclEntryString) throws NotesException Liefert den Zugriffslevel als int-Wert (siehe getCurrentAccessLevel). Ent-
scheidend ist hierbei, dass für die Auswertung durch diese Methode auch Gruppen aufgelöst werden. queryAccess ist die Methode, um programmatisch festzustellen, welchen Zugriff der aktuelle Benutzer hat. •
int queryAccessPrivileges(String aclEntryString) throws NotesException
Neu seit Domino R6.5. Liefert die besonderen Zugriffsrechte (s. Tabelle 9-4), die dem Benutzer (oder seiner Gruppe) zugeordnet wurden. Der Wert entsteht als Addition der möglichen Werte der Konstanten aus Database DBACL_CREATE_DOCS, DBACL_DELETE_DOCS, DBACL_CREATE_PRIV_AGENTS, DBACL_CREATE_PRIV_FOLDERS_VIEWS, DBACL_CREATE_SHARED_FOLDERS_VIEWS, DBACL_CREATE_SCRIPT_AGENTS, DBACL_READ_PUBLIC_DOCS, DBACL_WRITE_PUBLIC_DOCS oder DBACL_REPLICATE_COPY_DOCS aus Database, die binär codiert sind und über bitweises Und
(&) abgefragt werden können. •
void revokeAccess(String aclEntryString) throws NotesException Entfernt den Eintrag aclEntryString aus der ACL. Dies ist nicht zu verwechseln mit grantAccess (aclEntryString, Database.LEVEL_NOACCESS).
Wurde ein Eintrag aus der ACL entfernt, gelten entweder die Default-Rechte oder die Rechte einer Gruppe, in der der Benutzer sich möglicherweise befindet. •
Vector queryAccessRoles(String s) throws NotesException Neu seit Domino R6. Liefert einen Vector der Namen der benutzerdefinierten
•
boolean isCurrentAccessPublicReader() throws NotesException Neu seit Domino R6.5. Liefert true, falls der aktuelle Benutzer der Session berechtigt ist, öffentliche Dokumente zu lesen (Eigenschaft Database. DBACL_READ_PUBLIC_DOCS). boolean isCurrentAccessPublicWriter() throws NotesException
Rollen, für die ein Benutzer berechtigt ist.
•
9.6 Designelemente und Ordner
361
Neu seit Domino R6.5. Liefert true, falls der aktuelle Benutzer der Session berechtigt ist, öffentliche Dokumente zu schreiben (Eigenschaft Database. DBACL_WRITE_PUBLIC_DOCS).
9.6
Designelemente und Ordner
Wenn Sie Java-Programme für Domino entwickeln, werden Sie in der Regel nicht in die Situation kommen, dass Sie Designelemente der Domino-Datenbank referenzieren müssen. Lediglich der View und der Agent nehmen hier eine Zwitterrolle ein, da diese Java-Objekte zwar das Designelement „View“ bzw. „Agent“ repräsentieren, aber gleichzeitig die „in Memory“-Darstellung einer Ansicht bzw. eines Agenten repräsentieren, über den Dokumente gesucht und selektiert werden und Agenten angesteuert und gestartet werden. Näheres zu Ansichten finden Sie in Kapitel 10 zu Agenten in Kapitel 4.10ff. Alle übrigen Designelemente können unterschiedlich gut über das Java-API manipuliert werden. Seit der Einführung der XML-Unterstützung in Domino können Designelemente zumindest sehr gut angezeigt, exportiert und sogar wieder importiert werden, da für jede Note, also auch für Designelemente, eine XML-Darstellung existiert. Ab Version 6 stellt Domino im Designer drei kleine Tools zur Verfügung, mit denen die XML-Darstellung einfach sichtbar gemacht werden kann. Öffnen Sie die Demo-Datenbank djbuch.nsf im Domino Designer und wählen Sie „Shared Code“ -> „Agents“. Im Menü finden Sie unter „Tools“ -> „DXL Utilities“ diese Tools. Der Viewer und der Exporter generieren den XML-Code zu dem markierten XML-Dokument und zeigen ihn im Browser an bzw. speichern ihn im Dateisystem. Der Transformer erlaubt zusätzlich die Anwendung eines XSL Stylesheets zur Transformation des XML-Codes. Die Funktionen des DXL-Exporters sind ebenfalls im Java-API vorhanden und können programmatisch eingesetzt werden. Zur Demonstration wurde der DemoDatenbank djbuch.nsf ein kleiner Agent beigefügt. Sie finden ihn, wenn Sie die Datenbank im Notes Client öffnen und im Menü „Actions“ den Befehl „Tools“ -> „Gesamtes Design als XML“ wählen. Der Agent erzeugt ein neues Dokument, das Sie in der Ansicht „Alle Dokumente“ mit dem Titel „Alle Designelemente dieser Datenbank als XML“ finden. Ein weiterer Demo-Agent erzeugt den XML-Code für ein einzelnes Dokument. Hierfür ist es wichtig, dass Sie die Datenbank lokal, nicht über einen Server öffnen. Der Agent „Actions“ -> „Tools“ -> „3. Dokument als XML“ erzeugt zu einem zuvor markierten Dokument ein Antwortdokument, in dem alle Items als XML dargestellt werden. Die zweite Möglichkeit Designelemente darzustellen, wurde in Kapitel 9.2 bereits kurz angedeutet. Die Klasse NoteCollection ermöglicht den programmatischen Zugriff auf alle Designelemente einer Datenbank. Die dritte Möglichkeit sind verschiedene Methoden in Database, die direkten Zugriff auf bestimmte Designelemente erlauben. In der Demo-Datenbank befindet sich ein Agent, der über „Actions“ -> „Tools“ -> „1. Masken anzeigen“ erreichbar ist und von der Methode getForms() Gebrauch macht. Er erzeugt ein neues Doku-
362
9 Database
ment in der Ansicht „Alle Dokumente“ und gibt einen Überblick über die Masken und deren Felder der aktuellen Datenbank, indem er die verschiedenen Properties der über getForms bezogenen Form-Objekte ausliest. Direkt über das Objekt Database können Agenten, Masken, Ansichten, Outlines und Ordner manipuliert werden. Beim programmatischen Erstellen von Designelementen über die folgenden Methoden benötigt der Benutzer mindestens Designerrechte für die Datenbank.
9.6.1
Designspezifische Methoden in Database
Das Database-Objekt stellt eine große Auswahl an Methoden zur Verfügung, über die die Gestaltung (im Sinne der Programmierung) der Domino-Datenbank über das API manipuliert werden kann, ohne den Domino Designer verwenden zu müssen. Diese Methoden sind grundsätzlich mit Vorsicht zu genießen, denn ihre Mächtigkeit bedingt auch ihr Risiko. •
Agent getAgent(String agentName) throws NotesException Liefert ein Agent-Objekt des Agenten mit dem Namen agentName. Dieses Ob-
jekt repräsentiert sowohl das Designelement des Agenten als auch den programmatischen Agenten. Typische Methoden des Designelements sind getName(), getTrigger (), getTarget(), die verschiedenen Methoden des Designlockings (s. Kap. 7.7) und natürlich save() oder remove(). Insbesondere bei der letzten Methode wird am ehesten deutlich, dass man es mit dem Designelemente zu tun hat, da diese Methode den Agenten endgültig aus der Datenbank löscht. Gleichzeitig kann das Agent-Objekt zur programmatischen Steuerung eines Agenten genutzt werden. Die wichtigsten Methoden run und runOnServer wurden bereits in Kapitel 3.4 besprochen. •
Vector getAgents() throws NotesException Liefert einen Vector von Agent-Objekten aller in der Datenbank befindlichen
•
Form getForm(String formName) throws NotesException Liefert das Form-Objekt einer durch formName spezifizierten Maske. Neben dem Designlocking können über dieses Objekt die Felder der Maske über getFields als Vector aus Strings bezogen werden. Die Sicherheitsvorgaben, über die be-
Agenten.
stimmt wird, ob die Maske nur bestimmten Benutzern erlauben soll, mit Hilfe dieser Maske Dokumente zu erstellen oder zu lesen, können über Vector getFormUsers(), void setFormUsers(Vector vector), Vector getReaders(), void setReaders(Vector vector), boolean isProtectReaders(), void setProtectReaders(boolean flag), boolean isProtectUsers() oder void setProtectUsers(boolean flag) beeinflusst werden. • •
Vector getForms() throws NotesException Liefert einen Vector von Form-Objekten aller Masken einer Datenbank. View getView(String viewName) throws NotesException
9.6.1 Designspezifische Methoden in Database
363
Lädt ein View-Objekt der durch viewName spezifizierten Ansicht. Der Name ist nicht abhängig von Groß-Kleinschreibung. Es kann entweder der Klarname oder der Alias verwendet werden. Es wird empfohlen bei einer programmatischen Verwendung wie durch getView immer Aliasnamen zu verwenden, um von späteren Änderungen von im GUI Verwendung findendenden Namen unabhängig zu sein. •
Vector getViews() throws NotesException Liefert einen Vector von View-Objekten aller in der Datenbank befindlichen
Ansichten. Allerdings werden auch für diese Methoden die Berechtigungen angewendet. In den Sicherheitseinstellungen für eine Ansicht im Domino Designer findet sich die Einstellung „Who may use this view“. Ist ein Benutzer hier nicht aufgelistet, wird dieser View auch nicht von getViews angezeigt. Sie können die Liste der erlaubten Benutzer auch über Vector getReaders() in View abfragen. •
View createView() throws NotesException View createView(String viewName) throws NotesException View createView(String viewName, String selectFormula) throws NotesException View createView(String viewName, String selectFormula, View templateView) throws NotesException View createView(String viewName, String selectFormula, View templateView, boolean prohibitDesignRefresh) throws NotesException
Neu seit Domino R6.5. Erstellt eine neue Ansicht in der Datenbank und gibt ein entsprechendes View-Objekt zurück. Diese Methode ist sehr mächtig und daher mit besonderer Vorsicht zu behandeln. Durch die Erstellung einer Ansicht werden auch entsprechende Indizes für sortierte Spalten in der Datenbank angelegt. Hierdurch entstehender Speicherbedarf und Performanceaufwand für die Aktualisierung sind zu berücksichtigen. Wenn in der Datenbank ein View existiert, der die Eigenschaft „Default design for new folders and views“ (Abb. 9-3) hat, so wird der neue View von diesem kopiert und es werden nur die in der Methode createView übergebenen Parameter neu gesetzt. Ist dies nicht der Fall, wird wie folgt vorgegangen. Wird kein Parameter verwendet, wird die Ansicht mit einer einzigen Spalte mit der Spaltenberechnung @DocNumber und der Select Formel @all und dem Titel „(untitled)“ angelegt. Werden nur einzelne Parameter weggelassen, so gelten Defaulteinstellungen. Für den viewName ist dies „(untitled)“, für die selectFormula ist dies „SELECT @all“. Wird kein templateView angegeben, so wird der View mit einer Spalte mit der Spaltenformel „@DocNumber“ angelegt. Der Parameter prohibitDesignRefresh setzt das Flag „Prohibit design refresh or replace to modify“, das auch in den Ansichteneigenschaften im Domino Designer gesetzt werden kann. Wird dieser Parameter nicht übergeben, so wird das Flag gesetzt.
364
9 Database Beachten Sie, dass folglich über createView erzeugte Ansichten
vor Design Updates geschützt sind, wenn nicht ausdrücklich der Parameter prohibitDesignRefresh == false gesetzt wird. Insgesamt entspricht createView dem Verhalten beim Erstellen einer neuen Ansicht über das GUI des Domino Designers. •
•
Outline getOutline(String outlineName) throws NotesException Abb. 9-3 Eigenschaft "Default design for Liefert ein Outline-Objekt der new folders and views" Outline mit Namen outlineName. Outline createOutline(String outlineName) throws NotesException Outline createOutline(String outlineName, boolean useDefaultDesign) throws NotesException Erstellt eine Outline mit Namen outlineName. Ist useDefaultDesign == true, wird optional aufgrund der vorhandenen Ansichten und Ordner ein De-
fault Design zusammengestellt, andernfalls wird eine leere Outline erstellt. Eine Outline besteht aus hierarchisch gestaffelt angeordneten OutlineEntryObjekten, die einen Navigationsbaum darstellen. Über das zurückgegebene Outline-Objekt können in der Outline Einträge (der Klasse OutlineEntry) hinzugefügt und entfernt werden. Dies kann dazu verwendet werden, dynamische Navigationselemente zu erzeugen. Für die Verwendung von Ordnern stehen neben den Methoden in View (s. Kap. 10 und 3.3) noch drei Methoden in Database zur Verfügung: •
void enableFolder(String folderName) throws NotesException Während bei der Verwendung von putInFolder ("folderName", true) in Document ein Ordner neu erstellt wird, wenn er noch nicht existiert, kann über enableFolder direkt ein neuer Ordner mit Namen folderName erstellt wer-
den. Die Gestaltung des Ordners (Spalten) wird von der als Defaultview gekennzeichneten Ansicht kopiert. •
boolean getFolderReferencesEnabled() throws NotesException void setFolderReferencesEnabled(boolean enable) throws NotesException
9.7 Locking und Signatur
365
Wenn Dokumente in Ordner kopiert werden, genauer gesagt, wenn Referenzen auf Dokument in Ordnern hinterlegt werden, so wird im Normalfall nicht im Dokument hinterlegt, in welchen Ordnern sich dieses Dokument befindet. Damit diese umgekehrte Referenzierung möglich wird, muss für die Datenbank die entsprechende Funktionalität über setFolderReferencesEnabled (true) aktiviert werden. Beachten Sie, dass dies nagtiven Einfluss auf die Geschwindigkeit der Datenbank haben kann.
9.7
Locking und Signatur
Die Methoden isDocumentLockingEnabled, setDocumentLockingEnabled, isDesignLockingEnabled, setDesignLockingEnabled und sign wurden in den Kapiteln 7.7 und 7.5 ausführlich erläutert.
9.8
Service Tasks
Wie alle Datenbanken benötigen auch Domino-Datenbanken verschiedene Wartungstools, insbesondere eine Komprimierung und Wartung zur Wiederherstellung der Dateiintegrität nach eventuellen Abstürzen. Insbesondere seit Domino R6.5 können diese Wartungstasks nicht nur über den Domino Administrator, sondern auch programmatisch angestoßen werden. •
int compact() throws NotesException
Beim Löschen von Daten innerhalb einer Domino-Datenbank wird Domino versuchen, sofern nicht die Option Database.DBOPT_NOOVERWRITE gesetzt ist, den so frei werdenden internen Speicher wiederzuverwenden. Dies wird nicht immer möglich sein, wenn z.B. neue größere Inhalte nicht in kleinere freigewordene Segmente passen, so dass es zur Fragmentierung des Speicherplatzes kommen wird. Durch die Komprimierung einer Datenbank kann die Fragmentierung rückgängig gemacht und der interne Speicheraufbau reorganisiert werden. Dieser Vorgang kann auch programmatisch auf eine Datenbank angewendet werden, allerdings ist die Ausführung von compact auf lokale Datenbanken beschränkt. Entgegen älteren Hilfe-Einträgen in der Domino-Designerhilfe kann auch die aktuelle Datenbank, in der der programmatische Aufruf von compact erfolgt, komprimiert werden (Stand Domino R6.5.3). Domino kennt drei Arten des Compact, den datenbankinternen Compact ohne Dateiverkleinerung, mit Dateiverkleinerung und den „Copy-style“ Compact, bei dem eine neue und komprimierte Datenbank aus der vorliegenden als Kopie erstellt wird. Bei der Verwendung des Compact ohne Parameter durch compact() wird wie folgt vorgegangen:
366
9 Database Datenbanken mit Transaktionslogging werden durch Compact ohne Dateiverkleinerung komprimiert. Hierbei wird interner Speicher reorganisiert und freier Speicher freigegeben. Die Größe der Datenbank im Dateisystem ändert sich nicht. Datenbanken ohne Transaktionslogging werden duch Compact mit Dateiverkleinerung komprimiert. Hierbei wird zusätzlich die Datenbankgröße im Dateisystem verkleinert. Bei Datenbanken, für die strukturelle Änderungen anstehen (wg. Veränderung der Parameter Database.DBOPT_NOUNREAD, Database.DBOPT_OPTIMIZATION oder Database.DBOPT_NORESPONSEINFO), wird die Copy-style-Komprimierung verwendet. Rückgabewert der Methode ist die Anzahl an Bytes, um die die Dateigröße verkleinert wurde.
•
int compactWithOptions(String optionString) throws NotesException int compactWithOptions(int optionInt) throws NotesException int compactWithOptions(int optionInt, String spaceThreshhold) throws NotesException
Neu seit Domino R6.5. Ermöglicht einen Compact mit den auch für die Kommandozeile verfügbaren Optionen. Diese können entweder als String optionString oder als int-Konstante optionInt übergeben werden. Hierfür stehen in Database verschiedene Konstanten zur Verfügung. Es können nur lokale Datenbanken komprimiert werden und nicht die aktuelle Datenbank, in der der Code ausgeführt wird. •
void fixup() throws NotesException void fixup(int optionInt) throws NotesException
Neu seit Domino R6.5. Korrupte Datenbanken können über fixup repariert werden. Über optionInt können Optionen anhand von Konstanten aus Database übergeben werden. Es können nur lokale Datenbanken repariert werden und nicht die aktuelle Datenbank, in der der Code ausgeführt wird. •
DateTime getLastFixup() throws NotesException
Neu seit Domino R6.5. Liefert Datum und Uhrzeit des letzten Fixup. Bei bestimmten Compact-Aufrufen wird ein impliziter Fixup durchgeführt, der dieses Datum ebenfalls beeinflusst.
9.9
Zusammenfassung
Das Databaseobjekt kapselt nicht nur die Domino-Datenbank in ein API-Objekt, sondern stellt alle Services rund um die Datenbank zur Verfügung bzw. ermöglicht deren Bezug. Hierzu gehörenen die Volltext- und Domainsearch, die Replikation, die Zugriffskontrolliste ACL und natürlich verschiedene Tasks für Wartung, Erstellung und Templating.
10
10 View
In diesem Kapitel: View API Indizierung und Performance Programmatische Manipulation von Ansichten View, ViewEntry, ViewEntryCollection ViewNavigator DocumentCollection Kapselung von Schleifen
368
10 View
Ansichten nehmen für Notes und Domino eine zentrale Rolle ein. Während in den folgenden Kapiteln die technische Seite beschrieben wird und die verschiedenen mit dem View im Zusammenhang stehenden Klassen und Methoden eingehend erläutert werden, finden Sie in Kapitel 15 zusätzliche Informationen zur Bedeutung von Ansichten für die Suche in Domino. Wichtig für das Verständnis ist, dass, wie bereits zuvor erwähnt, das Java-Objekt View eine Zwitterrolle einnimmt und einerseits das Designelement beschreibt, so dass es möglich ist mit entsprechenden Methoden in View das Design zu manipulieren, also z.B. neue Ansichtenspalten hinzuzufügen oder die SELECT-Formel zu manipulieren. Die zweite Rolle, die das View-Objekt einnimmt, ist die der Ansicht als Collection von Dokumenten, die gelistet, durchsucht oder durchschritten werden kann, um Dokumente zu finden und anzuzeigen. Auch wenn das API Ihnen die Funktionen zur programmatischen Manipulation des Designs einer Ansicht zur Verfügung stellt, sind diese mit Vorsicht zu verwenden. Rufen Sie sich nochmals den Aufbau einer Ansicht ins Gedächtnis (vgl. Kapitel 3.3ff.). Eine Ansicht besteht im Wesentlichen aus einer oder mehreren Spalten mit entsprechenden Spaltenberechnungen und -eigenschaften, wie Sortierung oder Kategorisierung, und einer SELECT-Formel, die festlegt, welche Dokumente in einer Ansicht vorliegen. Wenn nun dynamisch über das API Spalten oder die SELECT-Formel verändert werden, so müssen entsprechend die zugrunde liegenden Indizes, die z.B. für sortierte Spalten angelegt werden, neu aufgebaut werden. In einigen Fällen mag dies erwünscht sein, aber in Ansichten, in denen viele Dokumente angezeigt werden, kann dies einen erheblichen Performanceeinfluss haben. Da Ansichten zwischen den Benutzern geteilt werden – sie werden (in der Regel) als so genannte gemeinsame Ansichten angelegt – ist eine derartige dynamische Änderung natürlich auch nicht thread-save, da sich die dynamischen Änderungen mehrerer Benutzer gegenseitig in die Quere kommen würden. Darüber hinaus müssen diese Änderungen mit Designerberechtigung durchgeführt werden, die nicht jedem Benutzer gewährt wird. Ansichten sind daher (empfohlenerweise) nicht dynamisch zu verändern: Sie werden im Domino Designer oder eventuell auch über ein Installationsskript über das API, aber in jedem Fall nur einmalig angelegt und mit den benötigten Spalten, Eigenschaften und SELECT-Formel versehen. Spätere Änderungen sollten nur im Zuge der Versionspflege oder des Refactorings vorkommen. Berücksichtigen Sie zusätzlich, ob eine Ansicht im Notes Client, also im GUI, zum Einsatz kommen soll, oder ob die Ansicht internen Zwecke zur Suche und Selektion dienen soll. Wird eine Ansicht für beide Zwecke benötigt, sollte die Ansicht doppelt angelegt werden, damit spätere optische Gestaltungsänderungen für das GUI keine Nebenwirkungen auf die Programmierung haben. Suchen und Selektion von Untergruppen solcher Dokumente, die in einer Ansicht angezeigt werden, können dann anhand der hierfür vorgesehenen Methoden, wie FTSearch und getAllDocumentsByKey in View durchgeführt werden.
10.1 Updates, Indizes und Performance
369
Best Practice Berücksichtigen Sie, ob eine Ansicht im Notes Client, also im GUI, zum Einsatz kommen soll, oder ob die Ansicht internen Zwecke zur Suche und Selektion dienen soll. Wird eine Ansicht für beide Zwecke benötigt, sollte die Ansicht doppelt angelegt werden, damit spätere optische Gestaltungsänderungen für das GUI keine Nebenwirkungen auf die Programmierung haben.
10.1
Updates, Indizes und Performance
Für jede sortierte Spalte legt Domino automatisch einen Ansichtenindex an. Dies ist praktisch, transparent und einfach zu handhaben, da kein administrativer Aufwand anfällt. Allerdings wird dies zum einen durch den hierfür benötigten Speicherbedarf64 und natürlich durch die für die Updates der Indizes benötigte Performance erkauft. Domino unterscheidet zwischen den Volltextindizes und normalen Ansichtenindizes. Volltextindizes werden nur erstellt, wenn die entsprechende Datenbankeigenschaft über createFTIndex in Database aktiviert wurde. Sie kann über updateFTIndex abgeschaltet werden. Ansichtenindizes werden automatisch erstellt und können nicht abgeschaltet werden, es sei denn, eine Ansicht verzichtet komplett auf sortierte Spalten. Domino kennt verschiedene Optionen, wie die Volltextindizes aktualisiert werden sollen. Grundsätzlich wird zwischen periodischer und sofortiger Aktualisierung unterschieden. Dies kann durch setFTIndexFrequency in Database eingestellt werden. Beide Ansätze haben ihre Vorteile. In Datenbanken, die sich nicht schnell ändern, sollten Sie in jedem Fall die periodische Aktualisierung wählen. Die sofortige Aktualisierung wird benötigt, wenn Änderungen immer sofort zur Verfügung stehen müssen, was aber seltener der Fall ist, als man annehmen sollte. Da Domino vor allem im Zusammenhang mit unstrukturierten Daten zum Einsatz kommt, wie Sie in Collaboration- oder Datamining-Anwendungen entstehen, werden in den seltensten Fällen diese Daten in der gleichen Sekunde im Index für die Suche benötigt. Selbstverständlich ist die sofortige Aktualisierung ein Muss, wenn Sie über Ansichten Daten nachschlagen, für die eine referenzielle Integrität benötigt wird. Letztendlich muss zwischen vorhandener Leistungsfähigkeit und der für die Aktualisierung der Indizes benötigten Rechenleistung und der benötigten Aktualität abgewogen werden. Auch für die Ansichtenindizes kann eine sofortige Aktualisierung über setAutoUpdate(true) in View oder über den Domino Designer über das Kontextmenu der Ansichteigenschaften aktiviert werden. 64
Die Ansichtenindizes werden innerhalb der NSF-Datei angelegt und können über die Option Database.CMPC_DISCARD_VIEW_INDICES in compactWithOptions oder die -D Option im Kommandokonsolenbefehl compact gelöscht werden, so dass sie bei der nächsten Benutzung automatisch von Grund auf neu erstellt werden können. Volltextindizes werden, sofern für die Datenbank Volltextindizierung aktiviert ist, im Domino-Dataverzeichnis in Ordnern abgespeichert, die den Namen der Datenbank und die Endung .ft tragen.
370
10 View
Wenn ein Programm einen View verwendet, um in einem Loop durch die enthaltenen Dokumente zu schreiten und für diese Dokumente Veränderungen vorzunehmen, kann es sinnvoll sein, vorübergehend die sofortige Aktualisierung des Index zu aktivieren. Wird nämlich durch die Veränderung des Dokuments dieses von der Ansicht ausgeschlossen, so wird diese Veränderung nicht sofort sichtbar und das Dokument bliebe in der Ansicht. Insbesondere wenn z.B. Stempel im Dokument gesetzt werden, die kennzeichnen, dass eine Verarbeitung erfolgt ist, werden Sie dringend erwarten, dass das Dokument im zweiten Durchlauf nicht mehr in der Ansicht enthalten ist. In Kapitel 10.8 werden Codebeispiele vorgestellt, wie am besten Loops über Dokumente aus Ansichten oder Teilmengen der selektierten Dokumente gefunden und durchschritten werden.
10.2
Programmatische Manipulation des Designs von Views
Ähnlich wie für Database gilt auch für View, dass es in der Regel Sinn macht, die Gestaltung, also gewissermaßen die Programmierung einer Ansicht im Domino Designer zu erledigen. Wenn es jedoch notwendig ist, z.B. in einem Installationsscript, womöglich abhängig von Benutzerangaben oder sonst dynamisiert, Ansichten anzulegen, ist es hilfreich, die API-Funktionen nutzen zu können. Für Domino R6.5 wurden die API-Funktionen für die Manipulation von Ansichtenspalten und deren Eigenschaften erheblich ausgebaut, so dass inzwischen nahezu alle Eigenschaften einer Ansicht oder einer Ansichtenspalte auch durch das Java-API erreicht, angelegt, gelöscht oder sonst verändert werden können. Ein ausdrückliches Speichern, wie dies nach einer Veränderung im Domino Designer notwendig wäre, muss nach Veränderungen an einer Ansicht, die das Design betreffen, nicht erfolgen. Wenn also z.B. über createColumn einer Ansicht neue Spalten hinzugefügt werden, stehen diese sofort in der Ansicht zur Verfügung. Folglich ist also zu berücksichtigen, dass die Manipulation einer Ansicht direkt Auswirkungen auf andere Programmbestandteile und die Programmierung haben kann. Zur Manipulation des Gestaltungselements „View“ über API-Funktionen stehen in View folgende Methoden zur Verfügung: •
ViewColumn getColumn(int columnNumber) throws NotesException Liefert ein ViewColumn-Objekt der Spalte mit der Ordnungsnummer columnNumber. Eine Spalte einer Ansicht ist in keinem Fall mit einem Result Set zu ver-
wechseln. Sie enthält keine Daten, sondern die Definition von Eigenschaften für die Darstellung im Notes Client, bzw. Spaltenberechnungen, die festlegen, welche Daten bei der Benutzung der Ansicht später anzuzeigen sind. Hier tritt die eingangs erwähnte Zwitterrolle deutlich zu Tage.
10.2 Programmatische Manipulation des Designs von Views
371
Beachten Sie Beim Verwenden der setter für Spalteneigenschaften sollte unbedingt vermieden werden, null-Parameter oder leere Strings für Stringparameter zu übergeben. Einerseits ist dies in-
haltlich ohnehin nicht sinnvoll und vermeidet unerwartete Ergebnisse. z.B. sollte setFormula (null) vermieden werden.
Die Spalten sind von 1 aufsteigend nummeriert. Die ViewColumn bietet insbesondere ab Domino R6.5 für fast alle Eigenschaften einer Spalte setter und getter. Beachten Sie, dass die Veränderung dieser Eigenschaften der ViewColumn sofort wirksam werden und direkt in der Ansicht gespeichert werden. Ein kleiner Wermutstropfen ist, dass in Domino 6.5.x vereinzelt Abstürze bei Verwendung derjenigen setter-Methoden festgestellt wurden, die neu seit Domino 6.5 sind. Eine Ursache konnte hier leider nicht nachvollzogen werden. Dies wurde nur bei der Benutzung einer lokalen Notes-Session auf einem Client festgestellt. In Verbindung mit DIIOP traten diese Probleme nicht auf. Im Folgenden eine kleine Auswahl der Methoden in ViewColumn: –
boolean isFormula() throws NotesException boolean isField() throws NotesException void setFormula(String atFormula) throws NotesException (seit R6.5) String getFormula() throws NotesException
Setzen und Lesen der Spaltenformel, die zur Berechnung der in dieser Spalte angezeigten Werte dient. isFormula liefert true, falls es sich bei der Spaltenberechnung um eine Formel handelt. isField liefert true, falls es sich bei der Spaltenberechnung lediglich um den Namen eines Items handelt. Falls die Formel mit einer so genannten einfachen Spaltenberechnung identisch ist (z.B. @Author), dann liefern sowohl isField false und isFormula true, im Domino Designer wird aber die @Formel als einfache Formel angezeigt. setFormula darf weder null noch einen leeren String übergeben. Dies ist sehr wichtig, nicht nur weil es inhaltlich keinen Sinn machen würde, sondern da vereinzelt in diesem Zusammenhang Serverabstürze (Domino 6.5.3) durch die Übergabe von null-Parametern provoziert werden konnten. Ungültige @Fomeln werden mit einer NotesException quittiert. Alternativ können über den Domino Designer statt einer Berechnung auch einfache Feldnamen für einen Spalteninhalt festgelegt werden. Die Änderung einer Spaltenberechnung ist auch z.B. für eine programmatische Suche über getAllDocumentsByKey relevant. Bedenken Sie, dass eine Veränderung einer Spaltenberechnung in der Regel eine Neuberechnung des Ansichtenindexes zur Folge hat. –
boolean isSorted() throws NotesException
372
10 View void setSorted(boolean flag) throws NotesException boolean isSortDescending() throws NotesException void setSortDescending(boolean flag) throws NotesException
Neu seit Domino R6.5. Die Sortierung einer Spalte lässt sich ebenfalls programmatisch über das ViewColumn-Objekt verändern. Ob eine Spalte kategorisiert ist, lässt sich allerdings nur über boolean isCategory() auslesen. Diese Eigenschaft kann nicht geändert werden (s. Kap. 3.3.2). –
void setFontStyle(int styleConstant) throws NotesException int getFontStyle() throws NotesException
Neu seit Domino R6.5. Über die Konstanten ViewColumn.FONT_PLAIN (seit R6.5), ViewColumn.FONT_BOLD, ViewColumn.FONT_ITALIC, ViewColumn.FONT_UNDERLINE, ViewColumn.FONT_STRIKEOUT, ViewColumn.FONT_STRIKETHROUGH (seit R6.5) kann die Darstellung der Schrift in einer Ansichtenspalte geändert werden. Diese Änderung ist relevant für die Darstellung im Notes Client oder bei Ansichten, die von Domino für den Browser gerendert wurden. –
int getFontColor() throws NotesException void setFontColor(int colorConstant) throws NotesException
Neu seit Domino R6.5. Die Schriftfarbe für eine Ansichtenspalte kann über die in Kapitel 8 im Zusammenhang mit dem ColorObject beschriebenen und in RichTextStyle definierten Konstanten für Farben gesetzt werden. –
String getFontFace() throws NotesException void setFontFace(String fontName) throws NotesException Neu seit Domino R6.5. Schriften werden in der ViewColumn über deren Na-
men angesprochen. Gültige Werte sind z.B. „Arial“, „Courier“, „Default Sans Serif“, „Helvetica“ oder „Times New Roman“. Die Darstellung der Schrift ist relevant für die Anzeige einer Ansicht (und deren Ansichtenspalten) im Notes Client –
void setFontPointSize(int fontPointSize) throws NotesException int getFontPointSize() throws NotesException
Neu seit Domino R6.5. Für die Anzeige von Spaltenschriften im NotesClient kann die Schriftgröße für eine Spalte verändert werden. –
boolean isHidden() throws NotesException void setHidden(boolean hideMe) throws NotesException
Neu seit Version R6.5. Eine Spalte kann seit Version 6 versteckt werden. –
boolean isResponse() throws NotesException
10.2 Programmatische Manipulation des Designs von Views
373
Die Methode isResponse liefert true, wenn die Eigenschaft „Show responses only“ in der Spalte gesetzt ist. Diese Eigenschaft ist etwas ungewohnt in der Verarbeitung und steht im Zusammenhang mit Antwortdokumenten (s. Kap. 7.3). Wird diese Eigenschaft gesetzt, so werden im NotesClient, aber auch bei der Auswertung durch API-Java-Methoden nur für Anwortdokumente Werte in dieser Spalte angezeigt. Alternativ könnte dies durch eine Spaltenberechnungsformel in der Art @if (@isResponseDoc;">";""); ersetzt werden. •
Vector getColumns() throws NotesException Liefert einen Vector aus ViewColumn-Objekten mit allen Spalten einer An-
sicht. Beachten Sie, dass hier nicht etwa ein Result Set zurückgegeben wird, sondern die oben beschriebenen ViewColumn-Objekte, die die Gestaltung, also im Wesentlichen Eigenschaften für die Darstellung oder Spaltenberechnungen betreffen. •
ViewColumn createColumn() throws NotesException ViewColumn createColumn(int position) throws NotesException ViewColumn createColumn(int position, String title) throws NotesException ViewColumn createColumn(int position, String title, String columnFormula) throws NotesException
Neu seit Domino R6.5. Erstellt eine neue Spalte für die Ansicht. Der Parameter columnFormula bestimmt die festzulegende Spaltenberechnungsformel. Wird er weggelassen, so wird die Formel @DocNumber verwendet. Der Parameter titel bestimmt den anzuzeigenden Titel der Spalten, wie er im Notes Client erscheint. Wird er weggelassen, wirft Domino (R6.5.3) eine NullPointerException. Der Parameter position gibt die Position der neu zu erstellenden Spalte an, wobei die erste Spalte die Nummer 1 erhält. Wird er weggelassen, so wird eine neue Spalte ganz rechts an den View angefügt. Es wird empfohlen, immer die ausführliche Signatur mit den Parametern position, title und columnFormula zu verwenden. Vermeiden Sie null-Werte und leere Strings für title und columnFormula. •
ViewColumn copyColumn(int numOfCopyColumn) throws NotesException ViewColumn copyColumn(String titleOfCopyColumn) throws NotesException ViewColumn copyColumn(ViewColumn copyColumn) throws NotesException ViewColumn copyColumn(int numOfCopyColumn, int newPosition) throws NotesException ViewColumn copyColumn(String titleOfCopyColumn, int newPosition) throws NotesException ViewColumn copyColumn(ViewColumn copyColumn, int newPosition) throws NotesException
374
10 View Neu seit Domino R6.5. Kopiert eine Spalte einer Ansicht. Werden die Signaturen mit den Parametern numOfCopyColumn oder titleOfCopyColumn verwendet, so können nur Spalten derselben Ansicht kopiert werden. Die Parameter geben die Nummer (beginnend mit 1) oder den Titel der zu kopierenden Spalte an. Unter Titel ist der Anzeigetitel der Spalte zu verstehen. Alternativ kann ein ViewColumn-Objekt als Parameter übergeben werden, so dass diese Ansichtenspalte in die aufrufende Ansicht kopiert wird. Optional kann die Position der neuen Spalte mit newPosition angegeben werden. Vermeiden Sie leere Strings und null-Werte für titleOfCopyColumn.
•
void removeColumn() throws NotesException
Neu seit Domino R6.5. Löscht die am weitesten rechts liegende Spalte (höchste Ordnungsnummer) einer Ansicht. •
void removeColumn(int position) throws NotesException Neu seit Domino R6.5. Löscht die Spalte an der Position position (beginnend
ab 1 nummeriert). Gibt es die Spalte an der genannten Position nicht, wird eine •
NotesException geworfen. void removeColumn(String titleOfColumn) throws NotesException
Neu seit Domino R6.5. Löscht die Spalte mit dem Titel titleOfColumn. Existiert keine Spalte mit dem angegebenen Titel, wird keine Aktion durchgeführt. Vermeiden Sie null-Werte und leere Strings für titleOfColumn. •
int getColumnCount() throws NotesException
Liefert die Anzahl der Spalten eines Views. • •
Vector getColumnNames() throws NotesException Liefert die Titel der Spalten einer Ansicht als Vector aus Strings. int getHeaderLines() throws NotesException
Die Titel einer Ansicht werden im Notes Client standardmäßig einzeilig dargestellt, können aber auch mehrere Zeilen in Anspruch nehmen. getHeaderLines zeigt diese Anzahl der Zeilen an. •
int getRowLines() throws NotesException
Liefert die Anzahl der Ausgabezeilen, die pro Datensatzzeile einer Ansicht angezeigt werden. •
int getSpacing() throws NotesException void setSpacing(int spacing) throws NotesException
Das Spacing bestimmt für die Darstellung im NotesClient den Zeilenabstand zwischen einzelnen Datensatzzeilen einer Ansicht. Der Parameter spacing kann Werte in Abstufungungen von 1/4 Zeilenabstand von ein- bis zweizeilig annehmen, repräsentiert durch die Konstanten SPACING_SINGLE, SPA-
•
CING_ONE_POINT_25, SPACING_ONE_POINT_50, SPACING_ONE_POINT_ 75, SPACING_DOUBLE. int getBackgroundColor() throws NotesException void setBackgroundColor(int colorConstant) throws NotesException
10.2 Programmatische Manipulation des Designs von Views
375
Abb. 10-1 Kategorisierte Ansicht
Bestimmt die Hintergrundfarbe der gesamten Ansicht für die Darstellung im Notes Client. Der Parameter colorConstant kann die in Kapitel 8 im Zusammenhang mit dem ColorObject beschriebenen und in RichTextStyle definierten Konstanten für Farben annehmen. •
boolean isCategorized() throws NotesException
Zeigt an, ob eine Ansicht eine kategorisierte Spalte enthält. Abb. 10-1 zeigt ein Beispiel einer kategorisierten Ansicht. Im Beispiel ist die erste Spalte kategorisiert (s. Kap. 3.3.2 und 10.5). Nur sortierte Spalten können kategorisiert werden. Hierbei fasst Domino mehrere identische Einträge einer Spalte als Gruppe zusammen und zeigt (optional) ein so genanntes Twistie (dreieckiges Symbol) an, mit dem die Gruppe auf- oder zugeklappt werden kann. Zwar handelt es sich hier lediglich um ein Gestaltungsmerkmal zur Anzeige, das zunächst nur im Notes Client zum Tragen kommt, aber auch bei Such-Operationen durch die API-Funktionen müssen Kategorisierungen berücksichtigt werden. So können z.B. innerhalb einer Spalte Subkategorien gestaffelt angezeigt werden, indem diese Spalte Werte enthält, die einen Backslash „\“ enthalten. Domino fasst dann Untergruppen ebenfalls zu einer Gruppe zusammen und zeigt ein Twistie an und zwar werden alle Einträge, für die der Teil rechts vom Backslash identisch ist, als jeweilige Untergruppe angezeigt und alle Untergruppen, für die der Teil links vom Backslash gleich ist, werden als Gruppe angezeigt.
376
10 View Wenn also eine Ansicht Dokumente anzeigt, die in der kategorisierten Spalte Werte der Art „Organisation\Nord“ und „Organisation\Süd“ enthalten, so wird Domino die Kategorie „Organisation“ anzeigen und darunter die Kategorien „Nord“ und „Süd“ gruppieren. Optisch wird dies zusätzlich durch Einrückung kenntlich gemacht. Wenn eine derartig kategorisierte Spalte durchsucht wird, ist zu bedenken, dass bei exakter Suche nach Werten der Art „Organisation\Nord“ einschließlich Backslash gesucht werden muss. Eine nicht exakte Suche nach Werten, die mit „Nord“ beginnen, ist nicht möglich, da ja die Werte in der Spalte tatsächlich nicht mit Nord, sonder mit „Organisation“ beginnen.
•
boolean isHierarchical() throws NotesException
Eine hierarchische Darstellung einer Ansicht erfolgt für Haupt- und deren Antwortdokumente. Enthält eine Ansicht Haupt- und Antwortdokumente und liefert isHierarchical == true, so werden Haupt- und Antwortdokumente ähnlich wie in kategorisierten Ansichten angezeigt, wobei die Antwortdokumente den Hauptdokumenten untergeordnet werden. •
boolean isCalendar() throws NotesException
Da der Lotus Notes Client ein Collaboration Tool ist, das selbstverständlich neben dem Mail-Client einen Kalender enthält, benötigt Domino ein Gestaltungselement, mit dem Kalender dargestellt werden können. Hierfür besitzen Ansichten eine spezielle Eigenschaft, durch die sie als Kalender anzeigt werden. Dokumente werden dann nicht linear untereinander, sonder dem jeweiligen Tag zugeordnet angezeigt. Damit Dokumente korrekt in einer Kalenderansicht angezeigt werden, muss die erste Spalte der Ansicht unsichtbar sein, zu einem DateTimeWert evaluieren und aufsteigend sortiert sein. Dieser Wert ordnet einen Ansichteneintrag dem Kalendertag zu. Die zweite Spalte muss ebenfalls aufsteigend sortiert sein und auf einen Zeit-Wert evaluieren und spezifiziert die Dauer eines Ereignisses. Auch diese Spalte sollte versteckt werden. •
boolean isDefaultView() throws NotesException void setDefaultView(boolean setDefault) throws NotesException
Für eine Datenbank kann ein Default View definiert werden. Dieser wird, sofern z.B. in einem Frameset nichts anderes definiert ist, als erstes beim Öffnen angezeigt. •
boolean isFolder() throws NotesException
Ordner und Ansichten werden in ihrer Repräsentation als Java-Objekt identisch behandelt. Allerdings haben Ordner z.B. keine SELECT-Formel. isFolder liefert true, wenn es sich um einen Ordner handelt. Näheres zu Ordnern finden Sie in Kapitel 3.3ff. •
boolean isPrivate() throws NotesException
Benutzer, die keine Designer-Rechte in der ACL haben, können dennoch so genannte private Ansichten erstellen, sofern ihnen dies in der ACL ausdrücklich erlaubt wurde (queryAccessPrivileges in Database liefert Database. DBACL_CREATE_PRIV_FOLDERS_VIEWS) •
Vector getAliases() throws NotesException void setAliases(Vector someAliases) throws NotesException void setAliases(String oneAlias) throws NotesException
10.3 Allgemeine Properties und Methoden
377
Wie jedes Designelement kann eine Ansicht einen Alias haben. getAliases liefert diese Werte als Vector aus Strings, setAliases setzt ihn, wobei vorhandene Werte überschrieben werden. Es werden nur die Aliasnamen ausgegeben, nicht der Klarname. •
String getName() throws NotesException void setName(String viewName) throws NotesException
Liest und setzt den Namen der Ansicht. •
String getViewInheritedName() throws NotesException
Wenn eine Ansicht auf Basis einer Ansichtenvorlage angelegt wurde, z.B. über
•
createView(String viewName, String selectFormula, View templateView), dann hinterlegt Domino im Designelement der Ansicht ein Item $ViewInheritedFrom, in dem der Name der Ansicht geführt wird, von der die Ansicht abgeleitet wurde. getViewInheritedName liefert diesen Wert. boolean isProhibitDesignRefresh() throws NotesException void setProhibitDesignRefresh(boolean setProhibit) throws NotesException
Wie in Kapitel 9 erläutert, können Datenbanken auf Datenbankschablonen basieren. Die Gestaltung einer solchen Schablone kann regelmäßig automatisiert oder manuell neu geladen werden. Daher hat jedes Gestaltungselement ein Flag, über das bestimmt werden kann, dass bei einem solchen Gestaltungsupdate dieses Gestaltungselement nicht verändert werden soll. isProhibitDesignRefresh und setProhibitDesignRefresh steuern dieses Flag.
10.3
Allgemeine Properties und Methoden
Das View-Objekt stellt in Java verschiedene getter und setter zur Verfügung, über die allgemeine Parameter gelesen und geschrieben werden können. Hierzu gehören natürlich zunächst verschiedene Zeitstempel, die sich auf das Design-Element „Ansicht“ beziehen und beschreiben, wann die Gestaltung einer Ansicht verändert oder die Ansicht zum ersten Mal erstellt wurde. Interessant ist die seit Version 6.5 in Java zur Verfügung stehende Möglichkeit die SELECT-Formel programmatisch zu manipulieren. Auch hier die Warnung vor unerwarteten Nebenwirkungen. Schließlich ist die SELECT-Formel das „Herz“ der Ansicht. • •
DateTime getCreated() throws NotesException Liefert ein DateTime-Objekt, das anzeigt, wann eine Ansicht erstellt wurde. DateTime getLastModified() throws NotesException Liefert ein DateTime-Objekt, das die letzte Designänderung einer Ansicht an-
zeigt. Beachten Sie, dass hier nur Änderungen an dem Objekt selbst gemeint sind und nicht etwa Änderungen, die dadurch zustande kommen, dass aufgrund von Veränderungen im Datenbestand, wenn z.B. neue Dokumente erstellt wurden, die Ansicht ein anderes Ergebnis anzeigt.
378 •
10 View Database getParent() throws NotesException
Gibt die Datenbank zurück, in der sich die Ansicht befindet. Das zurückgegebene Database-Objekt ist ein und dasselbe, über das die Ansicht geöffnet wurde. In Kapitel 14 wird noch ausführlich auf das Thema Recycling eingegangen werden. An dieser Stelle soll als Vorgriff darauf hingewiesen werden, dass für sämtliche Domino-Java-Objekte deren Parent-Objekte nicht recycelt werden dürfen. Würde für die Ansicht die Datenbank getParent() recycelt, wird automatisch auch der View mit recycelt. Als Faustregel gilt: Es dürfen immer nur die Objekte, die im Programmcode aktiv erzeugt wurden, recycelt werden. Ein sauberes Recycling erhalten Sie, indem nach getaner Arbeit zunächst der View recycelt wird und danach die Datenbank, über den dieser geladen wurde. In jedem Fall sollte das Recycling nur nach einer Überprüfung der Objekte auf null erfolgen und in einem finally-Block untergebracht sein. •
String getUniversalID() throws NotesException
Wie jede Note (s. Kap. 2.2.1) einer Domino-Datenbank hat auch eine Ansicht eine Universal ID, die als String durch getUniversalID zurückgegeben wird. Eine Universal ID ist eine 32-stellige Hexadezimalzahl, d.h. der zurückgegebene String enthält nur die Zeiten 0..9 und A..Z. •
boolean isConflict() throws NotesException
Zeigt an, ob eine Kalender-Ansicht65 für die Überprüfung nach Zeit-Konflikten aktiviert ist. Diese Eigenschaft kann programmatisch nicht verändert werden. •
boolean isModified() throws NotesException
Diese Methode zeigt an, ob die Ansicht, die gerade für den aktuellen Benutzer angezeigt wird, dem aktuellen Stand der Ansicht auf dem Server entspricht, für den Fall, dass eventuell ein anderer Benutzer Dokumente so verändert oder erstellt hat, dass die Ansicht andere, mehr oder weniger Dokumente anzeigen müsste, also einen Refresh benötigt. Die Methode isModified liefert immer true, falls sie in einer Remoteverbindung eingesetzt wird und immer false, falls sie in einem Agenten oder einer Standalone-Anwendung (lokale Notes-Session) eingesetzt wird. Dies hat seinen Ursprung darin, dass diese Methode ihren Ursprung in der korrespondierenden UI-Methode hat. Folgt man den Ausschlüssen, für die diese Methode keine sinnvollen Ergebnisse liefert (keine Agenten, keine Remoteverbindungen, keine lokale Notes-Session), so bleiben nur noch Anwendungsbereiche, für die nur LotusScript eingesetzt werden kann, wie z.B. eine QueryOpen-Formel einer Ansicht. •
boolean isQueryView() throws NotesException
Neu seit Domino R7. Zeigt an, ob eine Ansicht ein Query View ist. Diese Methode ist nur sinnvoll für Datenbanken im NSFDB2-Format (s. Kap. 13.4.6). 65
Für eine Ansicht kann im Domino Designer im Kontext-Menü der Ansicht die Eigenschaft „Style“ einen der beiden Werte „Standard Outline“ oder „Calendar“ erhalten. Wird Style auf „Calendar“ gestellt, so wird die Ansicht nicht wie gewohnt als Liste im Notes Client dargestellt, sondern in der so genannten Kalenderansicht. Der persönliche Kalender im persönlichen Mail File ist ein Beispiel hierfür. Siehe auch isCalendar() in View.
10.3 Allgemeine Properties und Methoden •
379
void setSelectionFormula(String s) throws NotesException
Neu seit Domino R6.5. Setzt die SELECT-Formel einer Ansicht. Auch hier die Warnung vor unerwarteten Nebenwirkungen. Schließlich ist die SELECT-Formel das „Herz“ der Ansicht mit reichlich Nebenwirkungen bei Veränderung (Neuaufbau von Indizes). •
String getSelectionFormula() throws NotesException
Neu seit Domino R6.5. Liefert die SELECT-Formel einer Ansicht. •
void remove() throws NotesException
Löscht eine Ansicht endgültig aus der Datenbank. Beachten Sie, dass das ViewObjekt, in dem Sie remove() aufrufen, sehr wohl noch recycelt werden muss, denn das Java-Objekt ist nur eine logische Instanz der physischen Implementierung der Ansicht in der Datenbank. Wird eine Ansicht gelöscht, werden auch beim nächsten Compact der Datenbank die (internen) Ansichtenindizes gelöscht. Das Löschen einer Ansicht hat nicht das Löschen von Dokumenten zur Folge. •
Vector getReaders() throws NotesException void setReaders(Vector vector) throws NotesException
Ansichten können so konfiguriert werden, dass nur bestimmte Benutzer sie benutzen dürfen. Dies wird, wie auch für Dokumente üblich, über Leserfelder geregelt. Für jede Ansicht kann über setReaders ein Vector mit berechtigten Benutzern eingestellt werden. Beachten Sie unbedingt, dass für Leserfelder die Langform von Benutzernamen verwendet werden muss. Anders als in manchen anderen Situationen übersetzt hier Domino die Kurzform nicht in die Langform. Sie müssen also für Benutzernamen immer die Form „CN=User Name/O=Organisation“ oder „CN=User Name/OU=Organisational Unit/O=Organisation/C=Country“ usw. verwenden, wenn Sie Leserfelder verwenden. Dies gilt übrigens nicht nur an dieser Stelle für setReaders, sondern an jeder Stelle in Domino, an der programmatisch Leserfelder durch das API verändert werden. Wenn Sie dies nicht berücksichtigen, erzeugen Sie Ansichten, die von keinem Benutzer gelesen werden können, da bei der Überprüfung der Berechtigung eine Kurzform in der Art „User Name/Organisation“ sehr wohl in die Langform übersetzt wird. Die Langform des Benutzernamens findet sich aber dann nicht im Leserfeld. Um die Berechtigungen für eine Ansicht wieder zurückzusetzen, verwenden Sie setReaders (null). Abb. 10-2 zeigt die Eigenschaften des Design-Dokuments (der Design Note) für eine Ansicht, bei der das $Readers-Feld korrekt Abb. 10-2 $Readers-Feld im Design-Dogesetzt ist. kument für View
380 •
10 View boolean isProtectReaders() throws NotesException void setProtectReaders(boolean flag) throws NotesException
Zeigt an bzw. legt fest, ob das $Readers Item des View vor der Replikation geschützt werden soll66. •
String getURL() throws NotesException getNotesURL() throws NotesException String getHttpURL() throws NotesException
Diese Methoden arbeiten analog zu den gleichnamigen Methoden in Document (s. Kap. 7.1.4.2).
10.4
View und Document
Jede Ansicht kann als (sortierte) Sammlung von Referenzen auf Dokumente aufgefasst werden. Dementsprechend stellt das Java-Objekt View Methoden wie z.B. getFirstDocument und getNextDocument zur Verfügung, mit denen durch diese Elemente geschritten werden kann. Wie in Kapitel 7.3 erläutert, können Dokumenten Antwortdokumente zugeordnet sein. Diese Antwortdokumente können in Ansichten so angeordnet werden, dass sie dem übergeordneten Dokument zugeordnet sind. Ein Dokument kann innerhalb einer Ansicht seine Antwortdokumente mit getChild und dann mit getNextSibling referenzieren. Genauer wird auf das Verhalten von kategorisierten Ansichten im nächsten Kapitel eingegangen. Ein Antwortdokument kann sein übergeordnetes Dokument über die Methode getParentDocument erreichen. Einzelne Dokumente oder eine DocumentCollection von Dokumenten können in Ansichten über getDocumentByKey oder getAllDocumentsByKey gefunden werden. Bedenken Sie, dass die folgenden beschriebenen Methoden ein Document zurückgeben, also keinen Aufschluss darüber erlauben, welche Spaltendarstellung das Dokument in der Ansicht zur Folge hat, sondern lediglich eine Referenz auf den „Container“ Document erlauben. Werden Informationen über eine bestimmte Zeile der Ansicht einschließlich der in dieser Zeile angezeigten Spaltenwerte benötigt, steht ein weiteres Set von vergleichbaren Methoden zur Verfügung, die so genannte ViewEntry-Objekte zurückgeben. Ein ViewEntry erlaubt zwar zusätzlich eine Referenz auf das zugehörige Dokument, dient aber primär dazu, die Zeile der Ansicht (wie sie sich z.B. auch im NotesClient darstellt) zu referenzieren. Ein ViewEntry kann neben Dokumenten auch z.B. Kategorien repräsentieren, die ebenfalls als Zeile einer Ansicht angezeigt und auch programmatisch so aufgefasst werden. ViewEntries können am besten wie in Kapitel 10.5 beschrieben mit einem ViewNavigator verarbeitet werden. Zusätzlich kann eine Ansicht über die Methode FTSearch gefiltert werden. Nach der Anwendung von FTSearch verhält sich eine Ansicht so, als ob es lediglich die 66
Die genauen Randbedingungen für die Funktion dieser Methoden sind dem Autor unbekannt. In einer Testumgebung mit Domino 6.5.2 und Domino 7.0 konnte hier keine Funktion erkannt werden.
10.4 View und Document
381
durch FTSearch gefundenen Dokumente in der Ansicht gibt. Näheres zur Suche mit FTSearch finden Sie in Kapitel 15. Im Einzelnen stellen sich die Methoden wie folgt dar: •
Document getFirstDocument() throws NotesException Document getLastDocument() throws NotesException
Liefert das erste bzw. letzte Dokument in einer Ansicht. Falls eine Ansicht zuvor durch FTSearch gefiltert wurde, liefert diese Methode das erste bzw. letzte Dokument des gefilterten Sets. Gibt es keine Dokumente in der Ansicht, so liefern diese Methoden null. Ist ein View-Objekt älter, so kann es vorkommen, dass das Objekt nicht den aktuellen Stand der Ansicht repräsentiert, so dass mittels refresh() in View die Ansicht aktualisiert werden muss. Dies wird insbesondere dann auftreten, wenn während der Verarbeitung der eigene Prozess oder dritte Prozesse Dokumente derart ändern, dass sie nach der Änderung in der Ansicht auftauchen bzw. nicht mehr auftauchen. •
Document getNextDocument(Document document) throws NotesException Document getPrevDocument(Document document) throws NotesException
Liefert das nächste bzw. vorige Dokument zum Dokument aus dem Parameter. Gibt es kein nächstes bzw. voriges Dokument wird null zurückgegeben. Die Methoden in ViewNavigator (s. Kap. 10.5) liefern effizientere Methoden zur Behandlung von Dokumenten in Ansichten. •
Document getNthDocument(int n) throws NotesException
Lädt das n-te Dokument einer Ansicht beginnend mit einer Zählung bei 1. Bei kategorisierten Ansichten werden nur Dokumente der obersten Ebene zurückgegeben. Zur Vorsicht muss geraten werden, wenn die Ansicht Dokumente mit Leserfeldern enthält, die zur Folge haben, dass der aktuelle Benutzer diese Dokumente nicht sehen darf. Wird solch ein Dokument an n-ter Stelle angefordert, wird das nachfolgende Dokument zurückgegeben. So kann es sein, dass bei einer Veränderung von n durchaus mehrmals das gleiche Dokument zurückgegeben wird, wenn z.B. für die Dokumente n und n+1 keine Leseberechtigung vorliegt, wird für n, n+1 und n+2 jedesmal das Dokument n+2 zurückgegeben. Erst n+3 wird, dann das nachfolgende Dokument zurückgeben. Diese Methode ist nicht performant und sollte nicht in Schleifen eingesetzt werden. •
Document getChild(Document parentDoc) throws NotesException Document getParentDocument(Document childDoc) throws NotesException Die Methode getChild liefert das erste Antwortdokument auf ein gegebenes Dokument und umgekehrt liefert geParentDocument wieder das Hauptdokument. Sollen nach getChild weitere Antworten referenziert werden, kann getNextSibling verwendet werden. Alle Antworten eines Hauptdokuments können mit getResponses in Document als unsortierte DocumentCollection geladen werden.
382
10 View Hat ein Dokument kein Antwortdokument, bzw. ist das Dokument selbst kein Antwortdokument, so geben getChild bzw. getParentDocument null zurück.
•
Document getNextSibling(Document document) throws NotesException Document getPrevSibling(Document document) throws NotesException
Diese Methoden liefern die so genannten Geschwister eines Dokuments. Für Anwortdokumente (siehe getChild) liefern diese Methoden die benachbarte Antwort auf dasselbe Hauptdokument, bzw. null, sofern es keine weitere Antwort gibt. Hat eine Antwort selbst wieder Antworten, muss auch hier zunächst wieder die erste Antwort per getChild geladen werden. Werden die Methoden auf Hauptdokumente angewendet, so liefern die Methoden die benachbarten Hauptdokumente. Grundsätzlich dienen die Methoden dazu, insbesondere in kategorisierten Ansichten, Dokumente derselben Ebene anzusteuern. In Ansichten, die durch FTSearch gefiltert wurden, geben diese Methoden das jeweils vorige oder nächste Dokument innerhalb der Ansicht zurück und zwar unabhängig vom Level. •
Document getDocumentByKey(Vector vector) throws NotesException Document getDocumentByKey(Vector vector, boolean exactSearch) throws NotesException Document getDocumentByKey(Object dominoObject) throws NotesException Document getDocumentByKey(Object dominoObject, boolean exactSearch) throws NotesException Sortierte Spalten in Ansichten können mit getDocumentByKey durchsucht werden, wobei als Suchschlüssel String-, Number-, DateTime- oder DateRange-Objekte oder ein Vector solcher Objekte zum Einsatz kommen kann. Wird ein solches Objekt in der entsprechenden Signatur (Parameter dominoObject) direkt übergeben, so wird in der ersten Spalte danach gesucht. Wird ein Vector aus String, Number, DateTime oder DateRange übergeben, so wird
in der ersten Spalte nach dem ersten Element des Vectors, in der zweiten nach dem zweiten Element des Vectors usw. gesucht.67 Zurückgegeben wird der erste Treffer innerhalb der Ansicht. Wurde kein Treffer gefunden, ist der Rückgabewert null. Der Parameter exactSearch ist optional und erzwingt eine exakte Übereinstimmung für die Suche. Default ist für diesen Parameter der Wert false. Die Suche ist in jedem Fall nicht abhängig von Groß- und Kleinschreibung.
67
Es wird empfohlen, nicht mehr als 4 Spalten für die Suche zu verwenden.
10.4 View und Document
383
Bei exactSearch == false oder falls dieser Parameter weggelassen wird68, wird nach einer Übereinstimmung gesucht, für die gilt, dass der Suchparameter mit dem Anfang des Spaltenwertes (unabhängig von Groß- und Kleinschreibung) übereinstimmt. Wortvarianten werden bei dieser Suche ebensowenig gefunden wie Zeilen, die nicht mit dem Suchparameter beginnen. Ob eine Ansicht Zeilen enthält, die den Suchparameter enthalten (ohne damit zu beginnen), kann mit dieser Methode nicht überprüft werden. Wird ein Objekt übergeben, so kann dieses ein Objekt der Klasse String, lotus.domino.DateTime, lotus.domino.DateRange oder Number sein. Für Number-, DateTime- und DateRange-Objekte hat der Parameter exactSearch keine Bedeutung. •
•
DocumentCollection getAllDocumentsByKey(Vector vector) throws NotesException DocumentCollection getAllDocumentsByKey(Vector vector, boolean exactSearch) throws NotesException DocumentCollection getAllDocumentsByKey(Object dominoObject) throws NotesException DocumentCollection getAllDocumentsByKey (Object dominoObject, boolean exactSearch) throws NotesException Die Methode getAllDocumentsByKey arbeitet wie getDocumentByKey, gibt jedoch alle gefundenen Treffer zurück. Allerdings ist diese DocumentCollection nicht sortiert. Für entsprechend der Ansicht sortierte Resultsets verwenden Sie getEntryByKey und getAllEntriesByKey. void refresh() throws NotesException boolean isAutoUpdate() throws NotesException void setAutoUpdate(boolean flag) throws NotesException Das Objekt View reflektiert jeweils den Zustand, den die zugehörige Ansicht zum
Zeitpunkt des Erzeugens des Objektes hatte. Falls sich durch eine Veränderung oder das Erstellen oder Löschen von Dokumenten das ResultSet einer Ansicht verändert, wird das Objekt diese Änderung nur dann widerspiegeln, wenn die Ansicht ihre Änderungen automatisch aktualisiert. Dies wird durch isAutoUpdate == true angezeigt und kann (temporär) durch setAutoUpdate(false) deaktiviert werden. Aus Performancegründen kann es sinnvoll sein die automatische Aktualisierung abzuschalten, sofern die Verarbeitung nicht darauf angewiesen ist, auch während der Verarbeitung Änderungen zu reflektieren. Durch refresh() kann eine Aktualisierung erzwungen werden. Bei Remoteverbindungen wird der Cache deaktiviert, falls isAutoUpdate() == true und bei der Verwendung von refresh() zurückgesetzt.
68
Eine nicht exakte Suche kann durchaus unerwartete Ergebnisse liefern. Beachten Sie, dass z.B. der Suchparameter „Dienst“ auch ein Dokument mit dem entsprechenden Spaltenwert „DIENSTAG“, nicht aber z.B. „Verdienst“ oder „verdienen“ findet. Für die Suche nach Zahlen ist dies unkritisch. Hier gilt nur die exakte Suche.
384 •
10 View int FTSearch(String searchQuery) throws NotesException int FTSearch(String searchQuery, int max) throws NotesException Anders als die Volltextsuche FTSearch in Database liefert die FTSearch in View keine DocumentCollection. Stattdessen wird die Ansicht gefiltert und das ResultSet des Objektes View so reduziert, dass darin nur noch Referenzen enthal-
ten sind, die dem Ergebnis der Suche entsprechen. Rückgabewert der Methode ist die Anzahl der gefundenen Dokumente, die durch den Parameter max begrenzt werden kann, wobei der Wert 0 für diesen Parameter die Suche nicht einschränkt. Wird FTSearch mehrfach nacheinander auf eine Ansicht angewendet, so wird zuvor das Resultset allerdings jeweils wieder zurückgesetzt, d.h. die Suche bezieht sich immer auf den ungefilterten Originalzustand. Um das Ergebnis einer Suche weiter einzuschränken, können die Ergebnisse von getAllDocumentsByKey oder getAllEntriesByKey ihrerseits mit FTSearch durchsucht werden. Die Filterung der Ansicht durch FTSearch ist sehr durchgängig. Selbst ein wiederholtes getView in Database lädt nicht die komplette Ansicht neu. Erst ein ausdrückliches clear() in View oder das Recyceln des View-Objektes und ein nachfolgendes getView lädt die Ansicht entsprechend ihrem Originalzustand. Eine bereits gefilterte Datenbank wird nicht aktualisiert, wenn der Datenbank neue Dokumente hinzugefügt werden, die auch in der gefilterten Ansicht sichtbar sein müssten. Um solche Dokumente zu erfassen, muss sogar der Volltextindex aktualisiert werden, damit der komplette Prozess von Laden der Ansicht und Durchführen der FTSearch wiederholt werden kann. FTSearch sollte nur angewendet werden, wenn isFTIndexed() in Database true liefert, andernfalls ist die Suche ineffizient und nicht alle Funktionen der Suchsyntax liefern die erwarteten Ergebnisse, da für einige Suchoperationen ausdrücklich der Volltextindex benötigt wird. Hierzu gehören z.B. die Schlüsselwörter PARAGRAPH oder SENTENCE, mittels derer mehrere Suchwörter so verknüpft werden können, dass sie im gleichen Absatz bzw. Satz gefunden werden müssen. Für FTSearch kommt eine spezielle Syntax zum Einsatz, die über searchQuery spezifiziert wird (s. Kap. 15). •
void clear() throws NotesException Wurde eine Ansicht durch FTSearch gefiltert, kann sie durch clear wieder zu-
rückgesetzt werden, so dass das View Objekt wieder alle Dokumente anzeigt, die der SELECT Formel der Ansicht entsprechen.
10.5
ViewNavigator, View und ViewEntry
Ansichten wurden bisher als Selektionselement für Notes-Dokumente beschrieben. In Kapitel 10.2 wurde dann anhand verschiedener Methoden näher auf kategorisierte Ansichten eingegangen. Aus Sicht des Java-Objekts View gibt es nun zwei Betrachtungsweisen auf die Elemente einer Ansicht. Einerseits die bereits bekannte Betrachtung als Sammlung von Dokumenten. Folgerichtig können die Dokumente einer Ansicht über die Methoden getFirstDocument, getNextDocument etc. durch-
10.5 ViewNavigator, View und ViewEntry
385
schritten werden. Bei kategorisierten Ansichten gibt es neben den einzelnen „Zeilen“, die die Dokumente repräsentieren, noch weitere Einträge, die Kategorieüberschriften oder Spaltensummen darstellen. Hier kommen die Objekte ViewEntry und ViewNavigator zum Einsatz. Eine Ansicht besteht demnach aus einzelnen ViewEntry-Objekten, die jeweils eine Zeile repräsentieren. Ein ViewEntry kann vom Typ Dokument, Kategorie, Summe oder Speicherkonflikt sein. Dies kann anhand der Methoden isDocument(), isCategory(), isTotal(), isConflict() in ViewEntry überprüft werden. Die Ansicht V_viewNavigator_k10 aus der Beispieldatenbank hat nach Durchlauf des Beispielcodes aus Listing 10-1 – auf den Code wird später noch genauer eingegangen – insgesamt dreizehn ViewEntry-Einträge (s. Abb. 10-1). An erster Stelle befindet sich ein ViewEntry für die Kategorie „Erste Kategorie“, gefolgt von den beiden ViewEntry für die Dokumente „1. Erstes Dokument“ und „2. Zweites Dokument“ und dem Antwortdokument „Antwort auf Zweites Dokument“. Das Antwortdokument wird in dieser Ansicht besonders behandelt. Für die zweite Spalte wurde im Domino Designer (s. Abb. 10-3) die Eigenschaft „Show responses only“ aktiviert. Dies hat zur Folge, dass Werte in dieser Spalte nur für Antwortdokumente angezeigt werden. Außerdem werden für Antwortdokumente keine weiteren Spaltenwerte für weiter rechts liegende Spalten angezeigt. Alternativ zu dieser Eigenschaft der Spalte kann alternativ in einer „normalen“ Spalte mit der @Formel @IsResponseDoc zwischen Hauptdokumenten und Antwortdokumenten unterschieden werden. Diese Technik wurde in der dritten Spalte angewendet.
Abb. 10-3 Kategorisierte Ansicht im Domino Designer
386
10 View
Best Practice Verwenden Sie ViewNavigator für komplexe Navigationsoperationen innerhalb einer Ansicht. Insbesondere die „goto“-Methoden sind effizient, da sie lediglich einen Cursor bewegen, ohne aufwändig Daten laden zu müssen. Verwenden Sie getAllDocumentsByKey, wenn lediglich die Dokumente als Treffer in beliebiger Reihenfolge benötigt werden. Verwenden Sie getAllEntriesByKey, wenn die Treffer in sortierter Reihenfolge benötigt werden. Je nach Datenbasis ist die Performance dieser Methoden sehr unterschiedlich. Auch die Verwendung von FTSearch kann selbst bei der Einbeziehung von Performanceüberlegungen sinnvoll sein (s. Kap. 15).
Die Kategorisierung dieser Ansicht erfolgt über die erste Spalte. In dieser Spalte wird das Feld F_category der Dokumente angezeigt. Zusätzlich hat die Spalte die Eigenschaften „Sort: Ascending“ und „Type: Categorized“. Domino gruppiert alle Dokumente, für die das Feld F_category identisch ist. Enthält eine kategorisierte Spalte Werte, die einen Backslash „\“ enthalten, so wird die Kategorie an der Position des Backslashes geteilt und es werden mehrere Sub-Kategorien angezeigt. Dementsprechend ist das fünfte ViewEntry in dieser Ansicht vom Typ „Kategorie“, das sechste, siebte, neunte, elfte und zwölfte vom Typ Dokument und das achte und zehnte ebenfalls vom Typ Kategorie, wobei diese beiden ViewEntry die Unterkategorien anzeigen. Das dreizehnte ViewEntry ist eine Summe. Diese entsteht dadurch, dass die vierte Spalte mit dem Titel „Anzahl Docs je Kategorie“ die Eigenschaft Totals aktiviert und auf den Wert „Totals“ gesetzt wurde. Dies bewirkt, dass alle Zahlen in dieser Spalte je Kategorie summiert und angezeigt werden. Alternativ könnten als Total z.B. auch Durchschnitte und Prozente je Dokument oder Kategorie automatisch berechnet werden. Eine derartige Summenspalte ist in der programmatischen Bearbeitung über ViewEntries recht einfach zu handhaben. Für (angehende) Notes-Programmierer sei hinzugefügt, dass dies leider nicht für die @Formelprogrammierung in Ansichten gilt. Summenspalten enthalten so genannten „besonderen Text“, was nichts anderes bedeutet, als dass die Spalte zwar als Text auf dem Bildschirm angezeigt wird, aber in keiner Weise weiterverarbeitet werden kann. Dies gilt selbstverständlich nicht für die Werte des Vectors aus getColumnValues() (s.u.). In ViewEntry liefert die Methode getColumnValues() einen Vector mit den Anzeigewerten für alle Spalten in der aktuellen Zeile. Der Vector liefert für jede Spalte jeweils den angezeigten Wert als String oder den leeren String "". Dabei wird keine Spalte ausgelassen, d.h. für jede Zeile hat dieser Vector die gleiche Anzahl
10.5 ViewNavigator, View und ViewEntry
387
von Elementen. Die Kategorie wird in diesem Beispiel folgerichtig immer im ersten Element des Vectors und z.B. die Summen immer im vierten Element des Vectors angezeigt. Auch wenn in der Ansicht für die Dokumente nicht jeweils die Kategorie angezeigt wird (das war ja die Idee der Kategorisierung), liefert getColumnValues für diese Einträge im ersten Element des Vectors nochmals die Kategorie. Leider ist getColumnValues ähnlich inkonsequent für Spalten mit der Eigenschaft „Show responses only“. Für solche Spalten werden im Spaltenvektor für alle Dokumente Werte angezeigt, nicht wie im Notes Client nur für Antwortdokumente. Zusätzlich hat ViewEntry die Methode getDocument, die für Einträge, die ein Dokument repräsentieren, die Referenz auf das Document, für alle anderen aber null liefert. Eine Besonderheit stellt das Antwortdokument auf das Dokument „2. Zweites Dokument“ dar. Da für die Ansicht die Eigenschaft „Show response documents in a hierarchy“ aktiviert wurde, wird es in jedem Fall hierarchisch unterhalb des Hauptdokuments angezeigt. Dies gilt sogar, wenn das Antwortdokument eine andere Kategorie als das Hauptdokument hat. Eine weitere Besonderheit gilt für das Antwortdokument. Die Methode isConflict() liefert (leider) auch für Antwortdokumente true. Zusätzlich liefert natürlich auch die Methode isDocument() true. In der Kategorisierung stellt das Antwortdokument eine eigene Ebene dar. Gleiches gilt für Speicherkonflikte, die intern ebenfalls als Antwortdokumente behandelt werden. Die einfachste Methode mit den ViewEntries einer Ansicht zu arbeiten ist die ViewEntryCollection, die ähnlich der DocumentCollection ViewEntries als Collection sammelt. Eine ViewEntryCollection aller ViewEntry einer Ansicht liefert getAllEntries(). Diese kann mit getFirstEntry(), getLastEntry(), getNextEntry() oder getPrevEntry() durchschritten werden. In der Klasse View stehen hierfür folgende Methoden zur Verfügung: •
ViewEntry getEntryByKey(Vector vector) throws NotesException ViewEntry getEntryByKey(Vector vector, boolean exactSearch)throws NotesException ViewEntry getEntryByKey(Object dominoObject) throws NotesException ViewEntry getEntryByKey(Object dominoObject, boolean exactSearch) throws NotesException
388
10 View Sortierte Spalten in Ansichten können mit getEntryByKey durchsucht werden, wobei als Suchschlüssel String-, Number-, DateTime- oder DateRange-Objekte oder ein Vector solcher Objekte zum Einsatz kommen kann. Wird ein solches Objekt in der entsprechenden Signatur (Parameter dominoObject) direkt übergeben, so wird in der ersten Spalte danach gesucht. Wird ein Vector aus String, Number, DateTime oder DateRange übergeben, so wird in der ersten Spalte nach dem ersten Element des Vectors, in der zweiten nach dem zweiten Element des Vectors usw. gesucht.69 Zurückgegeben wird das zugehörige ViewEntry mit dem ersten Treffer und zwar anhand der Reihenfolge der Ansicht. Wurde kein Treffer gefunden, ist der Rückgabewert null. Der Parameter exactSearch ist optional und erzwingt eine exakte Übereinstimmung für die Suche. Default ist für diesen Parameter false. Die Suche ist in jedem Fall nicht abhängig von Groß- und Kleinschreibung. Bei exactSearch == false oder falls dieser Parameter weggelassen wird70, wird nach Zeilen in der Ansicht gesucht, deren Anfang mit dem Suchparameter (unabhängig von Groß- und Kleinschreibung) übereinstimmt. Wortvarianten werden bei dieser Suche ebensowenig gefunden wie Zeilen, die den Suchparameter lediglich enthalten, anstatt damit zu beginnen. Wird ein Object übergeben, so kann dies ein Objekt der Klasse String, lotus.domino.DateTime, lotus.domino.DateRange oder Number sein. Für Number-, DateTime- und DateRange-Objekte hat der Parameter exactSearch keine Bedeutung.
• •
•
69 70
ViewEntryCollection getAllEntries() throws NotesException ViewEntryCollection getAllEntriesByKey(Vector vector) throws NotesException ViewEntryCollection getAllEntriesByKey(Vector vector, boolean exactSearch) throws NotesException ViewEntryCollection getAllEntriesByKey(Object dominoObject) throws NotesException ViewEntryCollection getAllEntriesByKey (Object dominoObject, boolean exactSearch) throws NotesException Die Methoden getAllEntriesByKey arbeiten wie die entsprechenden Methoden getEntryByKey. Rückgabewert ist jedoch eine ViewEntryCollection mit allen gefundenen Treffern. Diese kann z.B. mit getFirstEntry oder getNextEntry durchschritten werden. Sie ist – anders als bei der Suche mit getAllDocumentsByKey – sortiert und zwar entsprechend der Reihenfolge der Ansicht. Wird kein Treffer gefunden, ist die ViewEntryCollection null. int getTopLevelEntryCount() throws NotesException
Es wird empfohlen, nicht mehr als 4 Spalten für die Suche zu verwenden. Eine nicht exakte Suche kann durchaus unerwartete Ergebnisse liefern. Beachten Sie, dass z.B. der Suchparameter „Dienst“ auch ein Dokument mit dem entsprechenden Spaltenwert „DIENSTAG“, nicht aber z.b. „Verdienst“ oder „verdienen“ findet.
10.5 ViewNavigator, View und ViewEntry
389
Gibt die Anzahl der Einträge in der obersten Hierarchieebene zurück. Ist die erste Spalte einer Ansicht kategorisiert, ist dies die Anzahl der Kategorien. Antwortdokumente werden hier nicht mitgezählt, auch wenn sie in flachen Hierarchien in der obersten Ebene angezeigt werden. •
int getEntryCount() throws NotesException
Liefert die Anzahl der Dokumente einer Ansicht einschließlich Antwortdokumenten. ViewEntry-Einträge für Kategorien oder Summen werden hier nicht mitgezählt. Eine Vereinfachung der Behandlung von ViewEntries stellt der so genannte ViewNavigator dar. Ein ViewNavigator wird in View über createViewNav und verwandte Signaturen erstellt. Ein ViewNavigator kann ähnlich einer ViewEntryCollection mit den Methoden getFirst, getNext oder getNth (int i) ein ViewEntry geladen werden. Zusätzlich verwaltet der ViewNavigator einen internen Cursor, der die aktuelle Position der Navigationsoperationen markiert, und kennt zusätzlich verschiedene „goto“-Methoden, z.B. gotoFirst, gotoNext, gotoPrev mit denen sich der Cursor positionieren lässt. Hierdurch kann schnell innerhalb einer Ansicht navigiert werden, ohne (!) dass das eigentliche ViewEntry oder gar Document geladen werden müsste. Dies kann dann im Nachgang duch getCurrent() erreicht werden. Ein weiterer Vorteil des ViewNavigators ist, dass er es erlaubt sehr genau innerhalb der Hierarchien einer kategorisierten Ansicht zu navigieren. Hierfür stehen z.B. für benachbarte Geschwister die Methoden getChild, getNextSibling, getPrevSibling und getParent bzw. gotoChild, gotoNextSibling, gotoPrevSibling und gotoParent zur Verfügung. Die „next“ und „previous“ Methoden stehen jeweils mit zwei Signaturen zur Verfügung, entweder ohne Parameter, wobei sie dann das nächste / vorige Element abhängig vom aktuellen Cursor liefern, oder mit einem ViewEntry als Parameter, so dass die Navigationsoperation abhängig von diesem ViewEntry vorgenommen wird. Darüber hinaus können sogar Kategorien direkt z.B. mit gotoNextCategory angesprungen werden. Insgesamt stehen in ViewNavigator folgende Methoden zur Verfügung: boolean gotoFirst() throws NotesException; boolean gotoFirstDocument() throws NotesException; boolean gotoNext() throws NotesException; boolean gotoNextCategory() throws NotesException; boolean gotoNextDocument() throws NotesException; boolean gotoPrev() throws NotesException; boolean gotoPrevCategory() throws NotesException; boolean gotoPrevDocument() throws NotesException; boolean gotoLast() throws NotesException; boolean gotoLastDocument() throws NotesException; boolean gotoPos(String hierarchie, char separatorChar) throws NotesException; boolean gotoChild() throws NotesException; boolean gotoNextSibling() throws NotesException;
390
10 View
boolean gotoPrevSibling() throws NotesException; boolean gotoParent() throws NotesException; boolean gotoEntry(Object obj) throws NotesException; boolean gotoNext(ViewEntry viewentry) throws NotesException; boolean gotoPrev(ViewEntry viewentry) throws NotesException; boolean gotoChild(ViewEntry viewentry) throws NotesException; boolean gotoNextSibling(ViewEntry viewentry) throws NotesException; boolean gotoPrevSibling(ViewEntry viewentry) throws NotesException; boolean gotoParent(ViewEntry viewentry) throws NotesException; ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry throws ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry ViewEntry throws ViewEntry throws ViewEntry
getFirst() throws NotesException; getFirstDocument() throws NotesException; getNext() throws NotesException; getNextCategory() throws NotesException; getNextDocument() throws NotesException; getPrev() throws NotesException; getPrevCategory() throws NotesException; getPrevDocument() throws NotesException; getCurrent() throws NotesException; getLast() throws NotesException; getLastDocument() throws NotesException; getPos(String hierarchy, char separatorChar) NotesException; getNth(int i) throws NotesException; getChild() throws NotesException; getNextSibling() throws NotesException; getPrevSibling() throws NotesException; getParent() throws NotesException; getNext(ViewEntry viewentry) throws NotesException; getPrev(ViewEntry viewentry) throws NotesException; getChild(ViewEntry viewentry) throws NotesException; getNextSibling(ViewEntry viewentry) NotesException; getPrevSibling(ViewEntry viewentry) NotesException; getParent(ViewEntry viewentry) throws NotesException;
Der Parameter viewentry ist immer eine Referenz, auf die die Navigation bezogen werden soll. i ist die Position, die angesprungen werden soll, obj ist entweder ein ViewEntry oder ein Document. Ein Element im ViewNavigator kann über einen Hierarchiestring mit getPos und gotoPos angesprungen werden. Als Parameter wird ein String übergeben, der für jeden Hierarchielevel die Position angibt. So wird z.B. mit dem String „3.7.1“ in der obersten Hierarchie der dritte Eintrag gewählt,
10.5 ViewNavigator, View und ViewEntry
391
dort in der darunter befindlichen Hierarchie der siebte und dort der erste. Dieser Eintrag im dritten Hierarchielevel wird zurückgegeben, entweder als ViewEntry (getPos) oder indem der Cursor an die entsprechende Stelle gesetzt wird (gotoPos). Sollte ein Eintrag nicht erreichbar sein, geben die „get“-Methoden null zurück und die „goto“-Methoden false. Neben den Methoden zur Navigation stehen in ViewNavigator noch folgende Methoden zur Verfügung: •
int getCacheSize() void setCacheSize(int numOfCachedViewEntries) throws NotesException;
Für DIIOP-Verbindungen kann die Größe des verwendeten Caches festgelegt werden. Angegeben wird die Anzahl der zu cachenden ViewEntries. Erlaubt sind laut Handbuch Werte von 0 bis 128 und Default 128. Es konnte jedoch festgestellt werden, dass Domino als Default 64 (R6.5.3) wählt und Werte von 0 bis 256 zulässt. Der Cache ist deaktiviert für numOfCachedViewEntries==0. Für lokale Sessions wird setCacheSize ignoriert und getCacheSize liefert immer 0. •
int getMaxLevel() void setMaxLevel(int maxDepth) throws NotesException
Wie beim Erzeugen eines ViewNavigator (s.u.), kann nachträglich die maximale Tiefe der Hierarchien festgelegt werden. Default ist 30. Ein Wert von 0 für maxDepth liefert die oberste Ebene. Für DIIOP-Verbindungen ist zu beachten, dass für einen ViewNavigator, der mit einer bestimmten Hierarchietiefe angelegt wurde, bestenfalls die Hierarchietiefe veringert werden kann. Ein nachträgliches Vergrößern der Hierarchietiefe hat keinen Einfluss auf die im ViewNavigator vorgefundenen ViewEntries. Bei lokalen Sessions ist auch ein nachträgliches Erweitern der Hierarchietiefe möglich, so dass der ViewNavigator nach der Erweiterung auch die tieferen Hierarchieebenen enthält. Zulässige Werte für maxDepth sind 0..30. •
View getParentView()
Liefert die Ansicht, aus der der ViewNavigator erzeugt wurde. •
int getCount()
Für lokale Sessions liefert diese Methode die Anzahl der ViewEntry im ViewNavigator. Für lokale oder DIIOP-Sessions liefert diese Methode für solche ViewNavigator, die mittels eines ViewEntry, eines Document oder einer Kategorie erzeugt wurden, die Anzahl der ViewEntry im ViewNavigator. Wurde ein ViewNavigator mit createViewNavMaxLevel und DIIOP erzeugt, liefert die Methode getCount immer die Anzahl aller Entries einer Ansicht. Ein ViewNavigator kann direkt in View erstellt werden und kann entweder auf der gesamten Ansicht, auf einem spezifizierten ViewEntry oder Document-Objekt basieren oder mittels einer benannten Kategorie erzeugt werden. Im Einzelnen sind dies folgende Methoden in View:
392
•
•
•
10 View
ViewNavigator createViewNav() throws NotesException ViewNavigator createViewNav(int numOfCachedViewEntries) throws NotesException Erzeugt einen ViewNavigator über alle Einträge einer Ansicht. Über numOfCachedViewEntries kann der Cache bei DIIOP-Verbindungen eingestellt werden (s.o. setCacheSize in ViewNavigator). ViewNavigator createViewNavMaxLevel(int maxDepth) throws NotesException ViewNavigator createViewNavMaxLevel (int maxDepth, int numOfCachedViewEntries) throws NotesException Erzeugt einen ViewNavigator über alle Einträge einer Ansicht, jedoch limitiert auf eine maximale Hierarchietiefe maxDepth. Siehe setMaxLevel in ViewNavigator. Über numOfCachedViewEntries kann der Cache bei DIIOP-Verbindungen eingestellt werden (s.o. setCacheSize in ViewNavigator). ViewNavigator createViewNavFrom(Object viewEntryOrDoc) throws NotesException ViewNavigator createViewNavFrom (Object viewEntryOrDoc, int numOfCachedViewEntries) throws NotesException Erzeugt einen ViewNavigator aller ViewEntry, die sich in der Reihenfolge der Ansicht nach einem als Parameter viewEntryOrDoc übergebenen ViewEntry oder Document befinden, einschließlich des übergebenen Objekts. Wider Erwarten arbeitet diese Methode wie folgt: Es werden alle ViewEntry in den ViewNavigator übernommen, die in der Reihenfolge der Ansicht nach
dem Objekt kommen. Die Definition ist also zu verstehen als „Ab dem Objekt“ und nicht als „Unterhalb des Objekts“. Das heißt im Klartext, dass wenn z.B. als Objekt eine ViewEntry einer Kategorie der obersten Ebene übergeben wird, nicht etwa nur diese Kategorie und deren abhängige enthalten sind, sondern alle (!) Einträge, die nach diesem ViewEntry entsprechend der Reihenfolge der Ansicht folgen. Sollen nur Einträge mit höherem Level unterhalb eines bestimmten Objektes selektiert werden, müssen die Methoden createViewNavFromChildren oder createViewNavFromDescendants verwendet werden. Diese Methode ist geeignet alle Einträge einer Ansicht ab einem bestimmten Punkt auszuwählen. Über numOfCachedViewEntries kann der Cache bei DIIOP-Verbindungen eingestellt werden (s.o. setCacheSize in ViewNavigator). •
ViewNavigator createViewNavFromChildren (Object viewEntryOrDoc) throws NotesException ViewNavigator createViewNavFromChildren (Object viewEntryOrDoc, int numOfCachedViewEntries) throws NotesException Erzeugt einen ViewNavigator mit allen ViewEntry, die sich im Level direkt
unterhalb eines Objektes befinden. Das als Parameter übergebene Objekt wird nicht mit in den ViewNavigator aufgenommen.
10.5 ViewNavigator, View und ViewEntry
393
Diese Methode ist geeignet, in einer Ansicht alle primären Einträge einer Kategorie oder alle direkten Antwortdokumente eines Dokuments ohne eventuelle Subkategorien oder weitere Antwort-aufAntwort-Dokumente zu wählen. Über numOfCachedViewEntries kann der Cache bei DIIOP-Verbindungen eingestellt werden (s.o. setCacheSize in ViewNavigator). •
ViewNavigator createViewNavFromDescendants (Object viewEntryOrDoc) throws NotesException ViewNavigator createViewNavFromDescendants (Object viewEntryOrDoc, int numOfCachedViewEntries) throws NotesException Erzeugt einen ViewNavigator mit allen ViewEntry, die sich im Level unterhalb eines Objektes befinden, einschließlich aller ViewEntry unterhalb dieser Einträge. Das als Parameter übergebene Objekt wird nicht mit in den ViewNavigator aufgenommen.
Diese Methode ist geeignet, in einer Ansicht alle primären Einträge einer Kategorie oder alle Antwortdokumente eines Dokuments einschließlich eventueller Subkategorien oder weiterer Antwort-aufAntwort-Dokumente zu wählen. Über numOfCachedViewEntries kann der Cache bei DIIOP-Verbindungen eingestellt werden (s.o. setCacheSize in ViewNavigator). •
ViewNavigator createViewNavFromCategory (String categoryName) throws NotesException ViewNavigator createViewNavFromCategory (String categoryName, int numOfCachedViewEntries) throws NotesException Diese Methode arbeitet wie createViewNavFromDescendants, wobei statt ei-
nes Objektes eine benannte Kategorie übergeben wird. Sub-Kategorien, die durch die Verwendung eines Backslash „\“ erzeugt wurden, können mit dieser Methode nicht selektiert werden. In Listing 10-1 werden verschiedene ViewNavigator-Objekte erzeugt und ihr Verhalten demonstriert. Um die Demonstration reproduzierbar zu machen, werden nach dem anfänglichen Erzeugen von Session, Database und View über die Me-
package djbuch.kapitel_10; import lotus.domino.*; import djbuch.kapitel_06.*; public class ViewNavigatorDemo extends NotesThread { private static final String PASSWORD = "geheim"; // DIIOP-Server u. -User, null für lokale Session private static final String HOST = null; private static final String USER = null; public static void main(String[] args) { ViewNavigatorDemo vnd = new ViewNavigatorDemo(); vnd.start(); } public void runNotes() { View view = null; Session session = null; ViewNavigator viewNav = null; try {
394
10 View session = NotesFactory.createSession(HOST, USER, PASSWORD); db = session .getDatabase(session.getServerName(), "djbuch/djbuch.nsf"); view=db.getView ("V_viewNavigator_k10"); setup(); //Demo 1 - Gesamte Ansicht per createViewNav System.out.println (LINE+"Gesamte Ansicht: "); view.refresh(); viewNav = view.createViewNav(); printNavigator(viewNav); viewNav = GC.recycle (viewNav); System.out.println ("... done ... \n\n"); //Demo 2 for (int i = 0; i < 3; i++) { System.out.println (LINE+"Nur bis Level " + i); viewNav = view.createViewNavMaxLevel(i); printNavigator(viewNav); viewNav=GC.recycle (viewNav); } System.out.println ("... done ... \n\n"); //Demo 3 - 5 demoChildrenDescendants ("createViewNavFrom", view,viewNav,TYPE_FROM); demoChildrenDescendants ("crViewNavChild", view,viewNav,TYPE_CHILDREN); demoChildrenDescendants ("crViewNavDesc", view,viewNav, TYPE_DESCENDANTS); //Demo 6 for (int i = 0; i < SUB_CATEGORIE.length; i++) { System.out.println (LINE+"Navigator der Kategorie " + SUB_CATEGORIE[i]); viewNav=view.createViewNavFromCategory(SUB_CATEGORIE[i]); printNavigator(viewNav); viewNav=GC.recycle (viewNav); } System.out.println ("... done ... \n\n"); } catch (Exception e) { e.printStackTrace(); } finally { GC.recycle (view); GC.recycle (viewNav); GC.recycle (db); GC.recycle (session); }
} private static void setup() { Document doc = null; Document respDoc = null; try { cleanup(); doc = createDoc ("Erste Kategorie", "1. Erstes Dokument"); GC.recycle (doc); doc = createDoc ("Erste Kategorie", "2. Zweites Dokument"); respDoc = createDoc ("Andere Kategorie", "Antw auf Zweites Dokument", doc); GC.recycle (doc); GC.recycle (respDoc); doc = createDoc ("Zweite Kategorie\\Sub eins", "3. Drittes Dokument"); GC.recycle (doc); ... doc = createDoc ("Zweite Kategorie", "6. Sechstes Dokument"); GC.recycle (doc); ... System.out.println ("Dokumente erzeugt."); } catch (NotesException e) { e.printStackTrace(); } finally {
10.5 ViewNavigator, View und ViewEntry GC.recycle (doc); GC.recycle (respDoc); } } private static Document createDoc (String cat, String title, Document parent) throws NotesException { Document doc = db.createDocument(); doc.replaceItemValue ("Form", "FO_dokument_k6"); doc.replaceItemValue ("F_Category", cat); doc.replaceItemValue ("F_title", title); doc.replaceItemValue ("F_marker_k6",new Integer (1)); if (parent != null) { doc.makeResponse(parent); } doc.save (true,false); return doc; } private static Document createDoc (String cat, String title) throws NotesException { return createDoc (cat, title, null); } public static final void printNavigator (ViewNavigator nav) { ViewEntry entry = null, nextEntry = null; try { entry = nav.getFirst(); while (entry != null) { if (entry.isDocument()) { System.out.println ("\tDokument: " + entry.getDocument().getItemValueString("F_title")); } if (entry.isCategory()) { System.out.println ("Kategorie: " + entry.getColumnValues().elementAt(0)); } if (entry.isTotal()) { System.out.println ("Total: " + entry.getColumnValues().elementAt(3)); } if (entry.isConflict()) { System.out.println ("\t\tSpeicherkonflikt: " + entry.getDocument().getItemValueString("F_title")); } nextEntry = nav.getNext(); GC.recycle(entry); entry=nextEntry; } } catch (NotesException e) { e.printStackTrace(); } finally { GC.recycle (entry); GC.recycle (nextEntry); } } private static final void demoChildrenDescendants (String msg, View view, ViewNavigator viewNav, int type) throws NotesException { //Demo A - Getestete Elemente sind ViewEntries System.out.println (LINE + msg + " - Demo A"); for (int i = 0; i < SUB_ENTRIES.length; i++) { switch (type) { case (TYPE_FROM): System.out.println (LINE+"Unterhalb von Entry " + SUB_ENTRIES[i]); viewNav = view.createViewNavFrom (getElement (view,SUB_ENTRIES[i]));
395
396
10 View break; case (TYPE_CHILDREN): System.out.println (LINE+"Children unterhalb von Entry " + SUB_ENTRIES[i]); viewNav = view.createViewNavFromChildren( getElement (view,SUB_ENTRIES[i])); break; case (TYPE_DESCENDANTS): System.out.println (LINE+"Descendants unterhalb von Entry " + SUB_ENTRIES[i]); viewNav = view.createViewNavFromDescendants( getElement (view,SUB_ENTRIES[i])); break; default: throw new NotesException (999,"Fehlerhafter Type."); } printNavigator(viewNav); viewNav=GC.recycle (viewNav); } //Demo B - Getestete Elemente sind Documents System.out.println (LINE + msg + " - Demo B"); for (int i = 0; i < SUB_DOCS.length; i++) { switch (type) { case (TYPE_FROM): System.out.println (LINE+"Unterhalb von Document " + SUB_DOCS[i]); viewNav = view .createViewNavFrom(getDocument (SUB_DOCS[i])); break; case (TYPE_CHILDREN): System.out.println (LINE+"Children unterhalb von Document " + SUB_DOCS[i]); viewNav = view .createViewNavFromChildren(getDocument (SUB_DOCS[i])); break; case (TYPE_DESCENDANTS): System.out.println (LINE+"Descendants unterhalb v. Document "+SUB_DOCS[i]); viewNav = view.createViewNavFromDescendants( getDocument (SUB_DOCS[i])); break; default: throw new NotesException (999,"Fehlerhafter Type."); } printNavigator(viewNav); viewNav=GC.recycle (viewNav); } System.out.println ("... done ... \n\n");
} private static final ViewEntry getElement(View view, String key){ ViewEntry entry = null, nextEntry = null; ViewNavigator nav = null; try { nav=view.createViewNav(); entry = nav.getFirst(); while (entry != null) { if (entry.isDocument()) { String title = entry.getDocument() .getItemValueString("F_title"); if (title.equalsIgnoreCase(key)) { return (entry); } } if (entry.isCategory()) { String category = entry
10.5 ViewNavigator, View und ViewEntry
397
.getColumnValues().elementAt(0).toString(); if (category.equalsIgnoreCase(key)) { return (entry); } } nextEntry = nav.getNext(); GC.recycle(entry); entry=nextEntry; } } catch (NotesException e) { e.printStackTrace(); } finally { GC.recycle (nav); } GC.recycle (entry); GC.recycle (nextEntry); return null; } private static final Document getDocument (String title) throws NotesException { View lookup = null; Document myDoc = null; try { lookup = db.getView("V_lookup_k6"); myDoc = lookup.getDocumentByKey(title.toLowerCase()); return myDoc; } finally { if (lookup!=null) { lookup.recycle(); } } } private static final void cleanup () throws NotesException { DocumentCollection col = null; try { col = db.search ("F_marker_k6=1"); System.out.println ("Aufräumen: Lösche " + col.getCount() + " Dokumente."); col.removeAll(true); } finally { if (col!=null) { col.recycle(); } } } private static Database db = null; private static final String LINE = "***************************\n"; private static final String[] SUB_ENTRIES = {"Zweite Kategorie","Sub zwei","1. Erstes Dokument"}; private static final String[] SUB_DOCS = {"2. Zw. Dokument","3. Dr. Dokument","6. Sechstes Dokument"}; private static final String [] SUB_CATEGORIE = {"Erste Kategorie", "Zweite Kategorie\\Sub eins", "Sub eins"}; private static final int TYPE_FROM = 0; private static final int TYPE_CHILDREN = 1; private static final int TYPE_DESCENDANTS = 2; }
Listing 10-1 Verhalten verschiedener ViewNavigator-Objekte
398
10 View
alte Testdokumente gelöscht und neue Dokumente angelegt, die thode setup() in der Ansicht V_viewNavigator_k10 in einer hierarchischen Struktur dargestellt werden. In setup() werden neue Dokumente über createDoc erstellt und über das Item F_Category unterschiedlichen Kategorien zugeordnet. Das Item F_title wird mit einem Titel belegt, damit später die Dokumente über diesen Titel wieder selektiert werden können, um als Wurzel für einen ViewNavigator zur Verfügung zu stehen. Das Item F_marker_k6 wird mit dem Wert 1 belegt, um bei jedem Aufräumen durch cleanup diese Testdokumente selektieren zu können. In setup werden zwei Arten von Dokumenten erstellt. Zum einen werden einfache Dokumente erstellt, die lediglich aus den Items für Kategorie und Titel bestehen . Die Kategorie selbst (und das zugehörige ViewEntry) wird nicht ausdrücklich erstellt, sondern entsteht durch die Spalteneigenschaft der Ansicht, die die Felder F_Category der Dokumente kategorisiert anzeigt (s. auch Kap. 3.3.2). Zusätzlich wird als Beispiel eines Antwortdokuments ein solches erstellt , wobei createDoc angewiesen wird, das Antwortdokument als Antwort auf einen zuvor selektierten Parent (Hauptdokument) zu beziehen. Hierfür kommt die Methode makeResponse in Document zum Einsatz . Im gesamten Beispielcode wird bereits eine bisher noch nicht eingeführte Klasse „GC“ verwendet (nicht zu verwechseln mit der Klasse sun.misc.GC). Sie wird ausführlich im Kapitel über das Recycling erläutert und kapselt im Wesentlichen die recycle-Aufrufe der Domino-Java-Objekte. In der Methode setup kommt eine der wichtigsten Regeln beim Recycling zum Tragen: Jedes Domino-Java-Objekt, das aufgrund einer Neu-Zuweisung seine Referenz auf das ursprüngliche Domino-Objekt verliert (subsequente Zuweisungen), muss recycelt werden. Die Variable doc wird in setup verwendet, um neue Dokumente über createDoc zu erzeugen. Jeder neue Aufruf von createDoc und die Zuweisung zu doc macht die Referenz auf das zuvor erzeugte Document (aus Sicht von Java) ungültig, muss also recycelt werden. Für das zu erzeugende Antwortdokument werden Antwortdokument und Hauptdokument gleichzeitig benötigt und können erst nach Erstellung des Antwortdokuments recycelt werden . Nachdem nun die benötigten Beispieldokumente erzeugt wurden, muss das View-Objekt aktualisiert werden, das vor dem Erstellen der Dokumente erzeugt wurde. Die erste Demonstration erzeugt mittels createViewNav() in View einen ViewNavigator über die gesamte Ansicht und gibt diese mit der Hilfsmethode printNavigator aus. Die Ausgabe zeigt alle ViewEntry der Ansicht einschließlich der Kategorien des Antwortdokuments und der Summenspalte und ähnelt der Darstellung im NotesClient (s. Abb. 10-1): Kategorie: Erste Kategorie Dokument: 1. Erstes Dokument Dokument: 2. Zweites Dokument Dokument: Antwort auf Zweites Dokument Kategorie: Zweite Kategorie Dokument: 6. Sechstes Dokument Dokument: 7. Siebtes Dokument
10.5 ViewNavigator, View und ViewEntry Kategorie: Sub Dokument: 3. Kategorie: Sub Dokument: 4. Dokument: 5. Total: 7.0
399
eins Drittes Dokument zwei Viertes Dokument Fünftes Dokument
Auch der ViewNavigator muss, sofern er nicht mehr benötigt wird, recycelt werden. Um spätere Fehler zu vermeiden liefert die Methode recycle in GC immer null zurück, so dass ein Objekt einfach in einem Zug recycelt und null gesetzt werden kann . Dies hilft Fehler zu vermeiden, da NullPointerExceptions leicht erkannt werden können, jedoch das Verhalten bei der (versehentlichen) Benutzung von recycelten Objekten nicht vorhersehbar ist. Die zweite Demonstration zeigt die Verwendung von Navigatoren, für die die Hierarchietiefe beschränkt ist. Für i == 0 ist hier z.B. erkennbar, dass lediglich die oberste Ebene, also die ViewEntry für die beiden Kategorien „Erste Kategorie“ und „Zweite Kategorie“ ausgegeben wird. Die Beispiele 3 bis 5 wurden zur Vereinfachung in der Methode demoChildrenDescendants gekapselt und demonstrieren das Verhalten der Methoden createViewNavFrom, createViewNavFromChildren und createViewNavFromDescendants und zwar jeweils für die beiden Möglichkeiten, als Parameter ein ViewEntry oder ein Document zu übergeben. Der Parameter type bestimmt, welche der drei Methoden zur Erzeugung eines ViewNavigator verwendet werden soll . Für die Demonstration A, für die ein ViewEntry als Wurzel zum Einsatz kommen soll, wird dieses mit einer Hilfsmethode getElement erzeugt . Diese Methode durchschreitet einen neu erzeugten ViewNavigator über alle Elemente einer Ansicht . Wird ein Dokument gefunden, dessen Titel (Feld F_title ) mit dem Parameter key übereinstimmt, so wird das korrespondierende ViewEntry zurückgegeben. Wird eine Kategorie mit diesem Namen gefunden, so wird dieses ViewEntry zurückgegeben. Die Methode getElement ist vorteilhaft für die gewählte Art der Demonstration, da über die statischen Arrays SUB_ENTRIES Kategorien und Dokumente gezielt angesprochen werden können. Variieren Sie die Einträge in SUB_ENTRIES , um das Verhalten der createViewNav...-Methoden für die verschiedenen Elemente einer Ansicht zu testen und zu verstehen. Die Demonstration B in demoChildrenDescendants arbeitet ähnlich der Demonstration A, jedoch werden Dokumente als Wurzel verwendet. Auch hier kommt ein Array zum Einsatz, um die verschiedenen Szenarien leicht durchspielen zu können, jedoch werden als Parameter Objekte der Klasse Document übergeben . Diese werden anhand ihres Titels in der Methode getDocument ausgewählt. Diese Methode ist übrigens ein typisches Beispiel für die Selektion von Dokumenten über eine Ansicht und getDocumentByKey. Beachten Sie, dass die Methode zwar im finally den View recycelt, nicht aber das Document. Dieses wird noch als Rückgabewert benötigt. Durch getDocument erzeugte Document-Objekte müssen von der aufrufenden Methode recycelt werden.
400
10 View
Anhand der Ausgaben der Demonstrationen 3 bis 5 lässt sich erkennen, dass es keinen Unterschied macht, ob als Wurzel für einen ViewNavigator ein Document oder das dieses Document repräsentierende ViewEntry als Parameter für die createViewNav...-Methoden verwendet wird. Die Ausgaben verdeutlichen das unterschiedliche Verhalten der verschiedenen createViewNav...-Methoden. createViewNavFrom zeigt alle einträge ab einer bestimmten Stelle und gibt dementsprechend z.B. für das Dokument mit Titel „2. Zweites Dokument“ aus: Unterhalb von Document 2. Zweites Dokument Dokument: 2. Zweites Dokument Dokument: Antwort auf Zweites Dokument Kategorie: Zweite Kategorie Dokument: 6. Sechstes Dokument Dokument: 7. Siebtes Dokument Kategorie: Sub eins Dokument: 3. Drittes Dokument Kategorie: Sub zwei Dokument: 4. Viertes Dokument Dokument: 5. Fünftes Dokument Total: 7.0
Sollen nur die Kinder eines Dokuments (Antwortdokumente) oder einer Kategorie (Elemente dieser Kategorie) erreicht werden, wird createViewNavFromChildren benötigt, wobei nur die direkten Nachkommen angezeigt werden, z.B.: Children unterhalb von Document 2. Zweites Dokument Dokument: Antwort auf Zweites Dokument
Sollen alle Elemente unterhalb eines Eintrages angezeigt werden, kommt createViewNavFromDescendants zum Einsatz, z.B.: Descendants Dokument: Dokument: Dokument:
unterhalb von Entry Erste Kategorie 1. Erstes Dokument 2. Zweites Dokument Antwort auf Zweites Dokument
Für Kategorien kann anstelle von createViewNavFromDescendants auch createViewNavFromCategory verwendet werden . Wie die Ausgabe der im Beispiel verwendeten Schleife zeigt, können hierbei keine Sub-Kategorien als Parameter verwendet werden: Navigator der Kategorie Zweite Kategorie\Sub eins Dokument: 6. Sechstes Dokument Dokument: 7. Siebtes Dokument Kategorie: Sub eins Dokument: 3. Drittes Dokument Kategorie: Sub zwei Dokument: 4. Viertes Dokument
10.6 Locking
401
Dokument: 5. Fünftes Dokument
Am Ende der Demonstration müssen natürlich noch die verwendeten Domino-Objekte dem Recycling zugeführt werden . Die erzeugten Dokumente werden erst beim nächsten Start und Aufruf von setup gelöscht , so dass Sie zur Begutachtung zur Verfügung stehen.
10.6
Locking
Das Design Locking für einen View arbeitet analog den in Kapitel 7.7 beschriebenen Mechanismen des Document Locking. Lediglich muss die Datenbank statt für Document Locking für Design Locking aktiviert sein (isDesignLockingEnabled in Database). Ein typischer Anwendungsfall für das Design Locking sind Anwendungen, die Gestaltungsänderungen an Agent, View oder Form vornehmen und z.B. eine Versionskontrolle oder Check-in/check-out-Mechanismen etablieren. In View wie in Agent und Form stehen für das Locking die folgenden Methoden zur Verfügung: • •
•
•
Vector getLockHolders() throws NotesException boolean lock() throws NotesException boolean lock(boolean acceptProvisional) throws NotesException boolean lock(String lockHolder) throws NotesException boolean lock(String lockHolder, boolean acceptProvisional) throws NotesException boolean lock(Vector lockHolders) throws NotesException boolean lock(Vector lockHolders, boolean acceptProvisional) throws NotesException boolean lockProvisional() throws NotesException boolean lockProvisional(String lockHolder) throws NotesException boolean lockProvisional(Vector lockHolders) throws NotesException void unlock() throws NotesException
10.7
DocumentCollection und ViewEntryCollection
Die Sammlungen DocumentCollection und ViewEntryCollection wurden bereits vielfach als ResultSet von Lookup-Operationen vorgestellt. Sie dienen dazu, Untermengen von Dokumenten der Datenbank oder aus Ansichten zu sammeln, können aber auch aus verschiedenen vordefinierten Dokumentenmengen, wie getAllDocuments in Database, getAllEntries in View oder getUnprocessedDocuments in AgentContext erzeugt werden.
402
10 View
Best Practice DocumentCollection wird verwendet, wenn unsortierte Dokumentensammlungen benö-
tigt werden, die nicht durch Ansichten abgebildet sind. ViewEntryCollection wird verwendet, wenn sortierte Dokumentensammlungen oder
Spaltenberechnungen benötigt werden. stampAll ist die Methode der Wahl, wenn für eine Sammlung von Dokumenten gleichzeitig
ein einzelnes Item auf einen bestimmten Wert gesetzt werden soll. View wird verwendet, wenn eine Ansicht zur Verfügung steht, die die benötigten Dokumente
bereits vorselektiert enthält. Achtung bei der Veränderung von Dokumenten und beim Einsatz von Zeitberechnungen. setAutoUpdate(false) und isAutoUpdate verwenden! ViewNavigator wird verwendet, wenn Unterkategorien selektiert werden müssen, oder
wenn nicht nur Dokumente, sondern auch ViewEntries für Kategorien oder Summen referenziert werden müssen.
Die beiden ähneln sich zwar sehr stark, unterscheiden sich aber dennoch in der Anwendung. Je nachdem, ob die Spalten und deren Spaltenberechnungen einer Ansicht oder direkt die Dokumente benötigt werden, kann zwischen DocumentCollection und ViewEntryCollection gewählt werden. In der Regel werden beide nicht direkt erzeugt, sondern sind das Ergebnis von Suchoperationen bzw. stellen die Repräsentation bestimmter vordefinierter Mengen dar (z.B. getAllEntries in View). Der typische Anwendungsfall ist die Suche über einen Ansichtenindex, der immer über die Suche über sortierte Spalten mit den Methoden getAllEntriesByKey oder getAllDocumentsByKey durchgeführt wird (s. Kap. 10.4f und 15). Zunächst ist die Verwendung via getAllEntriesByKey oder getAllDocumentsByKey bzw. getEntryByKey oder getDocumentByKey gleichermaßen einfach und aus Performancegesichtspunkten absolut gleichwertig, sofern man nur den Zeitaufwand für die Durchführung dieser Suchen betrachtet. Allerdings ist sehr genau zu unterscheiden, welche Daten tatsächlich benötigt werden und welcher Art und Menge die durchsuchten Daten sind. Werden sortierte Resultsets benötigt, oder sollen Spaltenwerte aus der zu durchsuchenden Ansicht und insbesondere die Ergebnisse der dort angewendeten Spaltenberechnungen gelesen werden, kommt nur die ViewEntryCollection in Betracht, da diese ein Abbild einer Teilmenge von Elementen (Zeilen) einer Ansicht darstellt und getAllEntriesByKey im Gegensatz zu der Suche via getAllDocumentsByKey entsprechend der Reihenfolge der durchsuchten Ansicht sortierte Ergebnisse liefert.
10.7 DocumentCollection und ViewEntryCollection
403
Für beide gilt, dass die Referenz auf die enthaltenen Dokumente zur Verfügung steht, bei der ViewEntryCollection indirekt über die Methode getDocument() in ViewEntry. Anders als ein ViewNavigator enthält eine ViewEntryCollection keine Einträge für Kategorien oder Summen. Um die Elemente einer ViewEntry- oder DocumentCollection zu durchschreiten, stellen beide entsprechende Funktionen wie getFirstEntry, getFirstDocument und getNextEntry oder getNextDocument zur Verfügung. Hier jedoch zeigen sich deutliche Performanceunterschiede. Um an die eigentlichen Daten, also an die Items der Dokumente des Resultsets heranzukommen, werden diese bei der Verwendung von DocumentCollection wie gewohnt über getItemValue usw. aus den zurückgegebenen Dokumenten geladen. Bei der ViewEntryCollection können alle Spaltenwerte gleichzeitig über getColumnValues als Vector bezogen und wie in Vector üblich, über elementAt referenziert werden. Versuche haben gezeigt, dass das reine Durchschreiten und das Laden eines einzigen Items bzw. Spaltenwertes einer ViewEntryCollection rund doppelt so viel Zeit in Anspruch nimmt wie das Durchschreiten einer DocumentCollection. Werden jedoch viele Items des jeweiligen Document benötigt und müssen diese jeweils mit getItemValue oder einer verwandten Methode geladen werden, verschiebt sich das Verhältnis mit der Anzahl der zu lesenden Items in Richtung der ViewEntryCollection. Im Versuchsaufbau war ein Gleichstand ungefähr bei 10 bis 20 Items, die gelesen werden sollten, erreicht. Diese Angaben sollten aber weniger als absolute Werte dienen, sondern lediglich einen Anhaltspunkt liefern, welche Voraussetzungen welchen Einflus auf die Performance haben. Das direkte Durchschreiten eines Views ist übrigens aus Performancesicht ungefähr vergleichbar mit dem Durchschreiten einer DocumentCollection. Bei diesen Überlegungen sind zwei weitere Punkte zu berücksichtigen, zumindest bei ressourcenkritischen Anwendungen. Ansichten belegen hauptsächlich Speicher, der von Domino verwaltet wird, also außerhalb der Reichweite von Java, so dass dieser nicht direkt durch Ihre Anwendung kontrolliert werden kann. Je komplexer Ansichten aufgebaut sind, desto größer wird der Hauptspeicherbedarf für eine solche Ansicht sein. Zusätzlich ist zu beachten, dass aufwändige Spaltenberechnungen die Laufzeit negativ beeinflussen werden. Finden sich Zeitberechnungen in den Spalten, muss Domino ständig seine Indizes aktualisieren, da sich die Zeit ja bekanntlich regelmäßig ändert :-). Werden solche Berechnungen benötigt, macht es unter Umständen Sinn, entsprechende Daten redundant in den Dokumenten zu speichern und eventuell regelmäßig durch einen periodischen Agenten aktuell zu halten. Zusammenfassend kann festgehalten werden, dass für die ViewEntryCollection die Verarbeitungsdauer mit der Anzahl der Spalten in der Ansicht zunimmt, wobei dieser Einfluss verhältnismäßig gering ist. Für die DocumentCollection nimmt die Verarbeitungszeit zu, wenn viele Items in den gefundenen Dokumenten gelesen werden sollen. Zusätzlich wird dieses Lesen der Items durch die Anzahl der Items in einem Dokument, mit steigender Anzahl negativ, beeinflusst, ebenso wie die Datenmenge in den Items einen Einfluss auf die Verarbeitungsgeschwindigkeit hat. Eine entsprechen-
404
10 View
de Gegenüberstellung der einzelnen Faktoren wurde in Tabelle 10-1 zusammengestellt. Wenn Sie sich für die praktische Seite dieser Übersicht interessieren, können Sie sich die Klasse CollectionComparison im Package djbuch.kapitel_10 näher ansehen. Diese ist ähnlich wie die SearchComparison aus Kapitel 15.7 aufgebaut und führt verschiedene Loop-Operationen über Ansichten und Collections aus. Bisher wurden DocumentCollection und ViewEntryCollection als Ergebnismenge vorgestellt. Darüber hinaus gibt es die Möglichkeit diesen Collections Elemente hinzuzufügen oder zu entfernen. Mit einem kleinen Trick ist es sogar möglich, sowohl die ViewEntryCollection als auch die DocumentCollection neu zu erstellen, so dass unabhängig von Abfragen manuell Dokumente hinzugefügt oder entfernt und somit frei definierte Zusammenstellungen von Dokumenten erstellt werden können. Eine leere DocumentCollection erhalten Sie, wenn das Ergebnis einer Suche kein Ergebnis liefert. Am einfachsten lässt sich dies mit dem Code emptyDC = myDatabase.search("1=0"); realisieren, da eine Suche ohne Ergebnisse automatisch eine leere DocumentCollection zurückgibt. Ähnliches gilt für die ViewEntryCollection, die aber immer von einer Ansicht abhängt. Hier muss eine Suche nach einem sicher nicht vorliegenden Spaltenindex in der Art emptyViewEntryCollection = view.getAllEntriesByKey("~~~DiesenSpaltenWertGibtEsNicht~~~",true); durchgeführt werden. Nun können via addDocument(Document doc) bzw. addEntry (Object viewEntryOrDoc) Einträge zu diesen neuen leeren Collections hinzugefügt werden, wobei der Parameter von addEntry ein ViewEntry oder ein Document sein kann. Interessant ist hierbei, dass ein auf diese Weise an eine ViewEntryCollection angefügtes Dokument wie alle Elemente der ViewEntryCollection behan-
delt wird, einschließlich der dort definierten Spaltenberechnungen, die sich dann in getColumnValues niederschlagen. Voraussetzung ist, dass das angefügte Element auch über die Ansicht gefunden würde, sonst führt addEntry keine Aktion durch.
Werden Dokumente bzw. ViewEntries aus den Collections entfernt, ist es notwendig, dass die zugehörigen Java-Objekte direkt aus der Collection erzeugt wurden. Wird dies nicht beachtet, wird eine entsprechende NotesException geworfen. Dementsprechend ist folgender Code nicht zulässig: doc=getDocFromSomeWhere(); dc.addDocument(doc); dc.deleteDocument (doc);
Stattdessen muss in der dritten Zeile das zu entfernende Dokument über die DocumentCollection neu geladen werden, z.B. über einen der Befehle getFirstDocument, getNthDocument etc. Ein einfaches Beispiel sieht (verkürzt) wie folgt aus: db = session.getDatabase("", "djbuch/djbuch.nsf"); dc = db.search("1=0"); doc = db.getAllDocuments().getLastDocument(); System.out.println (dc.getCount() + "/" +
10.7 DocumentCollection und ViewEntryCollection
405
Tabelle 10-1 DocumentCollection und ViewEntryCollection im Vergleich
Primäres Anwendungsgebiet
Ergebnisse entsprechend der Ansicht sortiert
Zugriff auf Document und damit alle Items Zugriff auf Kategorien oder Summen Erstellung unabhängig einer Ansicht möglich
Spaltenberechnungen möglich
Kann ein einzelnes Item aller enthaltenen Dokumente ändern Hinzufügen und Entfernen von Einträgen möglich Suche per getAllEntriesByKey/getAllDocumentsByKey Nettozeit für das Durchschreiten der Collection
DocumentCollection
ViewEntryCollection
Schnelle Referenz auf (in der Regel) unsortierte Ergebnismengen Nein, nur falls die DocumentCollection Ergebnis einer FTSearch ist, werden die Ergenisse entsprechend der SortOptions, bzw. nach Score sortiert Ja
Referenz auf sortierte Ergebnismengen oder auf Spaltenberechnungen Ja
Nein
Nein
Ja. Einige vordefinierte DocumentCollections wie z.B. getAllDocuments in Database stehen zur Verfügung Nein, es stehen nur die Werte der Items der gefundenen Dokumente zur Verfügung
Nein
Ja, per stampAll()
Ja, es stehen sowohl die Werte der Items, als auch die Spaltenwerte der Ansicht zur Verfügung Ja, per stampAll()
Ja
Ja
performant
performant
performant
performant, aber selbst bei wenigen Spalten in der Ansicht langsamer als bei DocumentCollection leicht negativer Einfluss
Einfluss auf Performance mit kein Einfluss zunehmender Anzahl der Spalten in der Ansicht Einfluss auf Performance mit negativ, wg. Einfluss von zunehmender Anzahl der zu getItemValue lesenden Items der gefundenen Dokumente
Einfluss auf Performance durch Größe der Items im Dokument / in der Ansichtenspalte
Ja
negativ
kein Einfluss, sofern auf die Ansichtenspalten per getColumnValues zurückgegriffen wird. Wird über getDocument das Dokument geladen, kommen hier natürlich wie für die DocumentCollection die Zeiten für getItemValue hinzu. kaum. Die Anzeige in Spalten ist allerdings auf 32.000 Zeichen je Eintrag limitiert.
406
10 View
db.getAllDocuments().getCount()71); dc.addDocument(doc); System.out.println (dc.getCount() + "/" + db.getAllDocuments().getCount()); dc.deleteDocument(dc.getFirstDocument()); System.out.println (dc.getCount() + "/" + db.getAllDocuments().getCount()); view = db.getView("V_lookup_k6"); otherView = db.getView("V_lookup_wide_k6"); vec = view.getAllEntriesByKey("~~~gibtsnicht~~~",true); System.out.println (vec.getCount()); vec.addEntry (otherView.getAllEntries().getFirstEntry()); vec.addEntry (doc); System.out.println (vec.getCount()); vec.deleteEntry(vec.getFirstEntry()); System.out.println (vec.getCount());
Mit dieser Technik können also Domino-Dokumente in Collections gesammelt und gewissermaßen im Hauptspeicher zwischengespeichert werden. Allerdings sind sie weiterhin an die hiermit verbundene Session gebunden. Aus Sicht von Java ist dies in einigen Situationen nicht befriedigend oder zumindest nicht ausreichend, insbesondere wenn Daten gecached oder über mehrere Threads referenziert werden müssen. Da Domino-Objekte ohnehin nicht über mehrere Threads gültig sind, sind hier andere Techniken notwendig, die in Kapitel 13.2 und 13.3 vorgestellt werden. Neben den beschriebenen Einsatzgebieten gibt es noch einige praktische Methoden, die die Arbeit mit Sammlungen von Dokumenten erleichtern. Dies sind vor allem die Methoden stampAll, mit der ein einzelnes Item der gesamten Sammlung in einem Schritt auf einen bestimmten Wert gesetzt werden kann, putAllInFolder und removeAllFromFolder, mit denen Referenzen der Dokumente in einen Ordner gelegt werden können (s. Kap. 3.3) und removeAll, mit der sämtliche Dokumente der Collection auf einmal und endgültig aus der Datenbank gelöscht werden. Einen Überblick über sämtliche Methoden in DocumentCollection und ViewEntryCollection finden Sie im Folgenden. Alle Methoden stehen übrigens für beide Klassen identisch bzw. analog zur Verfügung.
10.7.1 •
Methoden in DocumentCollection
int getCount() throws NotesException Liefert die Anzahl der Elemente in der DocumentCollection und 0, falls die
Collection keine Elemente enthält.
71
Der hier verwendete wiederholte Aufruf von getAllDocuments ist nicht performant und wurde hier nur der Einfachheit halber gewählt.
10.7.1 Methoden in DocumentCollection •
• •
•
407
String getQuery() throws NotesException Gibt die Suchquery zurück, mittels derer eine DocumentCollection entstanden ist. So z.B. für search oder FTSearch in Database, aber auch für FTSearch in DocumentCollection. Falls keine Suche im eigentlichen Sinne oder ein Lookup ohne Suchquery zugrunde lag, wie z.B. bei getAllDocumentsByKey, wird ein leerer String zurückgegeben. Database getParent() throws NotesException Liefert das Database Objekt, in dem die DocumentCollection erstellt wurde. Document getFirstDocument() throws NotesException Die Klasse DocumentCollection verwaltet einen internen Cursor, der die Position innerhalb der Collection anzeigt. getFirstDocument gibt das erste Document der DocumentCollection zurück und setzt den Cursor auf die erste Position. Gibt null zurück, falls die DocumentCollection leer ist. Document getLastDocument() throws NotesException Gibt das letzte Document der DocumentCollection zurück und setzt den Cursor auf die letzte Position. Gibt null zurück, falls die DocumentCollection leer
ist. •
Document getNextDocument() throws NotesException Document getNextDocument(Document document) throws NotesException Gibt das nächste Dokument nach dem Cursor innerhalb der DocumentCollection entsprechend der Reihenfolge in der DocumentCollection zurück und setzt den Cursor auf diese Position. Wird als Parameter ein Document übergeben, so wird die Position nach diesem Document gewählt, wobei das Dokument aus der DocumentCollection stammen muss. Um diese Bedingung zu
erfüllen, genügt es nicht, dass es sich um eine Referenz auf dasselbe Domino-Dokument handelt, sondern es muss sich um ein und dasselbe Objekt handeln. Andernfalls wird die NotesException „Document is not from this collection“ geworfen. Gibt es keine weiteren Dokumente in der DocumentCollection, gibt diese Methode null zurück. •
•
Document getPrevDocument() throws NotesException Document getPrevDocument(Document document) throws NotesException Arbeitet wie getNextDocument und gibt das jeweils vorige Dokument bzw. null zurück, falls es kein voriges Dokument gibt. Document getNthDocument(int pos) throws NotesException Gibt das Document an Position pos in der Reihenfolge der DocumentCollection zurück. Die Dokumente sind beginnend mit 1 nummeriert. Werden Doku-
mente zur Collection hinzugefügt oder entfernt, wird neu, fortlaufend und beginnend bei 1 nummeriert. Gibt es weniger als pos Dokumente in der Collection, wird null zurückgegeben. Diese Methode ist nicht performant und sollte nicht bei der Verwendung von Schleifen zum Einsatz kommen. •
Document getDocument(Document document) throws NotesException
408
10 View Die Methoden getNextDocument (Document), getPreviousDocument (Document) und deleteDocument (Document) erfordern, dass als Parameter ein und dieselben Objekte übergeben werden, die auch in der Collection gesammelt wurden. Anhand von getDocument (Document) kann als Parameter ein Dokument übergeben werden, das zwar durch ein anderes Java-Objekt repräsentiert wird, aber auf dasselbe Domino-Dokument verweist. Rückgabewert ist entweder null, falls das Dokument nicht in der Collection enthalten ist, oder das Objekt, wie es in der Collection geführt wird und das auf das gewünschte Domino-Dokument verweist.
•
void addDocument(Document document) throws NotesException void addDocument(Document document, boolean immediate) throws NotesException Fügt ein Document einer Collection hinzu. Wirft eine NotesException, falls das Document bereits in der Collection enthalten ist. Für DIIOP-Verbindungen kann über den Parameter immediate erzwungen werden, dass die Änderungen
sofort durchgeführt werden, so dass die Überprüfung auf doppelte Einträge sofort durchgeführt und die entsprechende NotesException geworfen werden kann. •
void deleteDocument(Document document) throws NotesException Entfernt ein Document aus einer Collection. Das Dokument selbst bleibt in der
Datenbank hiervon unberührt. Als Parameter dürfen nur Objekte aus der Collection übergeben werden, sonst wird eine NotesException geworfen. Siehe getNextDocument (Document) und getDocument(Document). •
void FTSearch(String query) throws NotesException void FTSearch(String query, int maxResults) throws NotesException
Führt eine Volltextsuche auf der Collection aus und reduziert die Menge der in der Collection enthaltenen Dokumente auf die gefundenen Treffer. Nach FTSearch ist die DocumentCollection entsprechend dem internen SearchScore sortiert und isSorted liefert true. Der SearchScore eines Dokuments kann nach einer FTSearch durch getFTSearchScore() geladen werden. Enthält die Collection keine Dokumente, wirft diese Methode eine NotesException. Der Parameter query übergibt die Suchanfrage in derselben Syntax wie für FTSearch in View oder Database. Näheres siehe in Kapitel 15. Die Trefferzahl kann auf maxResults Dokumente eingeschränkt werden. Per Default werden maximal 5000 Dokumente gefunden. •
void putAllInFolder(String folderName) throws NotesException void putAllInFolder(String folderName, boolean createOnFail) throws NotesException
10.7.1 Methoden in DocumentCollection
409
Eine gute Möglichkeit, Referenzen auf Dokumente in Domino zu speichern, sind Ordner. Über putAllInFolder werden für alle Dokumente der Collection Referenzen in einen Ordner mit Namen folderName gelegt. folderName kann, wie in Domino üblich, den Backslash enthalten, durch den veranlasst wird, dass Ordner in Ordnern erstellt werden (z.B. „Animals\Birds“ erzeugt den Ordner Birds im Ordner Animals). Ist createOnFail == true, wird ein neuer Ordner erzeugt, sofern er noch nicht existiert. Default ist true. Für createOnFail==false wird keine Aktion durchgeführt, wenn es den/die entsprechende(n) Ordner nicht gibt. •
void removeAllFromFolder(String folderName) throws NotesException
Entfernt die Dokumente der Collection aus dem Ordner. Hierbei werden, wie für Ordner üblich, lediglich die Referenzen aus dem Ordner entfernt. Die eigentlichen Dokumente in der Datenbank bleiben unberührt. Befinden sich Dokumente der Collection nicht im Ordner, wird dies ignoriert. •
void removeAll(boolean enforce) throws NotesException
Löscht alle Dokumente der Collection endgültig aus der Datenbank. Über den Parameter enforce kann erzwungen werden, dass auch Dokumente gelöscht werden, die gerade in Bearbeitung sind. •
void stampAll(String itemName, Object value) throws NotesException
Für alle Dokumente der Collection wird der Inhalt des Items mit dem Namen itemName durch den Wert value ersetzt. Zulässige Klassen für das Objekt value sind Integer, Double, String, DateTime oder Vector aus Integer, Double, String oder DateTime. Dokumente müssen nach der Verwendung nicht ausdrücklich gespeichert werden. Die Änderungen durch stampAll werden implizit gespeichert. Bereits geladene Objektreferenzen auf derart geänderte Dokumente müssen neu geladen werden, um die Änderungen nachzuvollziehen. Änderungen an bereits geladenen Dokumenten müssen vor der Anwendung von stampAll gespeichert werden, um Speicherkonflikte zu vermeiden. •
void updateAll() throws NotesException
Diese Methode findet nur innerhalb von Domino-Agenten-Anwendung. Sie markiert alle Dokumente der Collection als verarbeitet, so dass sie nicht mehr durch getUnprocessedDocuments, unprocessedFTSearch oder unprocessedSearch in AgentContext geladen werden. Kann nicht für DIIOP-Verbindungen verwendet werden. •
•
boolean isSorted() throws NotesException Liefert true, falls die DocumentCollection mindestens ein Element enthält und als Ergebnis aus einer FTSearch entstanden ist, die z.B. auf die Datenbank oder die DocumentCollection selbst angewendet wurde. DateTime getUntilTime() throws NotesException
410
10 View Neu seit Domino R7. Wurde eine DocumentCollection über getModifiedDocuments in Database erzeugt, so gibt getUntilTime die aktuelle Datenbankzeit zu dem Zeitpunkt zurück, als getModifiedDocuments ausgeführt wurde. Somit kann für einen nachfolgenden Aufruf von getModifiedDocuments getUntilTime als DateTimeParameter für das Attribut „notOderThan“ verwendet werden (s. Kap. 7.1.4.3).
10.7.2 •
Methoden in ViewEntryCollection
int getCount() throws NotesException Liefert die Anzahl der Dokumente in der ViewEntryCollection, und zwar
ohne Einträge für Kategorien oder Summmen und 0, falls die Collection keine Elemente enthält. •
String getQuery() throws NotesException Gibt die Suchquery zurück, mittels derer eine ViewEntryCollection entstanden ist; z.B. wenn eine ViewEntryCollections über getAllEntries aus einem View gewonnen wird, der zuvor durch FTSearch gefiltert wurde oder nach Anwendung von FTSearch in ViewEntryCollection.
Falls keine Suche im eigentlichen Sinne oder ein Lookup ohne Suchquery zugrunde lag, wie z.B. bei getAllEntriesByKey, wird ein leerer String zurückgegeben. •
View getParent() throws NotesException
Gibt die Ansicht zurück, über die die Collection erstellt wurde. •
•
•
ViewEntry getFirstEntry() throws NotesException Die Klasse ViewEntryCollection verwaltet einen internen Cursor, der die Position innerhalb der Collection anzeigt. getFirstEntry gibt das erste ViewEntry der ViewEntryCollection zurück und setzt den Cursor auf die erste Position. Gibt null zurück, falls die ViewEntryCollection leer ist. ViewEntry getLastEntry() throws NotesException Gibt das letzte ViewEntry der ViewEntryCollection zurück und setzt den Cursor auf die letzte Position. Gibt null zurück, falls die ViewEntryCollection leer ist. ViewEntry getNextEntry() throws NotesException ViewEntry getNextEntry(ViewEntry viewentry) throws NotesException Gibt das nächste ViewEntry nach dem Cursor innerhalb der ViewEntryCollection entsprechend der Reihenfolge in der ViewEntryCollection zurück und setzt den Cursor auf diese Position. Wird als Parameter ein ViewEntry übergeben, so wird die Position nach diesem ViewEntry gewählt, wobei das ViewEntry aus der ViewEntryCollection stammen muss. Um diese Bedin-
gung zu erfüllen, genügt es nicht, dass es sich um eine Referenz auf das selbe Domino-Dokument handelt, sondern es muss sich um ein und dasselbe ViewEntry-Objekt handeln. Andernfalls wird die NotesException „Entry is not from this collection“ geworfen. Gibt es keine weiteren ViewEntry in der ViewEntryCollection, gibt diese Methode null zurück.
10.7.2 Methoden in ViewEntryCollection •
411
ViewEntry getPrevEntry() throws NotesException ViewEntry getPrevEntry(ViewEntry viewentry) throws NotesException Arbeitet wie getNextEntry und gibt das jeweils vorige ViewEntry bzw. null
zurück, falls es kein voriges Dokument gibt. •
ViewEntry getNthEntry(int pos) throws NotesException Gibt das ViewEntry an Position pos in der Reihenfolge der ViewEntryCollection zurück. Die ViewEntry sind beginnend mit 1 nummeriert. Werden ViewEntry zur Collection hinzugefügt oder entfernt, wird neu, fortlaufend und beginnend bei 1 nummeriert. Gibt es weniger als pos ViewEntry in der Collection, wird null zurückgegeben. Diese Methode ist nicht performant und sollte
•
ViewEntry getEntry(Object obj) throws NotesException Die Methoden getNextEntry (ViewEntry), getPreviousEntry (ViewEntry) und deleteEntry (ViewEntry) erfordern, dass als Parameter ein
nicht bei der Verwendung von Schleifen zum Einsatz kommen.
und dieselben Objekte übergeben werden, die auch in der Collection gesammelt wurden. Anhand von getEntry (Object) kann als Parameter ein ViewEntry oder Document übergeben werden, das zwar durch ein anderes Java-Objekt repräsentiert wird, aber auf dasselbe Domino-Dokument verweist. Rückgabewert ist entweder null, falls das ViewEntry bzw. Document nicht in der ViewEntryCollection enthalten ist, oder das Objekt, wie es in der Collection geführt wird und das auf das gewünschte Domino-Dokument verweist. •
void addEntry(Object obj) throws NotesException void addEntry(Object obj, boolean immediate) throws NotesException Fügt ein ViewEntry einer Collection hinzu. Wirft eine NotesException, falls das ViewEntry bereits in der Collection enthalten ist. Wird als Parameter ein Document übergeben, so wird dieses Document in ein ViewEntry gekapselt und der ViewEntryCollection hinzugefügt. Bemerkenswert ist, dass dieses neue ViewEntry das Document so repräsentiert, wie für die zugrunde liegende
Ansicht üblich. Folglich werden z.B. Spaltenwerte für dieses Dokument über getColumnValues berechnet. Allerdings können der ViewEntryCollection nur Dokumente hinzugefügt
werden, die auch in der zugrunde liegenden Ansicht enthalten sind. Andere Parameter werden ignoriert. Für DIIOP-Verbindungen kann über den Parameter immediate erzwungen werden, dass die Änderungen sofort durchgeführt werden, so dass die Überprüfung auf doppelte Einträge sofort durchgeführt werden und die entsprechende NotesException geworfen werden kann. •
void deleteEntry(ViewEntry viewentry) throws NotesException Entfernt ein ViewEntry aus einer Collection. Das über das ViewEntry referen-
zierte Dokument selbst bleibt in der Datenbank hiervon unberührt. Als Parameter dürfen nur Objekte aus der Collection übergeben werden, sonst wird eine NotesException geworfen. Siehe getNextEntry (ViewEntry) und getEntry(Object).
412 •
10 View void FTSearch(String query) throws NotesException void FTSearch(String query, int maxResults) throws NotesException
Führt eine Volltextsuche auf der Collection aus und reduziert die Menge der in der Collection enthaltenen ViewEntry auf die gefundenen Treffer. Nach FTSearch ist die ViewEntryCollection entsprechend dem internen SearchScore sortiert. Der SearchScore eines Dokuments kann nach einer FTSearch durch getFTSearchScore() geladen werden. Enthält die Collection keine ViewEntry, wirft diese Methode eine NotesException. Der Parameter query übergibt die Suchanfrage in derselben Syntax wie für FTSearch in View oder Database. Näheres siehe in Kapitel 8. Die Trefferzahl kann auf maxResults Dokumente eingeschränkt werden. Per Default werden maximal 5000 Dokumente gefunden. •
void putAllInFolder(String folderName) throws NotesException void putAllInFolder(String folderName, boolean createOnFail) throws NotesException
Eine gute Möglichkeit, Referenzen auf Dokumente in Domino zu speichern, sind Ordner. Über putAllInFolder werden für alle der über die ViewEntryCollection referenzierten Dokumente Referenzen in einen Ordner mit Namen folderName gelegt. folderName kann, wie in Domino üblich, den Backslash enthalten, durch den veranlasst wird, dass Ordner in Ordnern erstellt werden (z.B. „Animals\Birds“ erzeugt den Ordner Birds im Ordner Animals). Ist createOnFail == true, wird ein neuer Ordner erzeugt, sofern er noch nicht existiert. Default ist true. Für createOnFail==false wird keine Aktion durchgeführt. •
void removeAllFromFolder(String folderName) throws NotesException Entfernt die über die ViewEntryCollection referenzierten Dokumente aus
dem Ordner. Hierbei werden, wie für Ordner üblich, lediglich die Referenzen aus dem Ordner entfernt. Die eigentlichen Dokumente in der Datenbank bleiben unberührt. Befinden sich Dokumente der Collection nicht im Ordner, wird dies ignoriert. •
void removeAll(boolean enforce) throws NotesException Löscht alle über die ViewEntry der Collection referenzierten Dokumente endgültig aus der Datenbank. Über den Parameter enforce kann erzwungen wer-
den, dass auch Dokumente gelöscht werden, die gerade in Bearbeitung sind.
10.8 Schleifen – View, DocumentCollection und ViewEntryCollection •
413
void stampAll(String itemName, Object value) throws NotesException Für alle über die ViewEntry der Collection referenzierten Dokumente wird der Inhalt des Items mit dem Namen itemName durch den Wert value ersetzt. Zulässige Klassen für das Objekt value sind Integer, Double, String, DateTime oder Vector aus Integer, Double, String oder DateTime. Dokumen-
te müssen nach der Verwendung nicht ausdrücklich gespeichert werden. Die Änderungen durch stampAll werden implizit gespeichert. Bereits geladene Objektreferenzen auf derart geänderte Dokumente müssen neu geladen werden, um die Änderungen nachzuvollziehen. Änderungen an bereits geladenen Dokumenten müssen vor der Anwendung von stampAll gespeichert werden, um Speicherkonflikte zu vermeiden. •
void updateAll() throws NotesException
Diese Methode findet nur innerhalb von Domino-Agenten-Anwendung. Sie markiert alle über die ViewEntry der Collection referenzierten Dokumente als verarbeitet, so dass sie nicht mehr durch getUnprocessedDocuments, unprocessedFTSearch oder unprocessedSearch in AgentContext geladen werden. Kann nicht für DIIOP-Verbindungen verwendet werden.
10.8
Schleifen – View, DocumentCollection und ViewEntryCollection
Wurden Dokumentensammlungen über eine Suche, manuelle Operationen oder schlicht über die Definition einer Ansicht zusammengestellt, gilt es, diese Dokumente zu verarbeiten, sei es lesend oder schreibend. Typische Schleifen in DocumentCollection (Kapitel 10.4 und 10.7), ViewEntryCollection (Kapitel 10.5 und 10.7), ViewNavigator (Kapitel 10.5) und View (Kapitel 10ff.) werden mit den jeweiligen Navigationsfunktionen getFirstDocument oder getFirstEntry und getNextDocument oder getNextEntry usw. realisiert. Nicht immer ist es notwendig, überhaupt eine Schleife über die gesammelten Dokumente zu durchschreiten. Insbesondere die Methode stampAll in ViewEntryCollection und DocumentCollection erlauben es, für die gesamte Collection den Inhalt eines einzelnes Items für alle Dokumente permanent zu verändern. Sie sollte, sofern dies zu dem Anwendungsfall passt, immer bevorzugt werden. Ähnlich können mit putAllInFolder, removeAllFromFolder oder removeAll Operationen auf der gesamten Sammlung ausgeführt werden, ohne jedes einzelne Dokument öffnen zu müssen. Schwieriger wird es bei Operationen, die direkt auf einem View ausgeführt werden sollen. Im Gegensatz zu den drei Collections DocumentCollection, ViewEntryCollection und ViewNavigator besitzt die Ansicht, wie könnte es anders sein, eine SELECT-Formel, die programmatisch und dynamisch bestimmt, welche Elemente sie enthält.
414
10 View
Dementsprechend wird sich die Ansicht dynamisch verändern, wenn Dokumente hinzugefügt, gelöscht oder geändert werden. Da Domino-Bestandteile der Ansichten cached, insbesondere bei lokalen Sessions, werden diese Veränderungen allerdings nicht immer sofort im bereits geladenen View-Objekt sichtbar werden. Zwar gibt es die Möglichkeit, mit setAutoUpdate(true) den View für einen sofortiges Update zu aktivieren, aber auch diese Updates stehen nicht garantiert sofort zur Verfügung. Für viele Anwendungsfälle, wie z.B. einen Batchlauf, der periodisch noch nicht verarbeitete Dokumente in die Verarbeitung nimmt, wird es jedoch gar nicht notwendig sein, dynamische Veränderungen an der Ansicht sofort wahrzunehmen, oft wird dies sogar eher störend sein, da diese Veränderungen sich negativ oder zumindest zufällig, z.B. auf Abbruchbedingungen, auswirken können. Daher sei an dieser Stelle empfohlen, per setAutoUpdate(false) bewusst auf die Aktualisierung zu verzichten. Dies führt nicht nur zu vorhersehbaren Ergebnissen, sondern wirkt sich positiv auf die Performance aus. Nach der Verarbeitung kann die Ansicht mit refresh() manuell aktualisiert werden. Alternativ sollte in jedem Fall in Erwägung gezogen werden, ob nicht ohnehin mit einer DocumentCollection gearbeitet werden kann. Wenn (ausgewählte) Dokumente in einer Ansicht gelöscht werden sollen, indem diese in einer Schleife durchschritten wird, ist zu beachten, dass getNextDocument(Document) in View nur in der Signatur mit einem Document als Parameter zur Verfügung steht. Wird also die Schleifenvariable gelöscht, kann das nächste folgende Dokument nicht mehr ermittelt werden. Hierfür kann aber ein kleiner Trick angewendet werden, der ähnlich der Technik beim Recycling das zu löschende Dokument zwischenspeichert: nextDoc = view.getNextDocument(doc); doc.remove(true); GC.recycle(doc); doc = nextDoc;
Allerdings sollte soweit möglich überhaupt darauf verzichtet werden, die Dokumente über eine Schleife abzuarbeiten. Schließlich lassen sich mit removeAll(boolean) sämtliche Dokumente einer ViewEntryCollection löschen, die leicht aus der Gesamtmenge einer Ansicht über getAllEntries erzeugt werden kann. Wird also die Ansicht, bzw. eine FTSearch oder sonst eine Suche, wie z.B. getAllEntriesByKey, so definiert, dass sie exakt die zu löschenden Dokumente enthält, lassen sich diese über removeAll sehr viel effizienter löschen. Die einzelnen Techniken zur Schleifenverarbeitung werden in Listing 10-2 demonstriert. Die Klasse CollectionDemo erzeugt wie gewohnt eine Domino-Session. Durch eine kleine Abfrage wurde sie in die Lage versetzt, je nach Bedarf die Verbindung zur lokalen Maschine oder zu einem Server herzustellen. Dies macht es notwendig, das Beenden des NotesThread bei lokalen Aufrufen mandatorisch zu gestalten . Um für die Demonstration geeignete Dokumente in der Ansicht V_lookup_k6 der Beispieldatenbank djbuch/djbuch.nsf vorzufinden, erzeugt die Methode
10.8 Schleifen – View, DocumentCollection und ViewEntryCollection
415
package djbuch.kapitel_10;import djbuch.kapitel_06.*; import java.util.Vector; import lotus.domino.*; public class CollectionDemo { private static final boolean localOrRemote = true; private static final String LOCALPASSWORD = "geheim"; private static final String REMOTEPASSWORD = "geheim"; private static final String REMOTEUSER = "DJBuch User/DJBUCH"; public static void main(String[] args){ Database db = null; Session session = null; try { if (localOrRemote) { NotesThread.sinitThread(); session = NotesFactory.createSession((String) null, (String) null, LOCALPASSWORD); } else { session = NotesFactory.createSession("www.djbuch.de", REMOTEUSER, REMOTEPASSWORD); } db = session.getDatabase("", "djbuch/djbuch.nsf"); DemoTools.startup(db, 20); int count1 = dumpDatabase(db); int count2 = dumpView(db, "V_lookup_k6", null); int count3 = dumpView(db, "V_lookup_k6", "FIELD F_category=Wombat"); int count4 = updateView(db, "V_lookup_k6", "FIELD F_category=Wombat", "F_newField", "value", false); int count5 = updateView(db, "V_lookup_k6", "FIELD F_category=Wombat", "F_newField", "value", true); int count6 = deleteFromView(db, "V_lookup_k6", "FIELD F_category=Wombat", false); System.out.println(count1 + " / " + count2 + " / " + count3+ " / " + count4 + " / " + count5 + " / " + count6); } catch (Exception e) { e.printStackTrace(); System.out.println ( (e instanceof NotesException?"id = " +((NotesException)e).id:"")); } finally { GC.recycle(db); GC.recycle(session); if (localOrRemote) NotesThread.stermThread(); } } private static final int dumpDatabase (Database db) throws NotesException{ DocumentCollection dc = null; Document doc = null, nextDoc = null; int count = 1, size=0; try { dc = db.getAllDocuments(); if (dc==null) return -1; size=dc.getCount(); doc = dc.getFirstDocument(); while (doc != null) { System.out.println ("Dokument " + count++ + " / " + size); dumpDoc (doc); nextDoc = dc.getNextDocument(); GC.recycle(doc); doc = nextDoc; } } finally { GC.recycle (doc); GC.recycle (nextDoc); GC.recycle(dc); } return size;
416
10 View
} private static final int dumpView (Database db, String viewName, String query) throws NotesException { View view = null; Document doc = null, nextDoc = null; int count = 1, size=0; try { view = db.getView(viewName); if (view==null) return -1; if (query!=null) view.FTSearch(query); size=view.getAllEntries().getCount(); doc = view.getFirstDocument(); while (doc != null) { System.out.println ("Dokument " + count++ + " / " + size); dumpDoc (doc); nextDoc = view.getNextDocument(doc); GC.recycle(doc); doc = nextDoc; } } finally { GC.recycle (doc); GC.recycle (nextDoc); GC.recycle(view); } return size; } private static final int updateView(Database db, String viewName, String query, String name, String value, boolean allInOne) throws NotesException { View view = null; ViewEntryCollection vec = null; Document doc = null, nextDoc = null; int count = 1, size=0; try { view = db.getView(viewName); if (view==null) return -1; if (query!=null) view.FTSearch(query); vec = view.getAllEntries(); size=vec.getCount(); if (allInOne) { vec.stampAll(name,value); } else { boolean remindAutoUpdateStatus = view.isAutoUpdate(); view.setAutoUpdate(false); doc = view.getFirstDocument(); while (doc != null) { System.out.println ("Dokument " + count++ + " / " + size); doc.replaceItemValue(name, "individuell " + count + " / " + value); nextDoc = view.getNextDocument(doc); GC.recycle(doc); doc = nextDoc; } view.setAutoUpdate(remindAutoUpdateStatus); view.refresh(); } } finally { GC.recycle (doc); GC.recycle (nextDoc); GC.recycle (vec); GC.recycle(view); } return size; } private static final int deleteFromView (Database db, String viewName, String query, boolean allInOne) throws NotesException { View view = null; Document doc = null, nextDoc = null; int count = 1, size=0; try { view = db.getView(viewName); if (view==null) return -1;
10.8 Schleifen – View, DocumentCollection und ViewEntryCollection
417
if (query!=null) view.FTSearch(query); size=view.getAllEntries().getCount(); if (allInOne) { view.getAllEntries().removeAll(true); } doc = view.getFirstDocument(); while (doc != null) { System.out.println ("Dokument " + count++ + " / " + size); nextDoc = view.getNextDocument(doc); boolean someExtraCondition = doc.hasItem ("Item_0"); if (someExtraCondition) { doc.remove(true); } GC.recycle(doc); doc = nextDoc; } } finally { GC.recycle (doc); GC.recycle (nextDoc); GC.recycle(view); } return size; } private static final void dumpDoc (Document doc) throws NotesException { Item item = null; try { Vector allItems = doc.getItems(); for (int i = 0; i“>“ -> Fliesstext -> „Wechsel auf nicht-Bold“ ...). Soll mitten im vorhandenen Inhalt eines RichTextes editiert weden, muss der Cursor des RichTextItems durch den Aufruf von beginInsert an die gewünschte Stelle gestellt werden. Jedes beginInsert muss durch endInsert wieder beendet werden. Die für Domino typischen Links auf Dokumente, Ansichten oder Datenbanken können programmatisch durch den Befehl appendDocLink erzeugt werden, wobei dieser Methode einfach das Objekt übergeben wird , auf das verlinkt werden soll. Das gesamte Beispiel finden Sie in der Klasse RichTextDemo im Code zum Buch im Package djbuch.kapitel_11.
11.3
RichTextNavigator
Ein RichTextNavigator ist ein Objekt, das aus dem RichTextItem mit createNavigator erzeugt wird und das es ermöglicht, alle Elemente eines RichTextItems zu referenzieren. Die Navigation erfolgt immer anhand des Typus eines Elements innerhalb des RichTextItems, indem über verschiedene „find“-Operationen ein
436
11 RichText
Element eines bestimmten Typus selektiert werden kann. RichTextNavigator kennt einen internen Cursor, der durch diese Methoden auf die verschiedenen Elementpositionen gesetzt werden kann. Der Typus wird als Integerkonstante übergeben und kann durch die in Tabelle 11-1 aufgelisteten Konstanten in RichTextItem bestimmt werden. Hierbei sind für die Find-Operationen die Konstanten RTELEM_TYPE_TABLE, RTELEM_TYPE_TEXTRUN, RTELEM_TYPE_TEXTPARAGRAPH, RTELEM_TYPE_ DOCLINK, RTELEM_TYPE_SECTION, RTELEM_TYPE_TABLECELL, RTELEM_TYPE_FILEATTACHMENT und RTELEM_TYPE_OLE zulässige Werte für den Parameter type. Für die Get-Operationen sind lediglich die Konstanten RTELEM_TYPE_TABLE, RTELEM_TYPE_DOCLINK, RTELEM_TYPE_SECTION, RTELEM_TYPE_FILEATTACHMENT und RTELEM_TYPE_OLE erlaubt, da nur für diese Konstanten korre-
spondierende Objektklassen existieren. Einige dieser Methoden geben keinen Typus der Elemente an, so dass dann das jeweils nächste (etc.) Element des zuvor gewählten Typus selektiert wird. Zusätzlich gibt es vergleichbare „get“-Methoden, die nicht den Cursor auf die Position des Elementes stellen, sondern das Element als Rückgabewert zurückgeben. Über die find-Methoden lässt sich einfach überprüfen, ob das gewünschte Element bzw. die gewünschte Position auch erreichbar ist. Daher sind diese Methoden zu bevorzugen. Ein typischer Code könnte wie folgt aussehen: if (!rtNavigator.findFirstElement (RichTextItem.RTELEM_TYPE_SECTION)) return; do { rtsection = (RichTextSection)rtNavigator.getElement(); System.out.println (rtsection.getTitle()); } while (rtNavigator.findNextElement (RichTextItem.RTELEM_TYPE_SECTION));
Im Beispiel wird über die Methoden findFirstElement und findNextElement navigiert. Der einleitende Aufruf von findFirstElement überprüft bereits, ob es ein Element vom gewünschten Typ gibt . Nur falls dies der Fall ist, werden in einer do-while-Schleife alle Elemente des gewünschten Typus verarbeitet, indem diese über die Methode getElement() geladen werden . Da findNextElement einen boolean zurückgibt, der anzeigt, ob es überhaupt ein weiteres Element gibt, kann diese Methode als Abbruchbedingung für die Schleife genutzt werden, wobei gleichzeitig der Cursor des RichTextNavigator im Erfolgsfall weitergesetzt wird . Ein RichTextNavigator wird mit createNavigator in RichTextItem erzeugt oder durch getNavigator in RichTextRange bezogen. RichTextNavigator ist neu seit Domino R6.5.
11.3.1 Methoden in RichTextNavigator
437
RichTextNavigator beherrschen Die Methoden in RichTextNavigator basieren entweder auf einem ausdrücklich spezifizierten Typus eines Elements oder beziehen sich auf den zuvor einleitend implizit festgelegten Typus. Die Typen müssen bei der Anwendung der Methoden konsequent kontrolliert werden, da sie relativ schlecht intern überwacht werden. Ein Aufruf getFirstElement (TypusA) gefolgt von getNextElement (TypusB) führt sicher zu Problemen. Es wird zwischen der Operation innerhalb des Navigators auf Basis der „find“-Methoden und den getter-Methoden unterschieden. Diese arbeiten autark, oder anders formuliert, die getter-Methoden setzen nicht den Cursor für die find-Methoden. Auch hier sollte auf eine Stringenz bei der Benutzung des Typus geachtet werden. Eine gemischte Anwendung beider Methoden sollte vermieden werden. Besser nacheinander anwenden. Bei Verwendung der find-Methoden kann jederzeit über getElement() das aktuelle Element als Objekt bezogen werden. Nicht jedes Element kann als Objekt geladen werden und hat auch nicht zwingend eine Repräsentation in einer Java-Klasse. Sämtliche Parameter sollten auf Plausibilität überprüft werden. Nullparameter sind zu vermeiden. Am besten wird zunächst mit findNextElement überprüft, ob es weitere Elemente gibt, bevor versucht wird, sie zu laden, da offenbar im Fehlerfall (kein weiteres Element vorhanden) nicht immer sicher Exceptions geworfen werden. Die find-Methoden sollten bevorzugt genutzt werden.
11.3.1 •
Methoden in RichTextNavigator
boolean findNthElement(int type, int position) throws NotesException Neu seit Domino R6.5. Setzt den Cursor des RichTextNavigator auf das Element vom Typus type an der Position position, wobei nur Elemente diesen
Typs gezählt werden. Als Position muss ein Wert größer als 0 angegeben werden. Der Rückgabewert zeigt an, ob das gewünschte Element gefunden werden konnte. •
boolean findFirstElement(int type) throws NotesException boolean findLastElement(int type) throws NotesException Neu seit Domino R6.5. Setzt den Cursor des RichTextNavigator auf das erste bzw. letzte Element vom Typus type.
438
11 RichText Der Rückgabewert zeigt an, ob das gewünschte Element gefunden werden konnte.
•
boolean findNextElement() throws NotesException boolean findNextElement(int type) throws NotesException boolean findNextElement(int type, int position) throws NotesException Neu seit Domino R6.5. Setzt den Cursor des RichTextNavigator auf das nächste Element im Navigator. Wird kein Typ über den Parameter type spezifi-
ziert, wird nach Elementen vom Typus des zuletzt navigierten Elements gesucht. Wird zusätzlich der Parameter position angegeben, wird eine entsprechende Anzahl von Elementen übersprungen. Für position==1 wird das nächste Element diesen Typs gewählt. Der Rückgabewert zeigt an, ob das gewünschte Element gefunden werden konnte. •
Base getElement() throws NotesException
Neu seit Domino R6.5. Gibt das aktuelle Element zurück, auf das der Cursor des RichTextNavigator zeigt. Verweist der Cursor des RichTextNavigator auf eine ungültige Position, wird eine NotesException „Position is not valid“ geworfen. •
•
•
Base getNthElement(int type, int position) throws NotesException Neu seit Domino R6.5. Gibt das Element vom Typ type an der Position position zurück. Die Parameter type und position müssen gültige Werte sein. Base getFirstElement(int type) throws NotesException Base getLastElement(int type) throws NotesException Neu seit Domino R6.5. Gibt das erste bzw. letzte Element vom Typ type zurück. Base getNextElement() throws NotesException Base getNextElement(int type) throws NotesException Base getNextElement(int type, int position) throws NotesException
Neu seit Domino R6.5. Gibt das nächste Element des aktuellen Typus zurück, bzw. das nächste Element vom Typ type. Wird zusätzlich der optionale Parameter position übergeben, wird eine entsprechende Anzahl von Elementen übersprungen, wobei für position==1 das nächste Element zurückgegeben wird. •
boolean findFirstString(String searchString) throws NotesException boolean findFirstString(String searchString, int searchOptions) throws NotesException
Neu seit Domino R6.5. Führt eine Suche nach dem Parameter searchString durch und gibt true zurück, falls dieser gefunden wurde. Optionale Suchparameter werden als Integerkonstante searchOptions übergeben, wobei die Optionen als Addition der gewünschten Optionskonstanten RT_FIND_CASEINSENSITIVE, RT_FIND_PITCHINSENSITIVE oder RT_FIND_ACCENTINSENSITIVE aus RichTextItem übergeben werden.
11.4 RichTextRange •
439
boolean findNextString(String searchString) throws NotesException boolean findNextString(String searchString, int searchOptions) throws NotesException
Neu seit Domino R6.5. Führt eine erneute Suche durch. Liefert nur sinnvolle Werte, wenn zuvor eine gleichlautende Suche mit getFirstString durchgeführt wurde. • •
RichTextNavigator Clone() throws NotesException Neu seit Domino R6.5. Erzeugt eine Dublette des RichTextNavigator. void setPosition(Base rtElement) throws NotesException
Neu seit Domino R6.5. Setzt den Cursor vor die Position des Elements. Zulässig sind Objekte vom Typ RichTextNavigator, RichTextRange, RichTextDocLink, RichTextTable oder EmbeddedObject. •
void setPositionAtEnd(Base rtElement) throws NotesException
Setzt den Cursor hinter die Position des Elements. Zulässig sind Objekte vom Typ RichTextNavigator, RichTextRange, RichTextDocLink, RichTextTable oder EmbeddedObject. •
void setCharOffset(int charOffset) throws NotesException Neu seit Domino R6.5. Verschiebt den Cursor um einen Offset von charOffset
Zeichen. Der Parameter muss größer als 0 sein. Diese Methode kann nur angewendet werden, falls der RichTextNavigator auf ein Element vom Typ RTELEM_TYPE_TEXTRUN oder RTELEM_TYPE_TEXTPARAGRAPH zeigt oder zuvor eine Suche durchgeführt wurde (der RichTextNavigator verweist dann auf ein Element vom Typ RTELEM_TYPE_TEXTSTRING). Um den verschobenen Offset „sichtbar“ zu machen wird ein RichTextRange benötigt, der dann über seine Methode getTextParagraph () den Text ausgeben kann, z.B.: System.out.println (rtNavigator.findNextString("Link")); rtNavigator.setCharOffset(3); rtrange = rti.createRange(); rtrange.setBegin(rtNavigator); System.out.println (rtrange.getTextParagraph());
Dieser Code gibt lediglich den Buchstaben „k“ aus. Die ersten drei Buchstaben des Wortes „Link“ entfallen wegen der Offsetverschiebung. Dies ist ein typischer Anwendungsfall für einen RichTextRange, der sozusagen einen verschobenen Punkt innerhalb des RichTextNavigators referenzieren kann.
11.4
RichTextRange
Ein RichTextRange wird benötigt, um Teilbereiche eines RichTextItems abzubilden. Es wird außerdem benötigt, um bestimmte Textinhalte auszulesen, wie zum Beispiel Tabellenzellen. Ein RichTextRange wird aus dem RichTextItem erzeugt und
440
11 RichText
die Grenzen, für die der Bereich gültig sein soll, über setBegin und setEnd gesetzt. Sind diese Grenzen festgelegt, kann über die Methoden getTextRun und getTextParagraph der beinhaltete Text gelesen und die zugehörigen Styles ausgelesen oder gesetzt werden. Über den RichTextRange wird die hierarchische Objektstruktur des RichTextItems deutlich. Ein RichTextItem enthält z.B. ein Element vom Typ Tabelle, gefolgt von den zugehörigen Zellen. Die Zellen können in einen Range eingelesen werden, der auf einem Navigator basiert, der die Zellen enthält. Die Inhalte der Zellen werden wieder als Paragraph aufgefasst und über getTextParagraph in RichTextRange können nun die Texte ausgelesen werden. Besteht der RichTextRange aus lediglich einem Element, werden die Operationen direkt für dieses Element angewandt. Diese Technik ist empfehlenswert, um gezielt einzelne Elemente im RichTextItem zu manipulieren. RichTextRange ist neu seit Domino R6.5.
11.4.1 •
Methoden in RichTextRange
RichTextStyle getStyle() throws NotesException void setStyle(RichTextStyle richtextstyle) throws NotesException Neu seit Domino R6.5. Setzt den RichTextStyle des gesamten Textes im Ran-
ge bzw. gibt diesen zurück. Ist das letzte Element ungleich dem ersten und das letzte Element vom Typ RTELEM_TYPE_TABLE, RTELEM_TYPE_DOCLINK, RTELEM_TYPE_SECTION, RTELEM_TYPE_FILEATTACHMENT oder RTELEM_TYPE_OLE, wird der Style nicht für das letzte Element gesetzt. •
void remove() throws NotesException
Neu seit Domino R6.5. Löscht alle Elemente im RichTextRange. Das letzte Element wird nicht gelöscht, falls es von einem der Typen RTELEM_TYPE_TABLE, RTELEM_TYPE_DOCLINK, RTELEM_TYPE_SECTION, RTELEM_TYPE_FILEATTACHMENT oder RTELEM_TYPE_OLE ist und das RichTextRange nicht nur
aus einem Element besteht. Die Methode kann nicht über die logischen Grenzen der einzelnen Elemente hinaus angewandt werden, d.h. in der Regel müssen komplexe Elemente (Abschnitt, Tabelle usw.) einzeln gelöscht werden. •
void setBegin(Base element) throws NotesException void setEnd(Base element) throws NotesException Neu seit Domino R6.5. Setzt die Grenzen des RichTextRange auf das durch den Parameter element übergebene Element. Zulässig sind Objekte der Klassen RichTextNavigator, RichTextRange, RichTextDocLink oder RichTextTable. Wird ein Objekt vom Typ RichTextSection übergeben, wird dies ignoriert. Gesetzt wird die Position des Elements bzw. beim RichTextNavigator dessen aktuelle Position. Wird setBegin und setEnd für ein und dasselbe Element angewendet, besteht der RichTextRange genau aus diesem Element.
11.4.1 Methoden in RichTextRange
441
•
void reset(boolean resetBegin, boolean resetEnd) throws NotesException Neu seit Domino R6.5. Stellt die ursprünglichen Grenzen des RichTextRanges wieder her (nachdem diese durch setBegin und setEnd verändert wurden), wobei die Parameter resetBegin bzw. resetEnd den Wert true erhalten müs-
•
int findandReplace(String source, String replacement) throws NotesException int findandReplace(String source, String replacement, long options) throws NotesException Neu seit Domino R6.5. Ersetzt das nächste Vorkommen des Textes source im RichTextRange durch den String replacement und setzt die Cursorpositi-
sen, damit die jeweilige Grenze zurückgesetzt wird.
on auf dieses Vorkommnis. Als Optionen können Additionen aus den Konstanten RT_FIND_CASEINSENSITIVE, RT_FIND_PITCHINSENSITIVE, RT_ FIND_ACCENTINSENSITIVE, RT_REPL_PRESERVECASE und RT_REPL_ALL eingesetzt werden. • •
RichTextRange Clone() throws NotesException Neu seit Domino R6.5. Erzeugt eine Kopie des RichTextRange. int getType() throws NotesException
Neu seit Domino R6.5. Gibt den Typ des ersten Elements entsprechend den Konstanten aus RichTextItem (s. Tabelle 11-1) zurück. •
String getTextRun() throws NotesException Neu seit Domino R6.5. Liefert den Fließtext im RichTextRange vom Anfang
des Ranges bis zum Ende des Paragraphen oder bis zu einem Stilwechsel. Liefert den (ersten) Fließtext für Elemente vom Typ RTELEM_TYPE_TEXTRUN, RTELEM_TYPE_TEXTPARAGRAPH, RTELEM_TYPE_SECTION oder RTELEM_ TYPE_TABLECELL. Für RTELEM_TYPE_DOCLINK liefert die Methode den Linktext, sofern vorhanden, und ansonsten den leeren String, wie auch für die Elemente vom Typ RTELEM_TYPE_FILEATTACHMENT RTELEM_TYPE_OLE und RTELEM_TYPE_TABLE. Da die Elemente RTELEM_TYPE_TEXTPARAGRAPH, RTELEM_TYPE_SECTION oder RTELEM_TYPE_TABLECELL aus mehreren Fließtextelementen bestehen können, liefert die Methode nur den ersten Fließtext. •
•
String getTextParagraph() throws NotesException Neu seit Domino R6.5. Arbeitet wie getTextRun, liefert jedoch für die Elemente RTELEM_TYPE_TEXTPARAGRAPH, RTELEM_TYPE_SECTION oder RTELEM_TYPE_TABLECELL den gesamten Text des aktuellen Paragraphen. RichTextNavigator getNavigator() throws NotesException
Neu seit Domino R6.5. Liefert einen Navigator, der den Elementen aus dem RichTextRange entspricht.
442
11 RichText
11.5
RichTextSection
Werden Abschnitte (s. Kap. 3.2.7) in einem RichTextItem definiert, können diese seit Domino R6.5 in einem Java-Objekt abgebildet werden. Ein RichTextSectionObjekt wird über findFirstElement (RichTextItem.RTELEM_TYPE_SECTION) ausgewählt oder über getFirstElement(RichTextItem.RTELEM_ TYPE_SECTION) in RichTextNavigator zurückgegeben. Es wird über beginSection und endSection in RichTextItem neu angelegt. RichTextSection ist neu seit Domino R6.5.
11.5.1 •
Methoden in RichTextSection
void remove() throws NotesException
Neu seit Domino R6.5. Löscht den Abschnitt. •
ColorObject getBarColor() throws NotesException void setBarColor(ColorObject colorOfTwistie) throws NotesException
Neu seit Domino R6.5. Setzt bzw. liest die Farbe des Twisties als ColorObject. •
String getTitle() throws NotesException void setTitle(String s) throws NotesException
Neu seit Domino R6.5. Setzt bzw. liest die Titelleiste des Abschnittes. •
RichTextStyle getTitleStyle() throws NotesException void setTitleStyle(RichTextStyle richtextstyle) throws NotesException
Neu seit Domino R6.5. Setzt bzw. liefert den RichTextStyle der Titellseiste des Abschnittes. •
boolean isExpanded() throws NotesException void setExpanded(boolean flag) throws NotesException
Neu seit Domino R6.5. Bestimmt bzw. verändert den Zustand des Abschnittes, der bestimmt, ob er beim Öffnen im Notes Client auf- oder zugeklappt ist.
11.6
RichTextTable
Tabellen in RichTextItems sind in Domino sehr komplex, da sie nicht nur als Tabelle, sondern z.B. auch als Basiselement für Karteikartenreiter-gesteuerte Navigationselemente genutzt werden. Viele Eigenschaften können nach wie vor nur über den Notes Client verändert oder erstellt werden. Seit Domino R6.5 ist es immerhin möglich, die Tabelle als Element zu laden und neu im RichTextItem zu erstellen und die Zellen auszulesen, zumindest sofern die Struktur nicht zu komplex ist, da die Tabellenzellen über den RichTextNavigator geladen werden müssen und dieser bei mehreren Tabellen nicht zwischen diesen unterscheidet. Insbesondere beim Neuerstellen von Tabellen sind die neuen Methoden sehr hilfreich.
11.6.1 Methoden in RichTextTable
443
Tabellen werden über appendTable in RichTextItem neu erstellt, über den RichTextNavigator selektiert über einen RichTextRange abgebildet, so dass die Zellen gelesen bzw. verändert werden können. Ist eine Tabelle erst einmal erstellt, kann lediglich ihre Zeilenzahl einfach vergrößert oder verkleinert werden. Soll eine Spalte gelöscht werden, muss über einen RichTextNavigator jede Zelle der betreffenden Spalte angesteuert werden, hieraus ein RichTextRange erzeugt und dann diese Zelle gelöscht werden. RichTextTable ist neu seit Domino R6.5.
11.6.1 •
Methoden in RichTextTable
void addRow() throws NotesException void addRow(int noOfNewRows) throws NotesException void addRow(int noOfNewRows, int insertAfter) throws NotesException
Neu seit Domino R6.5. Fügt einer Tabelle eine oder bei der Spezifikation von noOfNewRows mehrere Zeilen hinzu. Per Default wird nach der letzten Zeile eingefügt, bei Angabe von insertAfter nach der hierdurch angegebenen Zeile, bei einer Zählung beginnend mit 1. •
void removeRow() throws NotesException void removeRow(int noOfNewRows) throws NotesException void removeRow(int noOfNewRows, int deleteAt) throws NotesException
Neu seit Domino R6.5. Entfernt aus einer Tabelle eine oder bei der Spezifikation von noOfNewRows mehrere Zeilen. Per Default wird die letzte Zeile entfernt, bei Angabe von deleteAt nach der hierdurch angegebenen Zeile, bei einer Zählung beginnend mit 1. •
void remove() throws NotesException
Neu seit Domino R6.5. Entfernt die Tabelle aus dem RichTextItem. •
ColorObject getColor() throws NotesException void setColor(ColorObject colorobject) throws NotesException ColorObject getAlternateColor() throws NotesException void setAlternateColor(ColorObject colorobject) throws NotesException
Neu seit Domino R6.5. Tabellen können mit farbigen Zellhintergründen dargestellt werden. Diese Farbe kann entweder vollflächig angewendet werden oder durch die Verwendung von sowohl setColor als auch setAlternateColor dazu verwendet werden, entweder die Zeilen oder die Spalten in abwechselnden Farben darzustellen. Damit die durch setColor oder setAlternateColor gesetzten Farben zur Anwendung kommen, muss durch setStyle eine dieser drei Eigenschaften (vollflächig, wechselnde Zeilen, wechselnde Spalten) für die Tabelle gesetzt werden.
444 •
11 RichText int getStyle() throws NotesException void setStyle(int i) throws NotesException Neu seit Domino R6.5. getStyle liefert und setStyle setzt verschiedene Style-Informationen für die Tabelle. Gesetzt werden können die Styles durch int Konstanten in RichTextTable. Diese können jeweils nur ausschließlich ver-
wendet und nicht durch etwa Addition gleichzeitig verwendet werden. Zur Verfügung stehen: –
TABLESTYLE_NONE – Die Hintergrundfarben der Tabelle werden zurückge-
–
TABLESTYLE_LEFTTOP – Die linke Spalte und die erste Zeile erhalten die
–
TABLESTYLE_TOP – Die erste Zeile erhält die primäre, alle anderen die al-
–
TABLESTYLE_LEFT – Die linke Spalte erhält die primäre, alle anderen die
–
TABLESTYLE_ALTERNATINGCOLS – Die Spalten werden in wechselnden
–
TABLESTYLE_ALTERNATINGROWS – Die Zeilen werden in wechselnden Far-
–
TABLESTYLE_RIGHTTOP – Die rechte Spalte und die erste Zeile erhalten die
setzt. primäre, alle anderen die alternative Farbe. ternative Farbe. alternative Farbe. Farben dargestellt, beginnend mit der alternativen Farbe. ben dargestellt, beginnend mit der alternativen Farbe.
– –
•
primäre, alle anderen die alternative Farbe. TABLESTYLE_RIGHT – Die linke Spalte erhält die primäre, alle anderen die alternative Farbe. TABLESTYLE_SOLID – Der gesamte Tabellenhintergrund wird auf die primäre Farbe gesetzt.
int getRowCount() throws NotesException int getColumnCount() throws NotesException
Neu seit Domino R6.5. Liefert die Anzahl der Zeilen und Spalten der Tabelle. •
Vector getRowLabels() throws NotesException void setRowLabels(Vector vector) throws NotesException
Neu seit Domino R6.5. Setzt und liefert die Beschriftungen der Tabellenlabel. Siehe auch appendTable in RichTextItem. •
boolean isRightToLeft() throws NotesException void setRightToLeft(boolean readingOrder) throws NotesException Neu seit Domino R6.5. setRightToLeft (true) muss gesetzt werden, wenn
Schriften unterstützt werden sollen, für die die Leserichtung von rechts nach links läuft.
11.6.2 Im- und Export von RichText – Erzeugen von RichText 11.6.2
445
Im- und Export von RichText – Erzeugen von RichText
Tabellen werden im RichTextItem als eine Abfolge von Elementen bestehend aus einem Tabellenheader, der über die find-Methoden zum Typ RichTextItem. RTELEM_TYPE_TABLE bezogen werden kann und allen Zellen, die über die find-Methoden zum Typ RichTextItem.RTELEM_TYPE_TABLECELL bezogen werden können. Befinden sich mehrere Tabellen in einem RichTextItem, bestehen diese aus einer Abfolge eines Headers einer oder mehrerer Zellen, optionalen tabellenfremden Elementen, einem erneuten Header und einer oder mehrerer Zellen der zweiten Tabelle. Werden über einen RichTextNavigator die Tabellenzellen im RichTextItem referenziert, so erhält man alle (!) Zellen der beiden Tabellen. Zum Lesen der Tabelle kann über das geschickte Setzen von RichTextRange gezielt die Menge der Zellen einer der Tabellen erfasst werden. Soll eine Tabelle mit Inhalten belegt werden, kann diese Technik nicht angewendet werden, da derart gesetzte Ranges durch die notwendigen Operationen beginInsert und endInsert im RichTextItem gestört wird. Hierfür kann aber leicht durch geschicktes Auszählen ebenfalls eine beliebige der im RichTextItem enthaltenen Tabellen manipuliert werden. In den beiden Listings Listing 11-1 und Listing 11-2 befindet sich Beispielcode, in dem diese Problematiken diskutiert werden und darüber hinaus noch einige nützliche Methoden zum Umgang mit Tabellen aufgezeigt werden. Speziell in den beiden Methoden runImport und runExport wird aufgezeigt, wie sehr einfach und dynamisch Tabellen im RichTextItem erzeugt oder als Tabelle z.B. in eine Datei exportiert werden können. Um den Praxisbezug zu erhöhen wurde der Export anhand der Klassen aus dem jxl-Package von Andy Khan [AndyKhan, JExcel] so gestaltet, dass eine Konvertierung zwischen Excel- und RichTextTable ermöglicht wird. Aufgrund der gut angelegten und einfachen Objektstruktur dieses Pakets wird als angenehme Nebenwirkung erreicht, dass der Beispielcode sich auf die Bearbeitung des RichText konzentrieren kann ohne durch aufwändige Dateioperationen und Datenmanipulationen aufgebläht zu werden. Für die Erzeugung einer dynamischen RichText-Tabelle wird in Listing 11-1 zunächst ein Dokument benötigt, in dem ein RichTextItem erzeugt werden kann. Dies wird durch die Methode createDoc in DemoTools vereinfacht. Dieser Methode werden Parameter für die Datenbank, in der das Dokument erzeugt werden soll, der Name der Maske für das Dokument, eine Kategorie, ein Titel und ein optionales Mutterdokument, falls ein neues Antwortdokument erzeugt werden soll übergeben. Im Package befindet sich die Datei „demo.xls“, die als Basis für den Import dienen wird. Zur Abwicklung des Im- und Exportvorgangs wurde die Klasse ExcelToRichTextConverter (Listing 11-2) entwickelt, die zunächst instanziert wird , wobei das RichTextItem, in dem die neue Tabelle erzeugt werden soll und der Dateipfad der zu importierenden Datei übergeben wird.
446
11 RichText
package djbuch.kapitel_11; import lotus.domino.*; import djbuch.kapitel_06.*; public class RichTextTableDemo extends NotesThread { private static final String PASSWORD = "geheim"; private static final String HOST = null; // DIIOP-Host und -User private static final String USER = null; // oder null für lokal public static void main(String[] args) { RichTextTableDemo rtd = new RichTextTableDemo(); rtd.start(); } public void runNotes() { Document doc = null; Database db = null; Session session = null; RichTextItem rti = null; try { session = NotesFactory.createSession(HOST, USER, PASSWORD); db = session.getDatabase("", "djbuch/djbuch.nsf"); doc = DemoTools.createDoc(db,"FO_dokumentRT_k11","Wombat", "Ich beinhalte RichText", null); rti = doc.createRichTextItem("body"); String basePath = System.getProperty("user.dir") + "/src/djbuch/kapitel_11/"; basePath = DemoTools.replaceSubstring(basePath,"\\","/"); rti.appendText("Tabelle aus Excel (" + basePath+"demo.xls)"); rti.addNewLine(); ExcelToRichTextConverter etri = new ExcelToRichTextConverter (rti, basePath+"demo.xls"); etri.runImport(); doc.save (true,false); String unid = doc.getUniversalID(); GC.recycle(rti); GC.recycle(doc); doc = db.getDocumentByUNID(unid); rti = (RichTextItem) doc.getFirstItem("body"); etri = new ExcelToRichTextConverter (rti, basePath+"out.xls"); etri.runExport(1); GC.recycle (doc); } catch (Exception e) { e.printStackTrace(); System.err.println ((e instanceof NotesException?"id = " +((NotesException)e).id:"")); } finally { GC.recycle (rti); GC.recycle(doc); GC.recycle(db); GC.recycle(session); } } }
Listing 11-1 Lesen und Schreiben von Tabellen im RichTextItem
Nun muss lediglich die Methode runImport gestartet werden, um den Import zu starten. Hierbei wird im RichTextItem eine neue Tabelle erzeugt, die die Daten aus der Importdatei übernimmt und dieselbe Zeilen- und Spaltenzahl der Datei hat. Berücksichtigt werden nur Textinhalte. Die so erzeugte Tabelle im RichTextItem kann leider nicht direkt wieder exportiert werden, sondern muss zwischengespeichert werden, um nachfolgend als Basis
11.6.2 Im- und Export von RichText – Erzeugen von RichText
447
Abb. 11-1 Dynamisch erzeugte Tabelle im RichText
für den Export zu dienen. Dies liegt daran, dass RichTextItems ziemlich stark gepuffert sind, einschließlich der auf das RichTextItem angewandten Methoden. Auch ein update() schafft in diesem Beispiel keine Abhilfe. Die in diesem Beispiel erstellte Tabelle (Abb. 11-1) ist dennoch hervorragend geeignet, selbst wieder in eine Datei exportiert zu werden. Ein erneutes Laden des Dokuments – einfach über die Universal ID des Dokuments – flusht alle Puffer und lädt das Dokument und sein RichTextItem im durch den Import erzeugten endgültigen Zustand. Dabei ist es erforderlich, das Dokument zuvor nicht nur zu speichern sondern auch zu recyceln, damit Domino sämtliche Referenzen freigibt und nicht etwa versucht auf einen Cache des Dokuments zurückzugreifen. Auch für den Export muss wiederum nur ein ExcelToRichTextConverterObjekt instanziert und diesmal die Methode runExport gestartet werden. Diese erlaubt einen int-Parameter, durch den festgelegt werden kann, welche der im RichTextItem gefundenen Tabellen in ihrer Reihenfolge, beginnend mit 1, exportiert werden soll.
448
11 RichText
package djbuch.kapitel_11; import java.io.*; import java.util.Locale; import jxl.*; import jxl.write.*; import jxl.read.biff.BiffException; import lotus.domino.*; public class ExcelToRichTextConverter { private private private private private private
String fileName; RichTextItem richText; String encoding=null; Session session = null; ColorObject color = null; ColorObject altColor = null;
public ExcelToRichTextConverter (RichTextItem rti, String useFileName) throws NotesException { session = rti.getParent().getParentDatabase().getParent(); fileName = useFileName; richText = rti; color = session.createColorObject(); color.setNotesColor(RichTextStyle.COLOR_GRAY); altColor = session.createColorObject(); altColor.setNotesColor(RichTextStyle.COLOR_LIGHT_GRAY); } public void runImport() throws BiffException, IOException, NotesException { Workbook book = Workbook.getWorkbook(new File(fileName)); Sheet xlsSheet = book.getSheet(0); richText.appendTable(xlsSheet.getRows(),xlsSheet.getColumns()); RichTextTable rtt = getTable(getTableCount()); setColors (rtt,true); RichTextNavigator rtnav = richText.createNavigator(); int allCellCount = getCellCount(rtnav); if (allCellCount „Domino Web Engine“ im Feld „Java Servlets / ClassPath“ den Eintrag „domino\servlet\rssnewsservlet.jar“ hinzu und ergänzen die Datei servlets.properties um die Zeilen servlet.rssnewsservlet.code= djbuch.kapitel_13.RSSNewsServlet.class servlets.startup=rssnewsservlet
Nach einem Neustart des HTTP-Tasks können Sie das Servlet unter der URL http://www.IhrServer.de/servlet/rssnewsservlet erreichen. Fazit Der künstlich angelegte AgentContext bietet die Möglichkeit, Agenten auch außerhalb des Domino Designers und außerhalb des Domino-Agenten-Kontextes auszuführen. Dies kommt insbesondere bei der Umwandlung von (Web-) Agenten in Servlets und bei Tests zum Tragen. Ein Aspekt wurde bisher noch nicht beleuchtet. Die Session im Servlet wurde per NotesFactory.createSession(); geöffnet. Folglich ist der Server der Besitzer dieser Session. Dies kann ein Vor-, aber auch ein Nachteil sein. Wenn für den Agenten
498
13 Objekt- und Speichertechniken
die Eigenschaft „Run as Web User“ aktiviert war, muss hier nachgearbeitet werden. Nähere Informationen finden Sie in den Kapiteln 4 und 5, insbesondere in Kapitel 5.2.3. Schließlich sei noch darauf hingewiesen, dass Sie natürlich Ihre WebAgenten von Anfang an so konzipieren können, dass Sie ohne den AgentContext auskommen, bzw. dass diese von Beginn an als Servlet geplant werden. Dies gilt natürlich nur, wenn der notwendige Zugang zum Code besteht bzw. dieser neu entwickelt wird.
13.2
Cashing von Domino-Objekten
In Kapitel 13.1.1f. wurde gezeigt, wie die Funktionalitäten eines Document angepasst und durch „Nachbau“ des Interfaces das Document auch für Erweiterungen zur Verfügung gestellt werden kann. Das so entstandene DJDocument ist weiterhin eng mit dem lotus.domino. Document verwoben und steht daher ebenfalls in enger Verbindung mit der Domino-Session. Ein Domino-Objekt und somit auch das DJDocument steht immer nur in und während einer Domino-Session zur Verfügung. Vor allem bei Web-AnPerformance beim Lesen von Daten aus wendungen entsteht daher die Fralotus.domino.Document gestellung, wie ein Document zwischen zwei Web-Zugriffen vorgeSollen große Mengen von Dokumenten und / halten werden kann, am besten als oder viele Items aus Dokumenten gelesen werObjekt persistent im Hauptspeicher. Letzteres ist vor allem wichtig den, müssen die Verarbeitungszeiten der Mefür Objekte, die sich selten ändern, thoden getItemValue und verwandter Methoaber sehr häufig benötigt werden den im Auge behalten werden. oder deren Erzeugung zeit- und/ oder ressourcenintensiv ist. Mit eiUnter ungünstigen Bedingungen kann das Lesen nem lotus.domino.Document der Daten zum Flaschenhals einer Anwendung wäre dies eine nicht triviale Aufgawerden. be, da die Domino-Session über mehrere Webseiten und somit über mehrere autarke Programmaufrufe z.B. eines Servlets aufrecht erhalten werden müsste (s. Kap. 17.4.1ff). Alle bisher aufgezeigten Techniken bauen aber die Domino-Session für jeden Aufruf einer Seite (eines Servlets oder eines WebAgenten) neu auf. Natürlich stehen Domino-Dokumente immer über die Datenbank zur Verfügung und es ist denkbar, z.B. die Universal ID des Dokuments persistent zu speichern und das Dokument anhand dieser bei Bedarf wieder per getDocumentByUNID wieder zu laden. Hierfür ist aber in jedem Fall mindestens der Aufbau der Domino-Session, das Öffnen der Datenbank und letzlich das Laden des Dokumentes erforderlich. Zusätzlich kommt noch ein weiterer Punkt hinzu, der nicht unberücksichtigt bleiben darf. Das Auslesen von Items aus einem Document kann eine zeitintensive
13.2.1 Das DJCacheDocument
499
Angelegenheit sein. Der Zeitbedarf für das Auslesen über die Methoden getItemValue und verwandte Methoden ist nicht linear abhängig von der Anzahl der Items in einem Document und der Menge an Daten innerhalb der Items. Beim Profiling einer produktiven Domino-Java-Anwendungen wurden unter ungünstigen Bedingungen Situationen gemessen, in denen das reine Lesen (!) von Daten über getItemValueString 50% der gesamten Verarbeitungszeit einer Anwendung einnahm, in der Daten aus einer Domino-Datenbank gelesen und über eine Bubblesearch sortiert werden sollten. Zum Vergleich: Die Zeiten wurden für rund 1000 Dokumente ermittelt, wobei die Bubblesearch lediglich 2% der Verarbeitungszeit in Anspruch nahm. Zur Lösung dieser Fragestellung verspricht die Datenhaltung in Arraylistund HashMap-Objekten große Vorteile.
13.2.1
Das DJCacheDocument
Da Domino-Java-Objekte nur zusammen mit einer gültigen Domino-Session existieren können, wird für das Vorhaben, ein Dokument im Java-Hauptspeicher zu cachen, ein Objekt benötigt, das ohne diese Session auskommt. Prinzipiell würde es genügen, über getItemValue einfach alle Vectoren des Document auszulesen und zum Beispiel in einer HashMap zu speichern. Da dies nicht sehr komfortabel wäre, wurde das DJCacheDocument entwickelt. Dieses soll folgenden Bedingungen genügen: 1 Ein DJCacheDocument kann direkt aus einem lotus.domino.Document erzeugt werden. 2 Es bietet mindestens die Standardmethoden getItemValue, replaceItemValue und appendItemValue und verwandte Methoden. 3 Diese Methoden verhalten sich äquivalent zu den Methoden von lotus.domino.Document80. 4 Aus einem DJCacheDocument kann auf das Original-Document geschlossen werden, aus dem es erzeugt wurde. 5 Es soll als POJO angelegt sein und somit über einen CacheMechanismus einfach cachebar sein. Bis auf Punkt 3 sind diese Bedingungen leicht zu erfüllen und als Klasse zu implementieren. In Tabelle 7-3 und Tabelle 7-4 wurde das Verhalten der getItemValueMethoden beschrieben. Die Schwierigkeit liegt nun darin, die Rückgabewerte so abzustimmen, dass die Methoden dieses zum Teil ungewöhnliche Verhalten abbilden. Weitere Punkte sind folglich zu berücksichtigen: 6 Die Rückgabewerte der Methoden müssen den Vorgaben aus Tabelle 7-3 und Tabelle 7-4 entsprechen. 80
Dies ist nicht trivial, da diese Methoden zum Teil recht unkonventionell arbeiten. Insbesondere bei Zugriff auf nicht erwartete Datentypen werden in vielen Fällen unerwartete Werte zurückgegeben (s. Kap. 7.1 ff).
500 7
8
13 Objekt- und Speichertechniken Es muss zugelassen sein, dass mehrere Items gleichen Namens verwendet werden, wobei die getItemValue-Methoden immer nur das erste Item berücksichtigen. Jedes Item kann entweder diskrete Werte aufnehmen oder eine Liste aus Werten, die als Vector behandelt werden.
Zusätzlich ergeben sich noch folgende Randbedingungen: 9
10
11
12
13
Dokumente können DateTime-Objekte aufnehmen. Da diese eine Session benötigen, muss das DJCacheDocument diese auf Date-Objekte mappen. Die Rückgabe dieser Werte erfordert eine Session, damit eine Rückumwandlung in DateTime erfolgen kann. Die Klasse erhält zusätzlich die Methode Vector getAllItemNames(), die die Namen aller Items zurückgibt. Diese Methode dient als Ersatz für die Methode Vector getItems(). Zusätzlich erhält die Klasse die Methoden getParentDatabaseFileName und getParentDatabaseFilePath, die den bequemen Bezug der aus dem original Document bezogenen Informationen zur ParentDatabase liefern. Im Gegensatz zum Vorbild benötigt die Klasse als POJO einen Konstruktor, der als public DJCacheDocument(Document doc) throws NotesException implementiert wird und als Parameter das lotus.domino.Document erhält, aus dem es erzeugt werden soll. Die Verwaltung der Item-Namen übernimmt die innere Klasse FieldNames, die insbesondere das Verhalten bei der doppelten Verwendung von Item-Namen abbildet.
Im Listing 13-9 befindet sich der Code der Klasse, die diese Bedingungen erfüllen wird. Sie bildet die wichtigsten Methoden in Document ab, insbesondere natürlich getItemValue, replaceItemValue und appendItemValue. Sie besitzt eine innere Klasse FieldNames , die die Verwaltung der Item-Namen übernimmt. Jedes DJCacheDocument hat eine FieldNames-Instanzvariable fn , in der die Item-Namen gespeichert werden. Sie wird wie folgt verwendet: •
String addName (String fieldName)
Fügt einem vorhandenen Namen einen neuen (intern in FieldNames verwalteten) Namenseintrag hinzu bzw. legt einen neuen an. Wird in der Regel parallel zum Ablegen eines Objektes in der HashMap elements in DJCacheDocument verwendet (zu jedem Value-Eintrag wird parallel ein Namenseintrag verwaltet). Aus Sicht des DJCacheDocuments kann es mehrere Items mit dem gleichen Namen geben. Intern werden diesen Items über FieldNames eindeutige Namen zugeordnet.
13.2.1 Das DJCacheDocument
501
package djbuch.kapitel_13; import java.util.*; import lotus.domino.*; public class DJCacheDocument { private static final int DJCACHEDOMINOEXCEPTIONID = 999; private static final boolean SIMULATE_R7 = true; private static final boolean IS_IIOP = false; private Map elements = Collections .synchronizedMap(new HashMap ()); private FieldNames fn = new FieldNames(); private String universalID = null; private String parentDocumentUNID = null; private String parentDatabaseFilePath = null; private String parentDatabaseFileName = null; private String parentDatabaseReplicaID = null; public DJCacheDocument(Document doc) throws NotesException { if (doc != null) { init (doc); } } private void init (Document doc) throws NotesException { universalID = doc.getUniversalID(); parentDocumentUNID = doc.getParentDocumentUNID(); parentDatabaseFilePath = doc.getParentDatabase().getFilePath(); parentDatabaseFileName = doc.getParentDatabase().getFileName(); parentDatabaseReplicaID = doc.getParentDatabase().getReplicaID(); Vector v = doc.getItems(); Item item = null; for (int i = v.size() - 1; i >= 0; i--) { item = (Item) v.elementAt(i); if (item != null && (item.getType() == Item.TEXT || item.getType() == Item.AUTHORS || item.getType() == Item.DATETIMES || item.getType() == Item.NAMES || item.getType() == Item.NUMBERS)) { appendItemValue(item.getName(), item.getValues()); } } } public void appendItemValue(String fieldName, Object value) { if (!ok (fieldName)) { return; } if (value == null) { value = ""; } Vector v = new Vector(); boolean isDateTime = false; if (value instanceof Vector) { v = (Vector)value; if (isInteger(v)) { v = toDouble(v); } else if (isDateTime(v)) { isDateTime=true; v = toJavaDate(v); } } else if (value instanceof Integer) { v.addElement(new Double(((Integer)value).doubleValue())); } else if (value instanceof DateTime) { isDateTime=true; try { v.addElement (((DateTime) value).toJavaDate()); } catch (NotesException e) { v = new Vector(); v.addElement ("");
502
13 Objekt- und Speichertechniken } } else { v.addElement(value); } elements.put(fn.addName(fieldName), v); if (isDateTime) { fn.setType(fieldName, DateTime.class.getName()); }
} public void appendItemValue(String fieldName) { appendItemValue(fieldName, ""); } public void appendItemValue(String fieldName, int value) { appendItemValue(fieldName, new Double(value)); } public void appendItemValue(String fieldName, double value) { appendItemValue(fieldName, new Double(value)); } public Vector getItemValue(String fieldName) { if (!hasItem (fieldName)) { return new Vector(); } Object result = elements.get(fn.makeName(fieldName)); if (result != null) { //Werte werden intern nur als Vector gespeichert return (Vector) result; } else { return new Vector(); } } public String getItemValueString(String fieldName) { if (!hasItem (fieldName)) { return IS_IIOP?null:SIMULATE_R7?"":null; } Vector retVal = getItemValue (fieldName); try { return (String) retVal.firstElement(); } catch (Exception otherException) { return SIMULATE_R7?"":null; // sonst null/"". } } public int getItemValueInteger(String fieldName) { if (!hasItem (fieldName)) { return 0; } double val = getItemValueDouble (fieldName); if (!IS_IIOP) { int mult = val < 0?-1:1; return mult * Math .round(new Double (Math.abs (val)).floatValue()); } else { return Math.round(new Float(val).floatValue()); } } public double getItemValueDouble(String fieldName) { if (!hasItem (fieldName)) { return 0.0; } Vector retVal = getItemValue (fieldName); try { return ((Double) retVal.firstElement()).doubleValue(); } catch (Exception e) { return 0.0; } }
13.2.1 Das DJCacheDocument public Vector getItemValueDateTimeArray (String fieldName, Session session)throws NotesException { String type = fn.getType(fieldName); if (DateTime.class.getName().equals(type)) { Vector source = getItemValue(fieldName); Vector result = new Vector(); for (int i = 0; i<source.size();i++){ result.add (session .createDateTime((Date)source.elementAt(i))); } return result; } else { if (!IS_IIOP) { throw new NotesException ( NotesError.NOTES_ERR_NOT_A_DATE_ITEM, "Item value is not a date type"); } else { return new Vector(); } } } public Vector getAllItemNames () { return fn.getAllNames(); } public String getParentDocumentUNID() { return parentDocumentUNID; } public String getUniversalID() { return universalID; } public void replaceItemValue(String fieldName, Object value) { removeItem (fieldName); appendItemValue (fieldName, value); } public void removeItem (String fieldName) { if (!fn.hasName(fieldName)) { return; } Vector v = fn.getNames(fieldName); for (int i = 0; i < v.size(); i++) { elements.remove ((String) v.elementAt(i)); } fn.removeName(fieldName); } public boolean hasItem(String fieldName) { return fn.hasName(fieldName); } public void recycle() { } public String toString() { String tmp = elements.toString().replace(',', '\n'); return " " + tmp.substring(1, tmp.length() - 1); } public String getParentDatabaseFileName() { return parentDatabaseFileName; } public String getParentDatabaseFilePath() { return parentDatabaseFilePath; } public Database getParentDatabase(Session session) throws NotesException { if (parentDatabaseReplicaID==null || parentDatabaseReplicaID.equals("")) { throw new NotesException(DJCACHEDOMINOEXCEPTIONID, "Das DJCacheDocument hat keine Parent Datenbank.");
503
504
13 Objekt- und Speichertechniken } return session.getDbDirectory("") .openDatabaseByReplicaID(parentDatabaseReplicaID);
} private static boolean ok (String fieldName) { return fieldName != null && fieldName.length() > 0; } public Document getDocument (Session session) throws NotesException { if (universalID == null) { throw new NotesException(DJCACHEDOMINOEXCEPTIONID, "Das DJCacheDocument hat keine Universal ID."); } return getParentDatabase(session).getDocumentByUNID(universalID); } public Document createDocument (Database db) throws NotesException { Document result = db.createDocument(); Vector v = this.getAllItemNames(); for (int i=0, s=v.size(); i<s; i++) { String name=(String)v.elementAt(i); if (this.getType(name) != null && this.getType(name).equals(DateTime.class.getName())) { result.replaceItemValue (name, this.getItemValueDateTimeArray(name,db.getParent())); } else { result.replaceItemValue(name,this.getItemValue(name)); } } return result; } ... protected class FieldNames { ... } }
Listing 13-9 Das DJCacheDocument
•
String makeName (String fieldName) Nicht zu verwechseln mit addName. Gibt lediglich die korrekte Schreibweise des
intern zu verwendenden Namenseintrags zurück. Hinter einem Namen können sich viele (intern in FieldNames verwaltete) Namenseinträge verbergen. Dieser Mechanismus bildet das Verhalten der Document-Items ab, von denen bekanntlich mehrere mit gleichem Namen vorliegen dürfen, die aber in den Java-Objekten unter eindeutigen Namen verwaltet werden müssen. •
void setType (String fieldName, String className)
Optional kann einem Item-Namen ein Typ (Klassenname) zugeordnet werden. Dies wird insbesondere für DateTime-Items benötigt, damit eine Rückwärtskonvertierung von Date in DateTime stattfinden kann. •
String getType (String fieldName)
Gibt den Typ zu einem Item-Namen zurück oder null, falls keine Typzuordnung stattgefunden hat.
13.2.1 Das DJCacheDocument
505
Verhalten des DJCacheDocument Das DJCacheDocument bildet soweit möglich das Verhalten eines Document in einer lokalen Session ab. Einschränkungen Nicht alle Funktionen sind berücksichtigt. So sind z.B. Autoren- oder Leserfelder nicht implementiert. Ursprünglich verschlüsselte Items werden unverschlüselt gespeichert.
•
Vector getAllNames ()
Liefert alle Namen der vorliegenden Items in DJCacheDocument zurück. Namen doppelter Items werden nur einmal zurückgegeben, d.h. die Liste im Rückgabe-Vector ist eindeutig. • • •
boolean hasName (String fieldName) Liefert true, falls es einen Eintrag für den Item-Namen fieldName gibt. void removeName (String fieldName)
Löscht alle zu einem Item-Namen vorliegenden Einträge. Die übrigen Methoden werden für die interne Verarbeitung in FieldNames benötigt.
Während in der Instanzvariable fn die Item-Namen verwaltet werden, liegen die eigentlichen Werte (Items) des DJCacheDocument in der Instanzvariable elements . Um die Mimik des lotus.domino.Document, alle Items optional als Vector zu betrachten und zurückzugeben, möglichst einfach abzubilden, werden auch im DJCacheDocument alle Items als Vector in der HashMap elements gespeichert. Ein neues DJCacheDocument wird aus einem vorhandenen Document über den Konstruktor von DJCacheDocument erzeugt . In der gezeigten Implementierung wird auf eine Initialisierung verzichtet, falls versucht werden sollte, ein neues DJCacheDocument aus einem null-Wert zu erzeugen. Dieses Verhalten entspricht durchaus einem leeren Document-Rumpf. Für sämtliche getItemValue-Methoden wird immer null zurückgegeben. Durch replaceItemValue oder appendItemValue können neue Items hinzugefügt werden. Allerdings bleiben die Referenzen auf ein Original-Document leer. Im Normalfall werden zum initialisierenden Document verschiedene Parameter in Instanzvariablen zwischengespeichert , so dass es später möglich ist, z.B. die Datenbank, in der sich das Document befand, zu laden. Außerdem werden aus diesen Instanzvariablen die Methoden getUniversalID, getParentDocumentUNID (falls es sich um ein Antwortdokument handelte) und getParentDatabase bedient, wobei diese Methode im Gegensatz zu Document eine Session als Parameter benötigt. Anhand dieser Session und der intern gespeicherten Replik ID der ParentDatabase, kann dann die ursprünglich enthaltende Database neu geladen
506
13 Objekt- und Speichertechniken
Verhalten des DJCacheDocument Integer-Werte und Vectoren aus Integer-Werten werden als Double bzw. als Vectoren aus Double gespeichert. DateTime-Items müssen durch java.util.Date abgebildet werden, um nicht von einer Session abhängig zu sein. Alle Werte werden als Vector im DJCacheDocument gespeichert. Für DateTime-Objekte wird der Typ mit gespeichert um beim Auslesen zu wissen, dass in DateTime umgewandelt werden muss.
werden. So bleibt der Bezug zum erzeugenden Document erhalten. Die Methoden getUniversalID und getParentDocumentUNID funktionieren sogar völlig
transparent. Aus Bequemlichkeits- und Performancegründen werden zusätzlich der FileName und FilePath der übergeordneten Database mit gespeichert und über die Methoden getParentDatabaseFileName und getParentDatabaseFilePath zur Verfügung gestellt. Werden diese Werte benötigt – und dies ist für die Rückreferenzierung durchaus des öfteren der Fall –, erspart dies die (teure) Instanzierung der Database, die ja im Gegensatz zum Document für DJCacheDocument tatsächlich neu instanziert und geladen werden muss. Bei der Instanzierung des DJCacheDocument werden in der Methode init neben diesen Instanzvariablen nun die eigentlichen Items des Document in das DJCacheDocument übertragen. Hierfür wird eine Schleife über den Vector getItems in Document abgearbeitet. Diese Schleife verarbeitet die Items rückwärts . Hierdurch wird erreicht, dass später die Items in der gleichen Reihenfolge wie im Document zurückgegeben werden, falls mehrere Items gleichen Namens vorliegen. Zur Zeit verarbeitet das DJCacheDocument-Items vom Typ Text (normale Strings und Listen von Strings), Autorenfelder, DateTime-Items, Namensfelder und Items vom Typ Numbers (Integer, Double oder Listen davon) . Soll diese Liste erweitert werden, müsste die Methode appendItemValue erweitert werden. Aufgrund der Struktur der Klasse würde dies aber genügen, da alle übrigen betroffenen Methoden sich auf die Methode appendItemValue beziehen. Die Speicherung der Werte der Items des Document im DJCacheDocument wird bereits mit der Methode appendItemValue in DJCacheDocument abgebildet. Dies erleichtert spätere Erweiterungen. Diese Methode ist der Kern der Klasse und behandelt die meisten der „Eigenarten“ des Document. Hier werden die meisten notwendigen Typenkonvertierungen vorgenommen und dafür gesorgt, dass alle Werte intern als Vector verwaltet werden. Über die im Folgenden beschriebenen mehr oder weniger ungewöhnlichen Vorgehensweisen, Konvertierungen, Rückgabewerte, etc. lässt sich vortrefflich streiten.
13.2.1 Das DJCacheDocument
507
Im Einzelfall kann dies eine gute Gelegenheit sein, Optimierungen vorzunehmen. Grundlage aller Entscheidungen für die eine oder andere Vorgehensweise war immer, eine möglichst exakte Mimik des Verhaltens eines lotus.domino.Document zu erreichen. Der große Vorteil hiervon ist, dass Sie ein Document sehr einfach durch ein DJCacheDocument austauschen können, ohne befürchten zu müssen, dass Ihre Anwendung „zerbricht“. Beachten Sie, dass die vorliegende Implementierung das Verhalten einer lokalen Session abbildet. Soll mit DIIOP-Sessions gearbeitet werden, müssen die Unterschiede, die in Tabelle 7-3 und Tabelle 7-4 in Kapitel 7.1.2 beschrieben sind, nachimplementiert werden. Ebenso ist zu beachten, dass sämtliche Sicherheitsfunktionen eines Document ignoriert werden. Dies liegt durchaus in der Absicht dieser Klasse. Sie soll als einfacher und insbesondere persistenter und schneller Container eines Dokuments fungieren. Werden Verschlüsselung, Leserfelder oder Ähnliches benötigt, ist immer das ursprüngliche Document vorzuziehen. Hierbei ließe sich bei der vorgestellten Vorgehensweise kein Vorteil erzielen. Werden Sicherheitsfunktionen nicht benötigt, so lassen sich mit dem DJCacheDocument erhebliche Geschwindigkeitsvorteile erzielen, wie weiter unten (s. Kap. 13.2.2) aufgezeigt wird. Wird versucht, mit ungültigen Item-Namen ein Item zu erzeugen, so wird dies ignoriert . Wird versucht, einen null-Wert in ein Item zu schreiben , so wird ein leerer String ins Item geschrieben. Dies entspricht dem Verhalten von Document. Bei der Implementierung von DJCacheDocument wurde festgelegt, dass eventuell vorkommende Konvertierungen soweit möglich immer bereits beim Schreiben in der Methode appendItemValue erledigt werden. Daher werden Vectoren aus Integerwerten oder diskrete Integerwerte bereits in dieser Methode in Double umgewandelt. Bei Vectoren wird die Hilfsmethode isInteger benötigt, die true zurückgibt, wenn mindestens ein Element des Vectors vom Typ Integer ist. In diesem Fall wird mittels der Methode toDouble versucht, die Elemente des Vectors in Double umzuwandeln. Insgesamt wurde versucht die Klasse DJCacheDocument robust aufzubauen und tolerant mit Eingabe- oder Konvertierungsfehlern umzugehen. Daher werden Konvertierungsfehler einfach ignoriert, indem solche Elemente nicht geschrieben werden. Dies wird auch in der Methode so gehandhabt. Enthält ein Vector Integer und gleichzeitig Werte anderer Datentypen, so wird bei der Benutzung von appendItemValue ein Vector in das DJCacheDocument geschrieben, das nur die in Double umgewandelten Elemente enthält. Andere werden aus dem Vector entfernt. Sollen lotus.domino.DateTime-Elemente in das DJCacheDocument geschrieben werden, müssen diese in java.util.Date umgewandelt werden, da an den Typ DateTime die Domino-Session gekoppelt ist. Diese Umwandlung wird sowohl für Vectoren aus DateTime-Elementen als auch für diskrete Werte durchgeführt. Hierbei wird ebenso tolerant und robust wie für Integer-Werte verfahren.
508
13 Objekt- und Speichertechniken
. Dieses Intern speichert das DJCacheDocument sämtliche Werte als Vector Verfahren wird gewählt, da das lotus.domino.Document Werte sowohl diskret (z.B. durch getItemValueString) als auch als Vector (getItemValue) zurückgeben kann und so die Rückgabe vereinfacht wird. Sämtliche andere Objekte, die der Methode appendItemValue als Parameter übergeben werden, werden in einem Vector „verpackt“ , wenn es sich nicht ohnehin um einen solchen gehandelt hat. Nun muss ein Objekt nur noch in der HashMap gespeichert werden . Hierbei wird durch addName in FieldNames ein neuer Name generiert. Um eventuelle Doppelungen etc. kümmert sich bereits die Klasse FieldNames, so dass der Problematik von doppelten Items hier keine Aufmerksamkeit mehr geschenkt werden muss. Die Klasse FieldNames und insbesondere die Methode addName ist so gebaut, dass appendItemValue vorhandene Items nicht überschreibt, sondern parallel intern unter eindeutigen Namen gespeichert werden. Ähnlich wie im Vorbild Document ist es allerdings nicht (ohne Weiteres) möglich, solche Doppelungen wieder auszulesen81. , der es später erFür DateTime-Items wird intern noch ein Type gespeichert möglicht, zu erkennen, dass die interne Darstellung als Date wieder zurückverwandelt werden muss (darf). Da alle appendItemValue- und replaceItemValue-Methoden auf appendItemValue (String, Object) zurückgeführt werden können, ist die Mimik des Schreibens hiermit beschrieben. Als Wrapper steht die Methode appendItemValue (String) zur Verfügung, die lediglich ein leeres Item (leerer String) unter dem übergebenen Namen erzeugt und zwei neu eingeführte Varianten, die so im Document nicht zur Verfügung stehen, es aber erleichtern, int- und double-Werte zu schreiben, ohne dies vorher in ein Object umwandeln zu müssen . Auch die Methode replaceItemValue wird auf appendItemValue zurückgeführt. Hier wird lediglich zuvor das alte Item mittels removeItem gelöscht, wobei letztere Methode nicht nur das ursprüngliche Objekt aus der HashMap löscht, sondern auch den Namen in der Klassenvariablen fn durch den Aufruf von fn.removeName(fieldName) aufräumt. Die getter-Methoden bilden die Eigenarten des Document ab, insbesondere, wenn es um die Rückgabewerte bei dem Versuch geht, leere Items oder Items unerwarteten Datentyps zu lesen. Hierbei wurde Tabelle 7-3 zugrunde gelegt. Der einfachste Fall ist die Methode getItemValue (String fieldName). Sie gibt in jedem Fall einen Vector zurück. Im Fehlerfall oder gibt es das Item nicht , wird ein leerer Vector zurückgegeben. Hier sei nochmals darauf hingewiesen, dass hier makeName anstelle von addName verwendet werden muss, da kein Element hinzugefügt wird, sondern lediglich der gültige (interne) Name des Items benötigt wird, um das Element aus der HashMap auslesen zu können. Die Methode getItemValueString muss null bzw. "" zurückgeben, wenn es das Item nicht gibt oder wenn das Item nicht in einen String umgewandelt werden kann . Die Unterscheidung, ob für das original lotus.domino.Document in diesen Fällen null oder "" zurückgegeben wird, hängt (leider) davon ab, ob die Session lokal oder über DIIOP aufgebaut wird. Dazu hat sich das Verhalten noch seit 81
Dies ist im Document durch das Item-Modell – unter Zuhilfenahme von item.remove (s. Kap. 7.1.2) – möglich. Das DJCacheDocument sieht dies (noch) nicht vor.
13.2.1 Das DJCacheDocument
509
Domino R6.x geändert. Um dem Anspruch zu genügen, dass das DJCacheDocument auch diese Ausnahmefälle ebenso wie Domino handhabt, wurden hier entsprechende Entscheider eingebaut. Im Normalfall gibt die Methode getItemValueString das erste Element des gespeicherten Objekts als String zurück . Hier wird bewusst der Cast (String) verwendet und auf den Einsatz von toString() verzichtet. Nur so wird eine Äquivalenz zum Document erreicht. Ähnliches gilt für getItemValueInteger und getItemValueDouble, die 0 bzw. 0.0 im Fehlerfall zurückgeben müssen, wobei getItemValueInteger auf getItemValueDouble zurückgeführt werden kann . Leider rundet Domino (für lokale Verbindungen) anders als Java. Dies wird hier ebenfalls berücksichtigt. Schließlich bleibt noch getItemValueDateTimeArray, die für das DJCacheDocument anders als für das Document den zusätzlichen Parameter Session benötigt, damit der interne Date-Wert in einen DateTime-Wert zurückgewandelt werden kann . Dank des intern gespeicherten Type kann festgestellt werden, ob das Item auch wirklich ursprünglich ein DateTime (und nicht etwa ein orginäres Date)-Objekt war und es wird eine Exception geworfen, wenn dies nicht der Fall ist. Anders als im Document gibt getItemValue das interne Date-Objekt anstelle eines DateTime-Objektes zurück, da getItemValue keine Session zur Verfügung hat. Zur bequemeren Verwendung wurden die Methoden createDocument und getDocument eingeführt, die wiederum aus dem DJCacheDocument ein neues lotus.domino.Document erzeugen bzw. das Original laden, aus dem das DJCacheDocument erzeugt wurde, und somit eine Rückwärtskonvertierung ermöglichen. Insgesamt stehen folgende öffentliche Methoden zur Verfügung: •
DJCacheDocument(Document originalDoc) throws NotesException Im Gegensatz zu einem lotus.domino.Document hat DJCacheDocument einen Konstruktor. Er dient dazu, aus einem lotus.domino.Document ein DJCacheDocument zu erzeugen. Hierbei werden alle Items vom Typ Item.TEXT,
Item.AUTHORS, Item.DATETIMES, Item.NAMES, Item.NUMBERS übernommen. Autorenfelder werden als Textfelder gespeichert. Das erzeugte DJCacheDocument ist unabhängig vom lotus.domino.Document. Das DJCacheDocument darf auch mit einem null-Parameter erzeugt werden . Dann liefern zwar einige Methoden (getParentDatabase oder getParentDatabaseFileName) Exceptions oder null, aber die Methoden replaceItemValue usw. können wie gewohnt verwendet werden. Mit originalDoc==null wird ein neues leeres DJCacheDocument instanziert, das unabhängig von einem lotus.domino.Document ist. •
•
void appendItemValue(String fieldName) Fügt ein Item mit Namen fieldName und Wert "" (leerer String) DJCacheDocument hinzu. void appendItemValue(String fieldName, double value)
dem
Fügt dem Item einen Vector mit einem Element, das ein Double-Objekt mit Wert value enthält, hinzu.
510 •
13 Objekt- und Speichertechniken void appendItemValue(String fieldName, int value)
Fügt dem Item einen Vector mit einem Element, das ein Integer-Objekt mit Wert value enthält, hinzu. •
•
• •
void appendItemValue(String fieldName, Object value) Ist value ein Vector, wird das Objekt direkt hinzugefügt. Alle übrigen Objekte werden zuvor als Element in einen Vector gelegt. Integer und Vectoren aus Integer werden in Double- und DateTime-Objekte bzw. Vectoren aus DateTime in Date umgewandelt. Document createDocument(Database db) throws NotesException
Erzeugt ein neues lotus.domino.Document mit den Items des DJCacheDocument in der Datenbank db. Rückgabewert ist das neue Document. Es wird nur als Objekt zurückgegeben und ist nach dem Erzeugen nicht automatisch gespeichert (ein save() muss ausdrücklich nach dem createDocument ausgeführt werden). Vector getAllItemNames() Liefert alle Namen aller Items als Vector aus String . Document getDocument(Session session) throws NotesException
Versucht anhand der als Parameter übergebenen Session und der intern gespeicherten Universal ID das Original-lotus.domino.Document, aus dem das DJCacheDocument ursprünglich erzeugt worden war, wieder zu laden. Hierfür wird die ebenfalls intern anhand des Original-Dokument gespeicherte Replik ID der Datenbank, aus der das Original stammte, verwendet. • •
• • • •
Vector getItemValue(String fieldName) Liefert den Wert des Items fieldName als Vector . Siehe auch Tabelle 7-3. Vector getItemValueDateTimeArray(String fieldName, Session session) throws NotesException Liefert das DateTime-Objekt des Items mit Namen fieldName als Vector, sofern unter diesem Namen ein DateTime-Objekt im Original-Document gespeichert war. Ansonsten eine NotesException. Siehe auch Tabelle 7-3). double getItemValueDouble(String fieldName) Liefert den Wert des Items fieldName als double . Siehe auch Tabelle 7-3. int getItemValueInteger(String fieldName) Liefert den Wert des Items fieldName als int . Siehe auch Tabelle 7-3. String getItemValueString(String fieldName) Liefert den Wert des Items fieldName als String . Siehe auch Tabelle 7-3. Database getParentDatabase(Session session) throws NotesException Liefert die Datenbank, in der das Original lotus.domino.Document gespeichert war, als Database-Objekt . Daher wird als Parameter eine Session benötigt. Eine NotesException wird geworfen, wenn diese Datenbank nicht geladen werden kann oder falls das DJCacheDocument aus einem null-Objekt
erzeugt wurde. •
String getParentDatabaseFileName()
13.2.1 Das DJCacheDocument
511
Liefert den (intern zwischengespeicherten) Dateinamen der Datenbank, in der das ursprüngliche lotus.domino.Document gespeichert war ohne diese Datenbank zu laden oder null, falls das DJCacheDocument aus einem nullObjekt erzeugt wurde. •
String getParentDatabaseFilePath()
Liefert den (intern zwischengespeicherten) Dateipfad der Datenbank, in der das ursprüngliche lotus.domino.Document gespeichert war oder null, falls das DJCacheDocument aus einem null-Objekt erzeugt wurde. •
String getParentDocumentUNID()
Liefert die (intern zwischengespeicherte) Universal ID eines optionalen Hauptdokuments des ursprünglichen lotus.domino.Document, aus dem das DJCacheDocument erzeugt worden war oder null, falls das DJCacheDocument aus einem null-Objekt erzeugt wurde oder falls es kein Hauptdokument gab – wenn also das Original-Document kein Antwortdokument war. • •
String getType(String fieldName) Liefert den Type eines Items. Wird nur für DateTime-Items unterstützt. String getUniversalID()
Liefert die (intern zwischengespeicherte) Universal ID des Originallotus.domino.Document, aus dem das DJCacheDocument erzeugt wurde oder null, falls das DJCacheDocument aus einem null-Objekt erzeugt wurde. •
boolean hasItem(String fieldName) Liefert true, falls das DJCacheDocument ein Item mit Namen fieldName hat
•
void recycle()
. Hat keine Wirkung. Wurde nur der Vollständigkeit halber implementiert • •
•
.
removeItem(String fieldName) Entfernt das Item mit Namen fieldName . void replaceItemValue(String fieldName, Object value) Ersetzt das Item mit Namen fieldName mit dem Wert von value. Arbeitet ansonsten wie appendItemValue . String toString() Liefert eine sehr einfach gehaltene String-Repräsentation des DJCacheDocument .
Dem Code zum Buch wurde noch ein JUnit-Test in der Klasse DJCacheDocumentTest hinzugefügt. Dieser basiert auf einer lokalen Session. Das Verhalten wird im direkten Vergleich mit einem lotus.domino.Document geprüft, so dass sichergestellt ist, dass das Verhalten wirklich eins zu eins umgesetzt wurde. Der Code bietet die Möglichkeit, den Verbindungsaufbau auf eine DIIOP-Session umzustellen. Hier lässt sich dann gut erkennen, dass die Implementierung auch die Besonderheiten unterstützt, soll es in einer DIIOP-Umgebung eingesetzt werden,. Gleichzeitig bietet der JUnit-Test einen guten Ansatz, das DJCacheDocument Ihren Bedürfnissen anzupassen.
512
13 Objekt- und Speichertechniken
64 Items a 1000 Byte 1000 1000 1000 1000 1000 1000
neue neue x 64 x 64 x 64 x 64
DJCacheDocument erstellen: 6172ms Document erstellen: 19031ms DJCacheDocument.getItemValue Operationen: 31ms Document.getIemValue Operationen: 4000ms DJCacheDocument.replaceIemValue Operationen: 485ms Document.replaceIemValue Operationen: 3937ms
514 Items a 100 Byte 1000 1000 1000 1000 1000 1000
neue DJCacheDocument erstellen: 41844ms neue Document erstellen: 22515ms x 514 DJCacheDocument.getItemValue Operationen: 235ms x 514 Document.getIemValue Operationen: 31125ms x 514 DJCacheDocument.replaceIemValue Operationen: 3937ms x 514 Document.replaceIemValue Operationen: 35078ms
24 Items a 100 Byte 1000 1000 1000 1000 1000 1000
neue neue x 24 x 24 x 24 x 24
DJCacheDocument erstellen: 1875ms Document erstellen: 2203ms DJCacheDocument.getItemValue Operationen: 16ms Document.getIemValue Operationen: 672ms DJCacheDocument.replaceIemValue Operationen: 235ms Document.replaceIemValue Operationen: 734ms
Tabelle 13-1 Performance Test – DJCacheDocument und Document im Vergleich
13.2.2
Performancevergleich
Um einschätzen zu können, wie die Klasse sich in der Laufzeit verhält, wurde dem JUnit-Test ein Abschnitt hinzugefügt, der die Geschwindigkeit für die Erstellung des DJCacheDocument und das Ein- und Auslesen von Daten untersucht und mit dem Document vergleicht. Das Ergebnis eines Testlaufs wird in Tabelle 13-1 dargestellt, wobei die Zahlen für drei Kombinationen aus Anzahl und Größe der Items ermittelt wurden. Alle Ergebnisse wurden für die Durchführung von 1000 Wiederholungen ermittelt. Hierbei ist festzustellen: • • •
Das Lesen von Items im DJCacheDocument ist erheblich schneller (bis zu Faktor 100) als für das Document. Das Schreiben von Items im DJCacheDocument ist erheblich schneller (bis zu Faktor 10) als für das Document. Das Erstellen des DJCacheDocument ist unverhältnismäßig teuer. Es kann bis zu doppelt so lange dauern wie die Erstellung eines Document. Dies ist im Wesentlichen dem Auslesen aus dem Document zuzuschreiben.
Fazit: Ist das DJCacheDocument erst einmal erstellt, so bietet es einen erheblichen Geschwindigkeitsvorteil. Damit ist es prädestiniert für den ursprünglich geplanten Einsatzzweck: Das Cachen von teuer zu erzeugenden und häufig benötigten Konfigurationsdokumenten.
13.2.3 Verwendung des DJCacheDocument
513
Neben den Vorteilen des CacheDocument (Performance, Cachebarkeit) darf allerdings nicht vergessen werden, dass für dieses Objekt keinerlei der für Domino sonst üblichen Sicherheitsfunktionen implementiert sind.
13.2.3
Verwendung des DJCacheDocument
Das so implementierte DJCacheDocument erfüllt die oben definierten Eigenschaften. Durch die strenge Nachahmung der Mimik von Document lässt sich nun jedes lotus.domino.Document einfach über den Konstruktor new DJCacheDocument (Document) in ein solches umwandeln. Nun muss ab diesem Punkt im Code nur noch das neue DJCacheDocument weiterverwendet werden. In den meisten Fällen muss nur wenig oder kein Code angepasst werden, solange dieses DJCacheDocument nicht selbst wieder zum Parameter eines Domino-Java-Objekts wird. Nun soll natürlich auch an einem einfachen Beispiel die Verwendung des DJCacheDocument in einem Cache demonstriert werden. Zu diesem Zweck finden Sie in Listing 13-10 eine Implementierung unter Zuhilfenahme des ShiftOne Object Cache [ShiftOne]. Dieser Cache kennt bereits viele verschiedene Cache-Mechanismen und bietet neben einer leichten Konfiguration zusätzliche Wrapper, die ihn wirklich universell einsetzbar machen. ... public class DJCacheDemo implements Runnable { ... public void run() { Session session = null; Database db = null; Document doc = null; try { session = NotesFactory.createSession(HOST, USER, PASSWORD); db = session.getDatabase(session .getServerName(), "djbuch/djbuch.nsf"); Cache cache = DJCache.getCache(); doc = makeADoc(db); DJCacheDocument djDoc = new DJCacheDocument(doc); cache.addObject("key", djDoc); doc = GC.recycle(doc); djDoc = null; djDoc = (DJCacheDocument) cache.getObject("key"); doc = djDoc.createDocument(db); doc.save (true,false); System.out.println ("\n\nDJCacheDocument: "); DemoTools.dumpDoc (djDoc); System.out.println ("\n\nlotus.domino.Document: "); DemoTools.dumpDoc (doc); } catch (Exception e) { ... } finally { GC.recycle(db); GC.recycle(session); } } }
Listing 13-10 Die Verwendung des DJCacheDocument in einem Cache
514
13 Objekt- und Speichertechniken Im Code zum Buch im Package djbuch.kapitel_13 befindet sich die Klasse
DJCache, die das Setup des Caches erledigt und einen internen Singleton besitzt, in dem ein Cache geführt wird. Dieser kann über getCache () bezogen werden. Hilf-
reich ist, dass der ShiftOne Cache bereits eine Synchronized-Implementierung z.B. eines LRU Caches (least recently used) besitzt, die notwendig wird, wenn z.B. ein gemeinsamer Cache für Konfigurationsdokumente beim Einsatz von Servlets verwendet werden soll (also in Multithreaded-Umgebungen). Für den Einsatz des DJCacheDocument wird in Listing 13-10 zunächst ein Cache bezogen und mittels einer hier nicht aufgeführten Hilfsmethode ein lotus.domino.Document erzeugt. Die hierfür verwendete Methode makeADoc legt lediglich ein Dokument in der Datenbank an und versieht es mit einigen Items. Aus diesem Document kann ein DJCacheDocument erzeugt und dieses wiederum in den Cache gelegt werden. Um zu verdeutlichen, dass das DJCacheDocument wirklich im Cache bleibt, wird sowohl das Original-lotus.domino.Document recycelt als auch die Referenz des DJCacheDocument auf null gesetzt. Wie erwartet kann das DJCacheDocument wieder aus dem Cache geladen und sogar anhand der Methode createDocument wieder in ein lotus.domino. Document umgewandelt werden. Der Vergleich zeigt, dass diese beiden Dokumente gleich sind – bis auf die Einschränkung, dass aus dem Item $updatedby im DJCacheDocument ein Text-Item wurde. Ein kleiner Hinweis noch am Rande: Profildokumente sollen in Domino genau diese Aufgabe erfüllen. Es sind gecachte Dokumente, die performant referenziert werden können. Einzige Einschränkung ist hier, dass weiterhin eine Session benötigt wird, um ein Profildokument zu laden. Das DJCacheDocument wurde aber genau für den Fall entwickelt, dass keine (kontinuierliche) Session zur Verfügung steht.
13.3
Domino-Objekte in Multithreading-Umgebungen
Sollen Domino-Java-Objekte in Multithreaded-Umgebungen verwendet werden, so sind einige Besonderheiten zu berücksichtigen. Grundsätzlich können zwar Domino-Java-Objekte über Threads geteilt werden, hierbei sind aber besondere Regeln zu beachten. In Kapitel 5.2 wurde bereits ein Sonderfall vorgestellt: die Nutzung von Domino-Java-Objekten in Servlets. Dieser ist jedoch unkritisch (wie auch weiter unten noch aufgezeigt wird), da für jede Instanz des Servlets ein eigener Thread vorgesehen ist und dieser jeweils über die statischen Methoden in NotesThread in einen eigenen (Pseudo-) NotesThread konvertiert werden. Zu den Besonderheiten in Multithreaded-Umgebungen heißt es im Domino Designer-Handbuch82:
82
[Lotus, DesignerR65] – help65_designer.nsf – Kapitel NotesThread.class – Multithreading Issues. Nummerierung hinzugefügt durch den Autor.
13.3.1 Gemeinsame oder geteilte Session
515
„You should avoid multithreading unless you have good reason to use it, such as proceeding while file input/output and Web requests are processing. Observe the following guidelines: 1
2 3
4 5 6
Within a session, Domino Objects are shared, synchronized, and recycled across threads. Using a different session on each thread loses these capabilities; you must explicitly manage synchronization and recycle on a per-thread basis. Do not use DbDirectory across threads. Accessing an existing document on multiple threads is permissible, but accessing it on just one thread simplifies memory management. Restricting accesses to one thread allows you to recycle without checking the other threads. Creating documents across threads is always safe and these objects can be recycled without reference to the other threads. Profile documents are cached on a per-thread basis. In the event of an update contention, the last thread updating wins. Take care not to delete a document needed for navigation by a view or collection on another thread. When child objects are used on threads other than the parent, keep the parent thread alive until all child threads terminate. This is particularly important when using Domino Objects in AWT event handlers.“
An dieser Stelle soll nochmals darauf hingewiesen werden, dass die statischen Methoden sinitThread und stermThread immer nur einmal innerhalb eines Threads eingesetzt werden dürfen. Daher ist es gute Praxis, in der Regel NotesThreads durch Implementierung von Runnable (oder durch Erweiterung von NotesThread) zu erzeugen.
13.3.1
Gemeinsame oder geteilte Session
„Innerhalb einer Session werden Domino-Objekte geteilt, synchronisiert und recycelt. Wird für jeden Thread eine eigene Session aufgebaut, geht diese Eigenschaft verloren; Synchronisation und Recycling müssen dann für jeden Thread separat und manuell durchgeführt werden.“ 83 In dem ersten Punkt spricht das Handbuch zwar die Möglichkeit an, per NotesThread nt = new NotesThread ((Runnable) myClass); nt.start(); – wobei myClass Runnable implementiert – einen neuen NotesThread zu instanzieren, aber in diesem NotesThread keine eigene Notes-Session über die NotesFactory zu beziehen, sondern diese über jeden neuen Thread zu teilen. Dies erfordert, dass die Session außerhalb von myClass erzeugt und im Konstruktor von myClass mit übergeben wird, um dort zur Verfügung zu stehen. Die positive Folge ist, dass Objekte nur in einem der Threads erzeugt und recycelt werden müssen. Dies kann aber gleichzeitig ein Nachteil sein. Wenn Sie z.B. ein und dieselbe Datenbank in mehreren Threads über getDatabase in Session beziehen, so 83
Übersetzung durch den Autor.
516
13 Objekt- und Speichertechniken
handelt es sich aus Domino-Sicht um ein und dasselbe Objekt. Folglich dürfen Sie dieses Database-Objekt lediglich in einem der Threads recyceln. Hierauf wird weiter unten noch eingegangen. •
•
•
Domino-Java-Objekte können nicht immer miteinander interagieren, wenn sie nicht mit derselben Session erzeugt wurden. So ist es zum Beispiel durchaus möglich, ein Document, das über die eine Session bezogen wurde, mittels eines Database-Objekts, das aus einer anderen Session stammt, in diese Datenbank zu kopieren. Für DocumentCollections trifft dies nicht zu. DocumentCollections können nur Dokumente aufnehmen, die auch in derselben Session bezogen wurden, andernfalls wird dies mit der NotesException „Document is from a different database“ quittiert. Vermeiden Sie daher diese Mischung aus geteilten Objekten und nicht geteilter Session. Datenbanken, die über getDatabase in Session bezogen werden, verweisen immer auf ein und dasselbe Database-Java-Objekt, wenn sie durch dieselbe Session erzeugt wurden (equals in Database liefert true). Werden zwei Database-Objekte aus verschiedenen Sessions erzeugt, so werden auch jeweils verschiedene Java-Objekte zurückgegeben. Analog gilt dies für Ansichten, die über getView in Database bezogen werden.
13.3.2
DbDirectory darf nicht über Threads geteilt werden
„Benutzen Sie DbDirectory nicht über mehrere Threads“. Diese Regel fordert lediglich, dass Sie ein DbDirectory-Objekt nicht an andere Threads übergeben dürfen.
13.3.3
Dokumente und Multithreading
„Im Prinzip ist es erlaubt, auf Dokumente threadübergreifend zuzugreifen. Wird nur aus einem Thread zugegriffen, vereinfacht dies die Speicherverwaltung. Wird der Zugriff auf einen Thread beschränkt, so kann ein solches Dokument recycelt werden, ohne die übrigen Threads zu berücksichtigen. Das Erstellen neuer Dokumente ist immer sicher. Das Recycling solcher Dokumente kann ohne Rücksicht auf andere Threads durchgeführt werden.“ Die Hilfe ist an dieser Stelle nicht sehr ausführlich. Gemeint ist Folgendes: Wenn ein Dokument in einem Java-Objekt referenziert wird und dieses Java-Objekt über mehrere NotesThreads geteilt wird – indem es z.B. im Konstruktor oder über eine Methode an einen anderen Thread übergeben wird –, so ist dies zwar erlaubt, aber es ist mit nachteiligem Einfluss auf das Speichermanagement zu rechnen (mögliche Performanceeinbußen).
13.3.3 Dokumente und Multithreading
517
Best Practice Müssen Domino-Java-Objekte (insbesondere Document) von Threads gemeinsam genutzt werden, sollte auch die Session, auf der diese Objekte basieren, gemeinsam genutzt werden, d.h. die Session wird bereits bei der Erzeugung eines Threads als Parameter übergeben. Dies ist nicht zu verwechseln mit der gemeinsamen Nutzung eines Domino-Dokuments, das in der NSF-Datenbank gespeichert ist. Diese Dokumente können von beliebigen Threads jeweils autark mit eigener Domino-Session in ein eigenes Java-Objekt geladen und dem gesamten Lifecycle – Laden, Verändern, Löschen oder Speichern und Recycling – zugeführt werden. Wird die Session gemeinsam genutzt, dürfen Objekte, die von den Threads gemeinsam genutzt werden, nur von einem Thread dem Recycling zugeführt werden. Werden dieselben Datenbanken oder Ansichten von mehreren Threads bei gemeinsamer Session benötigt, so sollten diese Objekte möglichst nur an einer Stelle erzeugt und wieder aufgeräumt werden, um Fehler zu vermeiden, da Domino für getView und getDatabase immer ein und dasselbe Java-Objekt zurückgibt (bei gemeinsamer Session). Bei der gemeinsamen Nutzung von Domino-Objekten und insbesondere der Session sollte sichergestellt werden, dass der Thread, der diese Objekte erzeugt, bis zuletzt bestehen bleibt. Die Erweiterung von NotesThread oder die Implementierung von Runnable ist der Verwendung von sinitThread und stermThread zur Erzeugung von NotesThreads vorzuziehen.
Hinzugefügt sei hier, dass eine derartige Übergabe nur zu empfehlen ist, wenn sich die Threads die Session teilen. Es ist nicht (immer) zulässig, in zwei getrennten NotesThreads über die NotesFactory eine jeweils eigene Session aufzubauen und dann Domino-Java-Objekte zwischen den Threads auszutauschen (siehe Punkt 1). Oder anders formuliert: •
•
Domino-Java-Document-Objekte können miteinander interagieren, wenn sie mit derselben Session erzeugt wurden, auch wenn dies über mehrere Threads geschieht. Mehrere Sessions in mehreren Threads können völlig unabhängig voneinander ein und dasselbe Dokument einer Datenbank laden. Dies geschieht jedoch völlig unabhängig voneinander, so dass beide dem Recycling zugeführt werden müssen. Hierbei müssen Speicherkonflikte beachtet werden.
Gleichzeitig ist es aber sehr wohl zulässig, z.B. ein und dasselbe (in der Datenbank gespeicherte) Dokument in einer Domino-Datenbank z.B. per getDocumentByUNID in zwei getrennten Threads mit zwei getrennten Sessions zu laden. Die hieraus entstehenden individuellen Java-Objekte – einschließlich der dahinter liegenden C++ Re-
518
13 Objekt- und Speichertechniken
präsentation – sind unabhängig voneinander und müssen auch jeweils separat dem Recycling zugeführt werden. Der einzige Punkt, der für dieses Szenario zu berücksichtigen ist, ist die Synchronisation. Wird diese unterlassen, wird es bei der Verwendung von save() zu Speicherkonflikten kommen. Getrennte Sessions in getrennten Threads sind nichts anderes als unabhängige Client-Zugriffe, wie sie z.B. durch (mehrere) Notes Clients entstehen.
13.3.4
Profildokument und Multithreading
„Profildokumente werden je Thread gecacht. Falls mehrere Threads dasselbe Profildokument ändern, setzt sich die (zeitlich) letzte Änderung durch.“ Anders als „normale“ Dokumente kennen Profildokumente keine Speicher- oder Replizierkonflikte. Dementsprechend ist es konsequent, dass sich hier – wie für diesen Fall üblich – die letzte Änderung durchsetzt.
13.3.5
Dokumente löschen im Multithreading
„Beim Löschen von Dokumenten muss berücksichtigt werden, dass keine solchen Dokumente gelöscht werden, die von Ansichten oder Collections in anderen Threads für die Navigation benötigt werden.“ Hierbei handelt es sich um ein grundsätzliches (und für Multithreading typisches) Problem – unabhängig von Domino. Stellen Sie sicher, dass Sie keine Objekte löschen, die noch von anderen Threads benötigt werden. Dies macht insbesondere Probleme, wenn der eine Thread versucht, eine Ansicht oder DocumentCollection zu durchschreiten (mit z.B. getNextDocument()), und ein anderer Thread eines der in der Collection enthaltenen Dokumente löscht. Dies wird zu Fehlern führen, da die Collection gewissermaßen „unterbrochen“ wurde.
13.3.6
Parent- und Child-Objekte im Multithreading
„Wenn Objekte, die von einem übergeordneten Objekt abhängen, in einem anderen Thread als dem des übergeordneten Objekts verwendet werden, so muss sichergestellt werden, dass der Thread des übergeordneten Objekts so lange aktiv bleibt, wie Threads, die abhängige Objekte verwenden. Dies ist insbesondere wichtig, wenn Domino-Objekte in AWT Event-Handlern eingesetzt werden.“ Ein einfaches Beispiel für eine derartige Situation ist ein RichTextItem, das aus einem Document bezogen wurde. Wird das RichTextItem in einem anderen Thread als das Document verwendet, so muss der Thread, in dem das Document bezogen wird, mindestens solange aktiv sein wie der Thread, der das RichTextItem verwendet.
13.3.7 Multithreading mit gemeinsamer Session
519
... public class DJStressRunnerOneSession { ... //diverse Konstanten private static final boolean[] info = new boolean [STRESS_COUNT]; private static DJStressTestExternalSession [] st = new DJStressTestExternalSession [STRESS_COUNT]; private static NotesThread [] nt = new NotesThread [STRESS_COUNT]; public static void main(String[] args){ Session session = null; try { if (HOST == null) {NotesThread.sinitThread();} session=NotesFactory.createSession (HOST, USER, PASSWORD); long L = System.currentTimeMillis(); for (int i=0; i < STRESS_COUNT; i++) { st [i] = new DJStressTestExternalSession (session,"Thread " + i); nt [i] = new NotesThread( st [i]); nt [i].start(); info[i] = false; } int allDone=0; for (int dCount = 0; dCount < MAX_DELAY_COUNT && allDone!=STRESS_COUNT; dCount++) { int tmpDone = allDone; for (int i = 0; i < STRESS_COUNT; i++) { if (st [i].isDone() && !info [i]) { allDone++; info [i] = true; } } if (tmpDone != allDone) { System.out.println (allDone + " Threads done. (" + (System.currentTimeMillis() - L) + ")"); } Thread.sleep(DELAY); } } catch (Exception e) { e.printStackTrace(); } finally { ... //Überfallige Threads stoppen, recycle, stermThread } } }
Listing 13-11 Domino-Multithreaded-Stresstest – Aufruf
13.3.7
Multithreading mit gemeinsamer Session
Die Handhabung von Multithreaded-Anwendungen soll anhand einer kleinen Applikation erläutert werden. Das Beispiel ist die Basis eines Stresstest-Moduls, mit dem Anwendungen (oder z.B. ein Domino-Server) unter Last gebracht und getestet werden können. Die Anwendung besteht aus zwei Klassen: Einer Basisklasse, die dazu dient eine beliebe Anzahl von Tests in einem jeweils eigenen Thread zu starten (Lis-
520
13 Objekt- und Speichertechniken
... public class DJStressTestExternalSession implements Runnable{ private boolean done=false; private String title="new Thread"; private Session session=null; public void run () { Database db = null; CMSDocument doc=null, nextDoc=null; View view=null; DocumentCollection dc=null; try { System.out.println ("Running Thread " + title); db = session.getDatabase (session.getServerName(),"djbuch/djbuch.nsf"); String key = "Document in Thread " + title; doc = CMSDocument.getInstance (DemoTools.createDoc(db,"Wombat",key,null)); GC.recycle(doc);doc=null; view = db.getView("V_lookup_k6"); dc=view.getAllDocumentsByKey(key,true); doc=CMSDocument.getInstance (dc.getFirstDocument()); while (doc!=null) { nextDoc = CMSDocument.getInstance(dc.getNextDocument()); doc.setActive(doc.isActive()?false:true); doc.save(true,false); GC.recycle(doc); doc=nextDoc; } } catch (NotesException e) { System.out.println ("id = " + e.id); e.printStackTrace(); } finally { done=true; GC.recycle(doc); GC.recycle(nextDoc); GC.recycle(dc); // GC.recycle(view); // GC.recycle(db); } } public boolean isDone() { return done;} public DJStressTestExternalSession (Session s, String t) { this.title=t; this.session = s; } }
Listing 13-12 Domino-Multithreaded-Stresstest – Test
ting 13-11) und dem eigentlichen Test (Listing 13-12). Der Test entstammt einem realen Anwendungsfall. Mit ihm wurde das Verhalten der in Kapitel 13.1.2 vorgestellten CMSDocument-Klasse getestet. Das Startermodul enthält zunächst drei Arrays, die jeweils neben einem Flag für die Fertigstellung die Tests basierend auf der Klasse DJStressTestExternalSession und die hieraus aufgebauten NotesThreads enthalten .
13.3.7 Multithreading mit gemeinsamer Session
521
Auch wenn die Session über sämtliche Instanzen des Tests geteilt und eingangs wie gewohnt über die NotesFactory erzeugt wird, müssen die eigentlichen Tests Runnable implementieren und dann über new NotesThread (Runnable) in einen NotesThread konvertiert werden. Hierfür wird innerhalb einer Schleife, die sämtliche (STRESS_COUNT) Tests startet, je ein neues Test-Objekt mit new DJStressTestExternalSession erzeugt. Beachten Sie, dass nur eine einzige Session an sämtliche DJStressTestExternalSession-Objekte übergeben wird. Nun können die Tests jeweils gestartet werden. Zusätzlich wird festgehalten, dass die Tests noch nicht fertiggestellt sind. Nachdem die Tests gestartet wurden, muss eigentlich nur darauf gewartet werden, dass die Threads ihre Fertigstellung (s.u.) zurückmelden. In einer Schleife, die durch eine maximale Wartezeit (oder alternativ die Fertigstellung aller Threads) begrenzt ist , wird für alle gestarteten Threads in einer weiteren Schleife der Status der Threads überprüft . Durch einen kleinen Delay wird sichergestellt, dass die Überprüfung während der Wartezeit nicht zu viele Ressourcen blockiert. Listing 13-12 zeigt den eigentlichen Test. Er sucht in der Datenbank djbuch.nsf nach Dokumenten mit einem bestimmten Titel und schaltet das Flag isActive für diese Dokumente. Hierbei kommt die Klasse CMSDocument zum Einsatz. Die Klasse DJStressTestExternalSession implementiert Runnable, um als Thread fungieren zu können. Seine Domino-Session bezieht sie über den Konstruktor . Das Database-Objekt db und die Ansicht view werden anhand dieser externen Session bezogen. Für das Verständnis des Multithreadings für Domino-Java-Objekte ist wichtig zu verstehen, dass für alle (!) Threads der Aufruf von getDatabase und von getView immer ein und dasselbe Java-Objekt liefert. Folglich wäre es fatal, diese Objekte in der Klasse DJStressTestExternalSession zu recyceln, da sonst für alle übrigen Threads diese Objekte nicht mehr verfügbar wären. Dies wird durch die auskommentierten Recycle-Aufrufe angedeutet . Für Document gilt dies nicht. Hier kann die Schleife wie gewohnt mit getNextDocument durchschritten und das Recycling durchgeführt werden. Die Verwendung von getInstance in CMSDocument wird in Kapitel 13.1.2 näher erläutert. Auch die DocumentCollection ist individuell für den Thread und wird dem Recycling zugeführt . Wie oben bereits angedeutet, konnte weder die Datenbank noch der View, die in DJStressTestExternalSession erzeugt werden, dort auch wieder recycelt werden . Die in den beiden Listings (Listing 13-11 und Listing 13-12) gezeigte Anwendung enthält in diesem Zustand daher noch ein Memory Leak. Weder das Database- noch das View-Objekt werden nach Ablauf sämtlicher Threads recycelt. Dies lässt sich beheben, indem innerhalb der Tests ganz auf den Bezug von Database und View verzichtet wird und diese ähnlich wie die Session außerhalb an einer (und nur einer) zentralen Stelle erzeugt und wieder freigegeben werden. Wenn Sie die Anwendung starten, werden Sie feststellen, dass der Test selbst bei hoher Last (z.B. 1000 Threads) noch sehr schnell durchläuft, obwohl immerhin jeder Test ein Dokument erzeugt, die gesamte Datenbank durchsuchen und alle Trefferdokumente speichern muss.
522
13 Objekt- und Speichertechniken
Die Bündelung von vielen Anfragen in einer Session über mehrere Threads funktioniert sehr gut. Diese Technik kann auch angewendet werden, wenn serielle Operationen parallelisiert werden sollen. Dabei muss beachtet werden, dass eine Session nicht durch zu viele parallele Operationen überladen wird. Fazit: Die Verteilung von mehreren Anfragen über mehrere Threads bei gemeinsamer Nutzung bietet gute Möglichkeiten, Ressourcen zu schonen – so wird z.B. bei DIIOP-Verbindungen nicht für jeden Thread ein neuer Socket benötigt, da sich alle einen Socket der Session teilen. Gleichzeitig birgt dies die Gefahr, durch Recyclingfehler Memory Leaks zu erzeugen.
13.3.8
Multithreading ohne gemeinsame Session
Um das Verhalten bei gemeinsamer und ohne gemeinsame Session vergleichen zu können, wurden dem Code zum Buch die beiden Klassen DJStressRunner und DJStressTest hinzugefügt. Sie erreichen durch eine kleine Abwandlung der Klassen DJStressRunnerOneSession und DJStressTestExternalSession, dass für jeden einzelnen Test eine neue Session aufgebaut wird. Anstatt in DJStressRunner eine gemeinsame Session zu erzeugen und an den Test zu übergeben, wird einfach die Erzeugung der Session in die Testklasse DJStressTest verlagert. Führt man diese Anwendung aus, so stellt man fest, dass die Performance hierunter erheblich leidet. Schließlich muss für jeden Thread eine neue Session initialisiert werden. Wird DIIOP genutzt, so kann man sehr gut über netstat -a (DOS-Konsole) beobachten, wie für jede Session ein TCP Socket eröffnet werden muss. Aus der Sicht eines Lasttests ist diese Herangehensweise allerdings in der Regel die sinnvollere, insbesondere wenn der Test via DIIOP auf eine entfernte Maschine angesetzt wird. In einer produktiven Umgebung werden ja viele (einzelne) Clients sich mit jeweils einer eigenen Session mit dem Server verbinden wollen. Die Unterschiede im Code beschränken sich ansonsten neben dem Verschieben des createSession-Aufrufes in den Test auf das unterschiedliche Recycling. Hat der Thread eine eigene Session, muss er sich auch selbst um das Recycling kümmern. Die in Listing 13-12 auskommentierten Recycle-Aufrufe für Database und View müssen hier wieder aktiviert werden.
13.4 Domino und DB2
13.4
523
Domino und DB2
Die Datenstruktur von Domino basiert auf dem bereits vielseitig diskutierten Modell loser Schemata, das im Wesentlichen durch das Document und seine Items repräsentiert wird. Offenkundig handelt es sich hierbei nicht um einen relationalen Datenbankansatz, auch wenn sich solche Strukturen in Domino abbilden lassen (s. Kap. 7.3.1). Vergleicht man Domino mit relationalen Datenbanken, steht die nachrichtenund objektzentrierte Welt verteilter Systeme von Domino der strukturierten Welt strikter Schemata und transaktionszentrierter SQL-Datenbanken gegenüber. Schon sehr früh wurde nach Konzepten gesucht, relationale (SQL-basierte) Datenbanksysteme mit Domino zu verbinden, um die Vorteile der beiden Welten miteinander zu kombinieren. Aus diesen Überlegungen entstanden verschiedene Konnektoren, wie DECS84 oder LEI85. Für Domino Version 7 hat nun Lotus den bisher radikalsten und dadurch auch nahtlosesten Ansatz gewählt. Domino-Datenbanken können anstatt in einem NSF File in einer DB2-RDBMS-Datenbank gespeichert werden (NSFDB2).
13.4.1
Einführung
Die Speicherung von Domino-Daten in einer relationalen Datenbank wie DB2 hat weitreichende Folgen und eröffnet bisher ungeahnte Möglichkeiten. Gleichzeitig bedeutet dies an vielen Stellen Veränderungen, die bei der Anwendungsentwicklung und der Administration berücksichtigt werden müssen. Neben der Einrichtung der Verbindungen zwischen Domino und DB2, der Verwaltung der DB2-basierten Domino-Datenbanken im Domino Administrator, müssen sich nun Domino Administratoren und teilweise auch Anwendungsentwickler mit der Administration und den Berechtigungskonzepten von DB2 auseinandersetzen. Vor allem aus Sicht von Java öffnet der Schritt in Richtung RDBMS Perspektiven, an die bisher nicht zu denken war. Die hieraus resultierenden Veränderungen sind aber nicht nur technischer Art, sondern vor allem in den Zielsetzungen und informellen Absichten von Anwendungen zu suchen. Allerdings sollte nicht vergessen werden, dass die DB2-Speicherung von Domino-Daten in einer relationalen Datenbank keineswegs ein Paradigmenwechsel ist, sondern lediglich neue Möglichkeiten eröffnen will. Um die Verarbeitung und Verwendung dieser neuen Technik verstehen zu können, sollen zunächst noch einmal die verschiedenen Perspektiven von Domino und relationalen Datenbanken untersucht werden. Domino basiert auf losen Schemata (dem Document) und zeichnet sich aus durch: 84 85
Domino Enterprise Connection Services – Dieser Ansatz ermöglicht die Definition von Datenverbindungen zu externen Datenquellen, die live abgefragt werden können. Lotus Enterprice Integrator – LEI erweitert die Funktionen von DECS dahingehend, dass Daten physisch zwischen Domino und externen Datenquellen synchronisiert werden können. Zusätzlich stehen Schnittstellen zu Lotus Script zur Verfügung.
524
• • • • •
• • • •
13 Objekt- und Speichertechniken
Zentrierung auf Kommunikation, Teamworkanwendungen, Workflow und Strategie. Unterstützung von mobilen Nutzern und Offline-Arbeitern ohne aktive Verbindung zu einem Server. Flexible Datenstrukturen auf Basis loser Schemata und entsprechend hochflexible Datentypen. Hieraus resultierende Konzentration auf Workflow, Kollaboration, Data Mining und Knowledge Management. Weitreichende Unterstützung von eingebetteten Objekten, Multimediaelementen, RichText, Dokument-Verlinkungen und Binärdaten, die alle in der einheitlichen Datenstruktur des Document gespeichert werden können. Berechtigung und Verschlüsselung bis auf Basis von Dokument und Feld. Ausgereifte Volltextindizierung. Dezentrale Verarbeitung in inhomogenen, verteilten Umgebungen. Buttom-up-Entwicklung.
Während sich also Domino auf die Verarbeitung unstrukturierter Daten beliebigen Datentyps (RichText, Multimedia, Binärdaten) für Knowledgemanagement, Teamarbeit und Workflow konzentriert, bedienen RDBMS-Datenbanken große Mengen strukturierter, streng typisierter Daten, die im Regelfall in einer nicht redundanten Normalform gespeichert werden: • • • • • • • •
Konzentration auf die effektive, operationale Verarbeitung großer Datenmengen. Transaktionszentriert. Verschiedene Commit-Strategien, wie Atomic Commit und Two Phase Commit. Strikte Einhaltung von Schemata und klar vorstrukturierten Datentypen. Redundanzfrei. Zentrale Datenhaltung für 2- oder 3-Trier-Anwendungen. Systemzentrierte Top-Down-Entwicklung. Konzentration auf ständig mit dem Server verbundene Online-Arbeitsplätze.
Die Schwerpunkte beider Systeme sind zum Teil so unterschiedlich und gegensätzlich, dass die Koexistenz beider Systeme seine Berechtigung findet. Lotus' Ansatz, die Domino-Daten in der RDBMS-Umgebung DB2 vorzuhalten und über verschiedene Techniken einerseits Domino-Daten in DB2 durch die so genannten DAV (DB2 Access View) und andererseits SQL-Daten in Domino (Query View) zur Verfügung zu stellen, ist ambitioniert und wird Vorteile für beide Systeme haben. DB2-Datenbankanwendungen werden insbesondere von der Möglichkeit, Daten offline und in verteilten Umgebungen einzusetzen (Replikation), profitieren. So ist zum Beispiel denkbar, dass für eine Anwendung Roadwarrior im Außendienst über eine Domino-Anwendung Daten erfassen, die später in einer RDBMS-Anwendung benötigt werden. Leicht werden diese Daten über Replikation und NSFDB2 in die DB2-Datenbank gelangen. Gleichzeitig profitiert DB2 von der Eigenschaft Dominos,
13.4.2 Struktur
525
Daten per Datensatz mit Benutzerrechten ausstatten zu können. Domino-Daten unterliegen auch in ihrer DB2-Repräsentation dem kompletten Berechtigungssystem von Domino. Domino-Datenbanken werden insbesondere von der Möglichkeit von RDBMSSystemen profitieren, Daten relational miteinander zu verknüpfen. Dies hebt für Domino die Grenze zwischen Domino-Datenbanken auf, da durch das NSFDB2-Format und die Verwendung einfacher SQL-Joins Daten verschiedener NSF-Files verknüpft angezeigt werden könnnen. In der Kombination von DAV und QueryView eröffnet sich die Möglichkeit, in Domino-Anwendungen Domino-Daten in einem QueryView über SQL zu selektieren. Der Vorteil liegt hierbei in der Möglichkeit, den SQL Select dynamisch, z.B. über Benutzereingaben, zu generieren.
13.4.2
Struktur
Auch wenn der Schritt von Lotus, die Speicherung von Domino-Daten in DB2 zu ermöglichen, im Hintergrund sicherlich enorme Veränderungen nach sich gezogen hat, ist aus Sicht von Domino an der Oberfläche der größte Teil dieser Veränderungen nahezu transparent. Letztendlich lässt sich NSFDB2 im Wesentlichen durch drei Neuerungen beschreiben: Das NSFDB2-Dateiformat NSFDB2 ist der Name für das neue Datenformat, in dem Domino-Daten in DB2 gespeichert werden. Wird eine Domino-Datenbank in DB2 gespeichert, hat dies aus Sicht einer Domino-Anwendung (zunächst) keine Änderung zur Folge. Das neue Speicherformat ist transparent. Auch ist mit der reinen Änderung der Datenhaltung noch keine unmittelbare Erweiterung von Funktionalitäten verbunden. Allerdings können NSFDB2-Datenbanken nur über einen Domino-Server erreicht werden. Lokal (Abb. 13-4) kommt nur das (alte) NSF-Format zum Einsatz. Verschiedene Repliken einer Datenbank können sowohl als NSFDB2 als auch als NSF in gemischten Umgebungen vorliegen, wobei zwischen den verschiedenen Formaten wie gewohnt repliziert werden kann. Das Format kann zwischen den beiden Möglichkeiten gewechselt werden.
Abb. 13-4 Lokaler Zugriff auf eine NSFDB2-Datenbank ist nicht möglich
526
13 Objekt- und Speichertechniken Domino-Daten im NSFDB2-Format sind auch aus Sicht von DB2 zunächst nicht erreichbar, denn die eigentliche Datenhaltung erfolgt in eigens hierfür entwickelten (verteilten) Tabellen mit Datenhaltungs- und Verwaltungsaufgaben. Um Domino-Daten über DB2 abzufragen, müssen DB2 Access Views (DAV) über den Domino Designer eingerichtet werden.
DAV – DB2 Access View Sollen Domino-Daten für DB2 verfügbar sein, muss ein DB2 Acccess View (kurz „DAV“) eingerichtet werden. Ein DAV wird über den Domino Designer eingerichtet, ist aber aus der Domino-Perspektive eher als ein Konfigurationsdokument als ein Designelement einzustufen. DAVs wiederum sind – auch wenn sie über den Domino Designer eingerichtet werden – nur für die Benutzung in DB2 bestimmt und können aus Domino nicht direkt referenziert werden (siehe jedoch „QueryView“). Wird ein DAV eingerichtet, so wird im Domino Designer festgelegt, welche Dokumente und Items dieser Dokumente in DB2 sichtbar sein sollen. Zur Zeit ist eine Spezifikation der Dokumente nur auf Basis ihrer Defaultmaske (also über das Item mit Namen Form) möglich. Items können beliebig angegeben werden. Dies können sowohl Items sein, die als Feld in der zugehörigen Maske definiert sind, als auch Items, die auf anderen Wegen in das Dokument gelangt sind. Liegen Items, die im DAV definiert wurden, in einem Dokument nicht vor, bleiben die korrespondierenden Werte in der DB2-Tabelle leer. Einzige Einschränkung hierbei ist die Liste der möglichen Datentypen, die für die Verwendung in DB2 freigegeben werden können. Dies sind Text, Textlist, Number, Numberlist, Date/ Time und Date/Time List or Range, wobei z.B. Namensfelder zumindest als Text (VARCHAR) in DB2 zur Verfügung gestellt werden können. RichText kann (zur Zeit?) nicht für den Zugriff über DB2 freigegeben werden. Ist ein DAV eingerichtet, muss über entsprechende Funktionsbuttons die Datenbank in DB2 upgedatet werden. Dieser Prozess ist vordefiniert und muss lediglich ausgelöst werden. Nach Erstellen eines DAV werden die Dominodaten redundant in einer DB2-Tabelle (_T) gespeichert und über eine DB2-Ansicht () bereitgestellt. Die Tabelle selbst bleibt für den Benutzer, der den DAV im Designer eingerichtet hat, gesperrt, da nur in der zugehörigen Ansicht die Domino-ACL-Sicherheit für DB2 durchgesetzt wird und daher immer auch nur diese Ansicht in Anwendungen verwendet werden soll. Ein DAV ermöglicht also die zeilenbezogene ACL-Sicherheit in einer DB2-Datenbank! Eine derart über ein DAV erzeugte Ansicht ist wie jede andere SQL-Tabelle in DB2 verwendbar und kann auch in der Kombination mit anderen Tabellen oder Ansichten eingesetzt werden.
13.4.2 Struktur
527
Die in einer DAV-Ansicht gezeigten Daten unterliegen der Domino-Sicherheit. Hierfür werden im Domino-Adressbuch (names.nsf) Mappings zwischen den DB2- und den Domino-Benutzern hinterlegt. Ein DB2-Benutzer darf in einer DAV-Ansicht nur die Daten lesen oder bearbeiten, die der zugeordnete DominoBenutzer lesen oder bearbeiten darf. Hierin liegt ein großer Vorteil, aber auch eine gewisse Schwierigkeit, da administrativ dafür gesorgt werden muss, dass die Mappings für alle beteiligten Benutzer richtig eingerichtet sind. Damit DB2 auf DAV-Ansichten aus Domino zugreifen kann, muss auf dem Domino-Server zusätzlich der so genannte DB2 Access Server installiert sein. Dies ist ein modifizierter Domino-Server, der für die Kommunikation zwischen beiden Systemen und insbesondere die Durchsetzung von Domino-Rechten in Richtung DB2 zuständig ist. Query View Ein Query View ermöglicht nun den umgekehrten Weg. Ein Query View ist eine normale Domino-Ansicht, deren SELECT-Formel86 nicht wie gewohnt über eine @Formel bestimmt wird, sondern über einen SQL-Ausdruck. Da grundsätzlich für den Domino-Server festgelegt ist, welche DB2-Datenbank die Domino-Daten hält, können in dieser SQL-Formel Datenbanken und Ansichten einfach über das Schema und den Tabellen- oder Ansichtenname eindeutig angesprochen werden. Im Umkehrschluss bedeutet dies allerdings, dass (zunächst) nur Daten angesprochen werden können, die sich in der dem Domino-Server zugeordneten DB2Datenbank befinden. Über die Einrichtung von DRDA-Datasource-Verbindungen lässt sich diese Hürde jedoch überbrücken. Aus Sicherheitsgründen sind in der SQL-SELECT-Formel nur Anweisungen zulässig, die ein Resultset zurückgeben. Die durch das SQL Statement selektierten Spalten der DB2-Tabellen oder -Ansichten können in den Spaltenformeln der Domino-Ansichten wie Items über den Namen referenziert werden. Handelt es sich bei den SQL-Daten um originäre SQL-Daten (nicht um einen DAV View), so werden die Daten nur in der Domino Query View angezeigt. Ein Doppelklick bewirkt kein Öffnen eines Domino-Dokuments, da eine derartige Zuordnung ja auch nicht vorliegt. Bei derartigen „originären“ DB2-Daten wird auch von „federated data“ gesprochen. Sollen in einer Domino-Datenbank im NSFDB2-Format nur federated data angesprochen werden, ist es nicht notwendig den DB2 Access Server zu installieren. Interessant wird es, wenn über einen Query View Domino-Daten referenziert werden, die zuvor als DAV dargestellt wurden. Im Query View können nämlich nun Domino-Daten über eine SQL-Formel ausgewählt und natürlich über einen SQL-JOIN verbunden werden. Dies gilt natürlich auch für DAV-Ansichten in DB2, die verschiedenen (!) Domino-Datenbanken entstammen. So wird es ermöglicht, datenbankübergreifend Domino-Daten miteinander zu verbinden und in einem Query View anzuzeigen. 86
Siehe auch Kapitel 3.3.3
528
13 Objekt- und Speichertechniken DAV-Ansichten enthalten eine Spalte #NOTEID, in der die NoteID der zugehörigen Dokumente gespeichert wird. Über diese wird es möglich, dass Dokumente eines DAV nicht nur im Query View angezeigt, sondern auch durch Doppelklick geöffnet werden können. Allerdings ist dabei darauf zu achten, dass nur solche Dokumente geöffnet werden können, die zu der Domino-Datenbank gehören, in der sich der Query View befindet. Zusätzlich ist erforderlich, dass im SQL Statement die Spalte #NOTEID ausdrücklich geladen wird. Ein weiterer interessanter Aspekt ergibt sich dadurch, dass die SQL-Anweisung, die für einen Query View definiert wird, nicht statisch angegeben werden muss, sondern als Ergebnis einer @Formel berechnet wird. Folglich kann die SQL-Anweisung dynamisch zur Laufzeit berechnet werden und kann daher zum Beispiel abhängig von Benutzereingaben definiert werden.
Was NSFDB2 leistet: • • • • •
• • •
Schaffung eines zentralen Datastores durch Speicherung von Domino-Daten in DB2. Überbrückung der Schranken zwischen Domino und RDBMS-Daten. Query Views stellen DB2-Daten in Domino zur Verfügung. DB2 Access Views stellen Domino-Daten in DB2 zur Verfügung. Dynamische Views – Live Selection von DB2- oder Domino-Daten auch via Benutzerinteraktion durch Verwendung von @Prompt bei der Berechnung der SQL SELECT-Anweisung. Datenbankübergreifende Verknüpfung von Domino-Daten. Verknüpfung von Unternehmensdaten aus DB2 mit Domino-Daten. Zeilenbasierte (Datensatzbasierte) Domino-ACL-Sicherheit in DB2 (für DAV Views).
Einschränkungen und Besonderheiten • • •
Query Views können (noch?) nicht im Browser angezeigt werden. Query Views werden nicht automatisch refreshed, sondern müssen ausdrücklich neu geladen werden (F9 im Notes Client oder view.refresh()). Jedem Domino-Benutzer, der einen Query View anzeigen soll, muss ein DB2-Benutzer zugeordnet sein. Dies ist insbesondere eine wichtige Voraussetzung für Notes Client-Anwendungen. Java-Anwendungen und -Agenten werden in der Regel ohnehin unter einem (einzelnen) Funktionsuser betrieben und sind im Normalfall hiervon nicht betroffen.
13.4.3 Sicherheit, Benutzer und Gruppen •
• •
529
Werden in Query Views federated data angezeigt und diese Daten mit Daten aus Dokumenten der aktuellen Datenbank verknüpft, können diese Dokumente der aktuellen Datenbank geöffnet werden. Dokumente entfernter Datenbanken können über einen Query View (in Kombination mit einem DAV) angezeigt, nicht aber geöffnet werden. DB2 Access Views können keine RichText-Inhalte verarbeiten. DB2-basierte Datenbanken können wie normale Datenbanken repliziert werden. Es kann auch zwischen den beiden Formaten NSFDB2 und NSF repliziert werden. Wird eine NSFDB2-basierte Datenbank mit einer NSF-Datenbank repliziert, bleiben die Designelemente, die das DB2-Format voraussetzen, unsichtbar bzw. nicht verwendbar. Lokale Repliken haben immer das klassische NSF-Format.
NSFDB2 ist eine neue Funktion für Domino R7. Daher soll in den beiden folgenden Kapiteln auf die beiden administrativen Themen Sicherheit und Setup der DB2-Anbindung eingegangen werden, auch wenn dies nur indirekt für die Java-Entwicklung unter Domino erforderlich ist.
13.4.3
Sicherheit, Benutzer und Gruppen
Für das Verständnis der Zusammenarbeit von Domino und DB2 ist das grundlegende Verständnis der Berechtigungen und der involvierten Benutzer und Gruppen bei der Zusammenarbeit beider Systeme von großer Hilfe. Da dieses Wissen insbesondere beim Setup erforderlich ist, ist dieses Kapitel einführend dem Setup vorangestellt. Zunächst ist zu beachten, dass einige beschriebene Aktionen erst ausgeführt werden können, wenn der Domino-Server für die Benutzung mit DB2 aktiviert ist. Die Erläuterung bereits an dieser Stelle wird das spätere Verständnis erleichtern. •
• •
•
Jedem Domino-Benutzer, der auf DB2-Daten zugreifen soll, muss ein DB2-Benutzer zugeordnet werden. Diese Zuordnung wird im Domino Administrator vorgenommen, indem im Menü „People“ -> „People“ -> „Set DB2 Username“ der zugehörige Benutzer eingetragen wird. Es können hier nur Benutzer eingetragen werden, die im Betriebssystem als Benutzer zur Verfügung stehen, da diese (und nur diese) gleichzeitig als Benutzer für DB2 verwendet werden (können) – s. Abb. 13-5. Die Zuordnung muss ein-eindeutig sein, d.h. es kann für jeden Domino-Benutzer nur genau einen zugeordneten DB2-Benutzer geben und jeder DB2-Benutzer darf nur genau einmal als zugeordnet eingetragen werden. DB2-Benutzer sind Betriebssystem-Benutzer und werden als solche angelegt. DB2 Access Views unterliegen den ACL-Berechtigungen von Domino. DB2 erkennt die Rechte, indem anhand des Domino-DB2-Benutzermappings der dem aktuellen DB2-Nutzer zugeordnete Domino-Benutzer ausgewählt wird. Query Views unterliegen den in DB2 eingerichteten Rechten des gemappten DB2-Benutzers.
530
13 Objekt- und Speichertechniken
Abb. 13-5 Mapping zwischen Domino und DB2-Benutzern
•
•
Sämtliche Rechte auf DB2-Strukturen müssen über die DB2-Tools eingerichtet werden – am besten über Gruppen in DB2. Dies gilt nicht für DB2 Access Views, deren Rechte beim Anlegen im Domino Designer korrekt gesetzt werden und da dort ohnehin die Domino-ACL gilt. Jeder lokale Administrator (unter Windows) ist gleichzeitig Datenbankadministrator auf einer DB2-Instanz auf derselben Maschine. Diese Berechtigung wird implizit gewährt und muss nicht ausdrücklich eingerichtet werden. Wird also auf einer Entwicklungsmaschine mit administrativen Accounts gearbeitet, ist dafür Sorge zu tragen, dass die DB2-Anwendung auch für „normale“ Benutzer geeignet ist, indem die entsprechenden Berechtigungen in der DB2-Steuerzentrale eingerichtet werden und die Anwendung mit entsprechenden Benutzeraccounts getestet wird.
Aus Sicht von Domino werden verschiedene Domino-Benutzer und deren zugehörige User IDs benötigt, um eine Kommunikation mit DB2 zu ermöglichen: Regulärer Domino-Benutzer / Domino-Benutzer-ID Jeder reguläre Benutzer, der Domino und die neuen NSFDB2-Funktionen nutzen soll, muss wie gewohnt über eine Domino-Benutzer-ID verfügen. Alternativ genügt auch ein so genannter Web User Account. Dies ist ein Benutzer, der nicht über den Domino Administrator und die dort verfügbaren Registrationswerkzeuge angelegt wird, sondern der lediglich im Domino-Adressbuch ein Benutzerdokument mit Benutzername und Internetkennwort besitzt. Ein derartiger Benutzer kann wie gewohnt über die ACL berechtigt werden und ist für den Zugriff über eine Internet-Session (über DIIOP – s. Kap. 5.3ff.) geeignet.
13.4.3 Sicherheit, Benutzer und Gruppen
531
Ein regulärer Benutzer, der auf DB2 zugreifen soll, benötigt ein DB2-User-Mapping. Dieses kann über den Domino Administrator eingerichtet werden (s. Abb. 13-5). Auch ein Web User benötigt ein DB2-User-Mapping. Die Rechte, die nun dieser gemappte Benutzer (und somit der eigentliche Domino-Benutzer) auf DB2 erhält, wird über die administrativen Möglichkeiten von DB2 (z.B. in der Steuerzentrale – s. Abb. 13-6 – oder über entsprechende SQL
Abb. 13-6 DB2-Benutzer mit Rechten ausstatten GRANT-Befehle) geregelt, wobei auch in DB2 der gemappte Benutzer angelegt werden muss (s. Abb. 13-7). Am besten wird eine Benutzergruppe (z.B. DB2Users – s.u.) auf der während des DB2-Enablements speziell für Domino eingerichteten DB2-Datenbank „DOMINO“ mit den gewünschten DB2-Rechten ausgestattet. Das Domino-DB2-Benutzermapping funktioniert übrigens in beide Richtungen. Versucht ein DB2-Benutzer auf einen DB2 Access View zuzugreifen, werden zunächst anhand des Mappings und des dort vorgefundenen Domino-Benutzers seine Domino-Rechte geprüft und durchgesetzt. Hierin liegt begründet, dass die Mappings ein-eindeutig sein müssen. Regulärer Domino-Server / Domino-Server-ID Jeder Domino-Server benötigt eine reguläre Server-ID. Diese ID regelt die Berechtigungen des Servers und wird bei der Registrierung eines Servers angelegt. Diese Server-ID wird immer in einer herkömmlichen Domino-Infrastruktur verwendet.
532
13 Objekt- und Speichertechniken
Abb. 13-7 Neuen Benutzer in DB2 anlegen
Während des DB2-Enablements des Domino-Servers (s.u.) wird nun in dieser Domino-Server-ID gespeichert, mit welchem Benutzernamen und Passwort der Domino-Server sich mit DB2 verbinden soll. Über den Domino Administrator kann über den Befehl „Edit DB2 Login Information“ dieses Login verändert werden (s. Abb. 13-8). Domino Access Server / Domino Access Server-ID Für die Verwendung der DB2 Access Views wird zusätzlich zu einer StandardDomino-Installation die Installation des so genannten Domino Access Servers benötigt, der die Domino-Berechtigungen im DAV durchsetzt.
13.4.3 Sicherheit, Benutzer und Gruppen
533
Abb. 13-8 DB2-Super-User – Anmeldung des Domino-Servers an DB2
Um diese Installation durchführen zu können, muss vor (!) der Installation des Domino Access Servers über den Server-Registrationsprozess im Domino Administrator ein zusätzlicher Server registriert werden. Hierbei wird die Server-ID für den Domino Access Server generiert, wobei kein Passwort für die Server-ID vergeben werden darf. Diese ID wird dann vom Domino Access Server verwendet. Die „normale“ Server-ID des eigentlichen Domino-Servers, der mit DB2 verbunden werden soll, darf hierfür nicht verwendet werden. Auf Betriebssystemebene (unter Windows) gibt es folgende Gruppen, die im Zusammenhang mit Domino und DB2 interessant sind: DB2Users In der Gruppe DB2Users sollen sich alle „regulären“ DB2-Benutzer befinden, die nicht-administrativen Zugriff auf DB2 erhalten sollen. Werden Domino-Benutzer mit DB2-Benutzern gemappt, müssen die gemappten DB2-User in diese Gruppe eingetragen werden. Alternativ können sich reguläre DB2-Benutzer auch in der Standard-Benutzergruppe des Betriebssystems („Users“ oder „Benutzer“) befinden.
534
13 Objekt- und Speichertechniken
DB2Admns In der Gruppe DB2Admins befinden sich die Administratoren der lokalen DB2Instanz. Die DB2-Administratorengruppe muss auf diese Gruppe verweisen87. Bei der Installation von DB2 wird ein so genannter Instanzeigner festgelegt, der als Ausführender der DB2-Prozesse fungiert. Dieser heißt in der Regel DB2admin (unter Windows und db2inst1..n unter Unix), muss sich in der Administratorengruppe des Betriebssystems befinden und zusätzliche Rechte (s.u.) erhalten. Er sollte ebenfalls in dieser Gruppe eingetragen sein. DB2Dom In der Gruppe DB2Dom befindet sich der „Domino Super User“ (s.o. DominoServer-ID), über den die Kommunikation zwischen Domino und DB2 abgewickelt wird. Diese Betriebssystem-Gruppe muss der DB2 SYSCTRL-Gruppe zugeordnet werden88. Fazit: Die Kommunikation zwischen Domino und DB2 wird über einen Super User geregelt. Dies ist ein Betriebssystem-User, der sich in der Gruppe DB2DOM befindet und dessen Anmeldeinformationen in der Domino-Server-ID gespeichert werden. Die Kommunikation in der umgekehrten Richtung zwischen DB2 und Domino wird über den Domino Access Server gesteuert, der separat installiert und zuvor registriert werden muss, wobei eine eigene Domino Access Server-ID erzeugt wird. Möchten Domino-Benutzer über Query Views auf DB2-Daten oder umgekehrt DB2-User über DB2 Access Views auf Domino-Daten zugreifen, muss es ein eindeutiges Mapping zwischen einem Domino-User und einem DB2-User geben, anhand dessen die jeweiligen Rechte ermittelt werden. Domino-User werden in Domino wie gewohnt mit Berechtigungen versehen, DB2-User sind System-User und werden in DB2 mit Rechten versehen. Für Domino-Anwender ist es etwas ungewohnt, dass DB2-User identisch mit Betriebssystem-Usern sind. Insbesondere hat dies zur Folge, dass sich die DB2-Rechte aus der aktuellen Anmeldung am Betriebssystem ergeben. Wird also z.B. die DB2Steuerzentrale mit einem angemeldeten Betriebssystem-User gestartet und versucht, auf einen DAV zuzugreifen, wird versucht über das Mapping einen Domino-Benutzer zu ermitteln, der auf diesen Betriebssystem (also DB2)-Benutzer gemapped ist. Unter Umständen, kann es also erforderlich sein, sich neu am Betriebssystem anzumelden, bzw. über „runas“89 die Steuerzentrale als anderer Benutzer zu starten. 87
88 89
In der DB2-Befehlskonsole kann der Parameter SYSADM_GROUP gesetzt werden, um dies zu erreichen. Mit dem Befehl get dbm cfg kann der Parameter ausgelesen und mit dem Befehl update dbm cfg using SYSADM_GROUP DB2Admns gesetzt werden. Die SYSCTRL Group kann ebenso über update dbm cfg using SYSCTRL_GROUP DB2DOM gesetzt werden. Um z.B. die DB2-Steuerzentrale mit den Rechten des Benutzers DB2ACC zu starten, verwenden Sie unter Windows den Befehl „runas /user:DB2ACC C:\Programme\IBM\ SQLLIB\BIN\db2cc.bat“. Dieser kann zur Vereinfachung auch in einer Verknüpfung verwendet werden.
13.4.4 Setup 13.4.4
535
Setup
Damit das Zusammenspiel zwischen DB2 und Domino reibungslos funktioniert, müssen bei der Installation von Domino, DB2 und DB2 Access Server bestimmte Regeln und Reihenfolgen eingehalten werden. Zur Installation (unter Windows) sind folgende Schritte notwendig: • • • • • • • • • •
Installation von Domino R7. Nachbereitung der Installation von Domino R7 – Aktivierung des Transaktionsloggings. Vorbereitung von Benutzern für die Installation und den Betrieb von DB2. Installation von DB2. Optional: Registrieren einer Domino-Server-ID für den DB2 Access Server. Optional: Installation des DB2 Access Servers. Enablement des Domino-Servers für die Verwendung mit DB2 durch das DB2Enablement-Tool im Domino Administrator. Nachbereitung der Installation von DB2 – Update der DOMINO-DB2-Datenbanken. Neustart und Testen der Verbindung. Mapping eines Domino-Benutzers auf einen DB2-Benutzer.
Installation von Domino R7 Der Domino R7 Server wird für die Vorbereitung für DB2 wie ein normaler DominoServer installiert. Ein vorhandener älterer Domino-Server kann durch Über-Installieren upgedated werden, wobei entsprechende Backups nicht vergessen werden sollten. Um die folgenden Schritte für die Konfiguration vornehmen zu können, wird ein Benutzer mit Server-Administrationsrechten für den Domino-Server benötigt. Nachbereitung der Installation von Domino R7 Damit Domino mit einer DB2-Datenbank zusammenarbeiten und das NSFDB2-Format aktiviert werden kann, muss das Transaktionslogging für Domino aktiviert werden. Dies ermöglicht es Domino im Falle eines Serverausfalls, die letzten Änderungen aus dem Transaktionslog zu rekonstruieren und so für eventuell beschädigte Datenbanken wieder einen konsistenten Zustand herzustellen. Vor der Aktivierung muss sichergestellt sein, dass alle Datenbanken, für die das Transaktionslog geschrieben werden soll, sich im Domino-Data-Directory befinden. Die Parameter können für eine Testumgebung entsprechend Abb. 13-9 eingestellt werden. Nach der Anpassung muss der Domino-Server neu gestartet werden.
536
13 Objekt- und Speichertechniken
Abb. 13-9 Domino-Transaktionsprotokollierung
Vorbereitung von Benutzern für die Installation und den Betrieb von DB2 Um DB2 korrekt (auf einer Windows-Maschine) installieren zu können, werden bestimmte Benutzerrechte während der Installation und ein ebenfalls mit speziellen Rechten ausgestatteter Benutzer für den Betrieb von DB2 benötigt. Am einfachsten ist es, einen Benutzer für beide Zwecke vor der Installation zu erstellen: • • •
Standardmäßig sollte der Benutzer db2admin genannt werden. Der Benutzer muss mit lokalen Administrationsrechten ausgestattet sein (er sollte sich in der lokalen Gruppe Administratoren befinden). Soll der Benutzer nicht lokal, sondern über ein Active Directory angemeldet werden, so muss er während des Installationsvorgangs entsprechende Domänenadministrationsrechte erhalten oder für die Installation ein separater Benutzer mit diesen Rechten verwendet werden.
13.4.4 Setup •
537
Der Benutzer muss folgende zusätzliche Rechte90 besitzen: – – – – –
Als Dienst anmelden (Log on as a service) Anpassen von Speicherkontingenten für einen Prozess (Increase quotas) Einsetzen als Teil des Betriebssystems (Act as part of the operating system) Ersetzen eines Tokens auf Prozessebene (Replace a process level token) Erstellen eines Tokenobjekts (Create a token object)
Installation von DB2 DB2 wird mit dem zuvor erstellten Benutzer installiert. Hierfür ist es erforderlich, sich zuvor als dieser Benutzer am Betriebssystem anzumelden. Das DB2-Installationsprogramm91 ist ein komfortabler Wizard, für den nur wenige Einstellungen vorgenommen werden müssen. DB2 sollte als alleinstehender, nicht partitionierter Server mit einer Instanz installiert werden. Hierfür werden die Standardvorgaben bei der Installation gewählt. Der während der Installation vergebene Instanzname muss notiert werden, da er für die spätere Einrichtung des Domino-Servers benötigt wird. Während der Installation kann eine DB2-Benachrichtigungsliste eingerichtet werden. Hierfür wird ein SMTP-Server benötigt, wobei Sie die TCP-Adresse des Domino-Servers verwenden können, sofern der SMTP Task für Domino aktiviert ist. Als DB2-Funktionsuser und Administrator wird der zuvor erstellte Benutzer db2admin eingesetzt. Soll der DB2-Server nicht auf derselben Maschine wie Domino, sondern auf einer entfernten Maschine installiert werden, muss auf der Maschine des Domino-Servers zusätzlich der DB2-Runtime-Client installiert werden, damit die Verbindung zwischen Domino und und dem entfernten DB2-Server hergestellt werden kann. Weitere Optionen werden während der Installation nicht benötigt. Optional: Registrieren einer Domino-Server-ID für den DB2 Access Server Sollen Domino-Daten in einem DB2 Access View für DB2 sichtbar gemacht werden, so wird der DB2 Access Server benötigt. Um diesen Server installieren zu können, wird zunächst im Domino Administrator eine neue Server-ID registriert.
90
91
Unter Windows muss der installierende und der DB2 ausführende Benutzer spezielle Benutzerrechte erhalten. Diese werden für Windows XP unter „Systemsteuerung->Verwaltung->Lokale Sicherheitsrichtlinie->Sicherheitseinstellungen->Zuweisen von Benutzerrechten“ eingestellt. Wird der Benutzer über ein Active Directory berechtigt, müssen dort die entsprechenden Rechte in der zugehörigen Policy eingestellt werden. Für die Installation unter AIX wird ein root login benötigt und es sind zusätzliche Schritte notwendig, die der Domino R7-Administrationshilfe entnommen werden können. Zum Stand von Domino R7 wird mindestens ein DB2-UDB-Server der Version 8.2.2 benötigt. Diese Version wird zum Teil auch als 8.1 Fixpack 9 bezeichnet. Die aktuell harmonierenden Versionen von Domino und DB2 müssen unbedingt beachtet werden und können den jeweils aktuellen Installationshinweisen und Systemvoraussetzungen entnommen werden.
538
13 Objekt- und Speichertechniken
Die Registrierung eines neuen Servers wird im Domino Administrator gestartet, indem der Domino-Server geöffnet und im Bereich „Configuration“ der Befehl „Registration“->„Server“ gewählt wird. Wichtig ist, dass die neue Server-ID kein Passwort erhält und die ID in einer Datei gespeichert wird, um für die DB2 Access Server-Installation zur Verfügung zu stehen (s. Abb. 13-10 und Abb. 13-11).
Abb. 13-10 DB2 Access Server registrieren
Optional: Installation des DB2 Access Servers Der DB2 Access Server wird auf der Maschine installiert, auf der sich der DB2-Datenbankserverbefindet. Er ist ein (reduzierter) Domino-Server mit einem Domino-Adressbuch, wobei dieses nur für Konfigurationszwecke verwendet wird. Versucht ein Benutzer auf einen DAV zuzugreifen, wird der DB2 Access Server nicht nur die ACL durchsetzen, sondern auch die Verbindung zum zugehörigen Domino-Server herstellen. Hierfür wird unter anderem ein so genanntes Verbindungsdokument im Adressbuch des Access Servers benötigt, das Informationen vorhält, wie
13.4.4 Setup
539
Abb. 13-11 DB2 Access Server: Eigenschaften
der Domino-Server erreicht werden kann. Wird der Access Server nach dem Enablements (s.u.) des Domino-Servers installiert, kann das Fehlen dieses Verbindungsdokument Ursache von Fehlern sein und muss nachträglich erstellt werden. Vor der Installation sollten DB2 und Domino angehalten werden. Der DB2 Access Server sollte unbedingt im FUNCTION92-Verzeichnis des DB2-Servers installiert werden. Ansonsten kann der Installer mit seinen Standardeinstellungen durchgeführt werden. Wird der DB2 Access Server installiert, nachdem der Domino-Server für die Verwendung von DB2 aktiviert (s.u.) wurde, so muss im Adressbuch des Domino-Servers in dessen Serverdokument im Abschnitt „DB2“ dafür gesorgt werden, dass dort die Informationen entsprechend dem DB2-Server eingestellt sind (s. Abb. 13-12). Zusätzlich muss in der Domino-Serverkonsole der Befehl DB2 ACCESS SET abgesetzt werden. Die Konfiguration kann mit dem Domino-Konsolenbefehl DB2 INFO abgefragt werden. 92
Unter Windows ist der Default für dieses Verzeichnis C:\Programme\IBM\SQLLIB\FUCTION. Dort wird bei der Installation eine Notes.ini Datei für den Access Server und ein Data-Verzeichnis erstellt.
540
13 Objekt- und Speichertechniken
Abb. 13-12 Domino-Serverdokument – DB2-Eigenschaften
Während der Installation muss der Speicherort der zuvor erzeugten Server-ID angegeben werden. Diese ID wird vom Installationsprozess im Dataverzeichnis des DB2 Access Servers (im FUNCTION-Verzeichnis von DB2) unter dem Namen „user.id“ abgelegt. Enablement des Domino-Servers für die Verwendung mit DB2 Für die Aktivierung der DB2-Fähigkeit von Domino müssen verschiedene Einstellungen gesetzt werden. Hierfür wurde ein Werkzeug vorbereitet, das über den Domino Administrator gestartet werden kann. Es wird über den Befehl „Enable Server for DB2“ in der Sektion „DB2 Server“ im Bereich „Configuration“ gestartet. Wurde der Server bereits aktiviert, ist dieser Befehl deaktiviert93. 93
Muss das Setup wiederholt werden, können die DB2spezifischen Parameter in der notes.ini-Datei des Domino-Servers entfernt und der Domino-Server neu gestartet werden. Dabei ist zu beachten, dass Domino versuchen wird, eine neue DB2-Datenbank zu erstellen. Es kann notwendig sein, entweder die vorhandene DB2-Datenbank zu löschen oder während des Enablement Prozesses einen anderen DB2-Datenbanknamen zu verwenden.
13.4.4 Setup
541
Für das Enablement wird ein lokaler Betriebssystem-Benutzer benötigt, der die Kommunikation mit DB2 übernimmt (siehe Domino-„Super-User“ in der Gruppe DB2DOM in Kapitel 13.4.3). Wird DB2 auf derselben Maschine installiert wie Domino, kann hierfür der DB2-Administrator „db2admin“ verwendet werden. Andernfalls wird ein mit denselben Rechten ausgestatteter Benutzer auf dem Domino-Server benötigt. Dieser Benutzer muss sich in der Gruppe DB2DOM befinden. Diese Gruppe muss als SYSCTRL_GROUP in DB2 registriert sein. Dies kann mit dem Befehl GET DBM CFG in der DB2-Befehlskonsole kontrolliert werden. Der Parameter SYSCTRL_ GROUP muss den Wert DB2DOM haben, bzw. kann über den Befehl UPDATE DBM CFG USING SYSCTRL_GROUP DB2DOM neu gesetzt werden, wobei dies einen Neustart des DB2-Datenbank Managers erfordert, was in der DB2-Konsole durch DB2STOP FORCE und DB2START erreicht werden kann. Beachten Sie, dass der Parameter force aktive Datenbanken anhält, auch wenn Benutzer verbunden sind. Vor dem Starten des Enablement-Prozesses müssen DB2 und Domino wieder gestartet und mit dem Domino Administrator der Domino-Server geöffnet werden. Nach dem Start des Enablement Prozesses werden verschiedene Parameter abgefragt, unter anderem der DB2-Instanzname, der während der Installation von DB2 angelegt wurde (s. Abb. 13-13)94. War der Enablement-Prozess erfolgreich, wird eine entsprechende positive Rückmeldung angezeigt und der Domino-Server muss neu gestartet werden. Nachbereitung der Installation von DB2 Während des Neustarts von Domino wird die DB2-Datenbank für Domino erstellt. Hierbei sollten eventuelle Fehlermeldungen in der Domino-Serverkonsole beachtet werden. In der DB2-Steuerzentrale kann nun kontrolliert werden, ob die Datenbank erfolgreich erstellt wurde. Nun muss der Domino-Server gestoppt und auch der Domino Administrator beendet werden. Um die neu erstellten Datenbanken auf den neuesten Stand der DB2-Installation zu bringen, muss in der DB2-Befehlskonsole (auch als CLP – Command Line Prozessor bezeichnet) ein Update-Befehl eingegeben und nachfolgend der DB2-Datenbankmanager durchgestartet werden: db2updv8 -d DOMINO -u db2admin -p db2adminPasswort db2stop force db2start
Hierbei ist „DOMINO“ der zuvor gewählte Name der DB2-Datenbank, db2admin der DB2-Administrator-Account (s.o.) und db2adminPasswort das zugehörige Passwort. Nun muss noch der Domino-Server und der Domino Administrator neu gestartet werden. Wurde der DB2 Access Server installiert, muss in der Dominokonsole der Befehl 94
Der Name des DB2 Access Servers sollte im Hostfile mit der IP des Servers verknüpft werden. Hierbei wird der Namensbestandteil (CN) verwendet. Alternativ kann der Name als CNAME im verwendeten DNS eingetragen werden oder anstelle des vollqualifizierten Notesnamens die IP des DB2 Access Servers verwendet werden.
542
13 Objekt- und Speichertechniken
Abb. 13-13 Domino-DB2-Enablement-Prozess
13.4.4 Setup
543
DB2 ACCESS SET
ausgeführt werden. Sicherheitshalber sollte mit der DB2-Befehlskonsole überprüft werden, ob die für Domino in DB2 angelegte Datenbank im UTF-8-Format angelegt wurde. get db config
sollte die Ausgabe Database code page = 1208 Database code set = UTF-8
liefern. Neustart und Testen der Verbindung Der Erfolg der Installation kann anhand der initialen Servermeldung „The map for DB2 errors was successfully created“ erkannt werden. Informationen über die DB2Verbindung liefert der Domino-Konsolenbefehl „DB2 INFO“. Für den Test des DB2 Access Servers wurde im Domino Administrator unter „Configurations“ in der rechten Befehlsleiste und „DB2 Server“ der Befehl „Test DB2 Access“ zur Verfügung gestellt (s. Abb. 13-14). Über den Befehl „Edit DB2 Access
Abb. 13-14 Test: DB2 Access
Connection können die Verbindungseinstellungen nochmals kontrolliert werden (s. Abb. 13-15). Domino-Datenbanken können nun im NSFDB2-Format angelegt werden. Dies erfolgt automatisch, wenn als Default-Format „DB2“ gewählt wurde (s. Abb. 13-13). Domino-Datenbanken im NSF-Format können im Domino Administrator mit dem Move-Befehl (s. Abb. 13-16) ins NSFDB2-Format überführt werden. Dieser steht im
544
13 Objekt- und Speichertechniken
Abb. 13-15 Test: DB2 Access Server-Verbindung
Bereich „Files“ in der rechten Befehlsleiste unter „Database“ zur Verfügung. Der Formatwechsel wird nachgelagert vom Administrationsprozess „adminp“ durchgeführt. Eventuelle Meldungen werden in der Domino-Serverkonsole ausgegeben.
Abb. 13-16 Formatwechsel zwischen NSF und NSFDB2
Mapping eines Domino-Benutzers auf einen DB2-Benutzer Bevor nun mit NSFDB2 gearbeitet werden kann, muss mindestens für einen Domino-Benutzer ein DB2-Username-Mapping eingerichtet werden.
13.4.5 DB2 Access View
545
Im Domino Administrator in der Ansicht „People and Groups“ wird der gewünschte Domino-Benutzer gewählt und der Befehl „Set DB2 User Name“ ausgeführt (s. Abb. 13-5). Sollten sich Schwierigkeiten mit dem Benutzermapping bei der Verwendung von DAVs ergeben, so kann in der Notes-INI-Variablen des DB2 Access Servers der Parameter DEBUG_DOMUDF_DIAG=C:\Programme\IBM\SQLLIB\FUNCTION\data\ diag.txt gesetzt werden95. Beim Mapping ist zu beachten, dass aus Sicht von Domino auch Benutzer auf den Server zugreifen können, die kein Personendokument im Domino-Adressbuch (names.nsf) besitzen. Solche Benutzer können sehr wohl im Serverdokument und in den ACLs der Datenbanken berechtigt werden. Selbst administrative Rechte (Serverdokument) sind denkbar. Für die Verwendung von NSFDB2 sind solche Benutzer aber ungeeignet, da ihnen kein Mapping zugeordnet werden kann. Fehler aufgrund fehlender Personendokumente können schwer erkannt werden, da in diesem Fall keine Meldungen in der Dominokonsole erscheinen. Abhilfe kann geschaffen werden, indem für solche Personen Personendokumente erstellt werden, die lediglich den Benutzernamen und das DB2-User-Mapping enthalten. Dies ist ausreichend, um Domino die Zuordnung zwischen Domino- und DB2-Benutzern zu ermöglichen.
13.4.5
DB2 Access View
Nachdem nun in den vorigen Kapiteln die Voraussetzungen für die Arbeit mit NSFDB2 geschaffen wurden, wird in den folgenden zwei Kapiteln die praktische Arbeit mit den beiden neuen Designelementen DB2 Access View und Query View behandelt. Damit mit DB2 Access Views gearbeitet werden kann, müssen zwei Voraussetzungen erfüllt werden. Die Domino-Datenbank muss im NSFDB2-Format erstellt worden sein bzw. muss in dieses überführt werden (s. Abb. 13-16) und der DB2 Access Server muss auf demjenigen Server installiert sein, auf dem die DB2-Datenbank betrieben wird. Ziel des DB2 Access Views (DAV) ist die Bereitstellung von Domino-Daten in einer DB2-Datenbank. Er wird im Designer erstellt und findet sich in der Rubrik „Shared Resources“ (s. Kap. 3.1). Der DAV ist kein Designelement im eigentlichen Sinne (daher auch die Einordnung als Shared Resource und nicht etwa als View), sondern lediglich die Definition des Mappings, über das festgelegt wird, wie die Domino-Daten in DB2 dargestellt werden sollen. Der Rest ist transparent und wird im Hintergrund erledigt. Nach dem Erstellen eines DAV im Designer wird über einen Dialog (s. Abb. 1317) festgelegt, welche Felder und deren Inhalte in DB2 sichtbar werden sollen. Hierbei 95
Nicht zu verwechseln mit der notes.ini-Datei des eigentlichen Domino-Servers. Wird der Parameter in die letzte Zeile der notes.ini geschrieben, muss nachfolgend noch eine Leerzeile eingefügt werden. Ein Neustart des DB2-Datenbankmanagers ist anschließend erforderlich. Danach wird in der angegebenen Datei ausführlich das Benutzermapping und eventuell dabei auftretende Probleme protokolliert.
546
13 Objekt- und Speichertechniken
Abb. 13-17 DB2 Access View – Felderdefinitionen
ist zu beachten, dass nur einfache Datentypen in DB2 zur Verfügung gestellt werden können. RichText kann nicht in DB2 für die Verwendung via SQL zur Verfügung gestellt werden, auch wenn aufgrund des NSFDB2-Formats die RichText-Inhalte in DB2 gespeichert sind. Im Dialogfenster stehen über eine Auswahlliste die in der Datenbank definierten Masken und die darin verwendeten Felder zur Auswahl. Weitere Feld-Mappings können später frei definiert werden. Im nächsten Schritt müssen die Mappings zwischen den Domino- und den DB2Datentypen hergestellt werden. Schwierig wird dies bei Domino-Datentypen, die in DB2 keine Entsprechung haben. Dies sind insbesondere Listenfelder. Der DAV bietet hierfür die Möglichkeit festzulegen, wie solche Daten in DB2 dargestellt werden sollen. Für Strings ist dies einfach: Listenfelder können z.B. durch Semikolon getrennt in einer VARCHAR-Spalte in DB2 dargestellt werden. Für Listenfelder, die in Domino-Zahlen enthalten, wird dies schwieriger. Hier besteht als einziges die Möglichkeit, solche Werte ebenfalls in einem VARCHAR zu speichern (s. Abb. 13-18 und Abb. 13-19). Andere Kombinationen werden durch die im Designer eingebaute Validierung abgelehnt. Alternativ kann die Einstellung „Store multiple values as“ auf „Single Value only“ geschaltet werden, hier werden dann aber Laufzeitfehler auftreten, sollten sich dennoch mehrere Werte in den Document Items befinden. Neben dem Datentyp müssen auch Zusatzinformationen wie z.B. die Spaltenbreite bei VARCHAR angegeben werden. Dem DAV können beliebige Items (Felder) hinzugefügt werden. Dies können auch Items sein, die zum gegenwärtigen Zeitpunkt in keinem der Dokumente der Domino-Datenbank vorliegen. Die so benannte Spalte des DAV in DB2 wird dann jeweils den Wert NULL zeigen. Es liegt in der Natur der Domino-Dokumente, dass ih-
13.4.5 DB2 Access View
547
Abb. 13-18 DB2 Access View – Typendefinitionen
nen beliebige Items – unabhängig von einem Schema – hinzugefügt werden können. Wird also einem Dokument in Domino ein so im DAV referenziertes Item hinzugefügt, so wird in der entsprechenden Spalte in DB2 dieser Wert angezeigt. Nachdem die Bedingungen für die Konvertierung der Datentypen feststehen, müssen nun noch Einstellungen vorgenommen werden, die festlegen, wie Dokumente in Domino behandelt werden sollen, die direkt per SQL erstellt wurden.
Abb. 13-19 DB2 Access View – Listenwerte bei Zahlen
548
13 Objekt- und Speichertechniken
Abb. 13-20 DB2 Access View – Dokumentenzuordnung
Dies gilt insbesondere für die Zuordnung von Datensätzen zu Domino-Masken (s. Abb. 13-20). In den Eigenschaften des DAV kann festgelegt werden, ob und welche Masken den Datensätzen zugeordnet sein sollen und ob z.B. ein so genannter computeWithForm (s. Kap. 7.1.4.4) erfolgen soll, wenn via DB2 neue Datensätze eingefügt werden. Hierbei ist zu berücksichtigen, dass dieser Vorgang performancerelevant ist. Nach Abschluss der Felddefinitionen muss nun diese DAV-Konfiguration an DB2 promotet werden (s. Abb. 13-21). Dies erfolgt durch Nacheinander-Betätigen der Funktionen „Create / Update in DB2“, „Populate in DB2“ und „Refresh Status“. Eventuelle spätere Änderungen erfordern die erneute Ausführung dieser Befehle.
Abb. 13-21 DB2 Access View in DB2 aktualisieren
13.4.5.1 DB2 Access View – Ein Beispiel
549
Ging alles glatt, dann wird der Status als OK gemeldet. In DB2 wird dann eine neue Tabelle und eine neue Ansicht erstellt worden sein. Die neue Tabelle für den DAV hat den Namen des DAV mit dem Suffix „_T“. Diese Tabelle ist so konfiguriert, dass der Benutzer, der im Domino Designer den DAV erstellt hat, keine Rechte in DB2 auf dieser Tabelle hat. Zugriff erhält der im Domino-Adressbuch eingetragene Funktionsuser, der für die Kommunikation zwischen Domino und DB2 zuständig ist (s. Kap. 13.4.3). Zusätzlich wird eine Ansicht mit Namen des DAV erstellt. Diese erhält Zugriff für das Schema PUBLIC. Allerdings ist innerhalb der Ansicht und durch den Domino Access Server gewährleistet, dass die Berechtigungen, die für die ursprünglichen Daten in Domino galten, auch in DB2 eingehalten werden – insbesondere Leser- und Autorenfelder werden hier respektiert. Hierin liegt auch der Grund für die Erstellung sowohl einer Tabelle als auch einer Ansicht. Die Tabelle dient lediglich der Speicherung der Daten, die Ansicht setzt die Berechtigungen durch96. Umgekehrt folgt hieraus, dass beim Zugriff auf die Daten per SQL / Java nur die Ansicht referenziert werden soll. Zu beachten ist, dass die erstellte Tabelle tatsächlich redundante Daten enthält. Das heißt, Domino führt einerseits seine Domino-Daten im NSFDB2-Format und andererseits wird bei der Verwendung eines DAV der hierdurch definierte Datenbestand in der neuen DAV-Tabelle in DB2 redundant geführt. DB2 nutzt hierfür Mechanismen, die es DB2 ermöglichen, den Datenbestand dieser Tabelle aktuell zu halten und umgekehrt. Domino trennt also konsequent die im NSFDB2 gehaltenen Domino-Daten von den nach außen zur Verfügung gestellten DAV-Daten. Vom direkten Zugriff auf die in DB2 vorliegenden Domino-Daten (in der Regel im Schema DOMINO) wird dringend abgeraten; er wird durch die DB2 Access Views auch überflüssig. Die beiden neuen DB2-Elemente (Tabelle und Ansicht) werden von Domino in einem neuen Schema angelegt. Dieses Schema wird nach dem Datenbanknamen aus Domino benannt.
13.4.5.1
DB2 Access View – Ein Beispiel
Der Einsatz eines DAV soll anhand eines Java-Beispiels demonstriert werden. Im Code zum Buch befindet sich die Datenbank djbuchdb2.nsf. Sie enthält lediglich eine Ansicht und eine Maske, die Felder mit verschiedenen Datentypen enthält, um das Mapping zwischen Domino und DB2 aufzuzeigen. Die Maske basiert auf FO_Dokument_k2, einer Maske mit verschiedenen Feldern unterschiedlicher Datentypen (siehe Beispieldatenbank). 96
Beachten Sie beim Zugriff immer, mit welchem Benutzer dies erfolgt. Dies gilt sowohl für die Verwendung des Domino Designers bei der Erstellung des DAV, als auch für die Benutzung der DB2-Steuerzentrale zur Visualisierung der DAV-Daten. Da unter Windows lokale Administratoren auch immer administrative Rechte auf DB2 erhalten, ist hier Vorsicht bei der Entwicklung geboten. Anwendungen sind immer mit Benutzern zu testen, die Rechte innehaben, die denen der späteren Benutzer in Produktion entsprechen.
550
13 Objekt- und Speichertechniken
Nun soll ein DAV mit Namen VDB2_Access eingerichtet werden. Die Abbildungen Abb. 13-18 bis Abb. 13-21 im vorigen Kapitel zeigen bereits die notwendigen Schritte. Nach der Erstellung des DAV sollten sich in der DB2-Datenbank DOMINO die Tabelle VDB2_Access_T und die Ansicht VDB2_Access befinden. Um nun mit Java auf diese SQL-Ansicht zugreifen zu können, sind einige Vorbereitungen notwendig. Bevor Sie mit der Java-Programmierung beginnen, sollten Sie sich mit einem geeigneten SQL IDE versorgen, damit Sie Ihre Arbeiten kontrollieren können. Wer mit Eclipse als Java IDE arbeitet, wird sehr gut mit dem QuantumDB Plugin für Eclipse [QuantumDB] zurechtkommen. Natürlich ist auch die von DB2 mitgelieferte Administrations- und Entwicklungskonsole hervorragend geeignet. Der Vorteil eines eigenen IDE liegt in der Integration ins Java IDE. Gleichzeitig ist das Wechseln von Benutzern durch die Verwendung von Datenbankprofilen recht einfach. Der Vorteil der DB2-Administrations- und Entwicklungskonsole liegt darin, dass sie sämtliche DB2Objekte verfügbar hält, die zum Teil in anderen IDEs nicht dargestellt werden (z.B. Zugriffsrechte auf DB2-Objekte). Wichtig ist, dass beim Aufbau einer JDBC-Verbindung nicht nur die DB2-JDBC Treiberklasse im ClassPath eingebunden wird, sondern auch die zugehörige LizenzKlasse. Dies ist eine Jar-Datei, in der die Lizenz für DB2 gespeichert ist (Beispiel für
Abb. 13-22 SQL Plugin
13.4.5.1 DB2 Access View – Ein Beispiel
551
QuantumDB – s. Abb. 13-22). Für eine Windows-Standardinstallation sind dies die Dateien db2jcc.jar und db2jcc_license_cu.jar97 im Verzeichnis C:\Programme\IBM\ SQLLIB\java. Für Ihre Anwendung gelten die gleichen Anforderungen für den ClassPath, d.h. auch hier müssen sich diese beiden Dateien im ClassPath befinden (Abb. 13-23). Um die Domino-Daten im DB2 Access View per JDBC und SQL auslesen zu kön-
Abb. 13-23 Java ClassPath für DB2-JDBC-Anwendungen
nen, müssen lediglich die durch DB2 Access Server und DAV definierten Mappings, die sich in DB2 als Tabellenspalten darstellen, ausgelesen und die zugehörigen Items und Datentypen korrekt interpretiert werden. Das Listing 13-13 zeigt eine einfache Anwendung, die den zuvor definierten DB2 Access View ausliest. Hierbei kommen einfache JDBC-Techniken zur Anwendung. Der JDBC Connection String setzt sich für DB2 aus der Kennung jdbc:db2://, dem Server oder dessen IP auf dem sich die Datenbank befindet, dem Port, unter dem die DB2-Instanz betrieben wird, gefolgt von einem Slash „/“ und dem Datenbanknamen zusammen: 97
Ohne die korrekte Lizenz erhalten sie eine SQL Exception com.ibm.db2.jcc.b.SqlException: The version of the IBM Universal JDBC driver in use is not licensed for connectivity to QDB2/NT databases. To connect to this DB2 server, please obtain a licensed copy of the IBM DB2 Universal Driver for JDBC and SQLJ. An appropriate license file db2jcc_license_*.jar for this target platform must be installed to the application classpath. Connectivity to QDB2/NT databases is enabled by any of the following license files: { db2jcc_license_cu.jar, db2jcc_license_cisuz.jar } at com.ibm.db2.jcc.b.o. eb(o.java:3323) ...
552
13 Objekt- und Speichertechniken
package djbuch.kapitel_13; import java.sql.*; import djbuch.kapitel_06.DemoTools; public class Db2AccessView { private static final String db2Url = "jdbc:db2://localhost:50000/DOMINO"; private static final String PASSWORD = "geheim"; private static final String USER = "db2admin"; private static final String SCHEMA = "VDB2_ACCESS"; private static final Column [] COLUMNS = { new Column ("#NOTEID",Column.TYPE_STRING), new Column ("F_TITEL",Column.TYPE_STRING), new Column ("F_DATUM",Column.TYPE_DATE), new Column ("F_DATUM_LISTE",Column.TYPE_DATELIST), new Column ("F_NAME",Column.TYPE_STRING), new Column ("F_NAME_LISTE",Column.TYPE_STRING), new Column ("F_PASSWORT",Column.TYPE_STRING), new Column ("F_TEXT",Column.TYPE_STRING), new Column ("F_TEXT_LISTE",Column.TYPE_STRING), new Column ("F_TITEL",Column.TYPE_STRING), new Column ("F_ZAHL",Column.TYPE_LONG), new Column ("F_ZAHL_LISTE",Column.TYPE_LONGLIST), new Column ("FORM",Column.TYPE_STRING) }; public static void main(String[] args) { Connection con=null; Statement stmt=null; try { Class.forName("com.ibm.db2.jcc.DB2Driver"); con = DriverManager.getConnection( db2Url, USER, PASSWORD ); stmt = con.createStatement(); String query = "SELECT" + generateColumnSQL() + "FROM DJBUCHDB2.VDB2_ACCESS AS VDB2_ACCESS"; System.out.println (query); ResultSet result = stmt.executeQuery(query); int marker=0; while (result.next()) { printColumns (result,marker++); } stmt = con.createStatement(); stmt.executeUpdate(generateRandomInsertSQL()); } catch (SQLException e) { e.printStackTrace(); ... } finally { try { if (stmt!=null) { stmt.close();} if (con!=null){ con.close(); } } catch (SQLException e1) { e1.printStackTrace(); } } } private static void printColumns(ResultSet result, int marker) throws SQLException { StringBuffer res=new StringBuffer(); res.append ("=== row ").append (marker).append (" =========\n"); for (int i = 0; i < COLUMNS.length;i++) { switch (COLUMNS[i].getType()) { case (Column.TYPE_STRING): case (Column.TYPE_DATELIST): case (Column.TYPE_LONGLIST): String val = result.getString(COLUMNS [i].getName());
13.4.5.1 DB2 Access View – Ein Beispiel
553
if (val==null) {break;} String[] arr = val.split(";"); for (int j = 0; j < arr.length; j++) { res.append ("\t").append (COLUMNS [i].getName()) .append ("(").append(j).append("): ") .append (arr[j]).append("\n"); } break; case (Column.TYPE_DATE): java.sql.Date dt = result .getDate(COLUMNS [i].getName()); if (dt==null) {break;} res.append ("\t") .append (COLUMNS [i].getName()).append ("(0): ") .append (dt.toString()).append("\n"); break; case (Column.TYPE_LONG): long l = result.getLong(COLUMNS [i].getName()); res.append ("\t") .append (COLUMNS [i].getName()).append ("(0): ") .append (l).append("\n"); break; default: break; } } System.out.println (res.toString()); } private static final String generateColumnSQL () { StringBuffer res = new StringBuffer(" "); for (int i = 0; i < COLUMNS.length; i++) { res.append(SCHEMA).append(".").append(COLUMNS [i].getName()) .append ((i TYPE_LONGLIST) { type = TYPE_UNDEFINED;} this.name=fieldName; this.type=type; } public String getName () { return name;} public int getType () { return type;} }
Listing 13-14 Klasse Column
Wird nun die Query ausgeführt , steht in einem ResultSet das Ergebnis zur Verfügung und kann ausgelesen werden. Um die Ergebnisse entsprechend ihrer Datentypen richtig interpretieren zu können, werden in der Methode printColumns() die in COLUMNS definierten Datentypen ausgewertet. Damit nach getaner Arbeit die Connection und das Statement wieder korrekt (und ausfallsicher) geschlossen und aufgeräumt werden kann, wurde deren Deklaration außerhalb des try-catch Statements platziert und im finally die entsprechenden close() Statements aufgerufen . Diese Konstruktion stellt sicher, dass auch im (unerwarteten) Fehlerfall zumindest versucht wird, die Verbindungen zu schließen, und ähnelt nicht zufällig dem für das Recycling von Domino-Objekten notwendigen Aufbau. Das Auslesen der Daten muss nicht nur die Datentypen, sondern auch die im DAV vorgenommenen Einstellungen berücksichtigen. Diese Einstellungen werden durch die Typen in den Column-Objekten des COLUMNS Array repräsentiert. Nach dem Abfeuern der SQL Query liegt das SQL ResultSet vor und kann ausgelesen werden. Das Ergebnis wird zeilenweise aus dem ResultSet der Methode printColumns() zur weiteren Verarbeitung übergeben. Dort können nun die Spaltenwerte jeder Zeile anhand ihres in COLUMNS definierten Typs korrekt interpretiert werden. Im Beispiel wurden folgende Datentypen vorgesehen: •
TYPE_UNDEFINED
Wird der Konstruktor von Column mit einem ungültigen Wert aufgerufen, wird dieser Datentyp angenommen. •
TYPE_STRING
556
13 Objekt- und Speichertechniken Domino kennt Text und Textliste als Datentyp für ein Item. Beide werden durch diesen Typ repräsentiert. Bei einer Auswertung muss die entsprechende Methode respektieren, dass mehrere Werte durch ein Trennzeichen getrennt in der DB2Tabellenspalte abgespeichert sind. Das Trennzeichen wird bei der Definition des DAV (s. Abb. 13-18 – DB2 Multivalue Delimiter) festgelegt und ist per Default das Semikolon „;“.
•
TYPE_DATELIST TYPE_LONGLIST
Da DB2 keine Listenelemente kennt, ist die einzige Chance, solche Domino-Objekte in DB2 abzulegen, diese in VARCHAR-Spalten, getrennt durch ein Trennzeichen, zu speichern. Folglich können und müssen diese ebenso wie TYPE_STRING per getString () aus der ResultSet-Zeile ausgelesen werden. Im Beispiel wurde bisher für die hierdurch geladenen Listenelemente noch keine Rückwärtskonvertierung implementiert. Diese Möglichkeit besteht natürlich weiterhin, da anhand des Typs ja bekannt ist, welcher Datentyp sich in der VARCHAR-Liste eigentlich verbirgt. •
TYPE_DATE
Domino und DB2 kennen einen Datumstyp, der bei korrekter Einstellung des Fieldmappings im DAV problemlos konvertiert werden kann. Wird über diesen Typ der Datumstyp bekanntgegeben, so kann beim Auslesen dieser Datetyp als java.sql.Date geladen werden. •
TYPE_LONG
Äquivalent zu TYPE_DATE kann mit TYPE_LONG
verfahren werden.
Wenn Sie Kapitel 13.2.1 gelesen haben, haben Sie dort das DJCacheDocument kennen gelernt. Es ist eine Repräsentation der Daten eines Domino-Document, das sich weitestgehend auch wie ein Document verhält, ohne auf eine Session angewiesen zu sein. Nun liegt es nahe, die Daten nicht einfach nur, wie im Beispiel, anzuzeigen , sondern entsprechend der ursprünglichen Datenstruktur abzubilden. Hierfür ist das DJCacheDocument geradezu prädestiniert. Beim Auslesen der Daten aus dem SQL Resultset können insbesondere die ansonsten schwer handhabbaren Listentypen im DJCacheDocument per replaceItemValue (String, Vector) abgelegt werden. So kann mit einfachen Mitteln die ursprüngliche Datenstruktur wiederhergestellt werden. Ein Ansatz zur Umsetzung wäre ein neuer Konstruktor für das DJCacheDocument mit einer Signatur in der Art DJCacheDocument (ResultSet row, Column[] columns). Mittels des Parameters row werden Domino und DB2 Hand in Hand dann die Daten einer Zeile Das DJCacheDocument – Kapitel 13.2.1 – ist ein gutes Hilfsmittel für das Auslesen der ursprünglichen Domino-Daten aus DB2. Listenelemente können wie gewohnt als Item abgelegt werden.
13.4.5.1 DB2 Access View – Ein Beispiel
557
übergeben, wobei columns definiert, wie die einzelnen Datentypen der jeweiligen Spalten zu interpretieren sind. Ist erst einmal dieses Dokument derart eingeführt, ergeben sich viele Möglichkeiten. Denkbar ist zum Beispiel, einen eigenen Wrapper um die JDBC-Verbindung zu definieren, der eine Methode insert (DJCacheDocument newData) kennt, die als Argument ein DJCacheDocument kennt und automatisch ein entsprechendes INSERT Statement generiert und ausführt oder umgekehrt über eine Methode DJCacheDocument load (String noteID) ein SQL Statement ausführt, das einen Datensatz mit einer bestimmten NoteID lädt und als DJCacheDocument zurückgibt. Das Beispiel zeigt auch den umgekehrten Weg auf: Die Erstellung neuer Datensätze durch ein SQL INSERT Statement . Hierbei wird ein neuer Datensatz erzeugt, der dann – aufgrund der Definition als DAV – auch in Domino sichtbar wird. Erwähnenswert ist, dass natürlich auch hier Listenfelder definiert werden können, indem Strings durch Semikolon getrennt übergeben werden, wie im Beispiel für ein TEXT-Listenfeld demonstriert . Selbstverständlich können hier auch Zahl-Listenfelder durch einen entsprechend durch Semikolon getrennten String-Wert gefüttert werden, sofern nur in der Definition des DAV das Mapping entsprechend eingerichtet ist. Abb. 13-24 zeigt ein Item, das durch das Beispiel generiert wurde. Deutlich zu erkennen ist, dass, obwohl ein String in eine VARCHAR Spalte Abb. 13-24 Zahl Liste –per SQL Insert erzeugt eingefügt wurde, Domino über die Definition des DAV eine Liste aus Long erzeugt hat. Da die Daten per SQL in eine Ansicht eingefügt wurden, die per DAV definiert wurde, stehen diese Daten ebenfalls in Domino zur Verfügung. Folglich können sie wie ganz normale Dokumente der Domino-Datenbank behandelt und z.B. über Ansichten gelistet werden. Darüber hinaus wird über die Definition im DAV den Datensätzen (Dokumenten) eine Maske (Item Form) zugeordnet und gleichzeitig durch den DB2 Access Server eine NoteID (Spalte #NoteID in DB2) erzeugt. Derart über DB2 und SQL INSERT erstellte Dokumente verhalten sich aus Sicht von Domino wie normale Domino-Dokumente. Im Beispiel erhalten solche Dokumente über die Definition im DAV die Default-Form „FO_dokument_k13“, sind über eine Ansicht mit einer entsprechenden SELECT-Formel in Domino anzeigbar und werden durch Doppelklick im NotesClient wie gewohnt in einer Notes-Maske geöffnet. Gleichzeitig stehen diese Dokumente auch sämtlichen Java-Funktionen zur Verfügung, die Sie bereits zur Verarbeitung von Domino-Dokumenten kennengelernt haben.
558
13 Objekt- und Speichertechniken
13.4.6
Query View
Während ein DAV also Domino-Daten in DB2 verfügbar macht, ermöglicht der Query View DB2-Daten in Domino zur Verfügung zu stellen. Die Erstellung eines Query View erfolgt über den Domino Designer. Im Gegensatz zum DAV ist kein DB2 Access Server notwendig, aber selbstverständlich muss die Datenbank im NSFDB2-Format vorliegen. Ein Query View wird als normale Ansicht behandelt, enthält alle gewohnten Funktionen und Elemente, wie z.B. Spaltendefinitionen etc., und unterscheidet sich lediglich dadurch von einer gewöhnlichen Domino-Ansicht, dass die SELECTFormel nicht als @Formel, sondern als SQL-Befehl definiert wird. Ein weiterer Unterschied ist natürlich, dass eine solche Ansicht – wie alle DB2basierten Domino-Designelemente – nicht lokal, sondern nur auf einem DominoServer zur Verfügung steht. Um einen Query View zu erstellen, wählen Sie im Domino Designer im Abschnitt „Views“ den Button „new View“. Für das folgende Beispiel wurde zunächst in DB2 eine neue Tabelle angelegt: CREATE TABLE DJBUCHDB2.NON_DOMINO_SQL_DATA ( ID BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NO CACHE), TEXT VARCHAR (20), ZAHL_INT BIGINT, ZAHL_DECIMAL DECIMAL (7, 3) NOT NULL, CONSTRAINT CC1130852963422 PRIMARY KEY (ID) ) ;
Abb. 13-25 Query View erstellen
13.4.6 Query View
559
Abb. 13-26 Query View im Domino Designer
Diese Tabelle wird über die DB2-Administrationskonsole mit Testdaten gefüllt und kann nun in dem neuen Query View mit einer SQL SELECT-Formel ausgelesen werden: "SELECT NON_DOMINO_SQL_DATA.ID, NON_DOMINO_SQL_DATA.TEXT, NON_DOMINO_SQL_DATA.ZAHL_INT, NON_DOMINO_SQL_DATA.ZAHL_DECIMAL FROM DJBUCHDB2.NON_DOMINO_SQL_DATA AS NON_DOMINO_SQL_DATA ORDER BY NON_DOMINO_SQL_DATA.ZAHL_DECIMAL"
Zu beachten ist, dass die Formel in Anführungszeichen zu setzen ist. Dies rührt daher, dass Domino es erlaubt, die SQL-Formel dynamisch (!) zu berechnen. Im Eingabefeld hierfür (Abb. 13-25 – „By SQL Query“) ist eine @Formel vorgesehen, die die eigentliche SQL Query berechnet. Die einfachste @Formel ist ein statischer String, gekennzeichnet durch die Anführungszeichen. Angenehmerweise ist es an dieser Stelle erlaubt, auch interaktive @Formeln zu verwenden, so ist z.B. die Verwendung von @Prompt zur Eingabe einer Benutzervorgabe möglich, so dass SQL-Abfragen dynamisch von Benutzereingaben abhängig gemacht werden können: "SELECT * FROM DJBUCHDB2.VDB2_ACCESS AS VDB2_ACCESS Where F_Text like '" + @Prompt ([OkCancelEdit];"Anfangsbuchstabe eingeben";"Feld F_Text beginnt mit ";"") + "%'"
560
13 Objekt- und Speichertechniken
Bedingungen für Query Views In der SQL Query eines Query Views dürfen (im Wesentlichen aus Sicherheitsgründen) nur solche SQL Statements verwendet werden, die ein ResultSet als Ergebnis haben. Spalten der DB2-Tabelle, die in Domino dargestellt werden sollen, müssen sowohl in der SQL Query des Query View selektiert, als auch in der Spaltendefinition der Domino-Ansicht definiert werden. Beim Öffnen und Anzeigen der derart aus DB2 geladenen Daten wird derjenige (DB2-) Benutzer verwendet, der dem aktuellen Domino-Benutzer über das Domino Directory (names.nsf) zugeordnet wurde. JEDER Domino-Benutzer, der einen Query View benutzen soll, muss über einen zugehörigen DB2-Benutzer verfügen. Jeder Domino-Benutzer bzw. dessen zugeordneter DB2-Benutzer muss über ausreichend Rechte auf dem DB2-Objekt verfügen. Die DB2-Berechtigungen müssen über DB2 administriert werden. Werden mehrere DB2 Access Views über einen JOIN als Query View dargestellt, können nur Dokumente der aktuellen Datenbank auf direktem Weg geöffnet werden (Doppelklick im Notes Client oder ViewEntry.getDocument in Java). „Entfernte“ Dokumente können referenziert werden, wenn im Query View für diese Dokumente im JOIN die Note ID und der Server und Replik ID der Datenbank bekannt ist. Da im Query View die Daten des geJOINten Dokuments natürlich über getColumnValues() verfügbar sind, ist es die NoteID und somit das entfernte Dokument. Serverseitig (DB2) ist das ResultSet per Default auf 500 Dokumente beschränkt.
Abb. 13-27 Voraussetzungen für die Verwendung von Query Views
Jede Spalte der DB2-Tabelle, die im Notes Client angezeigt oder über die entsprechenden Java-Methoden der Klasse View und ViewEntryCollection geladen werden soll, muss in der Ansichtendefinition (Abb. 13-26) als Spalte definiert und über die SQL Query selektiert werden. Gleichzeitig ist zu berücksichtigen, dass der dem DominoBenutzer zugeordnete DB2-Benutzer über ausreichende Berechtigungen auf dem DB2-Objekt verfügen muss (s. Abb. 13-27). Ist der Query View definiert, kann er wie eine gewöhnliche Domino-Ansicht verwendet werden (s. Abb. 13-28). Allerdings öffnet ein Doppelklick auf einen Eintrag in
13.4.6 Query View
561
Abb. 13-28 DB2 und Domino – Ansicht des Query View in Domino und DB2
der Liste keinesfalls ein Dokument in einer Maske, sondern dies wird durch eine Fehlermeldung quittiert, die darauf hinweist, dass derartige SQL-Daten nicht in Domino-Masken dargestellt werden können. Dies ist ein kleiner Wermutstropfen, zumal (zur Zeit) keine Möglichkeit besteht, dies abzufangen und z.B. das Dokument durch ein eigenes GUI darzustellen (Applet, Maske, die die gewünschten Daten per Java/SQL lädt und anzeigt etc.). Die eigentliche Leistungsfähigkeit des Query View erwacht, wenn durch einen Query View Daten eines DAV per SQL geladen werden. Warum sollte man dies tun, fragen Sie sich vielleicht? Stellt man zwei Ansichten – möglicherweise zweier verschiedener Datenbanken – als DAV zur Verfügung, dann können deren Daten in einem JOIN in einen Query View geladen werden: Voilà! Domino hat die Datenbankgrenzen überwunden! Wird in der SQL Query des Query View die Spalte #NoteID eines der DAV geladen, dann wird Domino dies automatisch erkennen und das Domino-Dokument aus diesem DAV bei einem Doppelklick auch in der zugehörigen Maske öffnen. Aber Achtung: Dies gilt nur für Dokumente in der aktuellen Datenbank. Das Gesagte lässt sich nun auf die Java-Programmierung der bekannten Klassen View und ViewEntryCollection übertragen.
562
13 Objekt- und Speichertechniken
Dort besteht natürlich zusätzlich die Möglichkeit, im JOIN die NoteID beider beteiligten Dokumente der beiden DAV zu laden und entsprechend damit umzugehen (Wie wär`s mit einem neuen DJCacheDokument, das die Items beider Domino-Dokumente in sich vereinigt?) Angenommen, es gibt auf dem Domino-Server zwei Datenbanken DJBUCHDB2_ THISDB.nsf und DJBUCHDB2_OTHERDB.nsf und dort sind jeweils ein DAV mit Namen V_DAV_THIS_DB und V_DAV_OTHER_DB definiert, dann kann durch folgende Query ein beispielhafter JOIN auf beide DAV ausgeführt und als Query View vereinigt werden: SELECT V_DAV_THIS_DB.#NOTEID, V_DAV_THIS_DB.F_DATUM, V_DAV_THIS_DB.F_DATUM_LISTE, V_DAV_THIS_DB.F_NAME, V_DAV_THIS_DB.F_NAME_LISTE, V_DAV_THIS_DB.F_PASSWORT, V_DAV_THIS_DB.F_TEXT, V_DAV_THIS_DB.F_TEXT_LISTE, V_DAV_THIS_DB.F_TITEL, V_DAV_THIS_DB.F_ZAHL, V_DAV_THIS_DB.F_ZAHL_LISTE, V_DAV_THIS_DB.FORM, V_DAV_OTHER_DB.#NOTEID AS NOTEID2, V_DAV_OTHER_DB.F_DATUM AS DATUM2, V_DAV_OTHER_DB.F_DATUM_LISTE AS DLISTE2, V_DAV_OTHER_DB.F_NAME AS NAME2, V_DAV_OTHER_DB.F_NAME_LISTE AS NLISTE2, V_DAV_OTHER_DB.F_PASSWORT AS PWORD2, V_DAV_OTHER_DB.F_TEXT AS TEXT2, V_DAV_OTHER_DB.F_TEXT_LISTE AS TLISTE2, V_DAV_OTHER_DB.F_TITEL AS TITEL2, V_DAV_OTHER_DB.F_ZAHL AS ZAHL2, V_DAV_OTHER_DB.F_ZAHL_LISTE AS ZLISTE2, V_DAV_OTHER_DB.FORM AS FORM2 FROM DJBUCHDB2_THISDB.V_DAV_THIS_DB AS V_DAV_THIS_DB LEFT JOIN DJBUCHDB2_OTHERDB.V_DAV_OTHER_DB AS V_DAV_OTHER_DB ON V_DAV_THIS_DB.F_ZAHL = V_DAV_OTHER_DB.F_ZAHL
Angenommen, in der Datenbank DJBUCHDB2_THISDB.nsf befindet sich dieser Query View und hieße V_Query_View. Dann könnte dieser als View in Java geladen und z.B. mit einem Loop über getAllEntries pro Entry sämtliche Spaltenwerte beider DAV per entry.getColumnValues() gelesen werden. Da in der Spalte V_DAV_OTHER_ DB.#NOTEID AS NOTEID2 die NoteID der entfernten Datenbank DJBUCHDB2_ OTHERDB zur Verfügung steht, kann – sofern deren Name und Pfad oder Replik ID bekannt ist – das dort befindliche Dokument geöffnet werden.
13.4.6.1 Query View – Ein Beispiel
563
Für einen Query View, der aus einem oder mehreren DAV besteht, können also sehr wohl alle beteiligten Dokumente geladen werden. Beim obigen Beispiel würde bei einem Doppelklick auf eine Zeile dieses Query View im Notes Client lediglich das Dokument aus DJBUCHDB2_THISDB.nsf geöffnet. Während über die NoteID in Java zusätzlich das zweite Dokument recht einfach geladen werden konnte, ist dies im Notes Client komplizierter. Dort müsste der Event „Queryopendocument“ abgefangen werden und das entfernte Dokument relativ aufwändig geladen und desssen Inhalt zu allem Überfluss auch noch im aktuellen oder einem völlig neuen Dokument gespeichert werden, um angezeigt werden zu können. Grundsätzlich gilt die Regel, dass ein DAV wie eine DB2-Tabelle behandelt wird. Folgerichtig ist jeder aus Sicht von SQL denkbare JOIN oder SELECT zulässig. Ist ein DAV an einem JOIN beteiligt und wird die dort bereitgestellte Spalte #NoteID selektiert, so wird ein Query View, der diese Spalte liest, automatisch die zu diesen NoteIDs gehörigen Dokumente referenzieren, sofern sie sich in der aktuellen Datenbank befinden. Dies gilt sowohl für den Notes Client, der dann beim Doppelklick auf eine Zeile im Query View das Dokument in seiner Maske öffnen wird, als auch für die (Java-) API-Funktionen. Auch dort wird z.B. ein View.getAllDocumentsByKey eine Collection der gefundenen Document-Objekte liefern, die wie gewohnt als Document verarbeitet werden können. Ein Unterschied zu herkömmlichen Domino-Views ist zu beachten: Query Views werden nicht automatisch aktualisiert und müssen im Notes Client über die Taste F9 und über das API mit View.refresh() aktualisiert werden. Erst dann wird die SQL Query neu ausgeführt.
13.4.6.1
Query View – Ein Beispiel
Wurde die DB2-Tabelle anhand des CREATE TABLE-Beispiels und der Query View mit der SELECT-Anweisung aus Kapitel 13.4.6 erstellt, kann dieser mit dem CodeBeispiel aus Listing 13-15 gelesen werden. Er hat den Namen V_NON_DOMINO_SQL_k13 und die SELECT-Formel (s. Abb. 13-26): "SELECT NON_DOMINO_SQL_DATA.ID, NON_DOMINO_SQL_DATA.TEXT, NON_DOMINO_SQL_DATA.ZAHL_INT, NON_DOMINO_SQL_DATA.ZAHL_DECIMAL FROM DJBUCHDB2.NON_DOMINO_SQL_DATA AS NON_DOMINO_SQL_DATA ORDER BY NON_DOMINO_SQL_DATA.ZAHL_DECIMAL"
Die Java-Anwendung Db2QueryView (Listing 13-15) liest einen einfachen Query View aus. Sie wird mit den bekannten Mitteln – im Beispiel als Implementierung von Runnable – aufgebaut . Dies gilt auch für die Variablendeklarationen und den typischen try-catch-finally-Block für das Recycling . Die Datenbank wird mit den klassischen Mitteln geöffnet. Bemerkenswert ist, dass sie, obwohl im NSFDB2-Format, also als DB2-Datenbank vorliegend, über ihren Pfad referenziert werden kann.
564
13 Objekt- und Speichertechniken
package djbuch.kapitel_13;import lotus.domino.*; import djbuch.kapitel_06.*; public class Db2QueryView implements Runnable { ... //definiere HOST, USER, PASSWORD public static void main(String[] args) { Db2QueryView db2QueryViewApplication = new Db2QueryView(); Thread nt = new Thread(db2QueryViewApplication); nt.start(); } public void run() { Session session = null; Database db = null; View view = null; ViewEntryCollection vec = null; ViewEntry entry = null, nextEntry = null; try { session = NotesFactory.createSession(HOST, USER, PASSWORD); db = session.getDatabase( session.getServerName(), "djbuch/djbuchdb2.nsf"); view = db.getView("V_NON_DOMINO_SQL_k13"); //view = db.getView("V_DokumenteSQL_k13"); vec = view.getAllEntries(); System.out.println ("Die Ansicht enthält Dokumente: " + (view.getFirstDocument()!=null)); System.out.println ("Die Ansicht enthält " + vec.getCount() + " Elemente."); entry = vec.getFirstEntry(); while (entry != null) { nextEntry = vec.getNextEntry(); System.out.print (entry.getDocument().getItems().size() + " Items - "); System.out.print (entry.getDocument().getParentDatabase() + " "); System.out.println (" values = " + entry.getColumnValues()); GC.recycle(entry); entry = nextEntry; } } catch (Exception e) { ... //catch ... finally, wie gewohnt einschliesslich Recycling } } }
Listing 13-15 Query View per Java auslesen
Zunächst soll das Verhalten eines Query View untersucht werden, der reine SQLDaten lädt, die also ausschließlich in DB2 vorliegen. Dieser Query View V_NON_DOMINO_SQL_k13 wird wie ein herkömmlicher View geladen und liefert seine Treffer als ViewEntryCollection über getAllEntries. Sämtliche Methoden dieser Objekte sind implementiert und können verwendet werden. Erst wenn man bis auf die Ebene der Daten vorstößt, werden Unterschiede sichtbar. Jeder ViewEntry hat zwar noch ein Document, das über getDocument bezogen werden kann, da es jedoch kein „eigentliches“ NotesDocument gibt, handelt es sich hierbei um einen „leeren Rumpf“ eines Document. So liefert getItems() einen leeren Vector (Abb. 13-29, Teil 1).
13.4.6.1 Query View – Ein Beispiel
565
Abb. 13-29 Ergebnisse eines Query View aus reinen SQL-Daten und aus einem DAV
Andere allgemeingültige Properties wie z.B. die beinhaltende Datenbank getParentDatabase() werden wie gewohnt zur Verfügung gestellt. Erst die Verwendung von getColumnValues() in ViewEntry ermöglicht den Zugriff auf alle im Query View ausgewiesenen Spalten und deren Spaltenwerte. Anders formuliert: Egal welche SQL Query einem Query View zugrunde liegt: Über getColumnValues stehen die zugehörigen Spaltenwerte zur Verfügung, wie sie im Query View und der zugehörigen SQL Query definiert wurden. Zum Vergleich wurde im Beispielcode ein Query View erstellt, der sich auf die SQL-Daten eines DAV bezieht . Dort stehen zusätzlich, wie für einen herkömmlichen View, die Document-Objekte für jeden ViewEntry zur Verfügung. Dementsprechend haben die Dokumente auch Items, die wie gewohnt verarbeitet werden können. In Abb. 13-29 (Teil 2) ist gut zu sehen, dass der DAV sowohl in Domino veränderte, als auch über DB2 veränderte Dokumente enthält, da sich in den Dokumenten unterschiedlich viele Items befinden. Grundsätzlich lässt sich sagen, dass die Darstellung der Query Views recht elegant gelöst ist. Dadurch, dass in jedem Fall ein Document und dessen „Rumpf“-Methoden zur Verfügung stehen, ist vielen potentiellen Fehlern vorgebeugt (NullPointerExceptions, wg. fehlender Objekte) und in jedem Fall lassen sich die Daten über die Spaltenwerte und getColumnValues auslesen.
566
13 Objekt- und Speichertechniken
13.4.7
Federated Data – Entfernte SQL-Daten und Domino in DB2
Das bisher beschriebene Konzept der Query Views hat eine Grenze: Die Daten müssen sich innerhalb der zu Domino in DB2 korrespondierenden Datenbank (in der Regel DOMINO) befinden. DB2 sieht über den Mechanismus so genannter zusammengeschlossener Datenbanken die Möglichkeit vor, Tabellen entfernter Datenbanken über eine Art „AliasVerknüpfung“ in einer dritten Datenbank zur Verfügung zu stellen. Hierdurch können also auch in der für Domino zuständigen Datenbank externe Daten referenziert werden, ohne dass diese ausdrücklich und physisch in der DB2-Datenbank DOMINO liegen müssten. Nur dann, wenn die Tabellen auch physisch (oder eben über zusammengeschlossene Datenbanken) in der von Domino verwendeten DB2-Datenbank vorliegen, können diese über einen Query View erreicht werden. Im Code zur Datenbank befindet sich die Datei djbuch/kapitel_13/ federateddata.sql mit einem SQL-Skript zur Erzeugung einer solchen externen Anbindung98.
13.5
Zusammenfassung
Die Domino-Objekte sind entsprechend dem Domino-Objekt- und Speicherkonzept (fast) alle aus der Domino-Session abgeleitet bzw. werden durch diese bereitgestellt. Dementsprechend sind sie nicht nur von dem Session-Objekt, sondern auch von einer bestehenden Domino-Session abhängig. Gleichzeitig sind alle Domino-Java-Objekte als Implementierung von Interfaces aufgebaut, deren tatsächliche Ausprägung als lotus.domino.cso bzw. lotus.domino. local-Implementierungen aber in der Verwendung durch die NotesFactory außerhalb des eigenen Einflussbereichs erzeugt werden. In diesem Kapitel wurden die sich hieraus ergebenden Konsequenzen und Techniken vorgestellt und Möglichkeiten aufzeigt, diese zu beherrschen. Das DJCacheDocument ist schließlich eine eigenständige Implementierung eines Dokuments, das in seinem Verhalten dem lotus.domino.Document sehr ähnlich ist, so dass es an dessen Stelle zum Einsatz kommen kann und gleichzeitig als Hauptspeicher-basierter Container für Document-Daten geeignet ist. In Multithreading-Umgebungen sind besondere Regeln zu beachten, wenn mit Domino-Java-Objekten gearbeitet wird. Objekte dürfen in der Regel nicht oder nur unter bestimmten Voraussetzungen zwischen Threads geteilt werden. Beim Multithreading ist in jedem Fall das Recycling 98
In der aktuellen Version 7.0 arbeitete Domino leider auf dem vorliegenden Testsystem unter Windows nicht sauber mit solchen Datenquellen (DB2 8.1.9) zusammen und gab einen SQL-Fehler in der Dominokonsole (SQL0428N Die SQL-Anweisung ist nur als erste Anweisung in einer Arbeitseinheit zulässig) zurück, während die zusammengeschlossenen Datenbanken bei einer reinen Verwendung in DB2 fehlerfrei arbeiteteten, so dass zumindest der Verknüpfung von DAV- und SQL-Daten bei der Verwendung in DB2 nichts im Wege stand.
13.5 Zusammenfassung
567
ein besonderes Thema, da dieses an die Domino-Session gebunden ist. Die DominoSession kann entweder zwischen den Java Threads geteilt werden oder jeder Thread baut eine eigene Domino-Session auf. Neben den klassischen Document Containern in einer Domino-NSF-Datenbank gibt es seit Domino Version 7 die Möglichkeit, Domino-Datenbanken in einer DB2Datenbank zu speichern. Dieses so genannte NSFDB2-Format ermöglicht es über DB2 Access Views, Domino-Daten in DB2 verfügbar zu machen und umgekehrt über Query Views DB2Daten in Domino zur Verfügung zu stellen. Der Einsatz von DB2 erfordert vom Domino-Programmierer neue Kenntnisse nicht nur in der SQL-Programmierung, sondern auch in der Administration von DB2. Hierbei ist neben dem Setup des Zusammenspiels von Domino-und DB2 auch die Verwaltung der Berechtigungen und die Abstimmung derselben zwischen beiden Systemen ein Thema, mit dem sich der Java-Programmierer auseinandersetzen muss. Der Einsatz von DB2 sprengt aber einige Schranken der bisherigen Domino-Datenbanken. Hierzu gehören: Die datenbankübergreifende Darstellung von Daten, die dynamische, durch eine Benutzerinteraktion gesteuerte Selektion von Dokumenten in einer Ansicht und nicht zuletzt der effiziente und nahtlos integrierte Zugriff auf Enterprise-Daten aus RDBMS-Datenbanksystemen.
14
14 Recyling von Domino-Objekten
In diesem Kapitel: Gründe für das Recycling Die wichtigsten Regeln Besonderheiten Unterschiede zwischen den Domino-Versionen beim Recycling
570
14 Recyling von Domino-Objekten
Domino-Java-Objekte konfrontieren den Java-Programmierer mit einer Notwendigkeit, die für Java scheinbar nicht existiert. Das Recycling von Java-Objekten. Die Notwendigkeit, Domino-Java-Objekte durch das Recycling freizugeben, und viele Anwendungsfälle finden sich in sämtlichen Beispielen dieses Buches. Dieses Kapitel wird nun diese Beispiele und Regeln zusammenfassen und einige erweiternde Informationen beisteuern. Beim Recycling in Domino ist zu beachten, dass dieser Bereich durchaus der Überarbeitung unterliegen kann. Insbesondere von Domino R5 nach R6 sind hier viele Verbesserungen erfolgt, die in R7 erneut überarbeitet wurden. Die folgenden Betrachtungen beziehen sich vor allem auf Domino R6.x, sind aber weiterhin für R7 gültig. Bei Abweichungen wird darauf hingewiesen.
14.1
Notwendigkeit des Recyclings
Die Domino-Objekte und Klassen sind weitestgehend in C++ implementiert und stehen plattformübergreifend als entsprechende Bibliotheken zur Verfügung. Um ein Java-API mit vollem Funktionsumfang nahtlos implementieren zu können, wurde von IBM der Weg gewählt, in Java lediglich Wrapper um die C++ Klassen zu implementieren und dort mittels JNI und entsprechenden native-Verweisen auf C++ zurückzugreifen. In Java wird ein Objekt, auf das keine Referenz mehr verweist, von der Garbage Collection erfasst und freigegeben. Die Java Garbage Collection weiß nun nichts von den dahinterliegenden, eigentlichen Objekten in C++. Daher würden bei einem turnusmäßigen Recycling durch die Garbage Collection diese C++ Objekte nicht erreicht und freigegeben. Daher ist es notwendig, dass der Programmierer sämtliche Domino-Java-Objekte, die er erzeugt, auch wieder freigibt. Die Methode recycle() existiert für jedes Domino-Java-Objekt. Die Ausführung dieser Methode markiert ein Objekt für das Recycling. Ein nachfolgender Zu-
Recycling von Domino-Java-Objekten Session, Database, View, DocumentCollection, ViewEntryCollection, EmbeddedObject, ViewEntry und Document sind speicherhungrige Domino-Objekte und müssen immer recycelt werden Domino-Objekte müssen recycelt werden, ehe sie dereferenziert werden Achtung in Loops Domino-Objekte werden von demjenigen recycelt, der sie erstellt hat Das Recycling sollte immer in einem finally-Block stattfinden
14.2 Grundregeln des Recyclings
571
griff auf das Objekt ist ungültig und kann zu unerwarteten Ergebnissen führen. Die Ausführung von recycle ist nicht (!) gleichbedeutend damit, ein Objekt auf null zu setzen. So einfach ist das. Und so kompliziert. Nicht in jedem Fall ist es ohne größeren Aufwand möglich, jederzeit sämtliche Domino-Objekte so lange im Focus zu behalten, bis sie endgültig nicht mehr gebraucht werden, um sie dann freigeben zu können. Nicht umsonst wurde in Java die Garbage Collection eingeführt. Dennoch ist es möglich, wenn einige Grundregeln eingehalten werden, effizienten Code, der vor allem frei von Speicherleaks ist, zu programmieren. Die Freigabe der Objekte erfolgt, sofern möglich, immer in einem finallyBlock, um sicherzustellen, dass das Recycling in jedem Fall, auch bei Exceptions und unerwarteten Abbrüchen, durchgeführt wird.
14.2
Grundregeln des Recyclings
§1 Das Recycling erfolgt auf der Basis und innerhalb einer Domino-Session. §2 Recyceln Sie alle Domino-Java-Objekte. §3 Recyceln Sie Domino-Java-Objekte immer, aber nur, wenn sie nicht mehr gebraucht werden. §4 Recyceln Sie Domino-Java-Objekte nur, wenn kein anderer sie mehr braucht. §5 Jeder, der ein Domino-Java-Objekt erzeugt oder bezieht, ist für das Recycling zuständig. §6 Recyceln Sie kein Domino-Java-Objekt, das als Parameter erhalten wurde. §7 Recyceln Sie Domino-Java-Objekte, bevor die Referenz auf sie verloren geht. §8 Recyceln Sie Dokumente in zu durchlaufenden Dokumentkollektionen oder Views. §9 Recyceln Sie Domino-Java-Objekte in einem finally-Block. §10 Objekte, die recyclingpflichtige Felder enthalten, benötigen selbst ein recycle(). §1 Das Recycling erfolgt auf der Basis und innerhalb einer Domino-Session Domino-Objekte werden direkt oder indirekt über die Domino-Session bezogen. Folglich erfolgt auch das Recycling im Kontext der Session. In der Theorie werden in einem Domino-Java-Agenten (s. Kap. 4.10) beim Recycling eines Objektes alle Objekte, die aus diesem erzeugt wurden, ebenfalls recycelt. Daher genügte es theoretisch, lediglich die Session zu recyceln – diese muss in jedem Fall (nach Gebrauch) immer recycelt werden. In der Praxis hat es sich bewährt, alle – insbesondere die großen, speicherintensiven – Objekte jeweils explizit zu recyceln. Für alleinstehende DominoJava-Anwendungen (s. Kap. 4.12) ist dies immer Pflicht. §2 Recyceln Sie alle Domino-Java-Objekte Session, Database, View, DocumentCollection, ViewEntryCollection, EmbeddedObject, ViewEntry und Document sind speicherhungrige Domino-Ob-
572
14 Recyling von Domino-Objekten
jekte, genauer gesagt sind es deren C++ Backendrepräsentationen. Daher macht es keinen Sinn, diese bis zum Ende der Session im Hauptspeicher zu lassen, neben dem langfristigen Speicherverbrauch entsteht sonst auch ein Verwaltungsüberhang für viele ungenutzte Objekte. Gleichzeitig besteht die Gefahr, durch verlorene Referenzen, die unter bestimmten Umständen entstehen könnten, Speicherleaks zu verursachen. Daher gilt die Regel (mit wenigen Ausnahmen), alle Domino-Java-Objekte, die vom Programmierer selbst erzeugt wurden, zu recyceln. Die eingangs genannten Objekte sind gleichzeitig diejenigen, bei denen das Recycling auf keinen Fall ausbleiben darf. Kleine Domino-Java-Objekte, insbesondere das Item, sollten nicht recycelt werden. Sie werden bereits beim Recycling des Document mit freigegeben99. §3 Recyceln Sie Domino-Java-Objekte immer, aber nur, wenn sie nicht mehr gebraucht werden Wird für ein Domino-Objekt die Methode recycle () aufgerufen, so ist dieses Objekt für das Recycling markiert. Dies bedeutet nicht, dass eine Garantie dafür besteht, dass das Objekt sofort aufgeräumt wäre. Daher ist eine strenge Kontrolle, wann ein Objekt dem Recycling zugeführt wird, notwendig. Nach dem Aufruf von recycle () darf ein Domino-Java-Objekt in keinem Fall mehr benutzt werden. Die Ergebnisse und das Verhalten eines solchen Objektes sind nicht vorhersehbar. In der Regel wird eine NotesException (id 4376) „Object has been removed or recycled“ geworfen. Eine Garantie dafür besteht aber nicht (daher s. Kap. 14.4). §4 Recyceln Sie Domino-Java-Objekte nur, wenn kein anderer sie mehr braucht Gemeint sind zwei Fälle. Der erste ist einfach erläutert. Ein Domino-Java-Objekt, das an anderer Stelle später im Code noch verwendet werden soll, darf erst danach recycelt werden. Ein Database-Objekt, das Dokumente enthält, die an anderer Stelle noch verwendet werden, darf nicht recycelt werden: Document doc = someDocument(); Item item = doc.getFirstItem("Form"); doc.recycle(); System.out.println (item.getText());
Das Item item ist Bestandteil des Document doc. Wird doc recycelt, so wird auch item recycelt, d.h. das Document war noch in Verwendung und hätte erst nach der letzten Verwendung von item recycelt werden dürfen. Der zweite Fall ist komplizierter. Domino-Java-Objekte sind nur Referenzen auf interne Objekte. In Domino R6 werden verschiedene Java-Objekte, die auf verschie-
99
In Version 6.5.2 gab es einen Bug, der zur Folge hatte, dass das Recycle eines Items unter bestimmten Bedingungen zur Folge hatte, dass das gesamte Document recycelt wurde.
14.2 Grundregeln des Recyclings
573
dene Weise eine Referenz auf das gleiche (z.B.) Domino-Document erhalten, letztendlich auf ein und dasselbe C++ Objekt verweisen. Folglich wird das Recycle des einen Java-Objekts Auswirkungen auf das andere haben: Document doc1 = db.getAllDocuments().getFirstDocument(); String aDocumentID = doc1.getUniversalID(); Document doc2 = db.getDocumentByUNID(aDocumentID); doc1.recycle(); System.out.println("Dokument " + doc2.getUniversalID() + " erfolgreich geladen.");
Die Anweisung System.out.println wird nie erfolgen, da beim Versuch, auf doc2 zuzugreifen, eine NotesException „Object has been removed or recycled“ geworfen wird. Auch wenn doc2 eigentlich ein eigenständiges (Java-) Objekt ist und dazu noch unabhängig von doc1 erzeugt wurde (doc1 verweist auf ein Element in einer DocumentCollection , während doc2 direkt auf ein Element einer Database zeigt ), verweisen beide auf ein und dasselbe C++ Objekt. Dieses Verhalten ist insofern sauber, als offensichtlich innerhalb einer DominoSession ein und dasselbe Datenbankobjekt ebenfalls durch ein und dasselbe Speicherobjekt repräsentiert wird. Der Bezug auf eine Session ist wichtig, da natürlich unterschiedliche Benutzer mit unterschiedlichen Rechten auf die Datenbank zugreifen und sich daher auch ein Speicherobjekt nicht teilen können. Das Verhalten war für Domino R5 noch anders. Dort musste doc2 zusätzlich recycelt werden. Wären die Zeilen und vertauscht, wäre der Code selbstverständlich in Ordnung. doc2 würde erneut in den Hauptspeicher geladen, müsste dann allerdings nachträglich ebenfalls dem Recycling zugeführt werden. §5 Jeder, der ein Domino-Java-Objekt erzeugt oder bezieht, ist für das Recycling zuständig Um den Überblick zu behalten, wer welches Objekt dem Recycling zuführen muss, ist es wichtig hier klare Hierarchien einzuhalten. Die Formulierung „Erzeugen oder Beziehen eines Domino-Java-Objektes“ meint Folgendes: Wenn im Code durch Benutzung der API-Funktionen z.B. ein View oder eine Database, aber auch eine DocumentCollection oder ein Document geladen wird, so ist hiermit der recyclingpflichtige Bezug eines Domino-Java-Objektes gemeint. Beispiele hierfür sind: Database db = session .getDatabase(session.getServerName(),"djbuch/djbuch.nsf"); View view = db.getView("V_meineAnsicht"); DocumentCollection dc = view.getAllDocumentsByKey("myKey"); Document doc1 db .getDocumentByID("10796010FBE23C5DC125704F005866F9"); Document doc2 = dc.getFirstDocument();
574
14 Recyling von Domino-Objekten
Alle diese Anweisungen haben recyclingpflichtige Objekte als Ergebnis, da diese Objekte erst ausdrücklich durch Aufruf der Methoden aus der Datenbank geladen und in neu erzeugten, korrespondierenden C++ Speicherobjekten vorgehalten werden. Gleiches gilt für das Erzeugen von neuen Objekten: View view2 = db.createView(); Database db2 = dbDir .createDatabase("djbuch/neuesbeispiel.nsf");
Objekte jedoch, die als Property eines anderen bezogen werden, sind nicht recyclingpflichtig. Solche Instanzvariablen müssen vom Objekt selbst, bzw. von Dritten, die für diese Objekte verantwortlich sind, verwaltet und dem Recycling zugeführt werden: Database db3 = doc.getParentDatabase(); Session session2 = db3.getParent(); Document doc2 = myObject.getContainedDocument(); DocumentCollection dc = db.getAllDocuments();
In diesen Beispielen verweist db3 auf die Datenbank, aus der das Dokument doc geladen wurde. Je nachdem, wer diese Datenbank ursprünglich bezogen hat, muss diese auch wieder recyceln. Gleiches gilt für session2, die ja durch getParent() nicht neu erzeugt oder geladen wird, sondern es handelt sich lediglich um einen Verweis auf die Session, innerhalb derer db3 geöffnet worden ist. In beiden Fällen sind Dritte für das Recycling verantwortlich – auch wenn das natürlich in der Regel der eigene Code an anderer Stelle ist. Anders verhält es sich im dritten Beispiel. myObject besitzt die Methode getContainedDocument, die ein von myObject im (z.B.) Konstruktor neu erzeugten Document zurückgibt. In diesem Fall ist myObject und damit derjenige, der myObject erzeugt hat, für das Recycling zuständig. §6 Recyceln Sie kein Domino-Java-Objekt, das als Parameter erhalten wurde Wird ein Domino-Java-Objekt als Parameter in eine Methode gegeben, ist die Methode nicht selbst für das Recycling zuständig, sondern derjenige, der den übergebenen Parameter erzeugt hat: Document doc = db.getDocumentByID("10796010FBE23C5DC125704F005866F9"); System.out.println(getSomeStringFromDoc (doc)); doc.recycle();
Die Methode getSomeStringFromDoc darf in keinem Fall selbst das Recycling übernehmen, da sonst externe Referenzen „zerstört“ würden. Stattdessen wird außerhalb, in der dritten Zeile dieses Beispiels, das Recycling angestoßen.
14.2 Grundregeln des Recyclings
575
§7 Recyceln Sie Domino-Java-Objekte, bevor die Referenz auf sie verloren geht In Java ist es gängige Praxis, sich darauf zu verlassen, dass Objekte, deren Referenz freigegeben (auf null gesetzt) wird, automatisch von der Garbage Collection aufgeräumt werden. Code der folgenden Art ist in Java eigentlich unkritisch, bei der Verwendung von Domino-Java-Objekten jedoch höchst problematisch: Document doc = getSomeDocument (); doSomeThingWithDoc (doc); doc = getOtherDocument(); doSomeThingWithDoc (doc);
Durch die neuerliche Zuweisung von doc geht die Referenz auf das ursprüngliche Speicherobjekt verloren. Ein Speicherleak ist entstanden. Vor der neuerlichen Zuweisung von doc hätte ein doc.recycle() erfolgen müssen. Bei Verkettungen von Anweisungen ist für jeden Einzelfall zu klären, ob hierdurch möglicherweise Referenzen auf recyclingpflichtige Objekte verloren gehen könnten. Die Anweisung Document doc1 = db.getAllDocuments().getFirstDocument(); beinhaltet zwar eine solche Verkettung mit der DocumentCollection getAllDocuments(). Allerdings ist diese DocumentCollection nicht recyclingpflichtig100. Wäre sie es, würde durch diesen Aufruf eine DocumentCollection erzeugt, deren Referenz sofort mit der Zuweisung von doc1 wieder verloren geht. Dann müsste diese Collection zunächst in ein separates Objekt geladen werden, um danach wieder recycelt werden zu können. Dies ist der Fall in folgendem Beispiel: System.out.println (session.getDatabase (session.getServerName(),"djbuch/djbuch.nsf").getTitle());
Die Referenz auf die Datenbank djbuch.nsf ist mit Ausgabe des Titels der Datenbank verloren. Hier hilft nur eine vorangestellte Zuweisung: Database db = session.getDatabase (session.getServerName(),"djbuch/djbuch.nsf"); System.out.println (db.getTitle()); db.recycle(); 100
Für einige Methoden ist unklar, ob nun neue, recyclingpflichtige Objekte zurückgegeben werden, oder ob dies intern, dauerhaft vorgehaltene Objekte sind, die nicht recycelt werden dürfen, zumal dieser Bereich auch regelmäßigen Code-Überarbeitungen unterliegt. Da die Collection getAllDocuments() als Property (Eigenschaft) der Datenbank angesehen werden kann, sollte aber auf das Recycling verzichtet werden (und es führt bei der Verwendung von DIIOP sogar zum Fehlerfall). Als Faustregel kann immer die Frage nach der Property erfolgen. Wurde ein „vorgefertigtes“ Objekt, also eine „Eigenschaft“, zurückgegeben, sollte kein Recycling erfolgen, wurde ein neues Objekt erstellt, ist es Pflicht.
576
14 Recyling von Domino-Objekten
Wobei natürlich eine Database in der Regel nicht nur für die Anzeige ihres Titels geladen wird. In der Regel wird solch eine Referenz zumindest für einen Großteil der Laufzeit einer Anwendung verfügbar gehalten, schließlich wird sie in der Regel an mehr als einer Stelle benötigt (§4). §8 Recyceln Sie Dokumente in zu durchlaufenden Dokumentkollektionen oder Views Der im vorigen Paragraphen angesprochene Punkt kommt insbesondere zum Tragen, wenn eine DocumentCollection oder ViewEntryCollection durchschritten wird. Mit jeder Zuweisung der Art doc=documentCollection.getNextDocument() geht die Referenz auf das vorherige Speicherobjekt verloren. Bei großen DocumentCollections können so erhebliche Speicherleaks erzeugt werden. Folgender Code löst dieses Problem: Database otherDb = null; DocumentCollection docCollection = null; Document thisDoc = null, nextDoc = null; int count = 1, size=0; try { docCollection = otherDb.getAllDocuments(); size= docCollection.getCount(); thisDoc = docCollection.getFirstDocument(); while (thisDoc != null) { System.out.println("Dokument " + count++ + " / " + size + ":\t" + thisDoc.getUniversalID()); nextDoc = docCollection.getNextDocument(); GC.recycle(thisDoc); thisDoc = nextDoc; } } finally { GC.recycle (thisDoc); GC.recycle (nextDoc); GC.recycle (docCollection); GC.recycle (otherDb); }
Durch die Zuhilfenahme eines zusätzlichen JavaObjekts kann eine Referenz auf das jeweils nächste Document eines Loops geladen werden. Nun kann das aktuelle Document dem Recycling zugeführt werden und die Schleifenvariable auf das zuvor bezogene, nächste Document verweisen. Abschließend werden alle Objekte im finally-Block dem Recycling zugeführt, so ist sichergestellt, dass auch in Ausnahmefällen ein Recycle erfolgt. Wichtig ist hierbei, das Recycling von den kleinen Objekten beginnend durchzuführen (erst das Document, dann die DocumentCollection, zuletzt die Database) . Gleiches gilt auch z.B. für ViewEntryCollection oder für Schleifen über einen View. Eine sehr elegante Lösung dieses Problems wird in Kapitel 10.9 vorgestellt.
14.2 Grundregeln des Recyclings
577
§9 Recyceln Sie Domino-Java-Objekte in einem finally-Block Wie im vorigen Paragraphen angesprochen: Das recycle erfolgt in einem finally-Block. Für die Schleifenvariablen thisDoc und nextDoc gilt dies nicht für das recycle innerhalb der Schleife . Die aktuelle Referenz von thisDoc und nextDoc würde in einem Ausnahmefall dennoch im finally erfasst. Der finally-Block wird immer durchlaufen, egal wie der Programmfluss unterbrochen wird. Selbst bei einer ThreadDeathException würde er noch abgearbeitet. Aus Java-Sicht hat diese Konstruktion durchaus auch Nachteile. Nicht nur, dass die Übersichtlichkeit leidet und sich die Fehleranfälligkeit erhöht, wenn Objekte außerhalb des Scope zur Verfügung stehen, in dem sie benötigt werden, wie die Objekte thisDoc, nextDoc, docCollection und otherDb, die ganz am Anfang des Codes definiert werden, sondern sie belegen daher für eine unnötig lange Zeit Speicher im JavaHeap. BestPractice in Java ist es (eigentlich), Objekte so nah an dem Context zu deklarieren, in dem sie verwendet werden, wie es geht. Aus den vorgenannten Gründen gibt es hierfür jedoch für die beschriebene Vorgehensweise leider keine Alternative. §10 Objekte, die recyclingpflichtige Felder enthalten, benötigen selbst ein recycle()
Wenn eigene Objekte definiert werden, die selbst Domino-Java-Objekte erzeugen oder beziehen, muss eine externe Möglichkeit geschaffen werden, um diese Objekte recyceln zu können. Ein einfacher Weg ist es, diesen Objekten selbst eine recycle Methode mitzugeben, um die Recyclingpflicht nach außen erkenntlich und zugänglich zu machen. Listing 14-1 zeigt ein einfaches Beispiel für verschiedene Situationen, die in diesem Zusammenhang entstehen können. Das Beispiel implementiert auf Basis einer Domino-Database einen kleinen Spielzeugladen mit der Klasse ToyStore. Beim Initialisieren wird dem ToyStore-Objekt der Name einer Datenbank mitgegeben, in der der Laden abgespeichert werden soll. Dieser Name wird intern in ToyStore verwendet, um eine Domino-Datenbank zu öffnen und als Instanzvariable vorzuhalten. Hierin begründet sich nun die Recyclingpflicht dieses-Objekts, denn diese Instanzvariable wurde erst innerhalb des ToyStore-Objektes erzeugt, so dass bei der Freigabe einer Referenz auf ein ToyStore-Objekt sichergestellt werden muss, dass ein recycle für die Instanzvariable mit der Database erfolgt. Zunächst soll die Klasse ToyStoreDemo näher betrachtet werden, die den ToyStore zum Leben erweckt. Ein neues ToyStore-Objekt wird mit dem Namen einer Datenbank und einer Domino-Session erzeugt. Mit der Methode addToy können Paare aus Artikelnummer und Name dem Store hinzugefügt werden . Um diese Objekte laden zu können, bietet die Klasse zwei verschiedene Methoden. getToy liefert lediglich den Namen eines Spielzeugs zu einer ID, wogegen getToyDocument das gesamte Domino-Document zurückgibt, in dem es gespeichert ist. Die removeToy Anweisungen dienen lediglich dazu, bei der Ausführung des Beispiels nicht unnötigen „Datenmüll“ zu hinterlassen.
578
14 Recyling von Domino-Objekten
package djbuch.kapitel_14; import djbuch.kapitel_06.GC; import lotus.domino.*; public class ToyStore { private Database db = null; private Session session = null; private static final String ITEM_ID = "F_ID"; public static final String ITEM_NAME = "F_NAME"; public static final String ITEM_FORM = "FORM"; public static final String DEFAULT_FORM = "TOYFORM"; private Vector trash = new Vector(); public ToyStore (String dbName, Session session) throws NotesException { db = session.getDatabase(session.getServerName(),dbName); if (db==null || !db.isOpen()) { throw new NotesException (999, "Konnte " + dbName + " nicht öffnen."); } this.session = session; } public String getToy (int id) throws NotesException { Document doc = null; DocumentCollection dc = null; try { dc = db.search(ITEM_ID + "=" + id); if (dc==null || dc.getCount()==0) { return null; } doc = dc.getFirstDocument(); return (doc==null?null:doc.getItemValueString(ITEM_NAME)); } finally { GC.recycle (doc); GC.recycle (dc); } } public Document getToyDocument (int id) throws NotesException { DocumentCollection dc = null; try { dc = db.search(ITEM_ID + "=" + id); if (dc==null || dc.getCount()==0) { return null; } Document doc = dc.getFirstDocument(); return doc; } finally { trash.add(dc); } } public void addToy (int id, String name) throws NotesException { Document doc = null; try { doc = db.createDocument(); doc.replaceItemValue (ITEM_ID, new Integer (id)); doc.replaceItemValue (ITEM_NAME, name); doc.replaceItemValue (ITEM_FORM, DEFAULT_FORM); doc.save(); } finally { GC.recycle (doc); } } public void removeToy (int id) throws NotesException { Document doc = null; try { doc = this.getToyDocument(id); if (doc!=null) {
14.2 Grundregeln des Recyclings
579
doc.remove(true); } } finally { doc.recycle(); } } public void recycle () { try { if (trash != null && trash.size() > 0) {session.recycle(trash);} } catch (NotesException e) {e.printStackTrace();} db = GC.recycle(db); } } /****************************************************/ package djbuch.kapitel_14; import lotus.domino.*; import djbuch.kapitel_06.*; public class ToyStoreDemo implements Runnable { ... //definiere HOST, USER, PASSWORD public static void main(String[] args) { ToyStoreDemo RecycleCases = new ToyStoreDemo (); NotesThread nt = new NotesThread (RecycleCases); nt.start(); } public void run () { Session session = null; ToyStore ts = null; Document doc = null; try { session=NotesFactory.createSession (HOST, USER, PASSWORD); ts = new ToyStore ("djbuch/djbuch.nsf", session); ts.addToy(1,"nasty dog"); ts.addToy(2,"tiny dog"); System.out.println (ts.getToy(1)); doc = ts.getToyDocument(2); System.out.println (doc.getItemValueString(ToyStore.ITEM_NAME)); ts.removeToy(1); ts.removeToy(2); } catch (NotesException e) { System.out.println ("id = " + e.id); e.printStackTrace(); } finally { GC.recycle (doc); if (ts!=null) { ts.recycle(); } GC.recycle(session); } } }
Listing 14-1 Recycling von Objekten mit recyclingpflichtigen internen Domino-Objekten
Nun zum Recycling. Da die Klasse ToyStore anhand des im Konstruktor mitgegebenen Datenbanknamens eigens ein Datenbank-Objekt erzeugt , entsteht eine recyclingpflichtige Instanzvariable db .
580
14 Recyling von Domino-Objekten
Um diesem Sachverhalt Rechnung zu tragen, wurde kurzerhand der Klasse eine hinzugefügt, die bei einem Aufruf die interne Daeigene recycle()-Methode tenbank recycelt. Folglich wird ein ToyStore-Objekt äquivalent zu einem anderen Domino-Java-Objekt dem Recycling zugeführt. Wie gewohnt wird es zunächst außerhalb eines try-catch-Blockes deklariert und nach Verwendung im finallyBlock wieder freigegeben . Beachten Sie, dass die im Konstruktor von ToyStore übergebene Session zwar ebenfalls als Instanzvariable geführt wird, aber nicht durch den recycle() von ToyStore erfasst wird. Schließlich handelt es sich hier nur um einen übergebenen Parameter und §6 kommt zur Anwendung. Neben der Behandlung dieser internen, recyclingpflichtigen Database erläutert der ToyStore noch weitere Recycling-Situationen. Die Methoden addToy und removeToy verwenden zwar intern ein Document-Objekt, das aber in beiden Fällen nach der Beendigung dieser Methoden nicht mehr benötigt wird. Also wird nach getaner Arbeit (save bzw. remove ) in beiden Fällen das Document doc wieder dem Recycling zugeführt – auch nachdem das Dokument aus der Datenbank gelöscht wurde, muss noch dessen Speicherobjekt recycelt werden. Der Benutzer dieser Methoden muss sich nicht mit dem Recycling auseinandersetzen. Recyclingpflichtige Objekte wurden gleich dort, wo sie entstanden, und sobald sie nicht mehr gebraucht wurden, wieder freigegeben. Ähnlich gelagert ist der Fall bei getToy . Auch diese Methode verlangt weder als Parameter ein Domino-Java-Objekt, noch wird ein solches zurückgegeben. Sämtliche vorübergehend für den Ladevorgang benötigten Domino-Objekte werden im finally-Block recycelt . Wie praktisch der Einsatz des finally-Blockes ist, wird hier besonders deutlich. Es kann bedenkenlos die Methode mit einem return verlassen werden, in jedem Fall wird die Programmflusssteuerung von Java dafür sorgen, dass dieser Block zum Schluss durchlaufen wird. Dass dies tatsächlich erst am Ende nach Abschluss aller Programmschritte der Methode erfolgt, wird deutlich bei der Rückgabe des Ergebnisstrings, der ja noch innerhalb des return-Statements auf das Document zugreift. Der Vollständigkeit halber sei darauf hingewiesen, dass diese Methode zwei erhebliche Vereinfachungen macht. Die Methode db.search () ist zwar sehr einfach zu handhaben und benötigt neben der Datenbank keine weiteren Objekte, sollte aber, da hier über die IDs eindeutige Schlüssel vorliegen, durch getDocumentByKey() in View ersetzt werden. Außerdem wurde in addToy darauf verzichtet, die Eindeutigkeit der Schlüssel sicherzustellen, daher wird in getToy einfach der erste gefundene Treffer zurückgegeben . Eine andere Situation findet sich in der Methode getToyDocument, die ein Domino-Document zurückgibt. Selbstverständlich darf dieses Document nicht innerhalb der Methode recycelt werden. Ganz im Gegenteil. Dies ist eine typische Methode, die recyclingpflichtige Objekte zurückgibt, d.h. Objekte, die durch den Aufruf dieser Methode erst erzeugt wurden (§5). Folglich muss externer Code, der getToyDocument verwendet, selbst dafür sorgen, dass das zurückgegebene Document ein recycle erfährt. Unangenehm ist die für getToyDocument benötigte interne DocumentCollection. Die DocumentCollection enthält das zurückgegebene Document und kann
14.2 Grundregeln des Recyclings
581
nicht dem Recycling übergeben werden. Stattdessen wird sie im Vector trash hinzugefügt und kann so im recycle von ToyStore entsorgt werden. Diese Technik hat einige Nachteile (s. Kap. 14.3.1), insbesondere, da ToyStore nicht wissen kann, wann die zurückgegebenen Dokumente nicht mehr benötigt werden und daher für jeden Aufruf von getToyDocument eine DocumentCollection zwischengespeichert werden muss. Ein echter Speicherfresser. In Kapitel 15 werden andere Suchtechniken vorgestellt, die helfen werden, dies zu vermeiden. Eine Alternative wäre, an Stelle eines Document einfach die DocumentCollection zurückzugeben, dann könnte der externe Code, der weiß, wie lange er das Document benötigt, sich um das Recycling kümmern. Sie sehen, manchmal ist das Recycling nicht trivial. Die Klasse ToyStore kann noch eine weitere Verbesserung erfahren. Wurde erst einmal die Methode recycle in ToyStore aufgerufen, so wird jeder Aufruf einer der Methoden dieser Klasse ungewisse Ergebnisse liefern, da der Kern, die Database, bereits recycelt ist. Daher ist es sinnvoll, nachzuhalten, ob das ToyStore-Objekt recycelt wurde, und im Falle einer Benutzung nach einem recycle eine entsprechende Exception zu werfen. Hierfür wurde zunächst die Methode recycle modifiziert und eine zusätzliche Methode checkRecycled eingeführt: public void recycle () { try { if (trash != null && trash.size() > 0) {session.recycle(trash);} } catch (NotesException e) {e.printStackTrace();} db = GC.recycle(db); } private void checkRecycled () throws NotesException { if (db==null) { throw new NotesException(NotesError.NOTES_ERR_DELETED, "Object has been removed or recycled"); } }
Die Methoden in GC (s. Kap. 14.4) wurden so implementiert, dass sie immer null zurückgeben, um schnell und einfach Objekte nach dem recycle auf null setzen zu können. Ist db erst einmal auf null gesetzt, kann sehr einfach erkannt werden , ob es noch gültig ist, und eine entsprechende Exception geworfen werden. Hierbei wird als ID der NotesException die von Domino ohnehin für diesen Fall vorgesehene Fehlernummer NotesError.NOTES_ERR_DELETED (4376) verwendet . Nun muss nur noch in jede Methode der Aufruf von checkRecycled als erste Zeile eingefügt werden und jede Benutzung eines ToyStore-Objektes nach Aufruf von recycle wird eine entsprechende Exception zur Folge haben:
582
14 Recyling von Domino-Objekten
public void addToy (int id, String name) throws NotesException { checkRecycled(); Document doc = null; try { ...
14.3
Besonderheiten in verschiedenen Umgebungen
Das Recycling der Domino-Java-Objekte ist regelmäßig Gegenstand von Überarbeitungen von Domino-Versionen. Sollten hier Unklarheiten bestehen, ist es ratsam, zunächst nochmals den gesamten Code auf fehlendes Recycling zu überprüfen. Als Faustregel sollten mindestens die in Kapitel 14.2 §2 genannten Objekte recycelt werden. Die gravierendsten Unterschiede zwischen verschiedenen Versionen oder Umgebungen finden sich beim Recycling zwischen Domino R5 und R6 und je nachdem, ob lokale Domino-Sessions oder DIIOP verwendet wird.
14.3.1
Recycle (Vector)
Theoretisch bietet Domino die Möglichkeit, über recycle (Vector) (diese Methode steht allen Domino-Objekten zur Verfügung), andere Domino-Objekte, die der Vector referenziert, dem Recycling zu übergeben. Anstatt dann in einer Schleife, wie im Beispiel zu §8 in Kapitel 14.2, das jeweilige Document gleich freizugeben, würden dann durch ein vector.add (doc) die Referenzen eingesammelt und abschließend durch ein session.recycle(vector) freigegeben. Diese Vorgehensweise hat in Theorie und Praxis einen leichten Performance-Vorteil beim Einsatz von DIIOP. In Tests konnten 5% Laufzeit eingespart werden, wobei das gleiche Szenario bei einer lokalen Session ohne DIIOP rund 20% längere Laufzeiten erforderte, so dass diese Vorgehensweise für lokale Domino-Sessions nicht anzuraten ist. Sieht man sich allerdings den Speicherverbrauch in einer DIIOP-Anwendung an, bei dem Versuch mehrere Hundert Document-Objekte in einer Schleife in einem Vector zwischenzuspeichern, um sie erst später zu recyceln, kann auch hier nur abgeraten werden. Abb. 14-1 und Abb. 14-2 zeigen eindrucksvoll das unterschiedliche Verhalten des Java Heap. Absolut gesehen wurde im zweiten Beispiel rund die zwanzigfache Menge an Java-Hauptspeicher belegt, der natürlich abschließend durch die Java Garbage Collection wieder freigegeben wird. Dieser Speicherverbrauch ist nicht zu verwechseln mit dem Hauptspeicher, der für die Domino C++ Objekte belegt wird (beim Einsatz von DIIOP liegen diese schließlich auf dem Server und die Grafiken zeigen den Speicherverbrauch der lokalen Java-Anwendung), sondern es handelt sich hier um den Java Heap, der für die Java-Wrapper (und IIOP-Cache-Objekte) benötigt wird. Dennoch ist diese Vorgehensweise aufgrund der Größe des temporär benötig-
14.3.2 Unterschiede zwischen R5 und R6/7
583
Abb. 14-1 Java-Speicherverbrauch, bei sofortigem Recycling
Abb. 14-2 Java-Speicherverbrauch bei nachgelagertem Recycling
ten Java Heaps nicht akzeptabel. Zusätzlich geben die Grafiken eine anschauliche Vorstellung davon, wie gravierend das Speicherleak ausfallen würde, wenn überhaupt kein Recycle erfolgte.
14.3.2
Unterschiede zwischen R5 und R6/7
Der wesentliche Unterschied zwischen Domino R5 und Domino R6 / R7 beim Recycling liegt im unterschiedlichen Umgang bei Verweisen von unterschiedlichen JavaObjekten auf ein und dasselbe Domino-Objekt.
584
14 Recyling von Domino-Objekten
Während seit Domino R6 sehr durchgängig dafür gesorgt wird, dass ein und dasselbe Domino-Objekt immer auch durch ein und dasselbe Speicherobjekt repräsentiert wird, konnte davon in R5 nicht ausgegangen werden. View thisView = otherDb.getView("V_dokumente_k6"); View otherView = otherDb.getView("V_dokumente_k6"); thisView .recycle(); System.out.println(otherView.getName());
Da thisView und otherView auf ein und dasselbe Domino-Objekt (die Ansicht V_dokumente_k6) verweisen, wird das recycle() von thisView ein recycle von otherView zur Folge haben. In Domino R6 / R7 hätte eine nachfolgende Verwendung von otherView eine NotesException zur Folge, in Domino R5 hätte otherView weiterhin verwendet werden können und ebenfalls recycelt werden müssen. Beachten Sie, dass mit dem Verweis auf ein und dasselbe Domino-Objekt nicht gemeint ist, dass thisView und otherView etwa das gleiche Java-Objekt wären. Eine Zuweisung der Art View otherView = otherDb.getView("V_ganzAndereAnsicht");
hätte lediglich zur Folge, dass nun otherView auf eine andere Ansicht verweist, während sich an thisView nichts ändert. Lediglich beim recycle() zweier Java-Objekte, die auf dasselbe Domino-Obejekt verweisen, kommt das beschriebene Verhalten zum Tragen. Dies gilt auch für andere Domino-Objekte, wie Document oder Database. Eine Einschränkung ist hierbei zu machen. Öffnet man Datenbanken per openDatabaseByReplicaID101 in DbDirectory, so besteht diese Beziehung auch in Domino R6 und R7 nicht: dbdir= session.getDbDirectory(null); db = dbdir.openDatabaseByReplicaID("C12570400064011C"); otherDb = dbdir.openDatabaseByReplicaID("C12570400064011C");
Ein recycle von db hat hier kein (!) recycle von otherDb zur Folge. otherDb muss zusätzlich recycelt werden.
14.3.3
Temporäre Dateien
Die Methoden Reader getReader(), InputSource getInputSource() und InputStream getInputStream() in den Klassen Item, RichTextItem und MIMEEntity erzeugen temporäre Dateien, die erst beim Recycling dieser Objekte wieder gelöscht werden. Wird hier das Recycling vergessen, wird man nicht nur mit Memory Leaks kämpfen, sondern es bleiben – je nach Anwendungshäufigkeit – Unmen101
Domino R6.5.x / Domino 7.0
14.4 Vereinfachung – Die DJBuch-GC-Klasse
585
gen von temporären Dateien liegen, was unter Windows erhebliche Probleme verursachen kann, da dessen Dateisystem nicht gut mit Verzeichnissen zurechtkommt, die (sehr) viele Dateien enthalten.
14.4
Vereinfachung – Die DJBuch-GC-Klasse
Bereits seit Kapitel 6 wird zur vereinfachten Darstellung in den Beispielen die Klasse GC verwendet, ohne näher darauf einzugehen. Sie erfüllt folgende Ziele und Anforderungen: 1
2
3
Der Aufruf von recycle soll keine Exception werfen. Die Methode recycle in den Domino-Java-Objekten kann eine NotesException werfen. Der Aufruf von recycle erfolgt in der Regel im finally-Block. Da dieser – das liegt in der Natur des finally-Blocks – sich außerhalb des try-catch befindet, würde die Verwendung von recycle einen zusätzlichen try-catchBlock innerhalb des finally erfordern, bzw. das Code-Objekt selbst müsste eine NotesException werfen. GC soll statische recycle-Methoden zur Verfügung stellen, die die NotesException intern abfangen. Das Verhalten eines Objektes soll nach dem recycle definiert sein. Wurde für ein Domino-Objekt recycle aufgerufen, ist dessen Verhalten normalerweise nicht mehr definiert. Die recycle-Methoden in GC sollen hierfür Abhilfe schaffen. Die Methoden dieser Klasse sollen robust gegen null-Parameter sein.
Die Klasse GC (Listing 14-2) enthält für jede Domino-Objekt-Klasse eine statische Methode recycle. Jede nimmt jeweils ein Domino-Objekt als Parameter . Der Parameter wird auf null überprüft und im Falle, dass er nicht gleich null ist, wird für ihn die Methode recycle() aufgerufen. Es ist wichtig, dass auch GC.recycle(null) aufgerufen werden kann, da aufgrund der typischen Deklaration der Domino-Objekte außerhalb des try-catchBlockes im finally nicht immer davon ausgegangen werden kann, dass die Objekte bereits zugewiesen wurden:
586
14 Recyling von Domino-Objekten
package djbuch.kapitel_06; import djbuch.kapitel_13.DJDocument; import lotus.domino.*; public class GC { public static Session recycle(Session session) { try { if (session != null){ session.recycle(); } } catch (NotesException notesexception) { throw new RuntimeException ("Fatal: Notes Exception while recycling Session."); } return null; } public static Document recycle(Document document) { try { if (document != null) { document.recycle(); } } catch (NotesException notesexception) { throw new RuntimeException ("Fatal Error while recycling Document."); } return null; } ... //usw. private GC() { //hide from Public } }
Listing 14-2 GC Klasse – Recycling vereinfacht
public void run () { Session session = null; Database db = null; Document doc = null; try { session=NotesFactory .createSession (HOST, USER, PASSWORD); db = session.getDatabase (session.getServerName(),"djbuch/djbuch.nsf"); doc = db.getAllDocuments().getFirstDocument(); } catch (NotesException e) { System.out.println ("id = " + e.id); e.printStackTrace(); } finally { GC.recycle(doc); GC.recycle(db); GC.recycle(session); } }
14.5 Recycling im MultiThreading
587
Würde bei der Initialisierung der Session oder der Datenbank ein Fehler auftreten, kann es beim Recycling des Document oder der Database vorkommen, dass diese Objekte noch nicht zugewiesen wurden und daher noch den Wert null haben. Daher die Forderung, dass GC.recycle auch null-Parameter zulässt. Innerhalb von GC.recycle wird die potentielle NotesException abgefangen . Tritt hier eine solche Exception auf, wird das durch eine RuntimeException quittiert . Durch die bewusste Verwendung einer unchecked Exception an dieser Stelle müssen die recycle-Methoden in GC nicht selbst in einem try-catch-Block untergebracht werden. Alle recycle-Methoden in GC geben ein null-Objekt des jeweiligen Typs zurück. Dies vereinfacht den Umgang mit Objekten, die bereits recycelt wurden. Die Ausführung von GC.recycle (doc) ruft lediglich das recycle auf. Wird versehentlich später doc benutzt, so ist dessen Verhalten unbestimmt (ein Zugriff könnte also unter bestimmten Umständen ohne Fehler zufällig erfolgreich sein). Soll dieser versehentliche nachfolgende Zugriff verhindert werden, kann über doc= GC.recycle(doc) einfach in einer Codezeile erreicht werden, dass das Objekt doc null ist. Das Verhalten wäre zumindest eindeutig (NullPointerException). Die Klasse GC hält für alle denkbaren Domino-Objekte entsprechende recycleMethoden zur Verfügung .
14.5
Recycling im MultiThreading
In Multithreading-Umgebungen ist darauf zu achten, dass Objekte in dem Thread recycelt werden, in dem sie erzeugt wurden. Sofern sich mehrere Threads eine DominoSession teilen und DIIOP verwendet wird, können Domino-Objekte auch threadübergreifend recycelt werden, wobei dann natürlich darauf zu achten ist, dass in keinem anderen Thread dieses Objekt noch verwendet wird.
14.6
Zusammenfassung
Wenn auch für Java-Programmierer ungewohnt, müssen Domino-Java-Objekte recycelt werden. Beherzigt man einige Grundregeln und setzt die vorgeschlagenen Code Pattern ein, so kann dennoch einfach und sicher mit dem Recycling umgegangen werden. Die wichtigsten Regeln sind das Recycling großer Objekte, das Recycling der Referenzen in Schleifen, die Anwendung von recycle in finally-Blöcken und die Einhaltung der Verantwortung, wer für das Recycling zuständig ist – der Erzeuger – und wann dies erfolgen darf – wenn ein Objekt nicht mehr gebraucht wird.
15
15 Wer suchet, der findet
In diesem Kapitel: Index Lookup über eine oder mehrere Ansichtenspalten Und- und Oderverknüpfungen bei getAllEntriesByKey Volltextsuche und Indizierung Einsatzgebiete der verschiedenen Suchstrategien und deren Optionen Performancevergleich
590
15 Wer suchet, der findet
Datenbanken enthalten Daten und die wollen geladen, gespeichert, gesucht und gefunden werden. Wie jedes andere Datenbanksystem bietet auch Domino verschiedene Mechanismen, die den Programmierer bei der Datenhaltung und Wartung unterstützen. Ungewohnt mag auf den ersten Blick das Fehlen einer SQL-Schnittstelle102 sein, stattdessen tritt bei Domino an Stelle von Tabellen und Ansichten der View (Ansicht), über den Dokumente der Datenbank zusammengefasst, indiziert, selektiert und durchsucht werden. Wurden in den Kapiteln 3.3ff., 7.1.4.1 bis 7.1.4.3 und 10 bis 10.8 Views vorgestellt und deren Signaturen und Methoden einerseits und Techniken zur Benutzung andererseits vorgestellt, wird dieses Kapitel die verschiedenen Techniken, die Domino zur Suche und Selektion von Dokumenten und Inhalten bietet, miteinander vergleichen und ihre jeweilige Philosophie näher erläutern. Neben Ansichten werden in diesem Zusammenhang die @Formel-gesteuerte datenbankweite Suche, die Volltextsuche, aber auch erweiterte Suchkonzepte, wie die Domainsearch und spezielle URL-Parameter zur Suche über eine Browserschnittstelle, erläutert.
15.1
Überblick über die Suchkonzepte
Dokumente oder Dokumentensammlungen (DocumentCollection oder ViewEntryCollection) können in Domino entweder direkt über eine eindeutige ID selektiert, über eine Ansicht anhand eines Schlüssels oder über verschiedene Volltextsuchen geladen werden. Bei der Entscheidung, welches Suchkonzept bei der Programmierung Anwendung finden soll, müssen verschiedene Fragestellungen in Betracht gezogen werden: Neben der Menge der zu durchsuchenden Dokumente und deren Größe spielt natürlich eine Rolle, welche Informationen über das oder die zu wählenden Dokumente bekannt sind bzw. anhand welcher Kriterien Dokumente selektiert werden sollen. Lässt sich ein Datensatz anhand der zumindest teilweisen Übereinstimmung eines oder mehrerer Schlüssel identifizieren, so ist immer eine Suche über eine Ansicht der Weg der Wahl. Durch den Einsatz von Spaltenformeln in der Ansicht kann dieses Konzept sogar sehr flexibel eingesetzt werden und bleibt nicht auf den reinen Einszu-eins-Lookup eines Schlüssels beschränkt. Ist die UniversalID eines Dokuments bekannt, wird man es direkt anhand dieser ID laden. Steht weder die UniversalID noch ein geeigneter View zur Verfügung, so kann durch den Einsatz von @Formeln ein Dokument (oder mehrere) direkt aus der Datenbank selektiert werden, wobei dieser Vorgang mit der dynamischen Generierung von SQL Queries verglichen werden kann. Domino versteht sich als Spezialist in der Verwaltung und Speicherung unstrukturierter Daten, einschließlich großer Binär- und Multimediadaten, wie sie in Kollaborationsanwendungen und Knowledgestores vorkommen. Dementsprechend bietet 102
Bis Ende 2004 bot Lotus einen JDBC-Treiber für Domino an, der mit der Einführung von Domino R7 und NSFDB2 wohl überflüssig geworden ist. Weiterhin verfügbar ist ein ODBC-Treiber für Windows [Domino, ODBC]. Dies ermöglicht den Zugriff per SQL auf Domino-Datenbanken.
15.1 Überblick über die Suchkonzepte
591
Domino viele Funktionen zur Volltextindizierung und Suche, die neben ausgefeilten Suchstrategien – z.B. „near“-Anweisungen bis hin auf Absatz oder Satzebene – auch die Indizierung generischer Dokumente wie z.B. von Word oder PDF erlauben. Diese Funktionen sind bereits integriert und können über ein einfaches API angesteuert werden. Im Detail stehen folgende Techniken zur Verfügung: 1
Selektion einzelner Dokumente über Methoden in Database Die Methoden getDocumentByID und getDocumentByUNID (s. Kap. 7.1.4.1) ermöglichen es, Dokumente anhand ihrer NoteID oder UniversalID zu laden. Diese Methoden kommen zum Einsatz, wenn es möglich ist, die UniversalID (oder NoteID) durchgängig als primären Schlüssel in der Anwendung zu verwenden oder wenn diese aus anderen Gründen bekannt ist.
2
Selektion einzelner Dokumente über einen oder mehrere Spaltenschlüssel in Ansichten Über die Methoden getDocumentByKey und getEntryByKey in View (s. Kap. 10.7ff.) wird die Übereinstimmung eines oder mehrerer Suchworte (Key) mit den ersten bis zur n-ten sortierten Spalte eines View überprüft und das erste passende Dokument zurückgegeben. Für getEntryByKey wird das erste Dokument in der Reihenfolge der Ansicht zurückgegeben. Für getDocumentByKey hängt eine Rückgabe von der internen Reihenfolge der Ansicht ab, die nicht näher bestimmt ist. Der Vergleich auf Übereinstimmung kann teilweise (Anfang muss übereinstimmen) oder exakt erfolgen. Voraussetzung ist, dass die ersten n Spalten des View sortiert sein müssen. Es können maximal n=4 Spalten durchsucht werden. Spaltenwerte können über @Formeln bei der Definition des View manipuliert werden, so dass die zu durchsuchenden Daten in einer Form vorliegen, die benötigt wird, um sie mit den Suchworten zu vergleichen. Domino durchsucht mit diesen Methoden interne Indizes, die angelegt werden, sobald eine sortierte Spalte erstellt wird. Dieses Verfahren ist das effektivste nach der direkten Selektion über eine ID.
3
Selektion mehrerer Dokumente über einen oder mehrere Spaltenschlüssel in Ansichten Die Methoden getAllDocumentsByKey und getAllEntriesByKey arbeiten wie die zuvor benannten, geben allerdings alle zu den Schlüsselwörtern passenden Dokumente zurück. Die Reihenfolge für getAllDocumentsByKey ist unbestimmt, bei getAllEntriesByKey entspricht sie derjenigen der Ansicht. Dieses Verfahren ist ebenfalls sehr effizient. Die Verfahren aus 2 und 3 sollten wann immer möglich anderen Techniken vorgezogen werden.
4
Selektion mehrerer Dokumente der Datenbank anhand einer @Formel
592
15 Wer suchet, der findet
Suche über Ansichten Suchen über sortierte Spalten in Ansichten sind effizient und sollten anderen Suchmethoden vorgezogen werden. Und-Verknüpfungen in einer Suche werden über Suchparameter als Vector und die Suche über mehrere Spalten realisiert. Oder-Verknüpfungen in einer Suche werden über Listenelemente aus mehreren Items in einer Spalte realisiert. Sortierte Ergebnisse werden durch getAllEntriesByKey – entsprechend der zugrundeliegenden Ansicht – zurückgegeben. Unsortierte Ergebnisse liefert getAllDocumentsByKey
Die Methode search in Database bietet die Möglichkeit, eine @Formel zu spezifizieren, über die alle Dokumente selektiert werden, die dieser @Formel genügen. Dieses Verfahren kommt zum Einsatz, wenn entweder das Selektionskriterium erst zur Laufzeit bekannt wird oder wenn aus einem Grund keine Ansicht vorliegt, die die benötigte Teilmenge an Dokumenten zur Verfügung stellt und entsprechend sortierte Spalten vorhält, die mit einem Suchwort verglichen werden könnten. Zurückgegeben wird eine unsortierte DocumentCollection. Dieses Verfahren kann verglichen werden mit der dynamischen Erstellung einer SELECT-Formel einer Ansicht zur Laufzeit, die ja selbst nur eine @Formel ist. Wenn auch nicht ausdrücklich als Volltextsuche deklariert, so ist dieses Verfahren nur effizient, wenn der Volltextindex für eine Datenbank aktiviert ist. 5
Selektion mehrerer Dokumente anhand einer Volltextsuche in einer Datenbank oder Ansicht Während die oben beschriebenen Verfahren immer auf einen SELECT oder eine Schlüsselübereinstimmung abzielen, bietet die Volltextsuche FTSearch in Database und View weitergehende Möglichkeiten. Neben dem möglichen Einsatz von Jokersymbolen können unscharfe Vergleiche bis hin zur Fuzzy Search anhand von Schlüsselwörtern und Suchparametern durchgeführt werden. Die Volltextsuche kommt dementsprechend genau dann zum Einsatz, wenn Binärdaten durchsucht oder unscharfe Vergleiche durchgeführt werden müssen. Die Ergebnisse können nach Relevanz oder Dokumentenerstellungs- oder änderungsdatum sortiert werden.
15.2 Zugriff über UniversalID und NoteID
593
Die API ist sehr einfach zu handhaben – es muss lediglich ein Suchstring mit einer eigenen Suchsyntax angegeben werden – und bedarf keines zusätzlichen Installations- oder Administrationsaufwandes. Die benötigten Volltextindizes werden erstellt, indem eine entsprechende Eigenschaft der Datenbank aktiviert wird. Ansonsten ist der Vorgang völlig transparent. Die Volltextsuche ist erwartungsgemäß rechenaufwändig und weniger effizient als die vorgenannten Suchmethoden. 6
Selektion mehrerer Dokumente anhand einer Volltextsuche in einer DocumentCollection oder ViewEntryCollection Neben Ansichten und Datenbanken können analog zu 5 auch DocumentCollection und ViewEntryCollection durchsucht werden.
7
Datenbankübergreifende, domänenweite Suche Domino bietet die Möglichkeit, auf administrativer Ebene die so genannte Domänensuche für einen Server zu aktivieren. Datenbanken, die über eine entsprechende Eigenschaft für diese Suche aktiviert sind, werden in diesen Index aufgenommen. Hierdurch wird es möglich, nicht nur auf einer einzelnen Datenbank oder Ansicht, sondern datenbankübergreifend zu suchen. Diese Suche arbeitet analog zu einer Volltextsuche über eine Datenbank, kann aber neben Datenbanken auch Dateisysteme in die Suche mit aufnehmen.
8
Suche über URL-Querys und Browser Über eine spezielle URL-Syntax können Ansichten und Datenbanken über eine vordefinierte Browserschnittstelle durchsucht werden. Diese Suche arbeitet analog zu einer Volltextuche über eine Ansicht.
15.2
Zugriff über UniversalID und NoteID
Ist die UniversalID oder Note ID eines Dokuments bekannt, so kann es direkt aus der Datenbank geladen werden (s. Kap. 7.1.4.1). Interessant ist die Möglichkeit, die UniversalID als Spaltenwert in einer Spalte mit auszugeben, dann kann effizient auf die UniversalID eines Dokuments über getColumnValues in ViewEntry zugegriffen werden, nachdem Dokumente in einer Ansicht gefunden wurden, und es kann umgekehrt geprüft werden, ob ein bestimmtes Dokument sich in einer Ansicht befindet. Zusätzlich kann die UniversalID genutzt werden, um Spaltenberechnung auf Dokumenten auszuführen und diese über getColumnValues zu laden, nachdem ein Dokument anhand einer Spalte mit UniversalIDs über die Techniken aus Kapitel 15.3 geladen wurde.
594
15 Wer suchet, der findet
15.3
Suche über Views
Die Suche über Ansichtenspalten ist die geeignetste Methode, wenn Dokumente anhand eines oder mehrerer Schlüssel selektiert werden sollen. Ein weiteres wichtiges Merkmal bei der Suche über Ansichten ist, dass durch die Ansicht eine vordefinierte Menge von Dokumenten durchsucht wird und nicht etwa die gesamte Datenbank. Hierfür stehen die vier bekannten Methoden getDocumentByKey, getAllDocumentsByKey, getEntryByKey und getAllEntriesByKey zur Verfügung, die alle jeweils in vier Signaturen mit den Parametern (Object | Vector [, boolean]) vorliegen. Die genauen Signaturen finden sich in den Kapiteln 10.4 und 10.5. Wird ein Object übergeben, so kann dies ein Objekt der Klasse String, lotus.domino.DateTime, lotus.domino.DateRange oder Number sein. Ein Vector im Parameter muss aus Elementen solcher Objekte bestehen. Wird ein Vector übergeben, so korrespondiert jedes Element mit einer zu durchsuchenden Spalte, wobei für n Vector-Elemente die ersten n Spalten durchsucht werden. Nutzen Sie die zur Verfügung stehenden Datentypen. Eine Suche über eine sortierte Ansichtenspalte aus Number-Elementen wird immer performanter sein als eine solche Suche über Strings und wird dazu noch weniger Speicher für den Index belegen, zumal Zahlen in ihrer String-Darstellung anders sortiert werden. Der boolean-Parameter gilt für Suchobjekte der Klasse String und erlaubt zwischen exakter und nur auf den Anfang überprüfender Suche zu unterscheiden. Nach Groß- und Kleinschreibung wird nie unterschieden. Wenn keine exakte Suche gewählt und gleichzeitig ein Vector als Suchparameter mit mehreren Elementen übergeben wird, kann es vorkommen, dass bestimmte Treffer nicht ausgegeben werden. Dies ist der Fall, wenn die Sortierung der zweiten Spalte sich mit der nur teilweisen Übereinstimmung der ersten Spalte ändert. Werden sortierte Suchergebnisse benötigt, muss auf die Methode getAllEntriesByKey zurückgegriffen werden. Das Ergebnis dieser Methode ist, im Gegensatz zu dem unsortierten Ergebnis von getAllDocumentsByKey, entsprechend dem zugrunde liegenden View sortiert. Aus Performance-Gesichtspunkten sollten Sie immer über Views suchen. Bei sehr großen Datenbanken werden Views allerdings ebenfalls performancerelevant für die Gesamtperformance des Servers, da die internen View-Indizes regelmäßig aktualisiert werden müssen. Enthalten viele Views einer Datenbank dieselben Dokumente, so ist zu bedenken, dass jedes Update eines Dokuments das Update ALLER dieser Ansichtsindizes erfordert. Zusätzlich sollten Sie den Überblick über die Views in Ihrer Datenbank behalten, denn jede sortierte Spalte bekommt einen Suchindex, der zusätzlichen Platz in Ihrer Datenbank belegt. Wird über einen Vector nach mehreren Such-Schlüsseln gesucht, so entspricht dies einer Und-Verknüpfung dieser Suchparameter. Oder-Verknüpfungen können über einen kleinen Trick realisiert werden. Wenn z.B. das Suchwort „Martin“ entweder im Item F_Vorname oder im Item F_Nachname gefunden werden soll, definieren Sie eine sortierte Spalte mit der Spaltenformel F_Vorname:F_Nachname. Durch den Doppelpunkt werden die beiden Items als
15.3 Suche über Views
595
Liste zusammengefasst. Nun muss noch die Spalteneigenschaft „Show multiple values as seperate entries“ gesetzt werden. Dies hat zur Folge, dass nun jedes Dokument in der Ansicht doppelt erscheint und in dieser Spalte sowohl der Vorname als auch der Nachname gelistet wird. Ein getAllEntriesByKey („Martin“) würde nun Dokumente finden, die entweder im Vornamen oder im Nachnamen (oder in beiden) den String „Martin“ enthalten. Nutzen Sie die Suche über Ansichten, wann immer möglich. Es ist die effizienteste Art, Dokumente in Domino zu selektieren. Die Möglichkeit, Ansichten per Volltextindex zu durchsuchen, wird weiter unten diskutiert. Zur Demonstration der praktischen Arbeit mit Ansichten wurde der Spielzeugladen aus dem vorigen Kapitel gründlich überarbeitet (Listing 15-1). Hierbei wurde die ID-Verwaltung und das Recycling optimiert und neue Suchfunktionen implementiert. Der wesentliche Umbau der ToyStoreAdvanced-Klasse besteht darin, dass nun interne Ansichten als Instanzvariablen zur Verfügung stehen. Sie werden im Konstruktor geladen und müssen daher wie die Datenbank im recycle der Klasse wieder freigegeben werden. Die Ansichten können nun an verschiedenen Stellen genutzt werden, um Nachschlageoperationen durchzuführen. Der entscheidende Vorteil, die Ansichten als Instanzvariable zu führen, besteht darin, dass das verhältnismäßig teure Laden der Ansicht, aber auch das Recycling jeweils nur einmal durchgeführt werden muss. Anhand der Ansicht VIEW_TOYBYID können nun TOY-Dokumente über deren ID verwaltet werden, ohne den teuren db.search anwenden zu müssen. Diese Ansicht (sie befindet sich in der Beispieldatenbank djbuch/djbuch.nsf unter dem Namen „V_toysByID“) ist einfach aufgebaut und hat lediglich zwei Spalten. Die erste Spalte ist sortiert und zeigt die Werte F_ID der Dokumente. Die SELECT-Formel der Ansicht lautet SELECT FORM="FO_TOYFORM" & @IsUnAvailable($Conflict) und wählt alle ToyStore-Objekte, die mit der Maske „FO_TOYFORM" erstellt werden und sorgt gleichzeitig dafür, dass keine eventuellen Speicherkonflikte in der Ansicht angezeigt werden, die einen eindeutigen Umgang mit den IDs unmöglich machen würden. Dies ermöglicht die Überarbeitung von addToy . Diese Methode muss nun nicht mehr über einen Parameter mit einer ID versorgt werden, sondern diese kann in getNewID berechnet werden , wobei diese neue ID nun auch als Rückgabewert von addToy zurückgegeben wird, so dass ein Anwender Aufschluss darüber erhält, welche neue Artikelnummer das neue Spielzeug erhalten hat. Selbstverständich ist auch dieser Ansatz noch stark vereinfachend. In einem nächsten Abstraktionslevel müssten die Spielzeuge nicht mehr nur anhand ihrer ID, sondern über eine eigene Klasse abgebildet werden. Zusätzlich wurde die im vorigen Kapitel vorgeschlagene Methode checkRecycled eingeführt. Ein Hersteller des Spielzeugs wird ebenso im Toy-Dokument gespeichert wie das Erstelldatum . Grundsätzlich wäre dies nicht erforderlich, da die @Formel @created auch in Spaltenwerten das Erstellungsdatum liefert. Die statische Speicherung dieses Datums in einem Item verbessert jedoch die Performance bei späteren lookup-Operationen auf solch einem Spaltenwert.
596
15 Wer suchet, der findet
package djbuch.kapitel_15; import java.util.*; import lotus.domino.*; ...//import ... public class ToyStoreAdvanced { private Database db = null; private View idSearchView = null, nameSearchView = null, dateSearchView = null; private Session session = null; private boolean isRecycled = false; ...//Konstanten definieren public ToyStoreAdvanced (String dbName, Session session) throws NotesException { db = session.getDatabase(session.getServerName(),dbName); if (db==null || !db.isOpen()) { throw new NotesException(APP_ERR_NUM, "Konnte DB nicht öffnen.");} idSearchView = db.getView(VIEW_TOYBYID); nameSearchView = db.getView(VIEW_TOYBYNAME); dateSearchView = db.getView(VIEW_TOYBYDATE); if (idSearchView==null || nameSearchView==null || dateSearchView==null) { throw new NotesException(APP_ERR_NUM, "Konnte Ansicht nicht öffnen.");} this.session = session; } public int addToy (String name, String manufacturer) throws NotesException { checkRecycled(); Document doc = null; try { doc = db.createDocument(); int newID=getNewID(); doc.replaceItemValue (ITEM_ID, new Integer (newID)); doc.replaceItemValue (ITEM_NAME, name); doc.replaceItemValue (ITEM_MANUFACTURER, manufacturer); doc.replaceItemValue (ITEM_FORM, DEFAULT_FORM); doc.replaceItemValue (ITEM_CREATIONDATE, session.createDateTime(Calendar.getInstance())); doc.save(); return newID; } finally { GC.recycle (doc); } } private int getNewID () throws NotesException { Document doc = null; try { synchronized (idSearchView) { idSearchView.refresh(); doc = idSearchView.getLastDocument(); if (doc==null) {return 1;} else { return doc.getItemValueInteger(ITEM_ID) + 1; } } } finally { GC.recycle(doc); } } public String getToy (int id) throws NotesException { checkRecycled(); Document doc = null; try { doc = getToyDocument(id); return (doc==null?null:doc.getItemValueString(ITEM_NAME)); } finally { GC.recycle (doc);
15.3 Suche über Views
597
} } public Document getToyDocument (int id) throws NotesException { checkRecycled(); idSearchView.refresh(); return idSearchView.getDocumentByKey(new Integer (id)); } public Document findToyByName (String name) throws NotesException { return findToyByName (name, null); } public Document findToyByName (String name, String manufacturer) throws NotesException { checkRecycled(); Vector searchKeys = new Vector (); searchKeys.add(name); if (manufacturer!=null) { searchKeys.add(manufacturer); } nameSearchView.refresh(); return nameSearchView.getDocumentByKey(searchKeys,false); } public DocumentCollection findAllToysByName (String name) throws NotesException { return findAllToysByName (name, null); } public DocumentCollection findAllToysByName (String name, String manufacturer) throws NotesException { checkRecycled(); Vector searchKeys = new Vector (); searchKeys.add(name); if (manufacturer!=null) { searchKeys.add(manufacturer); } nameSearchView.refresh(); return nameSearchView.getAllDocumentsByKey(searchKeys,false); } public DocumentCollection findAllToysByDate (DateTime dt) throws NotesException { checkRecycled(); DateTime datum = null; try { datum = session.createDateTime(dt.toJavaDate()); dateSearchView.refresh(); datum.setAnyTime(); return dateSearchView.getAllDocumentsByKey(datum); } finally { GC.recycle (datum); } } public ViewEntryCollection findAllFT (String searchQuery) throws NotesException { checkRecycled(); if (!db.isFTIndexed()) {System.err.println ("[!!!] NICHT FT indiziert.");} try { idSearchView.refresh(); idSearchView.FTSearch(searchQuery); return idSearchView.getAllEntries(); } finally { idSearchView.clear(); } } ...//public void removeToy (int id) throws NotesException { public void recycle () { GC.recycle (idSearchView); GC.recycle (nameSearchView);
598
15 Wer suchet, der findet GC.recycle (dateSearchView); GC.recycle(db); isRecycled=true;
} private void checkRecycled () throws NotesException { if (isRecycled) {throw new NotesException (NotesError.NOTES_ERR_DELETED, "Object has been removed or recycled");} } }
Listing 15-1 Suchkonzepte im Einsatz – ToyStoreAdvanced
in der Um eine eindeutige und neue ID zu generieren, schlägt getNewID Ansicht VIEW_TOYBYID den letzten Wert nach, der aufgrund der Sortierung in der Ansicht auch der größte ist und gibt einen um eins größeren Wert zurück. Damit der Wert wirklich eindeutig ist, muss synchronisiert und die Ansicht mit den Dokumenten durch ein refresh auf den neuesten Stand gebracht werden. Diese Technik ist ein typisches Designpattern, wenn in einem View der größte Wert in einer Spalte bestimmt werden muss, und eignet sich für die Erstellung aufsteigender Werte in einer Ansicht. Da nun eine Ansicht in der Klasse ToyStoreAdvanced zur Verfügung steht, mit der IDs nachgeschlagen werden können, können die Methoden getToy und getToyDocument optimiert werden. Erstere wurde direkt auf getToyDocument zurückgeführt. getToyDocument selbst konnte stark vereinfacht werden . Es muss lediglich in der Ansicht VIEW_TOYBYID die ID nachgeschlagen werden , allerdings auch hier erst nach einem refresh der Ansicht. Dies erfolgt mit dem int-Wert der ID auf der ersten Ansichtenspalte, die alle IDs aller TOYs zeigt. Der int-Wert muss in einen Integer umgewandelt werden, da getDocumentByKey als Parameter ein Object erwartet. Die Problematik der recyclingpflichtigen DocumentCollection (Listing 14-1) ist beseitigt, da die Ansicht, auf deren Basis der lookup erfolgt, während der gesamten Lebensdauer des ToyStoreAdvanced-Objektes bestehen bleibt und sich anders als die DocumentCollection der ersten Version nicht für jeden Aufruf von getToyDocument ändert. Neben dem Nachschlagen von IDs eigenen sich Ansichten aber auch für einfache Suchoperationen über ein oder mehrere Suchwörter. Hierfür wurden die Methoden findToyByName und findAllToysByName implementiert, beide in jeweils zwei Signaturen mit einem oder zwei Suchwörtern, wobei erstere auf letztere zurückgeführt wird. Die Suchwörter werden in einen Vector eingelesen und in dem durch einen refresh aktualisierten View gesucht . Beide sortierten Spalten der hier durchsuchten Ansicht „V_toysByName“ enthalten den Namen bzw. den Hersteller der Spielzeuge. Die SELECT-Formel ist dieselbe, wie für den lookup der IDs. findToyByName und findToysByName unterscheiden sich ausschließlich durch die Verwendung von findAllDocumentsByKey an Stelle von findDocu-
15.3 Suche über Views
599
. Das Beispiel zeigt eindrücklich, wie einfach Ansichten durchsucht werden können. Der gezeigte Aufbau ist für viele Anwendungsfälle geeignet und stellt ein Standard-Designpattern für derartige Suchen dar. Selbstverständlich ist für derartige Suchen über Ansichten, wie ohnehin schon für Ansichten, gewährleistet, dass nur Dokumente gefunden werden, für die der Anwender mindestens Leserechte hat. Wird in einer Javaanwendung, wie z.B. in dem Beispiel, allerdings mit einem vordefinierten Funktionsuser gearbeitet, so gelten selbstverständlich dessen Berechtigungen. Neben TEXT oder NUMBER Items können natürlich auch DATETIME Items durchsucht werden. Hier ist die Suche aber offenkundig etwas weniger flexibel, da nur eine exakte Übereinstimmung möglich ist (der boolean-Parameter exactSearch hat hier keine Auswirkung). Im Beispiel wurde die Methode findAllToysByDate dahingehend angepasst, dass nur der Datumsanteil des DateTime-Parameters für die Suche eingesetzt wird. Dieser wird innerhalb der Methode kopiert, um Nebenwirkungen auf dem Parameter zu unterbinden. Durch setAnyTime in DateTime wird erreicht, dass diese Methode alle Dokumente zurückgibt , die an dem Datum erstellt wurden, das dem Datumsanteil des Parameters dt entspricht. Der Spielzeugladen hat somit ein umfangreiches Refactoring erhalten. Die IDs der Toys werden intern über eine Ansichtenspalte mit Integer-Werten verwaltet und er kann effizient nach Namen und Herstellern durchsucht werden. Bleibt noch die Volltextsuche findAllTF, auf die in Kapitel 15.5 eingegangen wird. Zum Abschluss ein paar typische Anwendungsfälle:
mentByKey
/* Suche mit einem Key - ein Treffer */ doc = ts.findToyByName("sMal"); System.out.println("\n" + doc == null ? "NULL!" : "Gefunden: " + doc.getItemValueString(ToyStoreAdvanced.ITEM_NAME)); doc.recycle(); /* Suche mit zwei Keys - ein Treffer */ doc = ts.findToyByName("BIG","THIS"); System.out.println("\n" + doc == null ? "NULL!" : "Gefunden: " + doc.getItemValueString(ToyStoreAdvanced.ITEM_NAME)); /* Suche mit einem Key - alle Treffer */ dc = ts.findAllToysByName("biG"); dumpDC (dc); dc.recycle(); /* Suche mit einem Date Key - alle Treffer */ dt = session.createDateTime(Calendar.getInstance()); dc = ts.findAllToysByDate(dt); dumpDC (dc);
Die Suche nach einem Namen (genauer dem Anfang eines Namens) über findgibt den ersten Treffer zurück. Die gleiche Methode kann mit zwei Stringparametern die Spalten F_Name und F_Manufacturer durchsuchen . Werden alle Treffer benötigt, kann eine DocumentCollection bezogen werden.
ToyByName
600
15 Wer suchet, der findet
In allen Fällen erfolgt die Suche ohne Berücksichtigung von Groß- und Kleinschreibung. Das letzte Beispiel liefert nicht etwa nur die in der aktuellen Millisekunde erstellten Dokumente – Calendar.getInstance liefert ein Datum und eine Uhrzeit –, sondern aufgrund der Modifikation in findAllToysByDate , alle Dokumente, die am gleichen Tag erstellt wurden. Das Document und die DocumentCollection verlieren durch die Mehrfachzuweisung ihre Referenz und müssen recycelt werden.
15.4
Suche über Datenbanken mit db.search
Ein Nischendasein zwischen der Suche in Ansichtsspalten und der Volltextsuche nimmt die Methode search in Database ein, deren Signaturen bereits in Kapitel 9.4 beschrieben wurden. Wie eingangs erwähnt, lässt sich diese Suche mit einer Ansicht mit einer „dynamisch generierten SELECT-Formel“ beschreiben. Wie für eine SELECT-Formel einer Ansicht wird auch für search eine @Formel verwendet. Alle Dokumente der Datenbank, die dieser @Formel genügen, werden als unsortierte DocumentCollection zurückgegeben. Auch wenn es sich hier nicht ausdrücklich um eine Volltextsuche handelt, so basiert diese dennoch intern auf dem FTIndex der Datenbank. Sie wird auch ohne den Index arbeiten, aber unter erheblichen Performanceeinschränkungen. Es ist ratsam, die Methode möglichst nicht ohne den FTIndex anzuwenden, dessen Vorhandensein über isFTIndexed() geprüft werden kann. Per Default gibt die Methode maximal 5000 Dokumente zurück. Dieser Wert kann durch den Notes-INI-Parameter FT_MAX_SEARCH_RESULTS verändert werden. Existiert kein Volltextindex, so legt Domino einen temporären während der Suche an, dessen Limit über die Notes-INIVariable TEMP_INDEX_MAX_DOC gesteuert wird. Folgende Beispiele geben einen Eindruck von den Möglichkeiten. Die verwendeten @Formeln liefern jeweils einen Boolean Wert als Ergebnis: •
db.search ("F_Name = \"Big dog\"");
Felder werden einfach über einen direkten Vergleich, wie in @Formeln üblich, abgefragt. Stringliterale werden in Anführungszeichen gesetzt, Feldnamen (Variablen) direkt mit Namen benannt. •
db.search ("@contains (@lowerCase(F_Name);\"" + key.toLowerCase() + "\")");
Anders als die Suche mit getAllDocumentsByKey unterscheidet die @Formel @contains nach Groß- und Kleinschreibung. •
db.search ("@left (F_Name;\"dog\") = \"Big \""); db.search ("@left (F_Name;7) = \"Big dog\"");
Beide Suchen liefern alle Dokumente, deren Item F_Name mit „Big dog“ beginnt. •
db.search ("one := \"dog\"; (F_Name;7) = two + one");
two
:=
\"Big
\";
@left
15.5 Volltextindizes
601
@Formeln können aus mehreren, durch Semikolon getrennten Befehlen zusammengesetzt werden, Variablen werden durch := zugewiesen. Rückgabewert einer @Formel ist immer das Ergebnis der letzten Anweisung, hier also @left (F_Name;7) = two + one. Die Methode search ist sehr praktisch und universell einsetzbar, aber nicht so performant wie getAllEntriesByKey. Sie sollte immer dann eingesetzt werden, wenn erst zur Laufzeit die Suchbedingungen feststehen und kein geeigneter View vorgehalten werden kann. Die Suche über db.search erfolgt über alle Dokumente einer Datenbank, für die der Benutzer der Session mindestens Leseberechtigung hat.
15.5
Volltextindizes
Volltextindizes sind integraler Bestandteil der Domino-Infrastruktur. Sie sind einfach zu handhaben und werden pro Replik auf einer Datenbank aktiviert. Egal, ob die Volltextsuche über Datenbanken, Ansichten, DocumentCollections oder ViewEntryCollections erfolgt, immer wird auf den Volltextindex der gesamten Datenbank zurückgegriffen. Die verschiedenen Methoden zur Handhabung der Volltextindizes sind in Kapitel 9.4 beschrieben. Bei der Migration einer Datenbank von Domino R5 nach Domino R6 müssen auch die Volltextindizes migriert werden, indem diese zunächst in R5 gelöscht und dann für R6 wieder neu aufgebaut werden. Die Volltextsuche auf den verschiedenen Objekten hat folgende Eigenarten: •
Database DocumentCollection FTSearch (String searchQuery[, int max[, int options[, int otherOptions]]]) throws NotesException DocumentCollection FTSearchRange(String searchQuery, int max, int options,int otherOptions, int start) throws NotesException
Das Ergebnis von FTSearch in Database ist eine DocumentCollection. Durchsucht wird die gesamte Datenbank. Die Treffermenge kann durch den optionalen Parameter max eingeschränkt werden. Für max==0 werden alle Treffer zurückgegeben. Abhängig vom optionalen Parameter options mit Sortieroptionen (Database.FT_SCORES, Database.FT_DATECREATED_DES, Database.FT_DATECREATED_ASC, Database.FT_DATE_DES oder Database.FT_DATE_ASC (s. Kap. 9.4) ist das Ergebnis per Default absteigend
nach Relevanz oder auf- oder absteigend nach Erstell- oder Änderungsdatum der Treffer sortiert. Über den Parameter otherOptions mit zusätzlichen Optionen (Database.FT_FUZZY, Database.FT_STEMS) kann eine Fuzzy-Suche und die Suche über Stammwörter aktiviert werden.
602
15 Wer suchet, der findet Die Methode FTSearchRange besitzt den zusätzlichen Parameter start, der dafür sorgt, dass erst Ergebnisse ab einem bestimmten Treffer zurückgegeben werden. Werden die Parameter max und start gleichzeitig eingesetzt, werden max Treffer ab der Position start zurückgegeben. Um die Ergebnismenge durch eine erneute Suche weiter einzuschränken, kann auf die zurückgegebene DocumentCollection FTSearch angewandt werden. Es gelten die gleichen Randbedingungen wie bereits für search aufgezeigt: Die Volltextsuche basiert auf dem FTIndex der Datenbank. Sie wird auch ohne den Index arbeiten, aber unter erheblichen Performanceeinschränkungen. Es ist ratsam, die Methode möglichst nicht ohne den FTIndex anzuwenden, dessen Vorhandensein über isFTIndexed() in Database geprüft werden kann. Per Default gibt die Methode maximal 5000 Dokumente zurück. Dieser Wert kann durch den Notes-INI-Parameter FT_MAX_SEARCH_RESULTS verändert werden. Existiert kein Volltextindex, so legt Domino einen temporären Index während der Suche an, dessen Limit über die Notes-INI-Variable TEMP_INDEX_MAX_DOC gesteuert wird.
•
View int FTSearch(String searchQuery) throws NotesException int FTSearch(String searchQuery, int max) throws NotesException Die Methode FTSearch in View (Kapitel 10.4) gibt kein Ergebnisobjekt zu-
rück, sondern schränkt die Dokumentenmenge der Ansicht ein. Durchsucht werden lediglich Dokumente, die sich in der Ansicht befinden. Nach Anwendung von FTSearch finden sich in der Ansicht nur noch Dokumente, die der in FTSearch verwendeten Suchquery entsprechen. Das View-Objekt kann nun, wie gewohnt, verwendet werden. Alle Methoden, wie z.B. getNextDocument() etc. verhalten sich unverändert. Das gesamte Verfahren ist transparent. Eine wiederholte Anwendung von FTSearch löst eine komplett neue Suche auf der gesamten, ursprünglichen Ansicht aus, eine weiter einschränkende Suche durch Wiederholen von FTSearch ist also nicht möglich. Um eine Treffermenge weiter einzuschränken, kann aus einer durch FTSearch reduzierten Ansicht per getAllEntries eine ViewEntryCollection geladen werden, auf die ebenfalls FTSearch angewandt werden kann. Mit der Methode clear () lässt sich eine Ansicht nach der Verwendung von FTSearch wieder zurücksetzen. Rückgabewert der Methode ist die Anzahl der gefundenen Treffer. Sortier- und andere Optionen können FTSearch in View nicht mitgegeben werden und es kann auch kein Startpunkt angegeben werden. Lediglich über den Parameter max kann eine Maximal-Trefferzahl angegeben werden. Befinden sich in der Treffermenge mehrere Treffer mit der gleichen Relevanz (SearchScore), so ist bei der Verwendung des Parameters max Vorsicht geboten, da nicht gewährleistet ist, welche dieser Treffer ein- und welche ausgeschlossen werden.
15.5 Volltextindizes
603
Ist die Datenbank volltextindiziert (isFTIndexed()), ist das Ergebnis absteigend nach Relevanz sortiert, andernfalls bleibt die ursprüngliche Sortierung der Ansicht erhalten. Bezüglich der Menge der Rückgabewerte kommen auch hier die Notes-INI-Parameter FT_MAX_SEARCH_RESULTS und TEMP_INDEX_MAX_DOC zum Einsatz. •
DocumentCollection und ViewEntryCollection Die Methode FTSearch in DocumentCollection und ViewEntryCollection arbeitet wie FTSearch in View. Durchsucht werden die Dokumente der Document- bzw. ViewEntryCollection. Die Collections sind nach Anwendung der Suche auf die zur Suchquery passenden Treffer eingeschränkt, der Pointer der Collection zeigt auf das erste Dokument, die Verwendung der Collection und deren Methoden, wie z.B. getNextEntry, arbeiten wie gewohnt. Sortier- und Suchoptionen stehen nicht zur Verfügung, das Ergebnis ist immer absteigend nach Relevanz sortiert.
Der wichtigste Parameter beim Einsatz von FTSearch ist die Suchquery. Sie besteht aus einem Ausdruck, der anhand einer speziellen Syntax gebildet wird. Liegt kein Volltextindex vor, so wird diese Syntax nur eingeschränkt unterstützt. •
Einfaches Suchwort Einfache Suchwörter werden direkt als String übergeben. Groß- und Kleinschreibung wird hierbei nicht berücksichtigt. Gefunden werden Dokumente, die das Suchwort in einem beliebigen Item enthalten. Beispiel: db.FTSearch ("biG DoG");
•
Items durchsuchen Soll das Suchwort in bestimmten Items gesucht werden, so wird der Itemname durch das Schlüsselwort FIELD oder durch eckige Klammern [] kenntlich gemacht. Eine Besonderheit ist zu beachten: Stimmt das Suchwort mit einem einzelnen Wort des Items überein, so werden auch Bestandteile eines Items gefunden, z.B.: db.FTSearch ("FIELD F_Name=smaLL DoG") -> Findet auch „Very small
dog“ db.FTSearch ("[F_Name]=cat") -> Findet weder „tomcat“ noch „Catama-
ran“ aber „Tom's cat“ db.FTSearch ("[$File]=Word") -> Findet ein Dokument, das einen Datei-
anhang besitzt, in dem sich das Wort „Word“ befindet. Hierfür ist erforderlich, dass Dateianhänge mit in den Index aufgenommen wurden (s. Kap. 9.4). $File ist ein reservierter Itemname.
604 •
15 Wer suchet, der findet Joker-Symbole Sollen auch Wortbestandteile gefunden werden, müssen Jokersymbole eingesetzt werden. Das Jokersymbol * kann als Platzhalter für kein, ein oder beliebig viele Zeichen eingesetzt werden. Das Jokersymbol ? wird als Platzhalter für genau ein beliebiges Zeichen eingesetzt. db.FTSearch ("[F_Name]=T?m's cat") -> findet „Tom's cat“ db.FTSearch ("[F_Name]=T? cat") -> findet nicht „Tom's cat“ db.FTSearch ("[F_Name]=T* cat") -> findet „Tom's cat“
•
Vergleichsoperatoren Als Vergleichsoperatoren stehen die Zeichen > < = >= 0, so wird maximal diese Anzahl von Treffern angezeigt. Dieser Parameter ist nicht kompatibel mit SearchOrder=4. Die Treffer werden für diese Kombination (intern) nach Relevanz sortiert und davon maximal SearchMax Treffer in der Sortierung der Ansicht angezeigt. Count, Start – Es werden maximal Count Treffer pro Seite bis zur Erreichung von SearchMax angezeigt, beginnend ab Eintrag Start.
608
15 Wer suchet, der findet
15.7
Performanceoptimierung
Die verschiedenen Suchmethoden haben unterschiedliches Performanceverhalten. Grundsätzlich muss natürlich als erster Schritt entschieden werden, welche Suchoperation für das gewünschte Ergebnis geeignet ist. Falls möglich sollte die Suche über eine Ansicht und getAllEntriesByKey oder getAllDocumentsByKey erfolgen. Eine wichtige Voraussetzung für FTSearch in Database, View, DocumentCollection und ViewEntryCollection, aber auch für search in Database ist das Vorliegen eines Volltextindexes, damit eine Suche performant durchgeführt werden kann. Da die Volltextsuche limitiert ist, liefert sie möglicherweise nicht alle Treffer, wenn diese Limitierung nicht durch den Parameter FT_MAX_SEARCH_RESULTS angepasst wird. Um die Performanceunterschiede messen zu können, wurde dem Code zum Buch die Klasse SearchComparison beigelegt. Sie legt in der Datenbank einen Zoo aus einer frei definierbare Anzahl von Dokumenten mit dem Titel „Tier 1...n“ und „Amöbe 1..m“ an und sucht auf verschiedene Weise die Tiere, indem Dokumente beginnend mit dem Titel „Tier 1...“, „Tier 2...“, ...., „Tier 9999“ gesucht werden. Diese Suchworte werden, sofern möglich, der Menge der Dokumente angepasst, um möglichst synchrone Ergebnisse zwischen den einzelnen Suchstrategien zu erhalten. Dies kann über Konstanten, die im Code näher erläutert sind, angepasst werden. Zusätzlich kann eingestellt werden, dass anstelle der recht linearen Benennung der Tiere von 1..n eine zufällige gewählt werden kann, was aber die Ergebnisse natürlich variieren lässt. Performanceunterschiede konnten erwartungsgemäß nicht festgestellt werden, da in jedem Fall immer ein (natürlich sortierter) Index dazwischenliegt. Nur aufgrund der Suche nach Wortanfängen kommt überhaupt ein Vergleich mit getAllDocumentsByKey in Frage. Zum Vergleich lässt sich so im Vorfeld feststellen, dass natürlich nur eine „freie“ Entscheidungsmöglichkeit besteht, die schnelle Methode getAllDocumentsByKey zu verwenden, wenn die Suchaufgabe dies zulässt. Wird die Aufgabe komplexer, fällt diese Methode weg, aber es ist zu erwarten, dass auch die Zeiten für FTSearch und search schlechter werden. Im Test wurde jede Suchmethode 9-mal ausgeführt. Dabei wurden als Suchparameter immer „Tier 1“ bis „Tier 9“ verwendet, d.h. es wurden immer Treffer für alle Tiere zurückgegeben, deren Name mit „Tier 1“ usw. anfängt (alle Suchen wurden so ausgeführt, dass dieses Suchverhalten erreicht wurde), d.h. es wurden Treffermengen mit „Tier 1“, „Tier 10“, „Tier 11“ usw. zurückgegeben. Je nach Gesamtmenge lieferten daher die einzelnen Detailsuchen unterschiedliche Treffermengen, da z.B. bei 5000 Tieren natürlich keine 6000er usw. mehr vorliegen und eine Suche nach „Tier 111“ natürlich nur noch 11 Ergebnisse liefert. So konnte ein verhältnismäßig realitätsnahes Szenario mit mehreren Tests pro Suche erzeugt werden. Durch dieses Verfahren lagen für jedes Beispiel 9 Tests vor, deren Trefferzahl und Ausführungszeit gemittelt wurde (Tabelle 15-1). Für alle Tests lag der Volltextindex der Datenbank vor und war aktuell. Durchgeführt werden folgende Suchanfragen (X=1..9). •
getDocumentByKey ("Tier X")
15.7 Performanceoptimierung
609
Tabelle 15-1 Performancevergleich der verschiedenen Suchmethoden in Domino Datenbank mit 10.100 Dokumenten
Datenbank mit 50.500 Dokumenten
Laufzeit (*)
Laufzeit (*)
Treffer (*)
Treffer (*)
Methode getDocumentByKey getEntryByKey getAllDocumentsByKey getAllEntriesByKey view.FTSearch (A) view.FTSearch (B) db.FTSearch db.search
203 ms 188 ms 390 ms 391 ms 2422 ms 624 ms 2235 ms 1249 ms
1 1 1111 1111 1111 1111 1111 1111
1313 ms 1265 ms 2875 ms 3797 ms 41078 ms 12704 ms 37969 ms 9016 ms
1 1 5555 5555 5555 5555 5555 5555
(*) gemittelt (A) FTSearch mit Jokersymbol *, (B) FTSearch mit Jokersymbol ?
• • • • •
getEntryByKey ("Tier X") getAllDocumentsByKey ("Tier X") getAllEntriesByKey ("Tier X") view.FTSearch ("FIELD F_titel=\"Tier X*\"") view.FTSearch ("FIELD F_titel=\"Tier X????\"")
Da das Jokersymbol ? für genau ein Zeichen steht, wurden hier zusätzlich Suchen nach „Tier X?“, „Tier X??“ usw. durchgeführt, um dieselbe Treffermenge abzubilden. • •
db.FTSearch ("FIELD F_titel=\"Tier X*\") db.search ("@left (F_titel;6)=\"Tier X\")
Der Test wurde mehrfach durchgeführt und lieferte stets Ergebnisse, die ähnliche Verhältnisse wiedergaben, wobei die absoluten Werte nicht ausschlaggebend sind, da sie natürlich vom Testenvironment103 abhängen. Die Ergebnisse stimmen mit den Erfahrungen aus der Praxis überein und es lassen sich folgende Aussagen ableiten: •
•
103
getDocumentByKey und getEntryByKey sind die effizientesten Methoden zur Dokumentenselektion, dicht gefolgt von getAllDocumentsByKey und getAllEntriesByKey. Zwischen der Suche mit get[All]Document[s]ByKey und get[All]Entrie[s]ByKey waren in keinem Fall signifikante Performanceunterschiede feststellbar, es kann also je nach Bedarf auf eine Document- oder eine ViewEntryCollection zurückgegriffen werden.
Im Test kamen Server der Version R6.5 und 7 zum Einsatz. Beide lieferten vergleichbare Ergebnisse.
610 •
•
15 Wer suchet, der findet Interessanterweise waren alle Ergebnisse für FTSearch in Database stets etwas schneller als dieselbe Methode in View. Allerdings waren bei den Tests die Ansicht und die Dokumente so verteilt, dass die Ansicht bis auf rund 1% der Dokumente dieselben Dokumente beinhaltete wie die Datenbank. Enthält die Datenbank wesentlich mehr Dokumente, als im View gezeigt werden, ist zu erwarten, dass dies zum Nachteil der Suche mit FTSearch in Database ausfallen wird. search in Database ist gemessen an der Leistungsfähigkeit und Flexibilität sehr performant.
In jedem Fall ist bei dem Vergleich zu beachten, dass Aufgrund der Absicht, die Suchoperationen vergleichbar zu halten, nur eine bestimmte Art der Suche zum Tragen kam. Für bestimmte Anwendungsfälle wird sich teilweise aufgrund der Anforderungen ergeben, welche der Methoden zum Einsatz kommt. Abschließend sei noch darauf hingewiesen, dass gut gewartete Datenbanken und (in Hard- und Software) gut eingestellte Datenbanksysteme einen signifikanten Einfluss auf die Performance von Suchoperationen haben. Hierbei ist zu bedenken, dass jeder Index Plattenplatz aber auch Rechenleistung für dessen Update belegt. Gleichzeitig wirkt ein compact auf eine Datenbank oft Wunder, denn auch eine fragmentierte Datenstruktur wirkt sich nachteilig aus.
15.8
Zusammenfassung
Wie so oft führen auch bei der Entscheidung, welche Suchoperation in Domino die Richtige ist, viele Wege zum Ziel – aber jeder hat seine eigenen Stärken und Schwächen und somit sein eigenes Einsatzgebiet. Die Suche über Ansichtenspalten mit getAllDocumentsByKey und getAllEntriesByKey ist flexibel genug für viele Anwendungsfälle. Da letztere Methode die Ergebnisse dazu noch in der Reihenfolge der Ansicht zurückgibt, ist sie oft der Ansatz der Wahl. Werden komplexere Suchanforderungen gestellt, so ist search in Database eine flexible Variante neben den reichlich vorhandenen FTSearch Methoden. Müssen verschlüsselte Felder oder Dateianhänge durchsucht werden, so ist FTSearch die richtige Wahl. Keine andere der vorgestellten Techniken bietet diese Möglichkeit. Gerade die Volltextindizierung von Dateianhängen ist in vielen Datenbanksystemen eine komplexe Aufgabenstellung, die in Domino als integrierte API-Funktion nicht nur leicht zu handhaben ist, sondern auch flexible und komfortable Suchvarianten für den Benutzer bietet.
Teil 3
Domino-Enterprise-Anwendungen Domino-Enterprise-Anwendungen haben eine lange Tradition. Von der ersten Stunde an war immer die Integration in große Unternehmenszusammenhänge eine Stärke und ein Ziel von Domino. Mit dem Aufkommen von Java und später J2EE hat Lotus diese Entwicklung erkannt und unterstützt und bereits früh zunächst Java als wichtige API-Sprache unterstützt. Früh wurde auch ein Servlet Container in Domino integriert. Mit der Version 6 sollte Domino ursprünglich einen eigenen JSP-Container erhalten und zur vollen J2EE-Konformität ausgebaut werden. Hierfür wurden die gut ausgebauten TagLib-Bibliotheken Domtags und Domutils entwickelt und liegen Domino seit der Version 6 bei. Allerdings wurde kein eigener JSP-Container für Domino entwickelt, sondern Domino die Möglichkeit gegeben, über einen WebSphere Application Server JSPs zu verarbeiten. WebSphere Application Server WAS liegt inzwischen in der Version 6 vor und unterstützt den J2EE V1.4-Standard. Die Entscheidung, Domino und Application Server zu trennen, hat den großen Vorteil, dass über verschiedene Schnittstellen und Ansätze nun sehr einfach auch andere J2EE-Server zum Einsatz kommen können, um die Domtags Tag Library zu verarbeiten. Die Domtags Tag Library basiert auf dem JSP 1.1 und Servlet 2.2 Standard, so dass (nahezu) jeder J2EE-konforme JSP- bzw. Servlet-Container diese TagLib betreiben kann. Darüber hinaus steht natürlich die Möglichkeit offen, eine Anwendung mit den klassischen Designpattern eines Model View Controllers umzusetzten. Zwar können hier keine Enterprise Java Beans zum Einsatz kommen, aber die bereits in Kapitel 4 und insbesondere in Kapitel 5 beschriebenen Techniken ermöglichen es, aus jeder Java-Anwendung alle Domino-Objekte, wie Datenbanken, Ansichten und Dokumente, zu laden, anzuzeigen oder zu verändern. Klassische J2EE-Java-Anwendungen in der Verbindung mit den Standard-Domino-Java-APIs sind eine wichtige und richtige Möglichkeit, Domino-Unternehmensdaten zu integrieren, und sollten mindestens gleichwertig neben dem Einsatz der Domtags Tag Library für die Entwicklung in Betracht gezogen werden. Sie erlauben die Beibehaltung vorhandener Java-Infrastrukturen und -Designpattern. Vor allem Webanwendungen liegen im Focus der Überlegungen in den folgenden Kapiteln, wobei natürlich nicht vergessen werden soll, dass auch nicht-Web-basierte Anwendungen in einer Multitier-Anwendung auf einem Application Server betrieben werden können. Deren Entwicklung ist jedoch ebenso einfach wie geradlinig über die bereits besprochenen Domino-Java-APIs möglich.
Damit ein Application Server mit Domino nahtlos zusammenarbeitet, sind verschiedene Voraussetzungen zu erfüllen. Neben der Integration und Zusammenarbeit beider Systeme sind Fragen der Infrastruktur, eines eindeutigen Einstiegspunktes und der übergreifenden Authentifizierung zu klären. Neben diesen Themen wird sich der dritte Teil dieses Buches allgemein mit der Projektentwicklung von Domino-Enterprise-Anwendungen und deren Qualitätssicherung auseinandersetzen.
16
16 J2EE Infrastruktur
In diesem Kapitel: Integration von Domino und J2EE Servern Domino und JSP Domino als primärer Server J2EE Server als primärer Server Domino und J2EE Server gemeinsam Single Sign On
614
16 J2EE Infrastruktur
J2EE Application Server gibt es viele am Markt und so vielfältig sind auch die Möglichkeiten, Domino mit diesen Servern zu integrieren. Während die hierbei gewählten Ansätze unterschiedlich sind, werden folgende Themen zu betrachten sein: 1
Domino um JSP-Fähigkeit erweitern Der wohl wichtigste Beweggrund, einen Application Server und Domino miteinander zu integrieren, ist der Wunsch, JSP-Seiten mit Domino-Inhalten zu verarbeiten. Grundsätzlich werden hier zwei Ansätze verfolgt: Entweder wird über ein DSAPI Plugin im HTTP-Server von Domino eine Redirection auf einen dahinter stehenden JSP-Container ermöglicht, so dass Anfragen an JSP-Dokumente, die an Domino gerichtet werden, von diesem an den Anwendungsserver weitergeleitet dort verarbeitet und das Ergebnis wiederum vom Domino-HTTP-Server angezeigt wird. Die zweite Möglichkeit sieht vor, dass ein eigenständiger vorgelagerter HTTPServer die Anfragen auf verschiedene Systeme verteilt: generische Domino-Anfragen an Domino und JSP- oder Servletanfragen an einen Application Server.
2
Application Server um Domino-Fähigkeiten erweitern So wie JSP-Seiten über die Domtags Tag Library und Servlets über die NotesFactory auf Domino zugreiffen können, so kann auch eine klassische J2EE-Anwendung mit Domino zusammenarbeiten. Schließlicht darf nicht vergessen werden, dass die Model-Komponente eines MVC Patterns ebenfalls über die NotesFactory mit Domino verbunden werden kann.
3
Austausch des Domino-Servlet-Containers Gründe für den Austausch des Domino-Servlet-Containers gibt es viele. Neben dem Vorteil einen Container zu nutzen, dessen primärer Zweck es ist, als robuster Servlet Container zu dienen, wird die Clusteringfähigkeit verbessert. Vor allem Umgebungen, in denen bereits derartige Systeme im Einsatz sind, werden hiervon profitieren, da vorhandene Infrastruktur, Administrationskenntnisse und vorhandene Überwachungs- und Loggingmechanismen benutzt werden können. Der Servletcontainer wird mit den gleichen Mechanismen ausgetauscht wie die JSP-Fähigkeit hergestellt wird: Entweder werden Servletanfragen von Domino über einen DSAPI-Filter an den externen Container weitergegeben oder ein vorgelagerter HTTP-Server verteilt die Anfragen an das jeweilige System.
4
SSO – Anwendungsserver und Domino akzeptieren ein Login gegenseitig Arbeiten der Application Server und Domino zusammen, soll den Anwendern eine möglichst einfache und transparente Umgebung präsentiert werden. Hierfür kann es erwünscht sein, dass ein Login, das auf Domino oder dem Application Server eingegeben wurde, auch auf dem jeweils anderen erkannt und akzeptiert wird.
16 J2EE Infrastruktur
615
Für die Ansätze, bei denen Domino der primäre Server ist und ein DSAPI Redirector Filter zum Einsatz kommt, erübrigt sich diese Frage. Domino ist der alleinige Einstiegspunkt und wird ausschließlich für das Login verantwortlich sein. Bei den übrigen Ansätzen gilt es ein gemeinsames Token zu finden, das beide Systeme akzeptieren oder das zumindest die Möglichkeit gibt, anhand des Tokens das Login gegen den anderen Server zu verifizieren und dann zu akzeptieren. Als Produkt desselben Herstellers bietet WebSphere eine generische Möglichkeit, über LTPA und ein entsprechendes LTPA Token ein SSO herzustellen (s. Kap. 5.3.5.1). Für JBoss und somit für Tomcat stehen kommerzielle Produkte104 zur Verfügung, über die ein SSO ermöglicht werden kann. Alternativ kann ein SSO auch recht leicht selbst implementiert werden, indem die Domino-SSO-Infrastruktur mit LTPA Token verwendet wird (s.u.). 5
Domino in anderen Webserver HTTP Stack einbinden (Domino-Webserver austauschen) Neben der Möglichkeit, für die oben beschriebenen Integrationsszenarien HTTP-Anfragen an Domino und Application Server zu verteilen, kann der Austausch des HTTP Stacks von Domino mehrere Vorteile haben. Insbesondere die Verwendung des Apache HTTP Server, als Quasistandard, bietet sich an. Ein primärer HTTP Server bietet z.B. verbesserte Möglichkeiten der Lastverteilung. Zusätzlich kann aus einer Vielzahl von Modulen für URL-Rewriting, Proxiing und vieles andere ausgewählt werden. Die Administration, Überwachung, Stabilität und Sicherheit eines eigenständigen Webservers verbessern Verfügbarkeit und Wartbarkeit des Systems und nicht zuletzt können viele vorhandene Quasistandards, wie PHP oder Perl und darauf basierende „out of the box“Anwendungen einfach eingesetzt werden. Zur Verfügung stehen hier zwei grundsätzliche Techniken. Zum einen stehen für die wichtigsten Application Server Plugins für Apache (und auch z.B. für IIS) zur Verfügung. Gleichzeitig besteht immer die Möglichkeit, über einen so genannten Reverse Proxy Anfragen, die für Domino oder WebSphere bestimmt sind, an den jeweiligen HTTP-Task des jeweiligen Servers weiterzugeben, wobei dann allerdings zwei HTTP Stacks vorgehalten werden müssen.
6
Infrastruktur / Netzwerk Die beschriebenen Ansätze zur Integration von Domino und Application Server können in verschiedenen Infrastrukturen aufgebaut werden. In den Fällen, in denen nicht die Forderung besteht, dass Domino und Application Server auf derselben physischen Maschine betrieben werden, kann der primäre Server (Domino, HTTP) in einer DMZ platziert werden und der sekundäre Server (Domino und / oder Application Server) hinter einer weiteren Firewall in einer gesicherten Anwendungszone platziert werden. So wird neben der erhöhten Sicherheit eine gute Lastverteilung ermöglicht und nicht zuletzt wird jeder der einzelnen Server die Aufgabe übernehmen können, für die er am besten geeignet ist.
104
z.B. Domino/JBoss SSO. KEY Enterprise Solutions, www.keysolutions.com
616
16 J2EE Infrastruktur
Im Folgenden gilt es nun, die verschiedenen Ansätze der Integration vorzustellen, ihre Einsatzgebiete aufzuzeigen, welche Möglichkeiten des J2EE-Standards genutzt werden und wie ein Single Sign On umgesetzt werden kann.
16.1
Application Server
J2EE Application Server gibt es inzwischen viele am Markt. Für die Domino-Integration kommen vor allem JBoss, Tomcat und WebSphere in Frage, für die generische Plugins passend zu verschiedenen Anwendungsfällen zur Verfügung stehen. Werden keine generischen Domino-Web-Funktionen benötigt, kann Domino als Datastore eingesetzt und durch den Einsatz der Domino-Java-APIs oder der Domtags Tag Library selbstverständlich jeder (!) J2EE Application Server eingesetzt werden, solange dieser Application Server oder ein vorgelagerter HTTP Server der primäre Server ist. Erst wenn Domino und der Application Server parallel nebeneinander oder Domino als primärer Server betrieben werden soll, werden SSO und die Integration durch Plugins benötigt. Durch die Aufteilung der verschiedenen Aufgaben auf verschiedene Server – z.B. Apache für HTTP, Domino für NSF-Datenbanken und Tomcat für Servlets und JSPs – übernimmt jeder Server seine Spezialaufgabe. Werden die einzelnen Application Server auf verschiedene physische Hardwareplattformen verteilt, so wird nicht nur die Administration vereinfacht, sondern auch die Skalierbarkeit erheblich verbessert. Gleichzeitig können einzelne Server des Verbundes hinter eine zweite Firewall aus der DMZ in ein Application Environment verlagert und so besser gesichert werden.
16.2
Integrationsszenarien
Integrationsszenarien können anhand verschiedener DSAPI Plugins für Domino und verschiedener so-Module für Apache aufgebaut werden. Jedes Szenario hat unterschiedliche Funktionalitäten und Einsatzgebiete. 1
Domino als primärer Server
2
Application Server als primärer Server
3
Webserver als primärer Server – Domino und Application Server nebeneinander
16.2.1
Domino als primärer Server
Wird Domino als primärer Server eingesetzt, wird der HTTP-Task von Domino verwendet, um WebSeiten auszuliefern. Anfragen an Domino-Datenbanken werden wie
16.2.1 Domino als primärer Server
617
gewohnt ausgeliefert. Anfragen an JSP-Seiten und Servlets werden an einen dahinter liegenden Application Server durchgereicht, der die Seiten rendert und das HTMLErgebnis an Domino zurückgibt, der die Seite wieder ausliefert. Der Application Server ist hierbei nicht von außen erreichbar und lediglich für die Auswertung der Servlets und JSP-Seiten zuständig. Single Sign On muss für keines der im Folgenden aufgezeigten Szenarien aufgesetzt werden, da Domino der alleinige Einstiegspunkt und somit auch für die Verwaltung der Benutzerrechte zuständig ist. Domino WAS Plugin (Bestandteil von WAS) WebSphere Application Server kann über einen DSAPI-Filter (Bestandteil der WebSphere Installation) in den Domino-HTTP-Task eingebunden werden. Nach der Installation des Plugins über ein Installationsprogramm wird dieses konfiguriert und festgelegt, welcher WAS-Server über das Plugin angesprochen werden soll. Zusätzlich muss dieses DSAPI105-Modul in der HTTP-Konfiguration von Domino angemeldet werden. Im Serverdokument des Dominoservers wird im Abschnitt „Internet Protocols“ -> „HTTP“ -> „DSAPI“ der vollständige Pfad des zuvor installierten Moduls angegeben, wobei hier auch mehrere Einträge zulässig sind, falls bereits andere DSAPI-Module installiert waren. Des Weiteren muss noch der Servlet Container in Domino auf den neuen, externen Sevletcontainer des Application Servers umgeschaltet werden. Hierfür wird ebenfalls im Serverdokument im Abschnitt „Internet Protocolls“ -> „Domino Web Engine“ im Feld „Java Servlet Support“ der Wert „Third Party Servlet Support“ eingestellt. Ein großer Vorteil des DSAPI-Moduls ist seine Fähigkeit, auch auf entfernten Servern mit WAS zu kommunizieren. Abb. 16-1 zeigt ein typisches Szenario, bei dem der WAS-Server hinter einer zusätzlichen Firewall betrieben wird. Diese Infrastruktur bietet ein hohes Maß an Sicherheit für den WAS-Server. Diese zweite Firewall ist optional und es ist ebenfalls möglich und für kleine Installationen sinnvoll, den WAS-Server auf derselben physischen Maschine wie Domino zu betreiben. Der Vollständigkeit halber sei erwähnt, dass nun natürlich in den Servlets und JSP-Seiten wiederum über die NotesFactory eine Domino-Session erhalten werden kann, die mit den Domino-Objekten über das Domino-Java-API kommuniziert (s. Kap. 16.2.2). Die Aufgabenverteilung stellt sich wie folgt dar: •
105
Domino • Darstellung und Auslieferung statischer HTML-Seiten • Domino-Masken, -Seiten, -Ansichten, die über den Browser gerendert werden • Webagenten • Weiterleitung von Anfragen nach Servlets und JSP an den Application Server DSAPI ist eine Schnittstelle über die der HTTP Stack des Domino-Servers über ein API erweitert werden kann.
618
16 J2EE Infrastruktur *.html *.nsf *.jsp /servlet/* Web User
Domino WAS DSAPI Plugin
Internet
Domino Server (*.html, *.nsf)
Web User *.html *.nsf *.jsp /servlet/*
WebSphere Server (*.jsp, /servlet/*)
Domino TomCat Redirector
Web User Internet
Domino (*.html, *.nsf) und TomCat (*.jsp, /servlet/*) auf dem selben physischen Server
Web User
*.html (*.nsf) (*.jsp) /servlet/* Web User
WebService Request
WebService
Internet
Domino (*.html, *.nsf, /servlet/*), Tomcat/JBoss (*.html, *.jsp, /servlet/*), WebSphere (*.html, *.jsp, /servlet/*)
Web User
JBoss/Tomcat, WebSphere Domino
Domino DSAPI Plugin
Firewall
Firewall optional Serveranwendungen müssen nicht zwingend auf physisch getrennten Maschinen liegen
Abb. 16-1 Integrationsszenarien mit Domino als primärem Server
• • •
•
Auslieferung des durch JSP und Servlet gerenderten HTMLs im Domino-HTTP-Task Bereitstellung der Domino-Datenbank und aller Domino-Objekte Kein SSO notwendig – die Servlets / JSPs laufen innerhalb des Sicherheitskontextes von Domino.
Application Server • Auswertung von JSP • Auswertung von Servlets • Weitergabe des gerenderten HTMLs an den Domino-HTTP-Task • Optional Einsatz weiterer Techniken (SQL-Datenbank, MVC)
16.2.2 Application Server als primärer Server
619
Domino Tomcat Redirector Der Domino Tomcat Redirector (Abb. 16-1 ) geht denselben Weg wie WebSphere und bietet einen DSAPI-Filter an, der die jakarta-tomcat-connectors [Tomcat, Connectors] nutzt, um die Verbindung herzustellen. Eine Kurzanleitung findet sich im Internet [Tomcat, DSAPI Kurzinfo]. Domino wird ebenso konfiguriert wie oben beschrieben, auch die Aufgabenverteilung bleibt unverändert. Im Unterschied zu dem WAS Plugin muss sich der Tomcat-Server auf derselben physischen Maschine befinden, auf der auch Domino eingesetzt wird. Domino und WebServices Ein interessanter Ansatz ist es, Domino über WebServices (Abb. 16-1 ) Anfragen mit einen zurückgelagerten Server zu verbinden. Dies ist natürlich unabhängig vom eingesetzten Application Server, selbst Domino oder z.B. ein LotusScript Agent, der den WebService ausführt, könnte dies sein. Zwar ist auch hier die Infrastruktur flexibel, da WebServices über HTTP und somit beliebig remote ausgeführt werden, allerdings muss man sich im Klaren sein, dass dieses Szenario weniger für Hochverfügbarkeitsumgebungen geeignet ist. Denn anstatt dass wie oben über eine Anwendungsintegration die Anfragen über ein API direkt weitergegeben werden, muss für die Abfrage des WebServices ein weiterer Socket für die benötigte HTTP-Verbindung geöffnet und die Anfrage dann in einer neuen HTTP Session abgewickelt werden, ohne dass hierfür noch ein Connection Pooling oder sonstige Performanceoptimierungen vorgenommen werden könnten.
16.2.2
Application Server als primärer Server
Den Application Server als primären Server einzusetzen ist die einfachste und flexibelste Herangehensweise. Da hier keine Plugins zum Einsatz kommen, kann insbesondere bei diesem Konzept jeder beliebige Application Server eingesetzt werden, sofern er nur Java versteht, denn die Verbindung wird über die in Kapitel 4 und 5 eingeführten Techniken der Domino-Session aufgebaut. Je nach gewünschter Infrastruktur und erwarteter Last, kann entweder die lokale oder Remote-Session im DominoJava-API eingesetzt werden. Auch in diesem Szenario stellt sich die Frage nach Single Sign On (zunächst) nicht, da der Application Server der einzige Einstiegspunkt ist, zumindest solange die Verbindung zu Domino über einen Funktionsuser hergestellt wird. Sollen individuelle Sessions mit deren jeweiligen individuellen Benutzerkennungen hergestellt werden, so wird eine Synchronisation zwischen den in WebSphere erforderlichen HTTP Logins und den Domino-Benutzernamen notwendig. In diesem Fall können die in Kapitel 16.3 beschriebenen Techniken eingesetzt werden.
620
16 J2EE Infrastruktur *.html *.jsp /servlet/*
Domino wird lokal per Domino Java API erreicht
Web User Internet
Tomcat/JBoss, WebSphere (*.html, *.jsp, /servlet/*) und Domino (per lokale Domino Session) auf dem selben physischen Server
Web User
*.html *.jsp /servlet/*
Domino wird remote per DIIOP und Domino Java API erreicht
Web User Internet
Tomcat/JBoss, WebSphere (*.html, *.jsp, /servlet/*)
Web User
Domino Server (DIIOP) Optional: Domino Java API
*.html *.nsf *.jsp /servlet/*
WAS Plugin für Apache
Web User
mod_proxy
Internet
Apache (*.html)
Web User
WebSphere (*.jsp, /servlet/*)
mod_jk, mod_proxy mod_proxy_ajp
IBM HTTP Domino Plugin (iSeries) Domino Server (*.nsf)
Optional: Domino Java API Apache Module
JBoss/Tomcat (*.jsp, /servlet/*)
Abb. 16-2 Integrationsszenarien mit WAS oder Apache als primärem Server
Application Server und lokale Domino-Session Wird Domino auf derselben physischen Maschine (Abb. 16-2 ), wie der Application Server installiert, kann mit einer lokalen Session gearbeitet werden. In Bezug auf Performance und Konfiguration ist diese Vorgehensweise vorteilhaft. Es müssen lediglich die Laufzeitbibliotheken von Domino im Path (s. Kap. 4.2) angemeldet sein und eine Domino-Session kann aufgebaut werden. Der Application Server selbst wird wie gewohnt eingesetzt. Der Bezug und die Auswertung von Domino-Daten kann über sämtliche im Application Server zur Verfügung stehenden Techniken, sei es eine JSP-Seite, ein Servlet oder die Model-Componente einer MVC-Umgebung, erfolgen. Sollen in einer JSP-Seite die Domtags (s.u.) zum Einsatz kommen, muss DIIOP auf dem Domino-Server aktiviert sein. Die Aufgabenverteilung stellt sich wie folgt dar:
16.2.2 Application Server als primärer Server
621
•
Application Server • Darstellung und Auslieferung statischer HTML-Seiten • Auswertung und Darstellung von JSP-Seiten • Auswertung und Darstellung von Servlets • Kommunikation mit dem Domino-Server über die lokale Domino-Session in Servlets, JSPs oder MVC. Alle Domino-Objekte stehen entsprechend der Domino-Java-API zur Verfügung. • Fungiert als vollwertiger J2EE-Server • SSO notwendig, sobald die Verbindung nicht mit einem Funktionsuser, sondern mit individuellen Benutzerkennungen stattfinden soll.
•
Domino • Bereitstellung der Domino-Java-Objekte • Domino-Datenbank
Application Server und Remote-Domino-Session Der Einsatz von DIIOP ermöglicht neben dem Einsatz der Domtags Tag Library, den Domino-Server auf einer eigenständigen Hardware (Abb. 16-2 ) zu betreiben und ihn optional hinter eine zweite Firewall zu verlagern. Die Aufgabenverteilung stellt sich dann wie folgt dar: •
Application Server • Darstellung und Auslieferung statischer HTML-Seiten • Auswertung und Darstellung von JSP-Seiten • Auswertung und Darstellung von Servlets • Kommunikation mit dem Domino-Server über die Remote-DominoSession und DIIOP in Servlets, JSPs oder MVC. Alle Domino-Objekte stehen entsprechend der Domino-Java-API zur Verfügung. • Kommunikation mit dem Domino-Server über Domtags und DIIOP • Fungiert als vollwertiger J2EE-Server • SSO notwendig, sobald die Verbindung nicht mit einem Funktionsuser, sondern mit individuellen Benutzerkennungen stattfinden soll.
•
Domino • Bereitstellung der Domino-Java-Objekte • Bereitstellung von DIIOP • Domino-Datenbank
622
16 J2EE Infrastruktur
16.2.3
Domino und Application Server
Der Betrieb von Domino und einem Application Server nebeneinander ist das klassische Szenario für große Unternehmensinstallationen. Ein Webserver – wegen der Verfügbarkeit entsprechender Plugins in der Regel Apache oder der IBM-HTTP-Server, der ein Derivat des Apache-Servers ist – übernimmt hierbei die erste Linie. Sämtliche Anfragen werden vom Webserver entgegengenommen und entweder selbst verarbeitet, sofern es sich um statische Inhalte handelt oder an den zuständigen Server weitergeleitet (Abb. 16-2 ). Dies kann ein Application Server oder Domino sein. Je nach verwendetem Application Server stehen unterschiedliche Plugins und Techniken zur Verfügung, wobei für jeden anzusprechenden Server (Tomcat, Websphere, Domino) ein eigenes Plugin notwendig sein wird. Die Infrastruktur lässt sich mit dieser Herangehensweise mit höchster Flexibilität den Bedürfnissen anpassen. Lediglich der (Apache-) WebServer muss sich in der DMZ befinden, da er den HTTP Thread vorhält. Die Kommunikation zwischen Apache einerseits und Domino, WAS, Tomcat oder JBoss andrerseits erfolgt über das TCP/IP Protokoll, so dass die Application Server und Domino auch hinter einer weiteren Firewall positioniert werden können. WebSphere Apache Plugin Für WebSphere steht ein Apache Plugin zur Verfügung, der die Einbindung sehr einfach macht. Hierbei handelt es sich um ein Apache-Modul. Während der Installation (WAS 6.x) werden Apache und WebSphere bereits konfiguriert. Im Administrator von WebSphere muss lediglich das so generierte Plugin auf die WAS Server verteilt und die J2EE-Enterprise-Anwendungen dem Webserver zugeordnet werden. WebSphere und Apache oder IBM HTTP können sich auf derselben oder auf verschiedenen physischen Maschinen befinden. Domino und Apache Domino steht auf vielen Plattformen zur Verfügung, für die nicht alle ein Plugin106 für Apache ausgeliefert wird. Alternativ kann die Anbindung sehr einfach über das Apache-Modul mod_proxy [Apache, mod_proxy] erfolgen. Dieses Modul wird als so genannter Reverse Proxy eingesetzt. Nach entsprechender Konfiguration wird jede Anfrage an den Webserver, die einem bestimmten URL-Pattern entspricht – in der Regel wird dies ein bestimmtes Verzeichnis sein –, nicht von diesem selbst beantwortet, sondern an eine zugeordnete URL weitergeleitet. Das hierbei zurückgegebene Ergebnis wird ausgeliefert. Dieses Verfahren ist robust und für den produktiven Einsatz geeignet, hat aber zur Folge, dass zwei HTTP Stacks – einer von Domino und einer von Apache – betrieben werden, was nur in Konfigurationen eine Rolle spielen wird, bei der Domino und Apache auf derselben Hardware betrieben werden. Domino liefert weiterhin wie 106
Domino 6 for iSeries Apache plug-in [Redbook, Domino WAS].
16.2.3 Domino und Application Server
623
gewohnt mit seinem internen HTTP-Task Domino-Seiten aus, abgerufen werden diese Seiten aber von Apache und nicht vom Benutzer. Dieser stellt seine Anfrage an Apache, der sie weiterleitet. Für Domino und den Benutzer ist dieses Verfahren völlig transparent. Lediglich der Einsatz absoluter URLs in Domino ist zu überdenken. Wird Domino nicht unter dem Domänennamen107 betrieben, der nach außen gültig ist, so könnten absolute URLs generiert werden, die aus der Sicht des Benutzers nicht erreichbar sind. Das Modul mod_proxy leitet die Anfragen (im Betrieb als Reverse Proxy) lediglich weiter und filtert oder verändert den zurückerhaltenen Inhalt nicht. Gleiches gilt für die Verwendung eines abweichenden Ports (nicht 80). Folglich müssen absolute URLs entweder so generiert werden, dass hierbei die Domain anwendung findet, unter der Apache betrieben wird, oder es müssen relative Links zum Einsatz kommen, wie folgendes Beispiel zeigt: Ein Benutzer gibt die URL http://www.djbuch.de/domino/djbuch.nsf/Page? OpenPage in seinen Browser ein. Mod_proxy ist so konfiguriert, dass Anfragen, die dem Pattern /domino/* entsprechen, an den internen Server http://10.128.1.2/domino/* weitergeleitet werden. Apache wird eine Anfrage an diese URL des Domino-Servers absetzen und dessen Ergebnis zurückerhalten. Diese Seite liefert Apache an den Benutzer aus. Enthält die Seite „Page“ der Datenbank /domino/djbuch.nsf eine absolute URL in der Art , so wird dieser Link dem Benutzer präsentiert. Die IP 10.128.1.2 ist jedoch aus Sicht des Benutzers nicht erreichbar. Domino generiert normalerweise aber keine absoluten URLs. Wenn bei der Programmierung ebenfalls darauf verzichtet wird, sind keine Schwierigkeiten zu erwarten. Tomcat/JBoss und Apache Der Tomcat JSP-Container und der Apache-Webserver kommen aus demselben Haus und werden keine Kommunikationsschwierigkeiten haben. Die Module mod_jk, mod_proxy oder mod_proxy_ajp verfolgen leicht unterschiedliche Ansätze und können alle verwendet werden, wenn Anfragen von Apache an Tomcat weitergeleitet werden sollen [Tomcat, Apache]. mod_jk und mod_proxy_ajp unterstützen hierbei auch Sticky-Sessions durch die im Cluster Load Balancing erreicht wird, dass eine auf einem der Cluster Server einmal begonnene Session auf diesem zu Ende geführt wird. Kombination der Verfahren Dieses und das Verfahren aus Kapitel 16.2.2 kann kombiniert werden. Das ist wichtig, denn bisher ermöglicht Apache nur, generische Domino-Anfragen abzuholen und 107
Domino verwendet den Eintrag „Basics“ -> „fully qualified Internet host name“ im Serverdokument, wenn absolute URLs generiert werden müssen.
624
16 J2EE Infrastruktur
auszuliefern. Servlets und JSP-Seiten, die mit Domino-Objekten interagieren, werden unter Nutzung der lokalen oder Remote-Domino-Session wie in Kapitel 16.2.2 verwendet. Diese Option ist in Abb. 16-2 mit „Optional: Domino-Java-API“ gekennzeichnet. Aufgabenverteilung •
Webserver (Apache oder IBM HTTP) • Darstellung und Auslieferung statischer HTML-Seiten • Weiterleitung von Anfragen nach Servlets und JSP an den Application Server • Auslieferung des durch JSP und Servlet gerenderten HTMLs • Weiterleitung von Anfragen nach Domino-Masken, -Seiten, -Ansichten, also allen Domino-Designelementen, die über den Browser gerendert werden können, an den Domino-HTTP-Task • Auslieferung des durch den Domino-HTTP-Task gerenderten HTML • SSO notwendig
•
Application Server • Auswertung von JSP-Seiten • Auswertung von Servlets • Weitergabe des gerenderten HTMLs an den Apache HTTP-Task • Fungiert als vollwertiger J2EE-Server • Kommunikation mit dem Domino-Server über die lokale oder RemoteDomino-Session und DIIOP in Servlets, JSPs oder MVC. Alle DominoObjekte stehen entsprechend der Domino-Java-API zur Verfügung. • Kommunikation mit dem Domino-Server über Domtags und DIIOP • Einsatz weiterer Techniken (SQL-Datenbank, MVC) • SSO notwendig
•
Domino • Bereitstellung der Domino-Java-Objekte • Bereitstellung von DIIOP • Bereitstellung von Domino-Masken, -Seiten, -Ansichten, -Webagenten, die über den Browser gerendert werden, über den Domino-HTTP-Task (zur Abholung durch Apache) • Bereitstellung der Domino-Datenbank und aller Domino-Objekte • SSO notwendig
16.3
Single Sign On
Single Sign On wird immer dann erforderlich, wenn ein Benutzer sich bereits an einem der beteiligten Server angemeldet hat und diese Anmeldung an einem weiteren
16.3 Single Sign On
625
Server akzeptiert werden soll. Aus technischer Sicht ist dies nicht zwingend erforderlich, aber für den Benutzer schwer einzusehen, wenn er sein Login mehrfach eingeben muss. Die Fähigkeit von J2EE-Servern, LDAP-Verzeichnisse auszulesen, reicht hierfür nicht aus. Zwar kann Domino dem J2EE-Server sich als LDAP-Directory anbieten, aber auch wenn dann auf beiden Servern dieselben Logindaten gültig sind, müsste der Benutzer diese dennoch zweimal eingeben. Die Notwendigkeit des SSO ergibt sich für die Szenarien aus Kapitel 16.2.2 und 16.2.3. Da in allen Fällen dieser Szenarien die Authentifizierung gegen Domino entweder über einen direkten HTTP-Zugriff oder über eine DIIOP-Verbindung erfolgt oder eine lokale Internetsession (Kapitel 5.3.7) verwendet werden kann, wird Domino in allen Fällen Benutzername und HTTP-Passwort aus dem Personendokument verwenden. Für diese Fälle können drei Lösungen zum Einsatz kommen. Alle haben gemeinsam, dass sie diesen Benutzernamen und HTTP-Passwort des Domino-Personendokuments zur Authentifizierung benutzen, d.h. Domino verwaltet und hostet die Logindaten und ist für die Anerkennung oder Ablehnung eines Logins zuständig. Zwei Möglichkeiten sind generische, kommerzielle Lösungen und stehen für WebSphere (s. Kap. 5.3.5.1) bzw. JBoss (s. Kap. 16, Einleitung) zur Verfügung. Als dritter Weg besteht die Möglichkeit, SSO selbst unter Ausnutzung des Domino-LTPA-Tokens zu implementieren. Diese Möglichkeit steht für jeden Application Server zur Verfügung, der einen Servlet Container ab Version 2.3 und Filter unterstützt. Auf dem Application Server wird hierfür überprüft, ob es ein LTPA Token (als Cookie) gibt. Falls ja, wird versucht, anhand dieses Cookies und der Methode createSession(String host, HttpServletRequest req) in NotesFactory (s. Kap. 5.3.5 und 5.3.7) eine Domino-Session zu erhalten. Besitzt ein Benutzer ein gültiges LTPA Token, so befindet sich dieses im HttpServletRequest req und die Domino-Session wird aufgebaut. Falls dies möglich ist, wird ein neu zu erstellendes Authentifizierungsobjekt in der javax.servlet.http.HttpSession des Application Servers mit den aus der Session gewonnenen Benutzerinformationen gespeichert. Dieses Authentifizierungsobjekt dient dem Application Server dazu, die Benutzerinformationen in der HttpSession verfügbar zu halten. Falls nein oder falls kein Cookie vorlag, wird auf eine Loginseite umgeleitet und anhand der dort eingegebenen Logininformationen nun über NotesFactory. createSession(hostString, userString, passwortString) versucht, eine Domino-Session zu erhalten. Falls dies gelingt, wird wiederum ein Authentifizierungsobjekt neu erstellt und das Cookie mit dem LTPA Token an den Benutzer weitergegeben. Auf dem Domino-Server wird entweder dieses vom Domino-Server stammende LTPA Token akzeptiert oder über die normalen Domino-Mechanismen neu vergeben. Eine Implementierung dieses Verfahrens für Tomcat mit GNU General Public License findet sich unter [Domino Tomcat SSO].
626
16 J2EE Infrastruktur
16.4
Zusammenfassung
Domino-Enterprise-Anwendungen und Application Server des J2EE-Standards können gut zusammenarbeiten. Damit dies erfolgreich umgesetzt werden kann, sind Mechanismen und eine Infrastruktur vorzuhalten, die die Aufgaben HTTP-Auslieferung, Domino-Funktionen, JSP- und Servletverarbeitung auf die einzelnen Komponenten verteilt. Als einfachste und transparenteste Methode wurde die Möglichkeit vorgestellt, Domino als primären Server einzusetzen. Generische Domino- und HTTP-Anfragen werden weiterhin von Domino erledigt. Servlet- und JSP-Anfragen werden an einen Application Server über ein DSAPI Plugin weitergegeben. Auch die Servlets und JSPs können wiederum über das Domino-Java-API mit Domino kommunizieren, indem eine lokale oder Remote-Domino-Session über die NotesFactory bezogen wird. Einen J2EE-Server mit Domino-Fähigkeiten auszustatten ist so einfach wie geradeaus umzusetzen. In diesem Kapitel wurde aufgezeigt, wie über die lokale und Remote-Domino-Session Anfragen aus Servlets und JSP-Dokumenten heraus gestellt werden. Das Gesamtsystem bleibt der primäre J2EE-Server. Domino-Daten und Funktionen werden über die Domino-Java-API geladen. Die Enterprise-Lösung des Systems besteht darin, alle Aufgaben auf jeweils den Server zu verteilen, der die jeweilige Aufgabe am besten bewältigt. HTTP-Anfragen werden am Eingang von Apache erledigt. JSP- oder Servlet-Anfragen an einen J2EE-Server weitergeleitet. Benötigt dieser Domino-Daten kommt das zweite Szenario zum Einsatz. Werden generische Domino-HTTP-Funktionen gewünscht, kann über das mod_proxy-Modul Domino in Apache eingebunden werden. Zur Abrundung wurde das SSO erörtert, durch das erreicht werden kann, dass insbesondere im letzten Szenario alle beteiligten Server gegenseitig ihre Logins akzeptieren und ein Benutzer eine nahtlose Infrastruktur erlebt. Nachdem nun die theoretischen Grundlagen vorliegen, soll im nächsten Kapitel der praktische Einsatz erläutert werden.
17
17 J2EE @ Domino
In diesem Kapitel: Domino in J2EE-Anwendungen Domino und JSP Die Domino Domtags Tag Library Die Domino Domutil Tag Library Domino und das Model View Controller Design Pattern Domino und XML
628
17 J2EE @ Domino
Nachdem nun die Voraussetzungen für eine Integration von Domino und Application Server geschaffen sind, kann Domino erfolgreich im J2EE-Umfeld eingesetzt werden. Ziel dieses Kapitels ist es, nicht Konzept oder Techniken des J2EE-Standards zu erläutern, sondern vielmehr die Verwendung von Domino in diesem Kontext zu ermöglichen. Hierbei steht vor allem der Einsatz von JSPs und der Domtags Tag Library im Vordergrund.
17.1
Servlets
Servlets können im Servlet Container von Domino oder des Application Servers betrieben werden. In beiden Fällen müssen sie die Domino-Session über die NotesFactory entweder als lokale oder Remote-Session aufbauen. Servlets sind ein Kernthema der Domino-Java-Programmierung. Auf sie wird ausführlich in Kapitel 5.2ff. eingegangen.
17.2
JSP für Domino
JSP-Seiten können über Tag Libraries oder über eingebetteten Java-Code funktional erweitert werden. Folgerichtig besteht auch für die Domino-Anbindung diese Möglichkeit. Listing 17-1 zeigt die einfachste Verwendung einer Domino-Anwendung im JSP. Der Java-Code eines einfachen Java-Agenten wurde einfach als scriptlet in die Seite eingebunden. Das funktioniert wie jede Domino-Java-Anwendung. Voraussetzung ist zunächst, dass die Bibliothek Notes.jar108 sich im ClassPath des WebModuls befindet. Dann können die Domino-Bibliotheken importiert werden. Die Domino-Session wird normal über die NotesFactory bezogen – im Beispiel eine lokale Session mit statischem NotesThread. Auch alle übrigen Funktionen werden wie gewohnt bedient, bis hin zum Recycling und dem Schließen der Verbindung. Aber: Dieser Code ist weder „best practice“ noch gut wartbar, er zeigt lediglich die nahtlose Integration. In einem späteren Schritt wird noch aufgezeigt, wie dieser Ansatz wesentlich verbessert werden kann, zunächst soll jedoch der elegante Weg über die Verwendung der Domtags aufgezeigt werden. Gerade Programmierer, die sich in der Welt Dominos bereits gut auskennen, werden feststellen, dass diese Tags simultan zu den DominoDesignelementen diese Stück für Stück abbilden. Alle Objekte finden eine Entsprechung in Tags und Sie werden feststellen, dass viele Optionen, die sonst in den Kontextmenüs des Designers eingestellt werden, ihren Weg in die Parameter der Domtags gefunden haben. Die Domtags sind ein schneller und effizienter Schritt zur Verwendung der Domino-Objekte in JSP-Seiten. Allerdings darf nicht übersehen werden, dass sie unter 108
Notes.jar für Domino R7, NCSO.jar für Remote-Verbindungen in R6 und Notes.jar für lokale Verbindungen in R6
17.2 JSP für Domino
629
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <TITLE>DJBUCH DEMO
Listing 17-1 Einfache aber unschöne Domino-JSP-Einbindung bestimmten Bedingungen auch zu wenig flexibel sein können. Domtags sind eng verwandt mit den Domino-Designelementen. Werden, insbesondere bei Masken, deren Funktionen benötigt, sind sie in jedem Fall vorteilhaft. Muss von der bekannten Mimik, insbesondere von View, Form und Document, abgewichen werden, die diese haben, wenn sie von Domino für HTTP gerendert werden, sind Domtags eher hinderlich. Gerade ein Framework, das das Model-View-Controller Pattern einsetzt, wie Struts, kennt seine eigenen Anforderungen und erfordert die Verwendung der generischen Domino-Java-API. Weiterhin ist zu beachten, dass viele der Domtags mit JavaScript arbeiten, genauer gesagt ihr Ergebnis als JavaScript rendern bzw. zumindest Teilfunktionen auf JavaScript angewiesen sind, was nicht immer erwünscht sein mag. Domtags sind aber in jedem Fall der erste Schritt und perfekt für einen Einstieg.
630
17 J2EE @ Domino
17.3
Domtags – Installation und Verwendung
Für den WebSphere Studio Application Developer steht ein Plugin, das Lotus Domino Toolkit for WebSphere, zur Verfügung, mit dem eine Domino-Datenbankverbindung hergestellt und die Designelemente der Datenbank angezeigt (Abb. 17-1) und per Drag-And-Drop in die JSP-Seiten gezogen werden können. Das erleichtert die Arbeit, insbesondere, da dessen Installation auch gleich die nötigen Einstellungen für WebProjekte vorbereitet, die Domtags einsetzen sollen. Aber auch manuell kann die Domtags Tag Library leicht eingebunden werden. Vorausgesetzt, die Notes.jar befindet sich bereits im ClassPath des Projekts, muss diesem nun noch die Jar-Datei domtags.jar in den WEB-INF/ lib-Ordner des WebProjektes gelegt werden. Des Weiteren wird die Tag-Definitionsdatei domtags.tld und optional domutil.tld benötigt, die in den WEBINF-Ordner des Projekts gelegt werden muss. Die Taglib muss in web.xml, dem Abb. 17-1 Lotus Domino Toolkit for Web-Implementierungsdeskriptor des WebSphere Webprojektes, angemeldet werden. Hierfür wird folgendes XML Tag in diese Datei eingefügt: domtags.tld /WEB-INF/domtags.tld domutil.tld /WEB-INF/domutil.tld
Nun kann die Programmierung mit den Domtags beginnen. Alle Domtags haben Standardparameter, die in (fast) allen Tags verwendet werden. Für diese Parameter gibt es wiederum die Möglichkeit in der Datei web.xml Standards vorzugeben, so dass für den Fall, diese Parameter werden im Tag nicht angegeben, diese Standards verwendet werden. Vor allem für User, Passwort und zu verwendende Datenbank ist dies sehr hilfreich, wobei das Passwort nur über den Session-Kontext des Servlets über die web.xml definiert werden sollte, um nicht versehentlich als POST- oder GET-Parameter sichtbar zu werden (für den Page Scope).
17.3 Domtags – Installation und Verwendung
631
Defaultwerte können auf verschiedenste Weise gesetzt werden. Benannt werden sie in einer hierarchischen Form: • •
lotus.domino.preset.109 110 lotus.domino.default.
Einige Attribute werden von allen oder zumindest vielen Tags verwendet und dementsprechend häufig benötigt. Für diese Werte können wie unten beschrieben Default-Werte festgelegt werden111: •
•
• •
•
•
•
•
• 109 110 111
dbname Name der zu öffnenden Domino-Datenbank. Hierbei wird der relative Pfad zum Dataverzeichnis einschließlich eventueller Ordner benannt, z.B. „myDbs/ djbuch.nsf“ dbserver Domino-Server, auf dem sich die Datenbank befindet, die geöffnet werden soll, sofern sie nicht auf dem lokalen Server liegt. Default ist „Local“. viewname Name oder Aliasname einer Ansicht. host Name des DIIOP-Hosts, über den die Verbindung zu Domino hergestellt werden soll. Wird er nicht angegeben, wird versucht eine lokale Domino-Session aufzubauen. user Name des Benutzers, der sich für die Domino-Session anmeldet, in einfacher („Name/Organisation“) oder kanonischer („CN=Name/O=Organisation“) Form. password Passwort des Users, der die Session aufbaut. Sollte nicht für den Scope „page“ definiert werden, um nicht als URL-Parameter übermittelt zu werden. onfailure Legt fest, wie mit einem Fehlerfall umgegangen werden soll. Gilt onfailure= "inline" wird eine Exception auf der aktuellen Seite im Browser an der Stelle, an der der Fehler aufgetreten ist, angezeigt. Für onfailure="exception" wird die Exception durchgereicht und führt in der Regel zu einer Error 500-Serverfehlermeldung, sofern kein Exceptionhandling im Container eingestellt ist. locale Wert des locale-Strings, mit dem die Lokalisierung initialisiert wird, z.B. „fr-CH“. Per Default wird der vom Browser übermittelte Wert angenommen. timezone Für das Attribut „bundle“ gilt hier eine Ausnahme. Der Variablenname lautet lotus.domino.default.msgbundle bzw. lotus.domino.preset.msgbundle. Die Variablen lotus.domino.preset.* werden in der Rangfolge vor lotus.domino.default.* ausgewertet. Entnommen aus der Domino-Designerhilfe R7. Reihenfolge, Übersetzung und Ergänzungen durch den Autor.
632
•
• •
17 J2EE @ Domino Zeitzone (per Default des Servers) bei der Verarbeitung von DateTime-Werten. responseto Unique ID des übergeordneten Dokuments, zu dem ein neues Dokument Anwortdokument wird, sofern ein Dokument neu und als Antwortdokument über ein Form-Tag angelegt wird. unid Unique ID des Kontextdokuments (s. Kap. 4.10 und 5.1) bundle In WebAnwendungen kann in J2EE ein *.properties-File mit sprachenspezifischen Strings für lokalisierte Ausgaben definiert werden. In bundle wird dieser Name angegeben. Default ist „messages“.
Grundsätzlich können Attributwerte wie folgt festgelegt werden und werden hierbei auch in der im Folgenden gezeigten Rangfolge berücksichtigt: •
•
•
Explizit im Tag Attribute, die in einem Tag gültig sind, können direkt über dessen Definition angegeben werden, z.B.: <domino:view [...] dbname="djbuch/djbuch.nsf"> [...] Durch einen umschließenden Kontext-Tag (insbesondere domino:session oder domino:db) Die Tags domino:session und domino:db können für einen Codeblock auf
einer JSP-Seite einen Kontext definieren, in dem per Default auf die in diesen Tags definierte Session bzw. Database zurückgegriffen wird. Hierbei wird nicht nur das Tag-Attribut auf einen Defaultwert gesetzt, sondern später zur Laufzeit auch das eigentliche Session- bzw. Database-Objekt für sämtliche eingeschlossenen Tag-Objekte wiederverwendet (siehe unten). Im Servlet-Kontext Über den Servlet-Kontext können in der Datei web.xml Defaultwerte vorgegeben werden: <param-name>lotus.domino.preset.dbname <param-value>djbuch/djbuch.nsf
•
Diese Parameter können auch in scriptlets in Java per servletContext. getInitParameter() abgefragt werden. Werden die Parameter für lotus.domino.default. gesetzt, so werden diese in der Rangfolge erst nach den Werten ausgewertet, die im Page Context durch <domino:default> gesetzt wurden. Im Page-Kontext des Servlets
17.3 Domtags – Installation und Verwendung
633
Best Practice Legen Sie im web.xml die Default (nicht preset)-Parameter für die wichtigsten Variablen Ihrer Anwendung fest. Wann immer Sie diese nicht explizit angeben, werden Defaults folgendermaßen verwendet: <param-name>lotus.domino.default.dbname <param-value>djbuch/djbuch.nsf
Auf jeder JSP-Seite steht per Default das Java-Objekt pageContext zur Verfügung. Dort können Default-Variablen gesetzt werde. Dies ist entweder direkt über Java scriptlets per pageContext.setAttribute (String var, String value [, int scope]) oder über einen der Tags <domino:default> oder <domino:preset> möglich, z.B.: <domino:preset dbname="djbuch/djbuch.nsf" scope="session"/> Im Page-Kontext stehen die Gültigkeitsbereiche PageContext.APPLICATION_ SCOPE, PageContext.SESSION_SCOPE, PageContext.REQUEST_SCOPE, PageContext.PAGE_SCOPE zur Verfügung und werden sowohl für die
•
lotus.domino.preset-Werte, als auch für lotus.domino.default112 in dieser Reihenfolge ausgewertet. Liegen für einen beliebigen Scope preset-Werte vor, werden diese anstelle der default-Werte verwendet. Die lotus.domino.default-Werte werden noch (!) nach den POST- oder GET-Parametern (s.u.) ausgewertet. Als POST- oder GET-Parameter Beim Aufruf einer JSP-Seite kann über einen URL-Parameter oder über ein (hidden) Field ein entsprechender Wert übermittelt werden. URL-Encoding ist zu beachten.
•
112
http://www.djbuch.de/SSOLoginWeb/dominoView.jsp?dbname= djbuch%2Fdjbuch.nsf Über den in der Tagdefinition definierten Defaultwert In den Tagdefinitionen in der tld-Datei ist für einige Attribute ein Defaultwert festgelegt. Das für alle Domtags definierte Attribute onfailure hat zum Beispiel den Defaultwert „false“. Wird es also nicht explizit über einen der zuvor genannten Wege gesetzt, wird der Wert „false“ angenommen.
Entgegen der Domino 7-Designerhilfe.
634
17 J2EE @ Domino
Neben diesen Default-Attributen finden sich folgende optionale Attribute in den meisten Tags: •
•
• •
id Durch das Attribut wird der Name für die Java-Objekt-Variable festgelegt, die sich hinter einem Tag verbirgt. Wird das Attribut id zugewiesen, so kann unter diesem Namen im Java-Code auf der JSP-Seite auf das Objekt zurückgegriffen werden. Falls id nicht spezifiziert wird, stellt Domino dieses Objekt nicht zur Verfügung. debug Falls debug="true", gibt das Tag Debugmeldungen, insbesondere alle Attributwerte, an die Konsole aus. onfailure s.o. time Falls time="true", wird die Laufzeit eines Tags in der Konsole ausgegeben.
17.4
Domtags
Wie eingangs erwähnt orientieren sich die Domtags sehr nah an den Domino Designelementen. So ist es selbstverständlich, dass Tags für Database, View, Form und Document vorliegen. Da anders als im Notes Client der Benutzer noch keine Session hat, gibt es, wie im Java-API, ein Session-Tag. Mit einem domino:view-Tag wird festgelegt, welche Ansicht angezeigt werden soll und mit dem domino:viewloop-Tag werden die einzelnen ViewEntries iteriert. Mit dem domino:viewitem-Tag können dann in einer Viewloop Spaltenwerte angezeigt werden, die wiederum mit einem domino:formlink mit einer Maske verlinkt werden können, in der das ausgewählte Dokument angezeigt werden soll. Auf der durch domino:formlink verlinkten Seite kommt das domino:formTag zum Einsatz, der den Aufbau einer Seite erlaubt, wie sie entsteht, wenn DominoMasken generisch im Browser gerendert werden. Innerhalb des domino:form-Tags können mit domino:input-Tags Einoder Ausgabefelder definiert werden, die die einzelnen Items eines Dokuments anzeigen. Mit domino:validaterequired können serverseitig Validierungen durchgeführt werden (Abb. 17-2), die es dazu ermöglichen, auf einfache WeiAbb. 17-2 Serverseitige Validierung mit domino:validaterequired
17.4.1 domino:session
635
se entsprechende Hinweise neben den Eingabefeldern zu platzieren. Zusätzlich ist es sogar möglich die originären Feldvalidierungen der Maske zur Validierung einzusetzen. Mit domino:saveclosedoc oder domino:nextdoc stehen typische Maskenfunktionen zur Verfügung. Mit derartigen Tags können Dokumente erstellt, gelöscht, gespeichert und zwischen diesen navigiert werden. Mit den Tags domino:document, domino:item und domino:savenow können Doumente auch direkt manipuliert werden. Viele der Domtags unterliegen einer Hierarchie, die in einigen Fällen eingehalten werden muss (ein domino:viewloop-Tag macht nur innerhalb eines domino:view-Tags Sinn), zum Teil aber auch gezielt zur Optimierung genutzt werden kann. Dies gilt für die Tags domino:session und domino:db. Durch diese Tags werden eingeschlossene Tags gruppiert und im Kontext dieser Session bzw. Datenbank verwendet. <domino:session user="*webuser" duration="session"> <domino:db dbname="djbuch/djbuch.nsf"> <domino:view viewname="V_toysByID"> [...] <domino:view viewname="V_toysByName"> [...] <domino:view viewname="$users" dbname="names.nsf"> [...]
Listing 17-2 Hierarchie der Domtags
Ein kleines Beispiel zeigt Listing 17-2. Während alle Tags eine gemeinsame Session benutzen, werden die Ansichten V_toysByID und V_toysByName aus der Datenbank djbuch/djbuch.nsf geladen. Dagegen befindet sich die Ansicht $users in einer anderen Datenbank. Dieses domino:view-Tag gibt daher die gewünschte Datenbank names.nsf direkt als Attribut an. Da alle Tags die Attribute user, password dbname und optional dbserver haben, könnten alle Tags jeweils die Verbindung separat aufbauen. Dies sollte im Normalfall vermieden werden, damit Domino nicht für jedes einzelne Tag eine Session und ein Datenbankobjekt öffnen muss.
17.4.1
domino:session
Das Tag domino:session ist direkt aus der API-Funktion NotesFactory.createSession (host, user, password) abgeleitet. Wird host angegeben, so wird eine DIIOP-Session geöffnet, andernfalls eine lokale. User und Passwort werden wie gewohnt eingesetzt. Einziger Unterschied ist der Sonderfall user="*webuser" mit password="".
636
17 J2EE @ Domino
Dieser Sonderfall kann nur eingesetzt werden, falls SSO aktiviert ist. Es wird dann der aktuell am Browser angemeldete user auch als User für die Session verwendet. In der aktuellen Version der Domtags befindet sich ein neues Attribut duration="page|request|session". Dieses Attribut ermöglicht die Verwendung persistenter Sessions über mehrere JSP-Seiten hinweg. Hierfür kommen serverseitig so genannnte Cached-Sesssions zum Einsatz. Für duration="page" wird die Session lediglich über alle Tags der Seite, für duration="request" für den jeweiligen Request und schließlich für duration="session" für die gesamte Dauer der javax.servlet.http.HttpSession verwendet. Ausführlich wird dieser Aspekt in [Reske, Domino JSP]113 diskutiert. Das domino:session-Tag wird entsprechend Listing 17-3 definiert. Die Para<domino:session id="Name der Java-Objektvariable" user="Benutzername der Session" password="Passwort" host="DIIOP-Servername" duration="page|request|session" sessionmgrkey="Schlüsselname des SessionManagers" debug="false|true" onfailure="exception|inline" time="false|true" >
Listing 17-3 domino:session
meter user, password und host sind optional und bestimmen die Logindaten für die aufzubauende Session. In der Regel sollten hierfür entsprechend Kapitel 17.3 Defaultwerte definiert sein. Die Parameter debug, onfailure und time sind Attribute, die in vielen Domtags vorkommen und ebenfalls in Kapitel 17.3 erläutert wurden. Eine Besonderheit stellen die Parameter duration und sessionmgrkey dar. Mit diesen Parametern lässt sich das eingangs erwähnte Session Sharing konfigurieren. Soll die Session über mehrere Anwendungen geteilt werden, so wird ein Session Manager benötigt, der über die web.xml konfiguriert wird (siehe [Reske, Domino JSP]). Wird das Attribut id mit einem Wert versehen, so steht unter diesem Namen auf der JSP-Seite ein lotus.domino.Session-Objekt zur Verfügung. Das domino:session-Tag wird immer dann eingesetzt, wenn mehere Domtags auf einer Seite gebündelt auf eine Session zurückgreifen sollen bzw. wenn das Session Sharing zum Einsatz kommen soll. In das domino:session-Tag eingeschlossene Tags müssen keines der Attribute host, user oder password angeben. Anstatt anhand dieser Parameter eine eigene Domino-Session zu öffnen, wird die in domino:session definierte verwendet. 113
Thomas Reske geht in diesem RedPaper auch auf das von Lotus in der Designerhilfe nicht dokumentierte Attribut dojkey ein. Es ermöglicht ein Object Pooling, über das DominoObjekte in der HttpSession gehalten werden können. Neugierige können sich die domtags.tld in einem XML Tool oder Texteditor ansehen. Dort sind alle verfügbaren Attribute und deren Definition und Validierung erkennbar.
17.4.2 domino:db 17.4.2
637
domino:db
Das Tag domino:db hat eine sehr ähnliche Funktion wie domino:session. Es dient ebenfalls dazu, mehrere Tags zu bündeln, und ermöglicht es, für alle eingeschlossenen Objekte ein gemeinsames Datenbankobjekt verfügbar zu halten. Dementsprechend muss die Datenbank nicht für jedes einzelne Tag geöffnet werden. Darüber hinaus benötigen diese Tags keines der Attribute host, user, password, dbserver, dbname, sondern es wird die Datenbank (und zugehörige Session) verwendet, die in domino:db definiert ist. Listing 17-4 zeigt die Definition von domino:db. Neben den bekannten Attribu<domino:db id="Name der Java-Objektvariable" user="Benutzername der Session" password="Passwort" host="DIIOP-Servername" dbname="Name der zu öffnenden Datenbank" dbserver="Servername, auf dem sich diese DB befindet." debug="false|true" onfailure="exception|inline" time="false|true" >
Listing 17-4 domino:db
ten finden sich nur zwei zusätzliche: dbname und dbserver. Das Attribut dbname gibt den Dateinamen der Datenbank einschließlich relativen Pfades zum Dataverzeichnis an. dbserver ist optional und gibt einen optional entfernten Domino-Server an, von dem die Datenbank geladen wird. Gemeint ist hier der Domino-Name des Servers, nicht sein Hostname, in der einfachen oder kanonischen Form (z.B. „CN= Java/O=DJBUCH“). Alle übrigen Parameter arbeiten, wie für domino:session beschrieben.
17.4.3
domino:view
Eine Domino-Ansicht findet ihre Repräsentation im Tag domino:view. Dieses ist zunächst nur dafür zuständig, das View-Objekt zu laden. Iteration und Anzeige von Spaltenwerten übernehmen unter anderem die Tags domino:viewloop, domino:docloop und domino:viewitem. Der Definition in Listing 17-5 können die zulässigen Werte für die einzelnen Attribute entnommen werden. Diese werden wie folgt verwendet: • •
viewname Name der zu öffnenden Ansicht max Maximale Anzahl zurückgegebener ViewEntries
638
17 J2EE @ Domino <domino:view id="Name der Java-Objektvariable" user="Benutzername der Session" password="Passwort" host="DIIOP-Servername" dbname="Name der zu öffnenden Datenbank" dbserver="Servername, auf dem sich diese DB befindet." viewname="Name der zu öffnenden Ansicht" max="Maximale Anzahl zurückgegebener ViewEntries" toponly="false|true" depth="Maximale Hierarchietiefe in kategorisierten Ansichten" category="Einschränkung einer kateogrisierten Ansicht auf eine best. Kategorie" enableselect="false|true" key="Einschränkung des Views auf Key - vgl. getAllDocumentsByKey(...)" keyseparator="semicolon|comma|blank|colon|;|,| |:" keytype="string|number|date|daterange" keyexact="false|true" ftsearch="Einschränkung des Views auf FTSearch - vgl. FTSearch(...)" defaultname="Defaultname für viewname, falls in best. dynamischen Situationen dieser nicht verfügbar ist" debug="false|true" onfailure="exception|inline" time="false|true" >
Listing 17-5 domino:view
•
•
•
•
toponly In kategorisierten Ansichten (s. Kap. 10.5) kann die Anzeige auf die oberste Ebene von Dokumenten mit toponly="true" eingeschränkt werden. depth Ist toponly nicht true, so kann die Anzeige in einem kategorisierten View auf eine maximale Hierarchieebene eingeschränkt werden. Dabei wird nach Hauptdokumenten (0), Antwortdokumenten (1) und Antwort-auf-Antwortdokumenten (2) unterschieden. category In kategorisierten Ansichten besteht die Möglichkeit eine bestimmte Kategorie (der obersten Ebene) auszuwählen und die Anzeige auf die Dokumente dieser Ansicht einzuschränken. Dies ist eine schnelle und effiziente Möglichkeit, Dokumente einer bestimmten Eigenschaft zu selektieren und anzuzeigen, ohne Suchmechanismen bemühen zu müssen. enableselect Ansichten im Domino-Designer kennen diese Eigenschaft als „Web Access“ -> „Allow selection of documents“. Diese Eigenschaft wird durch enableselect= "true" aktiviert. Domino generiert dann über dieses JSP-Tag eine Checkbox, die im Browser angeklickt werden kann. Zusätzlich muss in einem domino:viewloop das Tag <domino:selectentry /> gesetzt werden. Hierdurch wird die Stelle gekennzeichnet, an der die Checkbox angezeigt wird.
17.4.3 domino:view
•
•
•
•
•
•
•
639
Selektiert ein Benutzer derart Zeilen in einer Ansichten-Ausgabe, werden diese in einer Collection zur Verfügung gestellt, die durch das Tag domino:selectedloop ausgelesen werden kann. key Ein Schlüssel, der mit der ersten (sortierten) Spalte der Ansicht verglichen wird. Nur übereinstimmende Dokumente werden zurückgegeben (vgl. Kap. 10.4, 10.8 und 15ff – getAllDocumentsByKey). keyseparator Sollen über das Attribut key mehrere Werte übergeben werden, die dann mit mehreren Spalten verglichen werden sollen, werden die einzelnen Werte in key durch einen keyseparator getrennt (vgl. Kap. 10.4, 10.8 und 15ff – getAllDocumentsByKey). keytype Der Schlüssel key kann verschiedene Datentypen annehmen. Da im Tag nur Strings übergeben werden, muss zur korrekten Konvertierung ein Typ angegeben werden. Zulässig sind string, number, date, daterange. Ein DateRange wird im Key mit zwei durch ein Minuszeichen getrennten Datumsangaben angezeigt. Enthält key mehrere Werte, muss auch keytype mehrere durch Komma getrennte Werte enthalten. keyexact Für keyexact="true" müssen key und Spaltenwert exakt übereinstimmen, für keyexact="false" genügt es, wenn der key dem Anfang des Spaltenwertes entspricht. Gesucht wird in jedem Fall ohne Unterscheidung nach Groß- und Kleinschreibung. ftsearch In ftsearch kann eine Suchquery wie für FTSearch in View angegeben werden. Die Funktionsweise ist dieselbe (s. Kap. 15.5). defaultname Angegeben wird ein Defaultwert für das Attribut viewname, falls dieser in bestimmten dynamischen Situationen nicht verfügbar ist. Die übrigen Parameter entsprechen denen aus domino:db bzw. domino:session.
Listing 17-6 zeigt typische Elemente einer Domino-Ansicht, die über domino:view geladen wird. Nachdem die Tag Library geladen , die Session und Datenbank geöffnet wurde, kann die Ansicht geladen werden. Das domino:view-Tag beschreibt hierbei lediglich den Namen der Ansicht, die übrigen Attribute werden
640
17 J2EE @ Domino
[...] <domino:session id="applicationSession" user="*webuser" host="www.djbuch.de" duration="session"> <domino:db dbserver="CN=Java/O=DJBUCH" dbname="djbuch/djbuch.nsf">
<domino:view viewname="V_toysByID" enableselect="true"> | ID | Name |
<domino:viewloop> <domino:selectentry /> | <domino:viewitem col="1" /> | <domino:formlink href="dominoForm.jsp"> <domino:viewitem col="2" />
|
<domino:view viewname="V_toysByID"> <domino:docloop id="name" start="1" direction="Ascending"> |
Listing 17-6 Beispielanwendung domino:view
nicht benötigt, da die Datenbank über das domino:db-Tag verfügbar ist. Damit Zeileneinträge später im Browser selektiert werden können wurde das Attribut enableselect="true" gesetzt114. Das Tag domino:view referenziert nur das View-Objekt. Die Daten werden über eine domino:viewloopoder domino:docloop-Schleife ausgelesen. 114
Die Auswertung der selektierten Dokumente wird nicht im Beispiel gezeigt und muss über den Einsatz von domino:action, domino:ifserverevent und domino:selectedloop umgesetzt werden. domino:action definiert einen Aktionsbutton, der die Aktion startet, die sich auf alle markierten Elemente beziehen soll, domino:ifserverevent fängt die so ausgelöste Aktion ab und bietet den Container für die Weiterverarbeitung. domino:selectedloop schließlich bietet eine Schleife an, die analog zu domino:viewloop arbeitet, aber nur über die zuvor selektierten Dokumente iteriert.
17.4.4 domino:document
641
Die enableselect-Eigenschaft wird über das domino:selectentry-Tag zum Leben erweckt. Dieses Tag beschreibt die Stelle, an der die CheckBox erscheinen soll (s. Abb. 17-3). Spaltenwerte der Ansicht können über domino:viewitem unter Angabe der Spaltennummer geladen werden. Sollen diese mit einer JSP-Seite, die sich auf eine Maske (diese muss als DominoDesignelement im Designer vorliegen) Abb. 17-3 Ausgabe des domino:view-Beibezieht, angezeigt werden, so können spiels beliebige Textversatzstücke mit automatischen Links versehen werden. Das Tag domino:formlink übernimmt dies. Nicht immer genügt das strikte Raster, das duch eine Maske und deren Funktionen definiert ist, den Ansprüchen. Daher können ohne Weiteres auch eigene Links generiert werden. Sobald im domino:docloop-Tag das Attribut id gesetzt ist, kann unter dem dort verwendeten Wert über Java ein gleichnamiges Document-Objekt gefunden werden. Die Verwendung versteht sich von selbst. Es handelt sich um ein gewohntes Document aus der Domino-Java-API. Zwei verschiedene Ansätze haben im Beispiel Ansichten durchschritten und Zeileneinträge verlinkt. Sowohl der Ansatz, die Ansicht zu durchschreiten, war unterschiedlich (domino:viewloop und domino:docloop) als auch die Methode, die verwendet wurde, um Links zu generieren (domino:formlink und ein scriptlet unter Verwendung des Document-Objekts). Allerdings sei darauf hingewiesen, dass der Einsatz der für die viewloop generierten formlinks sich nicht von den über das Document erzeugten Links unterscheidet (bis auf den Namen der Zielseite). Die Darstellung der Daten in einer Maske (formlink) erfolgt erst in der Zielseite des domino:formlink unter Zuhilfenahme des domino:form-Tags.
17.4.4
domino:document
Bevor auf die Maske und domino:form eingegangen wird, zunächst ein kurzer Abstecher zum Dokument. Über das Tag domino:document können Dokumente direkt manipuliert werden, sofern nur die UniversalID bekannt ist. Die im vorigen Beispiel Listing 17-6 generierten Links wurden mit der jeweiligen UniversalID der Dokumente ausgestattet. So kann eine JSP-Seite, die das domino:document-Tag einsetzt, über den URL-Parameter unid, in dem diese UniversalID übermittelt wurde, einzelne Dokumente manipulieren. Listing 17-7 enthält ein kleines Beispiel, anhand dessen der Einsatz des domino:document, aber auch einiger weiterer Besonderheiten gezeigt werden kann. Die Datenbank wird hier nicht innerhalb eines domino:session-Tags geöffnet. Schließlich wird nur ein einziges Tag verwendet, das eine Session benötigte, so genügt das einzelne domino:db-Tag. Dieses enthält nur ein einziges Attribut id="thisDB"
642
17 J2EE @ Domino
<domino:db id="thisDB"> <domino:document id="thisDoc" unid="">
<domino:item name="F_name" />
<domino:savenow/>
Listing 17-7 Beispielanwendung domino:document
. Dies ist möglich, da in der web.xml-Konfiguration für die Webanwendung alle übrigen Parameter als Defaultwerte definiert wurden. Die Konsequenz ist selbstverständlich, dass diese Seite in einem ganz anderen Benutzerkontext läuft als das vorige Beispiel, bei dem der angemeldete Web-Benutzer (über SSO und user="*webuser") auch die Domino-Session der JSP-Seite geöffnet hat – ein Risiko und eine Chance. So reizvoll die Möglichkeit ist, unterschiedliche Tags mit unterschiedlichen Benutzerkennungen zu betreiben, so hoch ist das Risiko, dass hier Sicherheitslöcher aufgerissen werden. Am besten, ein einmal eingeschlagenes Konzept (angemeldeter Benutzer ist auch Domino-Benutzer versus Anmeldung über Funktionsuser) wird konsequent durchgehalten. Das domino:document-Tag erhält über den Parameter thisUnid eine Universal ID, die zuvor entweder per HTTP GET oder POST übermittelt und in die Variable thisUnid geladen wurde. Dem Beispiel fehlt noch eine vernünftige Fehlerüberprüfung, ob die unid überhaupt geliefert wurde. Alternativ kann – aufmerksame Leser des vorigen Kapitels werden dies längst entdeckt haben – das Attribut unid auch ganz weggelassen werden und das Tag als < domino:document id="thisDoc"> oder gar als <domino:document> codiert werden. Da wurde von den Entwicklern bei Lotus wirklich mitgedacht, denn das unid-Attribut gehört zu den Attributen, für die ein Defaultwert gesetzt werden darf. Das ergibt im Allgemeinen natürlich keinen Sinn, aber im speziellen Kontext einer Seite, die ein domino:document-Tag verwenden soll, sehr viel. Die in der URL über-
17.4.4 domino:document
643
mittelte ID könnte dann für den Seitenkontext über <domino:default unid="< %=thisUnid%>" scope="page"/> als Default gesetzt werden, der dann für alle domino:document-Tags gültig ist. Die Items des Dokuments können über das domino:item-Tag oder natürlich über ein scriptlet und die API-Methoden, wenn zuvor das Attribut id spezifiziert wurde, angezeigt bzw. manipuliert werden. Das Beispiel will noch mehr. Wurde das Document erst einmal geladen, wird es in vielen Fällen vorkommen, dass eine zeitnahe Aktualisierung nicht zwingend notwendig ist. Dann darf das Document gecached werden und kann z.B. in der HttpSession zwischengespeichert werden. Ist das Document erst im Cache , wird es nicht erneut geladen, sondern der Cache angezeigt . Auch hier nochmals der Hinweis, dass das DJCacheDocument keine Benutzerrechte speichert und <domino:document id="Name der Java-Objektvariable" user="Benutzername der Session" password="Passwort" host="DIIOP-Servername" dbname="Name der zu öffnenden Datenbank" dbserver="Servername, auf dem sich diese DB befindet." unid="Universal id des zu ladenden / ändernden Dok" responseto="Universal ID eines opt. Mutterdokuments" schema="Maske, die (neuen) Dokumenten zugeordnet wird." debug="false|true" onfailure="exception|inline" time="false|true" >
Listing 17-8 domino:document
lediglich ein POJO darstellt – in der HttpSession des Benutzers ist dies aber kein Problem. Änderungen am Document können über das Tag domino:savenow , das entspricht einem save () in Document, gespeichert werden. Das Tag domino:document kennt drei neue Attribute (s. Listing 17-8), die noch nicht für domino:db oder domino:session erläutert wurden: •
•
•
unid Die Universal ID (s. Kap. 2.2.1ff. und 7.1.4.1) eines zu öffnenden Documents. Falls dieses Attribut weggelassen wird (und nicht über einen Defaultwert spezifiziert ist, wird ein neues Document-Objekt erzeugt. responseto Wird ein neues Dokument erstellt und soll es ein Antwortdokument werden, so muss über responseto die Universal ID des übergeordneten Dokuments angegeben werden (s. Kap. 7.3) schema
644
17 J2EE @ Domino Domino-sieht vor, dass jedem Dokument eine Maske zugeordnet ist. Diese wird über schema festgelegt. Beim Neuanlegen eines Dokuments wird der Wert aus schema in das Item „Form“ übernommen. Beim Speichern werden eventuelle globale, in der Maske definierten Validierungen ausgeführt.
17.4.5
domino:form
Mit Hilfe des domino:document-Tags können Dokumente geladen und manipuliert werden. Soll dies mit dem Schema einer Maske erfolgen, so kommt domino:form zum Einsatz. Direkt vergleichbar mit dem Designelement „Form“ des Domino Designers (Kapitel 3.2), wird über domino:form eine Maske geladen, in deren Kontext das Dokument dargestellt und bearbeitet werden kann. Einige wichtige Funktionen zeigt das Beispiel in Listing 17-9. Zur Abwechslung wird die Datenbank unter kompletter Nennung von Benutzer und Passwort geladen . Das domino:form-Tag benötigt einen internen Namen, um im Page-Kontext referenziert werden zu können. Zusätzlich kann über schema die korrespondierende Domino-Maske angegeben werden. Dieses Attribut ist kein Pflichtfeld, denn <domino:db id="thisDB" dbserver="CN=Java/O=DJBUCH" dbname="djbuch/djbuch.nsf" user="CN=Administrator/O=DJBUCH" password="geheim" host="www.djbuch.de">
<domino:form name="FO_TOYFORM" schema="FO_TOYFORM" editmode="edit" validhref="dominoView.jsp" viewname="V_toysByID"> Name | <domino:input name="F_Name" /> <domino:validaterequired name="F_Name" message="(*)" style="color=RED;" /> |
ID | <domino:input name="F_ID" datatype="number" /> <domino:validaterequired name="F_ID" message="(*)" style="color=RED;" /> |
zurück | <domino:saveclosedoc>Speichern <domino:nextdoc text="weiter" /> |
Listing 17-9 Beispielanwendung domino:form
17.4.5 domino:form
645
dieses JSP-Tag ist nicht zwingend mit einer Domino-Maske verknüpft. Wird das Attribut angegeben, so können nicht nur in der Maske definierte Validierungen und Feldberechnungen ausgeführt werden, sondern neu erstellte Dokumente erhalten eventuelle Vorbelegungen von Feldern. Neue Dokumente werden mit diesem Namen im Item „Form“ gespeichert. Wird das Attribut schema weggelassen, so bleibt dieses Item leer, was zu Problemen beim Einsatz des Notes Clients führen kann, denn dort ist dieses Item notwendig um eine Maske laden zu können, die benötigt wird, um ein Dokument darzustellen. Soll wie im Beispiel das Dokument editiert werden, so muss das Attribut editmode="edit" gesetzt werden. Soll es gespeichert werden, gibt validhref die Target URL an, die nach erfolgreicher Speicherung und Validierung angesteuert werden soll. Ein praktisches Feature ist die Möglichkeit, eine automatische Navigation zwischen Dokumenten aufzubauen. Damit Domino weiß, zwischen welchen Dokumenten hin- und hergesprungen werden soll, wird eine Ansicht mit dem Attribut viewname angegeben, navigiert wird dann mit dem Tag domino:nextdoc oder domino:prevdoc, wobei natürlich „normale“ Links weiterhin Anwendung finden . Einzelne Items werden über domino:input (optional zur Bearbeitung) angezeigt, entscheidend ist das Attribut editmode. Nur für editmode="edit" werden HTML-Input-Felder generiert, für editmode="display" (default) werden lediglich die Itemwerte dargestellt und schließlich für editmode="delete" wird das korrespondierende Dokument gelöscht. Validierungen, ein wichtiges Thema in Masken, werden mit domino:validaterequired angefordert. Diese werden serverseitig ausgeführt und geben im Fehlerfall den Fehlertext des Attributes message aus. Zur abschließenden Verarbeitung eines Dokumentes stehen eine Reihe von Standardfunktionen zur Verfügung. domino:saveclosedoc validiert ein Dokument bzw. die geänderten Eingaben, speichert es, sofern valide und springt an die in validhref (im domino:form-Tag) angegebene Adresse. Die Attribute des Tags domino:form können in drei Gruppen aufgeteilt werden. Die Attribute id, user, password, host, dbname, dbserver, debug, onfailure und time werden, wie zuvor für domino:db beschrieben, verwendet. Die zweite Gruppe von Tags entsprechen Standard HTML-Attributen, werden eins zu eins durchgeschleift und in dem -Tag im HTML abgebildet. Sie sind in Listing 17-10 als „Standard HTML-Parameter“ gekennzeichnet. Die dritte Gruppe besteht aus Attributen, die individuell für domino:form sind: • • •
unid Beschreibt die Universal ID des zu ladenden Dokuments. name Interner Name der Maske. editmode Bestimmt, ob das korrespondierende Dokument bearbeitbar (="edit") sein oder nur dargestellt (="display") werden soll. Für editmode="delete" wird das Dokument gelöscht.
646
17 J2EE @ Domino <domino:form id="Name der Java-Objektvariable" user="Benutzername der Session" password="Passwort" host="DIIOP-Servername" dbname="Name der zu öffnenden Datenbank" dbserver="Servername, auf dem sich diese DB befindet." unid="Universal id des zu ladenden / ändernden Dokuments" name="Interner Name des domino:form-Objekts" editmode="display|edit|delete" schema="Name der optional korrespondierenden Domino Maske" responseto="false|true" //Achtung - vgl. domino:document clientvalidate="false|true" viewname="Ansicht anhand der zw. Dok. navigiert werden soll" validhref="Target URL, nach Validierung und Speichern" counter="Name des 'formcounter' Parameters" debug="false|true" onfailure="exception|inline" time="false|true" accept="Standard HTML-Parameter" accept_charset="Standard HTML-Parameter" dir="Standard HTML-Parameter" lang="Standard HTML-Parameter" language="Standard HTML-Parameter" style="Standard HTML-Parameter" target="Standard HTML-Parameter" title="Standard HTML-Parameter" onclick="Standard HTML-Parameter" ondblclick="Standard HTML-Parameter" ondragstart="Standard HTML-Parameter" onhelp="Standard HTML-Parameter" onkeydown="Standard HTML-Parameter" onkeypress="Standard HTML-Parameter" onkeyup="Standard HTML-Parameter" onmousedown="Standard HTML-Parameter" onmousemove="Standard HTML-Parameter" onmouseout="Standard HTML-Parameter" onmouseover="Standard HTML-Parameter" onmouseup="Standard HTML-Parameter" onreset="Standard HTML-Parameter" onselectstart="Standard HTML-Parameter" onsubmit="Standard HTML-Parameter" >
Listing 17-10 domino:form
•
schema Name der korrespondierenden Domino-Maske. Bestimmt, welcher Eintrag im Item „Form“ bei neuen Dokumenten gespeichert werden soll. Bestimmt, welche Feldvalidierungen und -überarbeitungen beim Speichern (abhängig von und definiert in ebendieser Maske) durchgeführt werden sollen.
17.4.6 domino:ftsearch •
•
•
• •
647
responseto Im Gegensatz zu domino:document enthält das Attribut responseto keine unid, sondern einen der Werte „true“ oder „false“. Für den Wert „true“ und den Fall eines neu angelegten Dokuments bestimmt der Wert in unid die Mutter eines so erzeugten Antwortdokuments. clientvalidate Für clientvalidate="true" werden zusätzlich clientseitige Validierungen durchgeführt. viewname Wird eine ansichtenbasierte Steuerung gewünscht (über die Tags domino:nextdoc und domino:prevdoc), so muss hier ein Ansichtenname angegeben werden. validhref Zieladresse nach Speicher- und Validierungsoperationen. counter Da Maskenoperationen immer auf ein und derselben URL abgewickelt werden, kann es vorkommen, dass die Seiten von Browsern zwischengespeichert und nicht neu geladen werden. Um Konfusionen zu vermeiden, wird jede URL durch einen hochgezählten counter individualisiert. Das Attribut counter bestimmt den Namen dieses URL-Parameters.
17.4.6
domino:ftsearch
Volltextsuchen werden ausführlich in Kapitel 15.5 erläutert und können neben den dort beschriebenen Methoden mit Hilfe des Domtag domino:ftsearch (Listing 17-11) auf einer Datenbank durchgeführt werden. Dieses Tag entspricht der Suche FTSearch in Database und gibt, sofern id angegeben wurde, eine DocumentCollection zurück. Das Ergebnis kann über die domino:docloop durchschritten werden. Query, max, sort und options entsprechen den korrespondierenden Parametern in db.FTSearch, wobei im Einzelnen gilt: • • •
•
query Eine FT Volltext Suchanfrage. Die genaue Syntags wird in Kapitel 15.5 erläutert. max Die Maximalzahl der zurückzugebenden Treffer. sort Als Sortieroptionen stehen nur die beiden Möglichkeiten zur Verfügung, nach Erstelldatum auf- oder absteigend zu sortieren. Die Sortieroption „score“ – nach Relevanz – ist Default. options Optional kann eine Fuzzysearch oder die Verwendung von Wortstämmen bei der Suche aktiviert werden.
648
17 J2EE @ Domino <domino:ftsearch id="Name der Java-Objektvariable" user="Benutzername der Session" password="Passwort" host="DIIOP-Servername" dbname="Name der zu öffnenden und durchsuchenden Datenbank" dbserver="Servername, auf dem sich diese DB befindet." query="Such Query" max="Maximalzahl der zurückgegebenen Dokumente" sort="dateascending|datedescending|score" options="fuzzy|stems" debug="false|true" onfailure="exception|inline" time="false|true" >
Listing 17-11 domino:ftsearch
17.4.7
domino:runagent
Aus JSP-Seiten kann mit Hilfe des domino:runagent-Tags ein serverbasierter Agent gestartet werden. Hierbei handelt sich es aber lediglich um die Möglichkeit, diesen zu starten. Eine Interaktion oder die Übergabe von Streamdata an den Browser ist nicht möglich. Die einzigen individuellen Attribute (Listing 17-12) dieses Tags sind name und unid. Während name den Namen des zu startenden Agenten angibt, kann im Kontext des domino:runagent-Tags durch unid dem Agenten ein Parameterdokument übergeben werden (s. Kap. 4.15). <domino:runagent user="Benutzername der Session" password="Passwort" host="DIIOP-Servername" dbname="Name der zu öffnenden Datenbank" dbserver="Servername, auf dem sich diese DB befindet." unid="Universal ID des optionalen Parameterdokuments" name="Name des zu startenden Backend Agenten" debug="false|true" onfailure="exception|inline" time="false|true" >
Listing 17-12 domino:runagent
17.4.8
Weitere Domtags
Nahezu jedes Domino-Objekt kennt eine Repräsentation als Domtag. Eine Übersicht finden Sie im Anhang in Tabelle Anhang-1. Dort sind alle mit Stand Domino 7 ver-
17.4.8 Weitere Domtags [...] [...] <domutil:if> <domutil:condition><domino:item name="F_bool" /> F_bool nicht vorh. o. enth. "TRUE", "YES" o. "1"
<domutil:if condition="" trueaction="exclude"> Manchmal ist eben 1 gleich 0 <domutil:if condition="">1 == 0 <domutil:elseif browser="Navigator"> Aktueller Browser ist ein Navigator <domutil:elseif isblank="dbname">dbname ist null o. "" <domutil:elseif isnull="unid">Parameter unid ist null <domutil:elseif isblank="*"> Mind. ein Parameter ist null oder "" <domutil:else>was anderes <domutil:switch value="" autotrim="true" partialmatch="false" ignorecase="false"> <domutil:case value="eins">1 gewählt <domutil:case value="zwei">2 gewählt <domutil:case value="drei">3 gewählt <domutil:default>Was andres <domutil:switch> <domutil:switchvalue> <domutil:case value="eins">1 gewählt <domino:session id="applicationSession" user="*webuser" host="www.djbuch.de" duration="session"> <domutil:format format="DATE" locale="de_DE" value="12/24/2005" type="date"/> <domutil:format format="TIME" locale="de_DE" timezone="AST" value="17:33" type="date"/> <domutil:format id="datevar" format="DATE" locale="de_DE" value="12/24/2005" type="date"/> <domutil:format format="UPPER" locale="de_DE"> lower Text <domutil:format format="CURRENCY" locale="de_DE" type="double">30.175 <domutil:format format="PERCENT" type="double"> 0.999 http://www.djbuch.de?urlSaveParam=<domutil:format format="URL" locale="de_DE">url codiert äöü
Listing 17-13 Domutil – Beispiele
649
650
17 J2EE @ Domino
fügbaren Domtags gelistet. Die Tabelle ist als Übersicht und Nachschlagewerk gedacht und entspricht der domtag.tld. Gezeigt werden alle Attribute und die Angabe, ob diese als Pflichtangabe zu machen sind.
17.5
Domutils
Neben den Standard-Domino-Tags liefert Domino eine Utilities-Bibliothek mit einfachen, aber sehr wirksamen Hilfsfunktionen aus. Die domutils decken zwei Bereiche ab: Die Flusssteuerung und die Formatierung von Daten. Zur Flusssteuerung stehen die beiden Konstruke if-elseif-else und switch-case-default zur Verfügung. Formatiert wird mit domutil:urlconverter und domutil:format. Das Listing 17-13 zeigt in einfachen Beispielen die Verwendung der Tags. Damit die Bibliothek verwendet werden kann, muss sie, wie jede Tag-Bibliothek, zunächst wie oben beschrieben in der Datei web.xml angemeldet und in der JSP-Seite referenziert werden. Das Tag domutil:if kennt zwei Formen für die Bestimmung der Bedingung. Entweder wird diese als Attribut condition angegeben oder über das zusätzliche Tag domutil:condition injiziert. Die zweite Möglichkeit scheint zunächst redundant, ermöglicht aber, das Ergebnis eines anderen Tags als Bedingung für den domutil:if-Tag zu verwenden. Hierfür sind z.B. domutil:switch, domutil:browsertag, aber selbstverständich auch domino:item geeignet. Oder anders formuliert: Der Wert eines Document Items lässt sich als Bedingung für einen domutil:if-Tag einsetzen. Die im Beispiel gezeigte Variante hat leider einen kleinen Nachteil. Ist das Item F_bool überhaupt nicht vorhanden, so evaluiert die Bedingung ebenfalls zu True und der Body des domutil:if-Tags wird dargestellt. Die Ursache wird schnell klar, wenn man einen Blick in die Serverkonsole wirft. Dort wird eine Exception geworfen, die darauf hinweist, dass das domutil:condition-Tag lediglich die Werte "1", "0", "Yes", "No", "True" oder "False" annehmen darf. Folglich kann diese Technik nur dann angewendet werden, wenn sichergestellt ist, dass das F_bool Item tatsächlich nur diese Werte enthält. Selbstverständlich kann ein scriptlet als Bedingung verwendet werden. Das Beispiel wird entsprechend der Vorbelegung "1==0" keine Ausgabe generieren. Soll das Attribut condition verwendet werden, so muss es ebenfalls einen der Werte "1", "0", "Yes", "No", "True" oder "False" annehmen. Invertiert wird die Abfrage durch die Angabe des Attributs trueaction="exclude" , der Defaultwert dieses Attributs trueaction="include" kann weggelassen werden. Neben dem Attribut condition stehen alternativ die Attribute browser, isblank oder isnull zur Verfügung, über die eine bedingte Abfrage nach Browser oder Nullwerten erfolgen kann. browser kann die Werte "Navigator" oder "Explorer" annehmen, isblank liefert true, wenn der genannte Parameter den Wert null oder "" hat und isnull überprüft einen Parameter auf null. isblank kann anstelle eines konkreten Namens auch das JokerSymbol * als Wert annehmen, dann wird die Bedingung als wahr angenommen, sofern mindestens ein Parameter den Wert null oder "" annimmt.
17.5 Domutils
651
Das Tag domutil:if kann optional durch die Tags domutil:elseif und ergänzt werden. Syntaktisch werden diese jeweils komplett mit öffnendem und schließendem Tag hintereinandergestellt und nicht etwa ineinander geschachtelt: else
<domutil:if>[...] <domutil:elseif>[...] <domutil:elseif>[...] <domutil:else>[...]
Die konditionalen Bedingungen für domutil:elseif werden analog zu domutil:if gebildet. Soll eine Unterscheidung zwischen mehreren Fällen stattfinden, kommt domutil:switch zum Einsatz. Dieses Tag umschließt einen oder mehrere domutil:case-Tags durch die die möglichen Optionen gekennzeichnet werden. Optional kann ein domutil:default-Tag hinzugefügt werden. Zurückgegeben wird der erste Body eines der Tags domutil:case oder des domutil:de-
Tabelle 17-1 Übersicht Domutil – Attribute und Pflichtfelder browsertag debug id onfailure supports time useragent value
elseif N N N N N N N
case debug onfailure time value
browser condition debug isblank isnull onfailure time
format N N N J
condition debug onfailure time
N N N N N N N
N N N
default debug onfailure time
N N N
debug onfailure time
N N N
else
debug format id locale onfailure time timezone type value
N J N N N N N N N
browser condition debug inrole isblank isnull
N N N N N N
if
onfailure time Jaction
N N N
nocache debug onfailure time
N N N
switch autotrim debug ignorecase onfailure partialmatch time value
N N N N N N N
switchvalue debug onfailure time
N N N
urlconverter charset debug id onfailure time
N N N N N
652
17 J2EE @ Domino
fault, für den die Attribute value übereinstimmen. Auch hier kann anstelle der konkreten Verwendung des Attributs ein Wert durch die Verwendung von domutil:switchvalue injiziert werden. Formatierungen können mit dem Tag domutil:format vorgenommen werden. Dieses Tag kennt das Attribut format, das die Werte "DATE", "TIME", "UPPER", "LOWER", "NUMBER", "CURRENCY", "PERCENT", "URL" oder "XML" annehmen kann. Die Ergebnisse von domutil:format können entweder direkt ausgegeben werden oder in einer Java-Variablen über den Einsatz des Attributs id gespeichert werden. Für die Formatierung von Dateoder Time-Werten muss das Tag domutil:format in ein domino:sessionTag eingeschlossen sein. In jedem Fall kann über das locale-Attribut eine Lokalisierung und entsprechende Umwandlung vorgenommen werden. Eine Zeitumwandlung kann über timezone in einer bestimmten Zeitzone vorgenommen werden. Die Zeitzone wird anhand der in Kapitel 12.2 beschriebenen Kürzel bestimmt. Grundsätzlich geht dieses Tag so vor, dass zunächst eine Umwandlung in einen Java-Datentyp vorgenommen wird, wobei der gewünschte Typ optional über das Attribut type bestimmt werden kann. Praktisch ist die Umwandlung nach CURRENCY oder PERCENT, wobei im letzteren Fall mathematisch gerundet wird, das Beispiel liefert dementsprechend 100%. Ebenso hilfreich ist das URL- oder XML-Encoding, das über format="URL" oder format="XML" erreicht werden kann. Auch die Domutil-Tags tragen alle die Attribute debug, time und onfailure (s.o.). Eine ausführliche Übersicht zeigt Tabelle 17-1.
17.6
MVC Pattern für Domino-Anwendungen
Das Modell View Controller Pattern (MVC) ist eines der wichtigsten Designpattern in der J2EE-Entwicklung. Nun ist Domino an sich kein eigentlicher J2EE-Server, unterstützt aber doch einige der dort vorgesehenen Standards und lässt sich mit den in diesem und dem vorigen Kapitel beschriebenen Techniken in eine solche Umgebung integrieren. JSP als wichtiger Bestandteil des MVC Pattern wird von Domino zwar nicht derart unterstützt, dass Domino selbst einen JSP-Container anbieten würde, aber die Domtags Tag Library ist umfangreich und mächtig genug, um Domino-Objekte im JSP-Kontext zu nutzen. Sollen Domino-Objekte und die Domtags in einer J2EE-Anwendung eingesetzt werden und das MVC Pattern zur Anwendung kommen, sind einige Einschränkungen zu berücksichtigen. Die Domtags sind sehr nah an den Domino-Designobjekten orientiert und implementieren ähnlich wie diese bereits viele Funktionen. Hierzu gehören zum Beispiel die in Kapitel 17.4.5 eingeführten Maskenfunktionen domino:saveclosedoc und domino:nextdoc oder der Einsatz von domino:formlink, die alle selbst Aufgaben des Controllers übernehmen. Folglich wird immer genau zu kontrollieren sein, welche Aufgaben vom Controller an die Domtags abgegeben werden. Abb. 17-4 zeigt den typischen Ansatz einer Domtags-basierten Anwendung. Eingangs übernimmt der Controller die Ansteuerung der Businesslogik. Das Modell
17.6 MVC Pattern für Domino-Anwendungen View domino:view
653
View domino:form domino:document
Controller
Domino
Modell
Key = ... FTQuery = ... UNID = ... Domino
View POJO DJCacheDocument Collection
Controller
Modell POJO
Domino Java API Domino
Abb. 17-4 Modell View Controller und Domino
selbst hat möglicherweise selbst eine Anbindung über das Domino-Java-API an Domino und wird für die Anzeige der Daten z.B. einen Key (für domino:view mit Attribut key), eine FT Query oder eine Universal ID eines Dokuments evaluieren, die beschreiben, welche Daten anzuzeigen sind. Der Controller übergibt diese Schlüssel (nicht die eigentlichen Daten) an die View-Komponente, die dann mit Hilfe der Domtags die gewünschten Daten anzeigt. Die Grafik zeigt, wie die Kontrolle z.B. aus dem domino:view direkt an die JSPSeite mit dem domino:document-Tag übergeben wird. Der Controller ist hier nicht mehr einbezogen. Deutlich wird der Bruch in der MVC-Struktur auch daran, dass die Domtags selbst Verbindung zum Domino-Server haben, anstatt die Daten vom Controller übergeben zu bekommen.
654
17 J2EE @ Domino
Modell View Controller – MVC Design Pattern Das Modell View Controller Pattern sieht vor, dass eine Unternehmensanwendung in drei Komponenten aufgeteilt wird. Zentraler Bestandteil ist der Controller, der häufig in J2EEAnwendungen und Frameworks als Servlet umgesetzt wird. Im Controller wird entschieden, welcher Use Case vom Benutzer angefordert wurde und welche Businesslogik hierfür angesteuert werden muss. Die Businesslogik ist in der Modell-Komponente untergebracht und wird vom Controller angesteuert. Das Ergebniss der Businesslogik, die anhand der Benutzereingaben evaluiert wird, wird an den Controller zurückgegeben. Der Controller entscheidet, wie die generierten Daten anzuzeigen sind und wählt anhand der Vorgaben der Businesslogik eine geeignete View-Komponente aus, die dann die Daten anzeigt.
Eine Alternative wird in Abb. 17-4 aufgezeigt. Hier wird die Kommunikation zwischen der Anwendung und Domino komplett in die Modell-Komponente verlagert. Diese übernimmt die Kommunikation mit Domino und das Handling der Domino-Java-Objekte und der Domino-Session. So kann eine saubere Trennung zwischen den Komponenten beibehalten werden. Allerdings erfordert dies, dass über die Domino-Objekte ein Data Layer gelegt wird, mit dessen Hilfe die Domino-Objekte in POJO-Objekten (Plain Old Java Object) gehalten werden können. Zu diesem Zweck wurde in Kapitel 13.2.1 das DJCacheDocument eingeführt. Man muss sich hierbei im Klaren sein, dass dies unter Umständen zur Folge hat, dass große Mengen an Daten in der HttpSession gehalten werden müssen. Letztendlich liegt daher die Lösung, wie so oft, im Kompromiss. Unter strenger Kontrolle der Zuständigkeiten wird die Kommunikation mit Domino weitestgehend im Modell erledigt. Aber anstatt alle Daten in der HttpSession zu halten, werden nur z.B. Universal IDs gespeichert, die dann wieder über Domtags (oder eigene Tags) geladen werden. Man nutzt also die darstellenden Funktionen der Domtags und nicht deren – aus Sicht der Businesslogik – funktionale. So können sie dennoch eine große Hilfe in einer J2EE-MVC-Umgebung sein.
17.7
Domino-XML
XML ist ein inzwischen nicht mehr wegzudenkender Standard für die Repräsentation hierarchischer Datenstrukturen, wobei der Begriff der Daten sehr weit zu fassen ist und inzischen in der Regel weiter geht als die reine Speicherung von Inhalten. XML ist hervorragend geeignet, um Kontrollstrukturen, Konfigurationen oder Steuerdokumente abzubilden. Dank der Möglichkeit, über ein XML-Schema oder eine DTD die Daten zu validieren, ist immer gewährleistet, dass diese Daten- oder
17.7 Domino-XML
655
Kontrollstrukturen einem bestimmten Schema entsprechen. Domino unterstützt seit Version 6 XML sehr gut und bietet darüber hinaus viele Funktionen an, die es möglich machen, DominoDaten und eben auch Designelemente, aber auch Konfigurationsbestandteile wie z.B. eine ACL durch generische Me- Abb. 17-5 DXL Werkzeuge thoden leicht und schnell in XML zu überführen bzw. aus XML zu generieren. Hierfür steht eine DTD und seit Version 7 ein XML-Schema115 zur Verfügung, in dem für sämtliche Designelemente und Dokumente festgelegt ist, welche XML-Struktur diesen zugrunde liegen muss. Auf Basis dieser XML-Schema-Definition können nun sämtliche Domino-Designelemente in XML kon- Abb. 17-6 Domino View als XML vertiert werden und umgekehrt. Hierfür stehen im Java-API die beiden Klassen DxlExporter und DxlImporter zur Verfügung, im Domino Designer können XML-Funktionen direkt über das Menü (Abb. 17-5) abgefordert werden. Während die beiden Funktionen „Exporter“ und „Viewer“ lediglich das XML eines einzelnen Designelements generieren bzw. anzeigen, ermöglicht der „Transformer“ ein XSL Stylesheet auf eine Kollektion von Designelementen anzuwenden und so Übersichten oder Reports über das vorliegende Design anzulegen. Abb. 17-6 zeigt die Ansicht V_toysByID in ihrer XML-Repräsentation im Viewer. In der Beispieldatenbank djbuch/djbuch.nsf wurden zwei Beispielagenten vorbereitet, die die Anwendung der API-Funktionen zur XML-Generierung verdeutlichen. Der Agent „Actions“ -> „Tools“ -> „3. Dokument als XML“ zeigt die Umwandlung eines Domino-Dokuments in XML (kann nur auf der lokalen Replik gestartet werden) und der Agent „Actions“ -> „Tools“ -> „4. Gesamtes Design als XML“ die Anwendung auf Designelemente. Zur Verwendung des Agenten „3. Dokument als XML“ muss zunächst ein vorhandenes Dokument markiert werden, bevor der Agent gestartet wird. Er legt dann ein neues Dokument als Antwortdokument an, das die XML-Repräsentation des markierten Dokuments enthält. Der Agent „4. Gesamtes Design als XML“ kann ohne weitere Vorkehrung gestartet werden und erzeugt ein neues Dokument, das in der Ansicht „Alle Dokumente“ gefunden werden kann. Beide Agenten verwenden entsprechend Listing 17-14 einen DxlExporter , der in Session über createDxlExporter bezogen werden kann. Gestartet wird der Exportprozess, indem die 115
Im Verzeichnis xmlschemas im Programmordner des Domino-Servers oder Notes Clients befinden sich seit Version 7 die DTD und Schema-Dateien für eine Standard-Windows-Installation, also im Verzeichnis C:\Lotus\[Domino|Notes]\xmlschemas.
656
17 J2EE @ Domino
public void NotesMain() { DxlExporter exporter = null; Document doc = null; try { [...] doc = someDocument(); exporter = session.createDxlExporter(); String result = exporter.exportDxl(doc); [...] } catch (NotesException e) { [...] } finally { try { if (exporter != null) { System.out.println (exporter.getLog()); exporter.recycle(); } if (doc != null) {doc.recycle();} } catch (NotesException e1) { e1.printStackTrace(); } } }
Listing 17-14 Dokument in XML umwandeln
Methode exportDxl aufgerufen wird. Diese Methode akzeptiert die verschiedensten Domino-Objekte, für die dann XML generiert und als String zurückgegeben wird. Der Verarbeitungsstatus wird in einem Log gespeichert, das über getLog() in DxlExporter aufgerufen werden kann. Soll das gesamte Design exportiert werden, erfolgt der eigentliche Exportprozess analog und ebenfalls über einen DxlExporter. Zuvor müssen jedoch die Designelemente eingesammelt werden. Hierfür (Listing 17-15) ist eine NoteCollection geeignet, die über createNoteCollection (boolean selectAll) in Dapublic void NotesMain() { NoteCollection nc = null; DxlExporter exporter = null; Document doc = null; try { [...] nc = database.createNoteCollection(false); nc.selectAllDesignElements(true); nc.buildCollection(); exporter = session.createDxlExporter(); String result = exporter.exportDxl(nc); [...] } catch (NotesException e) { [...] //Error Handling } finally { [...] //recycle nc, exporter, doc }
Listing 17-15 Design in XML umwandeln
17.7.1 NoteCollection
657
tabase erzeugt werden kann. Der Parameter selectAll==false gibt an, dass zu-
nächst eine leere Collection erzeugt werden soll, die dann über verschiedene Methoden mit Note-Objekten gefüllt werden kann. selectAllDesignElements (boolean addOrRemove) liefert alle Designelemente, wobei über den Parameter addOrRemove==true bestimmt wird, dass die Designelemente in die NoteCollection eingeschlossen und nicht aus dieser entfernt werden sollen. Die NoteCollection ist so implementiert, dass die verschiedenen select-Methoden den eigentlichen Vorgang noch nicht auslösen, sondern über diese Methoden werden zunächst nur die Anforderungen eingesammelt und erst über buildCollection wird dann die Collection gefüllt. Der XML Export wird dann anhand des DxlExporters durch Aufrufen der Meausgelöst. thode exportDxl
17.7.1
NoteCollection
Die NoteCollection ist zwar weder zwingend für die XML-Verarbeitung erforderlich noch direkt damit verbunden, aber oft unerlässlich, um die Domino-Notes auszuwählen. Die Klasse NoteCollection ist neu seit Domino R6.5. Für die allgemeine Verarbeitung und Handhabung der Note-Elemente in NoteCollection stehen folgende Methoden zur Verfügung: •
void buildCollection() throws NotesException
Neu seit Domino R6.5. Startet die Auswahl der zuvor vorbereitend selektierten Note-Elemente. •
void clearCollection() throws NotesException
Neu seit Domino R6.5. Löscht die gesamte Auswahl. •
String getFirstNoteID() throws NotesException String getNextNoteID(String validNoteID) throws NotesException
Neu seit Domino R6.5. Ermöglicht eine Navigation durch die Note-Elemente einer Collection, wobei validNoteID eine gültige NoteID einer Domino-Note sein muss (nicht zu verwechseln mit der UniversalID). •
int getCount() throws NotesException
Neu seit Domino R6.5. Anzahl der Elemente in der Collection. •
void setSinceTime(DateTime cutOffDateTime) throws NotesException DateTime getSinceTime() throws NotesException
Neu seit Domino R6.5. Limitiert die NoteCollection auf Note-Elemente mit einem Erstelldatum nach cutOffDateTime. •
DateTime getLastBuildTime() throws NotesException
Neu seit Domino R6.5. Datum und Uhrzeit der letzten Ausführung von buildCollection.
658 •
17 J2EE @ Domino Database getParent() throws NotesException Neu seit Domino R6.5. Datenbank, in der das NoteCollection-Objekt erzeugt
wurde. Wie im vorigen Kapitel beschrieben, wird die NoteCollection zunächst als Objekt angelegt, dann wird durch verschiedene select-Methoden die Auswahl der gewünschten Notes vorbereitet und schließlich durch buildCollection ausgelöst. Alternativ stehen folgende Methoden zur vorbereitenden Auswahl zur Verfügung. Nach diesen Methoden muss buildCollection aufgerufen werden, um die eigentliche Collection aufzubauen. •
void selectAllNotes(boolean addOrRemove) throws NotesException
Neu seit Domino R6.5. Wählt alle Note-Elemente der Datenbank, in der die NoteCollection erzeugt wurde. •
void setSelectionFormula(String atFormula) throws NotesException String getSelectionFormula() throws NotesException
Neu seit Domino R6.5. Anhand einer @Formel können Note-Elemente selektiert werden. •
void add(String validNoteID) throws NotesException void add(int validNoteID) throws NotesException void add(Document document) throws NotesException void add(DocumentCollection documentcollection) throws NotesException void add(NoteCollection notecollection) throws NotesException void add(Form form) throws NotesException void add(Agent agent) throws NotesException void add(View view) throws NotesException
Neu seit Domino R6.5. Fügt der NoteCollection ein einzelnes Domino-Objekt hinzu. validNoteID beschreibt eine gültige NoteID als String oder int. •
void remove(String validNoteID) throws NotesException void remove(int validNoteID) throws NotesException void remove(Document document) throws NotesException void remove(DocumentCollection documentcollection) throws NotesException void remove(NoteCollection notecollection) throws NotesException void remove(Form form) throws NotesException void remove(Agent agent) throws NotesException void remove(View view) throws NotesException
Neu seit Domino R6.5. Entfernt aus der NoteCollection ein einzelnes DominoObjekt. validNoteID beschreibt eine gültige NoteID als String oder int. •
void intersect(String validNoteID) throws NotesException void intersect(int validNoteID) throws NotesException
17.7.1 NoteCollection
659
void intersect(Document document) throws NotesException void intersect(DocumentCollection documentcollection) throws NotesException void intersect(NoteCollection notecollection) throws NotesException void intersect(Form form) throws NotesException void intersect(Agent agent) throws NotesException void intersect(View view) throws NotesException
Neu seit Domino R6.5. Mischt einzelne Domino-Objekte mit den vorhandenen der NoteCollection. Am mächtigsten sind die select-Methoden, die ganze Gruppen von Note-Elementen selektieren. Hierdurch können jeweils alle Daten-, Design- oder administrativen Note-Elemente oder Untergruppen davon selektiert werden. Die Namen dieser Methoden sind alle nach dem Standard void select> (boolean addOrRemove) throws NotesException boolean getSelect> () throws NotesException
aufgebaut, wobei „>“ jeweils angibt, welche Elemente selektiert werden sollen. getSelect> zeigt an, ob diese Elemente in der NoteCollection enthalten sind. Zusätzlich gibt es übergeordnete Methoden, die ganze Gruppen verschiedener, aber zusammengehöriger Elemente selektieren. Die Namen der Methoden sind selbsterklärend und beziehen sich jeweils direkt auf den Namen des Designelements. Daten selektieren • void selectAllDataNotes(boolean addOrRemove) throws NotesException
Neu seit Domino R6.5. Diese Methode selektiert alle Daten-Notes, d.h. Dokumente und Profildokumente. Zusätzlich gibt es die Methoden void setSelectDocuments(boolean addOrRemove) throws NotesException boolean getSelectDocuments() throws NotesException void setSelectProfiles(boolean addOrRemove) throws NotesException boolean getSelectProfiles() throws NotesException
Designelemente selektieren • void selectAllDesignElements(boolean addOrRemove) throws NotesException
Neu seit Domino R6.5. Selektiert alle Designelemente. Diese können zusätzlich in folgenden Untergruppen selektiert werden: – void selectAllFormatElements(boolean addOrRemove)
660
17 J2EE @ Domino throws NotesException
Neu seit Domino R6.5. Selektiert Design-Note-Elemente, die Daten formatieren und darstellen. Im Einzelnen können diese ebenfalls mit Methoden der Art void select< <Elemente>> (boolean addOrRemove) bzw. boolean getSelect>() ausgewählt werden:
–
setSelectPages, setSelectForms, setSelectSubforms, setSelectActions, setSelectImageResources, setSelectFramesets, setSelectStylesheetResources, setSelectJavaResources, setSelectMiscFormatElements, jeweils mit den zugehörigen getSelect>-Methoden. void selectAllIndexElements(boolean addOrRemove) throws NotesException
Neu seit Domino R6.5. Selektiert Design-Note-Elemente, die Übersichtslisten bereitstellen. Im Einzelnen können diese ebenfalls mit Methoden der Art void select< <Elemente>> (boolean addOrRemove) bzw. boolean getSelect>() ausgewählt werden:
–
setSelectViews, setSelectFolders, setSelectNavigators, setSelectMiscIndexElements, jeweils mit den zugehörigen getSelect> Methoden. void selectAllCodeElements(boolean addOrRemove) throws NotesException
Neu seit Domino R6.5. Selektiert Design-Note-Elemente, die Codebestandteile repräsentieren. Im Einzelnen können diese ebenfalls mit Methoden der Art void select< <Elemente>> (boolean addOrRemove) bzw. boolean getSelect>() ausgewählt werden: setSelectAgents, setSelectDatabaseScript, setSelectScriptLibraries, setSelectOutlines, setSelectMiscCodeElements, jeweils mit den zugehörigen getSelect>-
Methoden. –
sonstige
Neu seit Domino R6.5. Selektiert sonstige Design-Note-Elemente. Im Einzelnen können diese ebenfalls mit Methoden der Art void select< <Elemente>> (boolean addOrRemove) bzw. boolean getSelect>() ausgewählt werden: setSelectSharedFields, setSelectIcon, setSelectHelpAbout, setSelectHelpUsing, setSelectHelpIndex, setSelectDataConnections, jeweils mit den zugehörigen getSelect>-Methoden.
17.7.2 XML-Methoden in Domino-Objekten
661
Administrative Elemente selektieren • void selectAllAdminNotes(boolean addOrRemove) throws NotesException
Neu seit Domino R6.5. Diese Methode selektiert alle administrativen Note-Elemente. Zusätzlich gibt es die Methoden void setSelectReplicationFormulas(boolean addOrRemove) throws NotesException boolean getSelectReplicationFormulas() throws NotesException void setSelectAcl(boolean addOrRemove) throws NotesException boolean getSelectAcl() throws NotesException
17.7.2
XML-Methoden in Domino-Objekten
Für einige Domino-Objekte kann deren XML wesentlich leichter als oben beschrieben direkt über Methoden des Objektes bezogen werden. Möglich ist dies für Document, Item, RichTextItem und MIMEEntity: Document: • String generateXML() throws NotesException Anhand der Domino-DTD wird XML-Code für das aktuelle Dokument (Abb. 710) erzeugt und als String zurückgegeben. • void generateXML(Writer writer) throws NotesException, IOException;
Statt den XML-Code als String zurückzugeben, kann das Ergebnis auf einen Writer umgeleitet werden. •
void generateXML(Object xslStyleSheet, XSLTResultTarget xsltResult) throws IOException, NotesException [, SaxException]116 Erzeugt das XML des Document und transformiert es mit einem XSL Stylesheet. Das Ergebnis wird als XSLTResultTarget zurückgegeben. Der Parameter xslStyleSheet darf vom Typ org.xml.sax.InputSource, lotus.domino.Item, lotus.domino.EmbeddedObject oder lotus. domino.MIMEEntity sein.
Ein typisches Code-Beispiel sieht wie folgt aus: String xsl = "//your XSL StyleSheet"; Item xslItem = styleDoc.replaceItemValue("F_xsl",xsl); XSLTResultTarget xsltResult = new XSLTResultTarget(); StringWriter wr = new StringWriter (); 116
Neue Signatur: Ab Domino Version 7 wird keine SaxException mehr geworfen.
662
17 J2EE @ Domino xsltResult.setCharacterStream(wr); sourceDoc.generateXML(xslItem,xsltResult); System.out.println (wr.toString());
Item, RichTextItem, MIMEEntity • org.xml.sax.InputSource getInputSource() throws NotesException
Liefert den Inhalt eines Items oder Attachments als InputSource für die weitere Verarbeitung. Diese Methode erzeugt eine temporäre Datei, die beim Recycling wieder gelöscht wird. •
java.io.InputStream getInputStream() throws NotesException Liefert den Inhalt des Items oder Attachments als java.io.InputStream-Re-
präsentation. Diese Methode erzeugt eine temporäre Datei, die beim Recycling wieder gelöscht wird. •
java.io.Reader getReader() throws NotesException Liefert den Inhalt des Items oder Attachments als java.io.Reader. Diese Me-
•
org.w3c.dom.Document parseXML(boolean validate) throws IOException, SAXException, NotesException org.w3c.dom.Document parseXML(boolean validate) throws IOException, NotesException117 Enthält ein Item oder ein Attachment mit Typ EmbeddedObject.EMBED_ATTACHMENT gültiges XML, so kann es mit dieser Methode in ein XML dom Document umgewandelt werden. Ist der Parameter validate true, so wird die Über-
•
transformXML(Object xslStyleSheet, XSLTResultTarget xsltResult) throws IOException, SAXException, NotesException transformXML(Object xslStyleSheet, XSLTResultTarget xsltResult) throws NotesException118 Wendet das XSL StyleSheet xslStyleSheet auf das XML an, das in dem Item oder in einem Attachment mit Typ EmbeddedObject.EMBED_ATTACHMENT
thode erzeugt eine temporäre Datei, die beim Recycling wieder gelöscht wird.
prüfung der DTD erzwungen.
gespeichert ist. xslStyleSheet darf vom Typ org.xml.sax.InputSource, lotus.domino. Item, lotus.domino.EmbeddedObject oder lotus.domino.MIMEEntity sein. Bei der Anwendung der obigen Methoden spielt das XSLTResultTarget eine wichtige Rolle. Mit transformXML in EmbeddedObject, Item, MIMEEntity kann XML über ein XSL Stylesheet transformiert werden. Das Ergebnis ist ein XSLTResultTarget. Diese Klasse ist ein Wrapper der Klasse com.lotus.xsl.XSLTResultTarget, die wiederum org.apache.xalan.xslt.XSLTResultTarget erweitert. Der Wrapper und das Extending sind absolut geradlinig ohne Nebenwirkungen implementiert, so dass die Konstruktoren und Methoden äquivalent zu denen 117 118
Neue Signatur: Ab Domino Version 7 wirft diese Methode nur noch eine NotesException. Neue Signatur: Ab Domino Version 7 wirft diese Methode nur noch eine NotesException.
17.7.3 DxlExporter und DxlImporter
663
aus org.apache.xalan.xslt.XSLTResultTarget verwendet werden können. Seit Domino R7 ist XSLTResultTarget eine eingenständige Klasse und basiert im Wesentlichen auf javax.xml.transform, da seit Java 1.4.x die XML- und XSLKlassen Bestandteil des Standard-JRE sind. Dementsprechend wurden aus der Domino 7 Core Version auch die Bibliotheken xml4j.jar und lotusxsl.jar entfernt. Dies hat leider Auswirkungen auf bestehende Installationen, da nun die neuen Parser verwendet werden müssen. Um die zugehörigen Transformationsmethoden benutzen zu können, müssen alleinstehende Javaanwendungen beim Einsatz von Domino-Versionen vor Domino R7 die Datei XML4J.jar im Classpath einbinden und Applets diese als ClassPath-Parameter verwenden.
17.7.3
DxlExporter und DxlImporter
Die Klassen DxlExporter und DxlImporter werden aus einer Domino-Session bezogen und dienen dem Ex- und Import von XML-Daten aus und in Domino. Dies ist für alle Typen von Domino-Design-, Daten- und administrativen Elementen möglich. Ein wichtiges Mittel zur Bestimmung, welche Elemente ex- bzw. importiert werden sollen, ist die Klasse NoteCollection (s. Kap. 17.7.1) in Session: DxlExporter createDxlExporter() throws NotesException DxlImporter createDxlImporter() throws NotesException
Erzeugt einen Dxl Importer bzw. Exporter. Dxl steht für Domino-XML. Diese Methoden sind neu seit R6.5. DxlExporter • String exportDxl(Database database) throws NotesException String exportDxl(NoteCollection notecollection) throws NotesException String exportDxl(Document document) throws NotesException String exportDxl(DocumentCollection documentcollection) throws NotesException
Neu seit Domino R6.5. Exportiert das als Parameter übergebene Objekt. •
void setExitOnFirstFatalError(boolean exitOnFirstErr) throws NotesException boolean getExitOnFirstFatalError() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, ob der Export abgebrochen werden soll, sobald ein fataler Fehler vorliegt.
664 •
17 J2EE @ Domino void setForceNoteFormat(boolean forceNoteFormat) throws NotesException boolean getForceNoteFormat() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, ob die exportierten XML-Elemente alle im Note-Format und nicht jeweils im Document-, Form-, View-, Agent- usw. Format erzeugt werden sollen. Da jedes Domino-Element auch eine DominoNote ist, stehen grundsätzlich beide Formate zur Verfügung. •
void setOutputDOCTYPE(boolean setDocType) throws NotesException boolean getOutputDOCTYPE() throws NotesException Neu seit Domino R6.5. Legt fest bzw. zeigt an, ob das -Tag expor-
•
void setConvertNotesBitmapsToGIF(boolean convertToGif) throws NotesException boolean getConvertNotesBitmapsToGIF() throws NotesException
tiert werden soll.
Neu seit Domino R6.5. Legt fest bzw. zeigt an, ob Bilder, die im Notes-BitmapFormat gespeichert sind (z.B. Bilder, die in RichTextItems per CopyPaste eingebettet wurden) ins Gif-Format konvertiert werden sollen. •
void setDoctypeSYSTEM(String docType) throws NotesException String getDoctypeSYSTEM() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, wie das Attribut SYSTEM im -Tag lauten soll. •
String getLog() throws NotesException void setLogComment(String logComment) throws NotesException String getLogComment() throws NotesException Neu seit Domino R6.5. Während getLog() das Log des XML-Exporters anzeigt, kann mit getLogComment und setLogComment ein eigener Kommentar zum
Log geschrieben werden. DxlImporter • void importDxl(String s, Database database) throws NotesException void importDxl(Stream stream, Database database) throws NotesException void importDxl(RichTextItem richtextitem, Database database) throws NotesException
Neu seit Domino R6.5. Importiert DXL (Domino-XML) aus einem String, einem Stream oder einem RichTextItem. Die importierten XML-Daten werden in die im Parameter database beschriebenen Datenbank importiert. Die folgenden Methoden legen fest, wie mit den verschiedenen Attributen und Objekten umgegangen werden soll. Ein DXL-Import ist stark vergleichbar mit einer Push-Replikation aus dem XML-Dokument in die Zieldatenbank. Über einige der folgenden Methoden kann festgelegt werden, wie mit Konflikten umgegangen werden soll.
17.7.3 DxlExporter und DxlImporter
665
Der DxlImporter ist ein mächtiges Instrument und sollte nur von erfahrenen Programmierern eingesetzt werden. Er birgt in jedem Fall eine Quelle möglicher Sicherheitrisiken in sich, da durch ihn alle Datenbankobjekte einschließlich der ACL überschrieben werden können. •
String getFirstImportedNoteID() throws NotesException String getNextImportedNoteID(String s) throws NotesException
Neu seit Domino R6.5. Nach einem Import können die importierten Note-Elemente über diese beiden Methoden durchschritten werden. •
void setExitOnFirstFatalError(boolean exitOnFirstErr) throws NotesException boolean getExitOnFirstFatalError() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, ob der Import-Vorgang beim ersten fatalen Fehler abgebrochen werden soll. •
int getImportedNoteCount() throws NotesException
Neu seit Domino R6.5. Gibt die Anzahl der importierten Note-Elemente zurück. •
void setAclImportOption(int option) throws NotesException int getAclImportOption() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, wie beim Import einer ACL verfahren werden soll. Es stehen folgende Optionen zur Verfügung: - DXLIMPORTOPTION_IGNORE – ACL nicht importieren - DXLIMPORTOPTION_REPLACE_ELSE_IGNORE – Vorhandene ACL ersetzen. - DXLIMPORTOPTION_UPDATE_ELSE_IGNORE – Vorhandene ACL-Einträge aktualisieren, neue ACL-Einträge ignorieren. - DXLIMPORTOPTION_UPDATE_ELSE_CREATE – Vorhandene ACL-Einträge aktualisieren, neue ACL-Einträge hinzufügen. Diese Vorgehensweise entspricht einem Mischen beider ACLs, wobei sich bei Konflikten die importierte ACL aus dem XML durchsetzt. •
void setDesignImportOption(int option) throws NotesException int getDesignImportOption() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, wie beim Import von Dokumenten verfahren werden soll. Es stehen folgende Optionen zur Verfügung: – –
DXLIMPORTOPTION_IGNORE – Kein Import von Designelementen DXLIMPORTOPTION_CREATE – Designelemente aus dem zu importierenden
–
DXLIMPORTOPTION_REPLACE_ELSE_IGNORE – Designelemente aus dem
XML werden neu erstellt, vorhandene nicht ersetzt oder verändert.
–
•
zu importierenden XML werden ersetzt, sofern vorhanden, andernfalls wird der Import ignoriert. DXLIMPORTOPTION_REPLACE_ELSE_CREATE – Designelemente aus dem zu importierenden XML werden neu erstellt, vorhandene vollständig ersetzt.
void setDocumentImportOption(int option) throws NotesException int getDocumentImportOption() throws NotesException
666
17 J2EE @ Domino Neu seit Domino R6.5. Legt fest bzw. zeigt an, wie beim Import von Dokumenten verfahren werden soll. Es stehen folgende Optionen zur Verfügung: – –
DXLIMPORTOPTION_IGNORE – Kein Import von Dokumenten DXLIMPORTOPTION_CREATE – Dokumente aus dem XML werden der Da-
–
DXLIMPORTOPTION_REPLACE_ELSE_IGNORE – Vorhandene Dokumente
tenbank neu hinzugefügt, vorhandene nicht ersetzt oder verändert.
–
–
–
•
in der Datenbank werden durch korrespondierende im XML ersetzt. Neue Dokumente werden nicht hinzugefügt. DXLIMPORTOPTION_REPLACE_ELSE_CREATE – Vorhandene Dokumente in der Datenbank werden durch korrespondierende im XML ersetzt. Neue Dokumente werden hinzugefügt. DXLIMPORTOPTION_UPDATE_ELSE_IGNORE – Items in vorhandenen Dokumenten in der Datenbank werden durch korrespondierende Items im XML ersetzt. Neue Items aus korrespondierenden Dokumenten werden hinzugefügt. Neue Dokumente werden nicht hinzugefügt. Dieses Verfahren entspricht einem Mischen korrespondierender Dokumente auf Item-Ebene. DXLIMPORTOPTION_UPDATE_ELSE_CREATE – Items in vorhandenen Dokumenten in der Datenbank werden durch korrespondierende Items im XML ersetzt. Neue Items aus korrespondierenden Dokumenten werden hinzugefügt. Neue Dokumente werden hinzugefügt. Dieses Verfahren entspricht einem Mischen korrespondierender Dokumente auf Item Ebene.
void setUnknownTokenLogOption(int option) throws NotesException int getUnknownTokenLogOption() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, wie verfahren werden soll, wenn im XML unbekannte Tags gefunden werden. – –
DXLLOGOPTION_IGNORE – Unbekannte Tags ignorieren. DXLLOGOPTION_WARNING – Warnungen für unbekannte Tags ins Log
schreiben. – –
DXLLOGOPTION_ERROR – Fehler für unbekannte Tags ins Log schreiben. DXLLOGOPTION_FATALERROR – Verarbeitung mit einem fatalen Fehler un-
terbrechen. •
void setInputValidationOption(int option) throws NotesException int getInputValidationOption() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, wie die zu importierenden Daten validiert werden sollen. –
DXLVALIDATIONOPTION_VALIDATE_NEVER – Keine Validierung
17.8 Zusammenfassung
•
667
–
DXLVALIDATIONOPTION_VALIDATE_ALWAYS – Validierung erzwingen,
–
auch wenn keine DTD oder ein Schema mit dem XML-Dokument verknüpft ist. DXLVALIDATIONOPTION_VALIDATE_AUTO – Validierung nur dann erzwingen, wenn eine DTD oder ein Schema mit dem XML-Dokument verknüpft ist.
void setReplaceDbProperties(boolean replaceProps) throws NotesException boolean getReplaceDbProperties() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, ob Datenbankeigenschaften durch einen XML-Import überschrieben werden dürfen. •
void setCreateFTIndex(boolean allowCreateFT) throws NotesException boolean getCreateFTIndex() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, ob die Erstellung eines FTIndex durch einen XML-Import aktiviert werden darf. •
void setReplicaRequiredForReplaceOrUpdate (boolean enforceConsistentRID) throws NotesException boolean getReplicaRequiredForReplaceOrUpdate() throws NotesException
Neu seit Domino R6.5. Legt fest bzw. zeigt an, ob die ReplikID, die im XML gespeichert ist, mit derjenigen der Datenbank übereinstimmen muss, in die importiert wird. •
String getLog() throws NotesException void setLogComment(String logComment) throws NotesException String getLogComment() throws NotesException Neu seit Domino R6.5. Während getLog() das Log des XML-Exporters anzeigt, kann mit getLogComment und setLogComment ein eigener Kommentar zum
Log geschrieben werden.
17.8
Zusammenfassung
Auch wenn Domino nicht selbst den J2EE-Standard als Application Server unterstützt, so arbeitet es doch auf zwei Ebenen mit J2EE-Anwendungen zusammen. Servlets kann Domino in einem eigenen Servlet Container verarbeitet, JSP-Seiten zwar nicht selbst darstellen, es stellt aber doch für die Verwendung über entsprechende Plugins bzw. über die Anbindung anhand der Domino-Java-API die Domtags und Domutil Tag Library zur Verfügung. Domino-Anwendungen werden nicht selbst als J2EE-Anwendung eingesetzt – dies würde auch dem Hauptaugenmerk von Domino auf die Verarbeitung von Collaboration-Anwendung und die Verwaltung unstrukturierter Daten in verteilten Umgebungen widersprechen –, sondern über die Domtags oder das Java-API in solche eingebunden.
668
17 J2EE @ Domino
Setzt man diese beiden Techniken geschickt ein, so kann Domino auch in ein MVC Pattern eingebunden werden. XML-Daten sind aus Enterprise-Anwendungen nicht mehr wegzudenken – angefangen als grundlegende Basis aller Konfigurationen bis hin zur Speicherung hierarchischer Datenstrukturen. Domino hat mit den Klassen DxlExporter, DxlImporter und NoteCollection ein wirksames Werkzeug bereitgestellt, Domino-Daten in eine XML-Umgebung einzubinden. Alle Domino-Note-Elemente, seien es Dokumente, Designelemente oder administrative Objekte, können in XML exportiert oder aus XML importiert werden. XML kann so zu einem alternativen Weg werden, um Domino-Daten in externen Anwendungen einzusetzen.
18
18 Domino-Projektentwicklung
In diesem Kapitel: Die Domino-Anwendungen Tasks im Domino-Server Wichtige Datenbanken im Domino-Server Enwicklungsszenarien für die Domino-Java-Entwicklung Entwicklungsteams zusammenstellen
670
18 Domino-Projektentwicklung
Domino-Projekte zu entwickeln bedarf, neben der reinen Programmierung, einer gewissen Vorbereitung, allein schon auf Grund der Tatsache, dass viele beteiligte Komponenten betrieben und verwaltet werden müssen. Um die Einstiegshürden für Neueinsteiger gering zu halten befindet sich im Folgenden eine kurze Übersicht über wichtige Komponenten in einer Domino-Infrastruktur, ergänzt durch Tipps für das Setup einer Entwicklungsumgebung.
18.1
Infrastruktur, Domino-Core-Applikationen
In einer Domino-Infrastruktur kann zwischen zwei Szenarien unterschieden werden. Domino bietet entweder internetbasierte Dienste wie IIOP oder HTTP oder auch SMTP mit POP3/IMAP an oder Domino-basierte Dienste, die über einen Notes Client genutzt werden, beginnend mit den Standard-Büroanwendungen, wie E-Mail, Kalender, Aufgaben, Chat oder Ressourcenplanung, erweitert durch zusätzlich auf Basis von Domino-Datenbanken bereitgestellte Anwendungen. Darüber hinaus sind im Notes Client einige Internet-Standard-Clients integriert, wie z.B. ein LDAP-Client, ein Messenger oder ein POP3 oder IMAP Client. Die reinen Internet-Dienste, die Domino bereitstellt, werden über die entsprechenden Clients, wie Browser, LDAP-Client oder E-Mail-Programm, abgeholt. Gleichzeitig gibt es die Möglichkeit, reine Domino-Datenbanken auch über den HTTP-Task rendern zu lassen, so dass Domino-Anwendungen auch über einen Browser zur Verfügung stehen. Insbesondere die generischen Domino-Büro-Anwendungen, wie E-Mail, Kalender oder Aufgaben, aber auch einige administrative Funktionen, stehen über den Browser zur Verfügung. In beiden Fällen ist Domino der zentrale Knoten, über den die Dienste bereitgestellt werden. Eine Domino-Infrastruktur besteht also mindestens aus einem einzelnen Dominoserver. Allerdings sei darauf hingewiesen, dass administrative Aufgaben am einfachsten über den Domino Administrator erledigt werden. Gestaltungsänderungen („Domino-Programmierung“) an Domino-Datenbanken können nur über den Domino Designer durchgeführt werden. Hierfür steht keine Browserschnittstelle zur Verfügung. Abb. 18-1 zeigt eine solche einfache Infrastruktur. Links in der Grafik wird der generische Domino-Zugriff auf dem Server gezeigt. Sowohl die Büroanwendungen als auch viele Erweiterungen können generisch über Domino erreicht werden. Zusätzlich wird der Server über den Domino Administrator verwaltet und Datenbanken über den Domino Designer gestaltet. Gleichzeitig befindet sich in der Grafik auf der rechten Seite ein Internet-Client. Dieser kann über den HTTP-Task entweder auf dem klassischen Weg statische HTML-Seiten aufrufen, die vom Domino-HTTP-Task bereit gestellt werden, oder Domino-Datenbanken über den Domino-HTTP-Task rendern lassen. Darüber hinaus ist natürlich insbesondere der Servlet Container interessant, über den DominoDaten im Browser dargestellt werden können.
18.1 Infrastruktur, Domino-Core-Applikationen
671
Abb. 18-1 Einfache Domino-Infrastruktur
Interessant wird die Erweiterung dieses Szenarios durch die Replikation (Abb. 18-2). In einer klassischen Redaktionsumgebung (Web Content Management) wird der Redakteur im Notes Client im geschützten Intranet auf seinem Redaktionsserver Inhalte in der Domino-Datenbank bereitstellen. Durch Replikation gelangen die Daten auf einen Domino-Server, der als Webserver fungiert und über den HTTP-Task oder z.B. einen Display Layer aus Servlets oder
Abb. 18-2 Domino-Infrastruktur mit Replikation, Redaktions- und Display Server
672
18 Domino-Projektentwicklung
JSP-Seiten (in Kombination mit einem Application Server) diese Inhalte in einer DMZ an den Endverbraucher ausliefert. Die Replikation erlaubt so eine einfache Trennung zwischen geschützter Redaktion und Internet. Bei der Anwendungsentwicklung von Domino-Java-Anwendungen werden zum einen Werkzeuge zur reinen Domino-Entwicklung, als auch die typischen Java-Entwicklungswerkzeuge benötigt. Hierbei verteilen sich für die Domino-Entwicklung die einzelnen Aufgaben auf die Anwendungen Notes Client, Domino Designer und Domino Administrator wie im Folgenden beschrieben.
18.1.1
Notes Client
Der Notes Client (Abb. 18-3) ist die Basisanwendung für den Domino-Benutzer. Er ist die Büroanwendung, über die E-Mail, Kalender, Aufgaben, aber auch Chat, Suchfunktionen und erweiterte Anwendungen abgewickelt werden. Für die Anwendungsentwicklung selbst wird der Client direkt nicht benötigt, ist aber natürlich das Frontend, in dem die Domino-Anwendungen betrieben werden, sofern es sich nicht um eine Webanwendung handelt. Technisch ist der Notes Client die Basis für den Domino Designer und den Domino Administrator, die in der Regel zusammen mit dem Notes Client installiert werden, auch wenn dies eigenständige Anwendungen sind.
Abb. 18-3 Notes Client
18.1.2 Domino Designer 18.1.2
673
Domino Designer
Der Domino Designer ist das IDE für die Domino-Anwendungsentwicklung, insbesondere für die Entwicklung von Domino-spezifischen Gestaltungselementen wie Ansichten und Masken (s. Kap. 3.1). In der Regel wird bei der Java-Entwicklung für Domino der Domino Designer verwendet, um die Basiselemente der Domino-Datenbank wie die Datenbank selbst, die grundlegenden Ansichten, Masken, Agenten und Skriptbibliotheken, anzulegen. Hat die Anwendung ein Frontend, das im Notes Client betrieben werden soll, werden auch die hierfür benötigten Elemente für die Benutzeroberfläche im Designer entwickelt. Auch wenn es technisch möglich ist, Java-Programmierung im Domino Designer vorzunehmen, so wird empfohlen, hierfür ein spezielles Java IDE zu verwenden. Lediglich der Basiscode wird im Designer angelegt und eventuell extern entwickelte Java-Bibliotheken über den Designer in Bibliotheken eingebunden.
18.1.3
Domino Administrator
Auf den Domino Administrator wurde bisher noch nicht eingegangen. Er ist die zentrale Stelle zur Bewältigung der administrativen Aufgaben in Domino. Zwar können einige Basisfunktionen direkt im Notes Client in der Datenbank names.nsf erledigt werden, aber der Administrator bietet insgesamt ein ausgereiftes und komfortables Werkzeug, auf das nicht verzichtet werden sollte. Bei der Domino-Anwendungsentwicklung darf weder unterschätzt noch übersehen werden, dass eine robuste und erfolgreiche Anwendung im Domino-Umfeld auch stark von einer guten Planung der Infrastruktur und der korrekten administrativen Einrichtung abhängt. Insbesondere die Berechtigungen auf der Anwendung und die Verteilung auf die Server muss gut geplant werden. Folgende Aufzählung gibt einen ersten Überblick über wichtige bei der Administration benötigte Datenbanken, während das nächste Kapitel einen Überblick über die Tasks und Services eines Domino-Servers gibt, wobei die meisten dieser Datenbanken implizit vom Domino-Server erstellt werden. Da die Domino-Administration Seminare und Bibliotheken füllt, kann die folgende Übersicht lediglich einen allerersten Überblick über wichtige Verwaltungsaufgaben und Datenbanken geben, um dem Entwickler einen Anhaltspunkt für den Einstieg zu geben.
•
Domino Directory – names.nsf Das Herz der Domino-Administration ist das Domino Directory, das durch die Datenbank names.nsf auf einem Domino-Server gebildet wird. Besteht eine Domino-Infrastruktur aus mehreren Servern in einer Domäne, so werden über alle Server Repliken der Datenbank names.nsf verwendet, wobei für jeden Server individuelle Konfigurationen in dieser Datenbank vorgehalten werden.
674
•
•
•
18 Domino-Projektentwicklung Die Datenbank names.nsf enthält Informationen über Benutzer, Gruppen, Server, Internetkonfigurationen, Berechtigungen und Policies. Darüber hinaus befinden sich dort Basisnformationen für den LDAP-Dienst und die Domino-Domäne. Ebenso werden in names.nsf Konfigurationen für das Mailrouting, Serververbindungen, aber auch Clusterkonfigurationen und Domino- und Internetzertifikate verwaltet. Besonders wichtige Elemente sind die Informationen über Benutzer und Gruppen und die Serverdokumente, in denen die wichtigsten Konfigurationen für die Server vorgehalten werden. Die Datenbank names.nsf kann direkt im Notes Client geöffnet werden, ist aber in den Domino Designer so integriert, dass vorzuziehen ist, die Konfigurationen dort vorzunehmen, zumal dort auch die Zusammenhänge zu anderen Konfigurationsdatenbanken deutlicher werden. Die Datenbank names.nsf gibt es übrigens nicht nur auf einem Domino-Server, sondern auch auf jedem Notes Client (oder Domino Designer oder Administrator). Dort dient sie als Speicher des persönlichen Adressbuchs und für lokale Konfigurationen. Sowohl auf dem Server als auch auf dem Client ist diese Datenbank obligatorisch. Mailbox – mail.box Das Mail Routing in Domino nutzt als zentrales Element die Datenbank mail.box. E-Mails und Domino-Mails werden in dieser Datenbank als DominoDokument erstellt, um dann vom Router weiterverteilt zu werden. In der Datenbank mail.box werden eventuelle nicht zustellbare Mails deponiert (genauer gesagt, bleiben sie dort liegen). Die zentrale Funktion der mail.box macht sich auch die Klasse djbuch.kapitel_19.EmailNotes aus dem Beispielcode zunutze, die durch direkten Zugriff auf diese Datenbank die Funktionen des Notes-Mailroutings komfortabel verfügbar macht. Normalerweise wird jedoch nicht direkt auf diese Datenbank zugegriffen, da sie lediglich als Repository für das Mailrouting gedacht ist. Domino Catalog – catalog.nsf Der Domino Domain Catalog ist eine Datenbank, in der zum einen Übersichten über die Datenbanken und Zugriffsrechte auf diese gesammelt und aufgezeigt werden und zum anderen auch die so genannte Domain Search – eine globale Suche über mehrere Datenbanken und Filesysteme einer Domino-Domäne – administriert wird. Der Catalog wird normalerweise genutzt, um einen Überblick über die in einer Domäne verfügbaren Datenbanken zu erhalten. Administration Requests – admin4.nsf
18.1.3 Domino Administrator
•
•
•
•
•
675
Komplexe administrative Aufgaben, die aus mehreren Transaktionen bestehen, wickelt Domino über die Datenbank admin4.nsf ab. Soll z.B. ein Benutzer aus der Domäne gelöscht werden, so ist es nicht sinnvoll, lediglich das Benutzerdokument aus der Datenbank names.nsf zu löschen, da zusätzlich weitere Arbeiten erledigt werden müssen. Hierzu gehört z.B. die Bereinigung der Zugriffskontrolllisten, das Entfernen der zugehörigen Maildatenbank oder der Eintrag des Benutzers in eine Ausschlussliste, um zukünftige Zugriffe auf das System auszuschließen. Daher sieht Domino vor, komplexe Aufgaben über den Domino Administrator auszulösen. Dieser legt eine entsprechende Anforderung in die Datenbank „Administration Requests“, wo diese dann je nach Art der Anforderung innerhalb einer bestimmten Periode oder spätestens im Nachtlauf erledigt werden. Diese Datenbank wird normalerweise lediglich zur Kontrolle vom Administrator geöffnet. Domino Log – log.nsf Die Datenbank log.nsf ist das Domino-Log und enthält die Meldungen der Serverkonsole. Die Logeinträge werden rotierend gelöscht. Sie sind nach verschiedenen Kategorien aufgeteilt, damit diese leichter gefunden werden können. Die Datenbank existiert nicht nur auf dem Server als Console Log, sondern auch auf dem Client. Dort werden lokale Ereignismeldungen in dieser Datenbank gespeichert. In jedem Fall sollte bei Problemen und Fragen immer frühzeitig ein Blick in die Serverkonsole oder in die Datenbank log.nsf geworfen werden. Dort findet sich oft schnell eine Antwort, wenn es mal klemmt. Domino-LDAP-Schema – schema.nsf Sobald der LDAP-Task aktiviert wird, wird die Datenbank schema.nsf erstellt. Sie enthält die LDAP-Schema-Definition des Domino Directory. Normalerweise muss und sollte in dieser Datenbank nichts geändert werden. Sie ist jedoch gut geeignet, um das Domino Directory besser zu verstehen und sich einen Überblick über die verschiedenen verwendeten Attribute zu verschaffen. Directory Assistance – da.nsf Sollen in Domino mehrere Verzeichnisse miteinander verbunden werden – es ist z.B. möglich, dem Domino Directory als sekundäres Verzeichnis ein LDAP-Verzeichnis beizusteuern –, so wird dies durch die Datenbank da.nsf ermöglicht. Die Directory-Assistance-Datenbank muss im Serverkonfigurationsdokument angemeldet werden. In der Datenbank da.nsf selbst wird die Anbindung der sekundären Verzeichnisse konfiguriert. CA Administration – certserv.nsf (R5) Zur Verwaltung von Schlüsselringen und Zertifikatsanfragen für Internet (SSL) Zertifikate, steht seit Domino R5 die Datenbank certserv.nsf zur Verfügung. Diese wird in Domino R6 durch den CA-Prozess abgelöst, stellt jedoch insbesondere für einfache Konfigurationen eine einfache Möglichkeit dar, SSL-Zertifikate für die Zertifizierung durch eine externe Rootinstanz bereitzustellen und in einem Schlüsselring zu verwalten. CA Prozess / ICL Datenbank – Issued Certificate List (ICL) database
676
•
18 Domino-Projektentwicklung Internetzertifikate und Dominozertifikate sind seit Domino R6 stark miteinander verzahnt und werden über den CA-Prozess verwaltet. Wird der CA-Prozess genutzt, so existiert für jede Domain eine ICL-Datenbank, in der die Zertifikatsanforderungen verwaltet werden, aber auch Ausschlusslisten geführt werden. Monitoring und Events- events4.nsf, ddm.nsf Die Datenbank events4.nsf enthält Konfigurationen für die Steuerung von Statistik und Monitoring. Hierbei können Serverstatistiken, ACLs und die Replikation überwacht werden. Seit Domino R7 werden in der Datenbank ddm.nsf, der so genannten Domino Domain Monitoring Database, ausführliche Informationen über kritische Ereignisse gesammelt. Die Überwachung und Sammlung im Monitoring wird ebenfalls über events4.nsf gesteuert.
18.2
Anwendungen, E-Mail, Replikation
Die Kenntnis der Tasks im Domino Designer ist auch für die Anwendungsentwicklung hilfreich. Spätestens wenn die Performance optimiert werden soll (oder muss), ist es wichtig zu wissen, wer sonst noch auf dem Server Ressourcen belegt. Domino bietet neben den für den eigenen Betrieb notwendigen Tasks fast alle gängigen Internet-Dienste als optionalen Service an. Dies reicht vom einfachen SMTP, HTTP oder POP3 Server bis hin zu NNTP oder IMAP. Die auf einem Server aktiven Tasks können im Domino Designer gut eingesehen werden (s. Abb. 18-4). Dort werden optional die verwendeten Ports und die benötigte Prozessorleistung angezeigt. Wichtige Tasks finden sich in der folgenden Übersicht: •
•
•
•
•
Database Server Der Task „Database Server“ ist der eigentliche Worker Task für die Abwicklung von Client-Verbindungen. Domino öffnet per Default einige davon auf Vorrat. Domino-Mail-Routing Ist das Domino-Mail-Routing, nicht zu verwechseln mit dem SMTP Mail Routing, aktiviert, wird es über diesen Task abgewickelt. Über das Domino-MailRouting werden innerhalb von Domino Namend Networks, die im Serverdokument definiert werden, Domino-Mails zugestellt. SMTP Mail Routing Der Domino-SMTP-Dienst zeigt sich im Task-SMTP Mail-Routing, sofern er aktiviert ist. Replikation Auch die Replikation wird in Domino durch einen eigenen Prozess abgewickelt. Ist dieser Task nicht aktiviert, so kann der Server keine Replikation abwickeln, wobei gleichwohl ein entfernter Server oder Client eine Replikation anfordern und (selbst) ausführen kann. POP3, IMAP und NNTP
18.2 Anwendungen, E-Mail, Replikation
677
Abb. 18-4 Domino-Administrator und Taskliste
•
•
•
Domino bietet die Standard-Internet-Dienste POP3, IMAP und NNTP an. POP3 und IMAP bilden die Mail-Datenbanken der Domino-Benutzer auf diesem Dienst ab. Oder anders formuliert: Domino-Mail kann auch durch POP3 oder IMAP abgeholt werden. Der NNTP („News“)-Dienst benötigt eigene Datenbanken auf Basis der Schablone „nntpcl6.nsf“. Diese Datenbank(en) können generisch über den Notes Client genutzt werden und stehen gleichzeitig über den NNTP-Dienst gewöhnlichen News Clients zur Verfügung. Agent Manager Domino-Agenten (s. Kap. 3.4, 4.10 und 4.18) werden im Kontext des Prozesses „Agent Manager“ ausgeführt. Der Agent Manager ist unter anderem für die periodische Ausführung von Agenten zuständig. Admin Prozess Komplexe administrative Transaktionen (s.o. Datenbank „Administration Requests“) werden über den Administrationsprozess abgewickelt. Dieser bildet diese Funktionen ab und kann gleichzeitig über die Java-Klasse AdministrationProcess instanziert werden. Ist dieser Prozess nicht aktiv, werden diese Anfragen nicht abgearbeitet. Calendar Connector und Schedule Manager
678
•
• •
•
18 Domino-Projektentwicklung Um Kalendersynchronisationen vornehmen zu können, bedient sich Domino des Tasks „Calendar Connector“. Abstimmung von Zeitplänen werden über den Schedule Manager abgewickelt. Rooms and Resources Gleiches gilt für die Abwicklung der Verwaltung von Räumen und Ressourcen bezüglich des Tasks „Rooms and Resources“ Directory Indexer Datenbankindizes werden vom Directory Indexer angelegt und aktualisiert. LDAP Der LDAP-Dienst hat einen separaten Task, der aus zwei Prozessen, einem Listener und einem Überwachungstask, besteht. Der Domino-LDAP-Dienst bildet das Domino Directory als LDAP-Verzeichnis ab. Hierdurch wird es möglich, Informationen über Domino-Benutzer, -Gruppen, -Server und -Zertifikate auch externen Anwendungen über den LDAP-Standard zugänglich zu machen. Auch Authentifizierungen kann somit Domino für externe Anwendungen übernehmen. HTTP Der Domino-Webserver arbeitet zunächst wie ein gewöhnlicher HTTP Server. Sein Rootverzeichnis befindet sich im Verzeichnis domino/html/ innerhalb des Domino-Dataverzeichnisses119. Dort abgelegte Webressourcen (HTML-Dateien, Bilder, etc.) können wie gewohnt an einen Browser ausgeliefert werden. Konfigurationen des Webservers werden über den Domino Administrator vorgenommen. So werden z.B. Filesystem-Berechtigungen (s. Abb. 18-5) für den Zugriff
Abb. 18-5 Filesystem-Berechtigung für HTTP-Zugriff
über den Browser im Abschnitt „Configuration“ -> „Internet Sites“ und dort in einem Internet-Site-Dokument mit dem Befehl „WebSite“ -> „Create File Protection“ angelegt. In dieser Ansicht und in den Internet-Site-Dokumenten werden auch virtuelle Server und Domains oder Redirections eingerichtet. Wie Domino auch mit externen HTTP Servern zusammenarbeitet, lesen Sie auch in Kapitel 16ff. 119
In einer Windows Standard Installation ist dies C:\Lotus\Domino\Data\domino\html\
18.3 Entwicklungsumgebungen
•
679
Neben der Anzeige statischer Inhalte ist der Domino-HTTP-Task auch für das Rendern von Domino-Masken und -Ansichten für den Browser zuständig (s. Kap. 3.2.11). DIIOP Der Domino-IIOP-Task ermöglicht die Remote-Ausführung von Domino-JavaAnwendungen. Ausführliche Informationen über Setup und Verwendung finden Sie in Kapitel 5.3.
18.3
Entwicklungsumgebungen
Um Domino-Java-Anwendungen entwickeln zu können, benötigen Sie einige Werkzeuge und eine vorbereitete Infrastruktur für die Entwicklung. Je nach Aufgabenstellung und Team sind verschiedene Umgebungen denkbar und es kommen unterschiedliche Werkzeuge zum Einsatz. Abb. 18-6 zeigt zunächst die in diesem Buch beschriebenen Möglichkeiten, an denen die Domino-Java-Programmentwicklung zum Einsatz kommt. Beginnen Sie immer mit Ihrer Entwicklung in dem Java IDE Ihrer Wahl. Die wesentlichen Bestandteile der Anwendungen entwickeln Sie dort. Im Domino Designer werden diese Bestandteile dann eingebettet. Nun sind drei Einsatzszenarien denkbar: •
Entwicklung von Domino-Java-Agenten zum Einsatz im Domino Designer.
•
Entwicklung von Domino-Servlets zum Betrieb im Domino-Servlet Container oder im Servlet Container eines Drittherstellers.
•
Entwicklung von Domino-Java-Anwendungen im J2EE-Umfeld.
Der Code der Domino-Java-Agenten wird vom IDE als Jar-Datei exportiert und am besten in eine Code-Bibliothek im Designer importiert. Diese Bibliothek wiederum wird im Domino-Java-Agenten eingebunden, um den Code dort zur Verfügung zu stellen. Der häufigste Anwendungsfall dieser Art werden periodische oder in einigen Fällen auch manuell gestartete Domino-Java-Agenten sein. Seltener werden JavaAgenten als QuerySave Agent in Masken eingebettet oder als Domino-Web-Agent gestartet werden. Der Einsatz von Domino-Servlets empfiehlt sich bei der Entwicklung von webgestützten Anwendungen, die auch in reinen Domino-Umgebungen zum Einsatz kommen sollen, ohne auf einen externen Application Server angewiesen zu sein. Größere Java-Anwendungen, die auf Domino-Objekte zurückgreifen, können im J2EE-Umfeld entwickelt werden und greifen über die API-Schnittstellen auf diese entweder direkt über die lokale Session oder remote über DIIOP zu. Hierbei können nun alle im J2EE-Umfeld verfügbaren Methoden, Werkzeuge und Server eingesetzt werden.
680
18 Domino-Projektentwicklung
Abb. 18-6 Typische Anwendungsfälle für Domino-Java-Entwicklung
Für die ersten beiden Fälle (Domino-Agenten und Domino-Servlets ) werden neben dem Java IDE lediglich der Domino Designer zur Entwicklung der Ansichten und Agenten und ein Domino-Server benötigt. Im J2EE-Umfeld werden zusätzlich die dort benötigten Application Server und Werkzeuge benötigt. Der Domino-Server ist hier zwar nur „ausführende“ Instanz, aber natürlich werden Sie weiterhin eine Domino-Datenbank und mindestens zugehörige Ansichten benötigen, um
18.3 Entwicklungsumgebungen
681
auf die Domino-Objekte sinnvoll zugreifen zu können. Bei der Entwicklung sind nun verschieden komplexe Szenarien denkbar, die unterschiedlichen Aufgabenstellungen gerecht werden: •
Szenario 1 – Java IDE, Designer und Server auf einem Arbeitsplatz Die einfachste Herangehensweise ist natürlich, die komplette Entwicklung auf einem einzelnen Arbeitsplatz vorzusehen (Abb. 18-7). Hierbei ist neben dem Java
Abb. 18-7 Einfache Entwicklungsumgebung auf alleinstehendem Arbeitsplatz
IDE, dem Domino Designer und dem Domino-Server in der Regel immer auch ein Domino Administrator notwendig, um einfach für ein korrektes Setup des Servers sorgen zu können. Technisch stellt dies keine Schwierigkeit dar. Der Server kann problemlos zusammen mit allen Clients – dem Notes Client, dem Domino Designer und dem Domino Administrator – auf einem Arbeitsplatz installiert werden. Zusätzlich ist es sinnvoll eine Codeversionskontrolle vorzusehen, z.B. durch ein CVS Repository. Einen Ausnahmefall bildet die Entwicklung von Domino-Java-Anwendungen, die lediglich für den lokalen Einsatz auf Notes Clients vorgesehen sind. Hier wird natürlich zwingend der Notes Client, nicht aber der Domino-Server benötigt. Dieses Setup ist für kleine Aufgaben hervorragend geeignet und kann sogar in (sehr) kleinen Teams (1..3 Entwickler) angewendet werden, wobei die jeweiligen Testserver der einzelnen Arbeitsplätze in derselben Domino-Domäne aufgesetzt werden sollten. Die so entwickelten Anwendungen können zwischen den Arbeitsplätzen repliziert und so auf dem aktuellen Stand gehalten werden.
682 •
18 Domino-Projektentwicklung Szenario 2 – Java IDE, Designer und optionaler Server auf einem Arbeitsplatz mit gleichzeitig getrenntem Entwicklungs- und Testserver In der Regel wird man schnell in die Situation kommen, in der auf einen zentralen Entwicklungs- und Testserver nicht verzichtet werden kann (Abb. 18-8).
Abb. 18-8 Erweiterte Entwicklungsumgebung mit mehreren Arbeitsplätzen
Auf den Arbeitsplätzen wird dann weiterhin mit der kompletten in Szenario 1 beschriebenen Umgebung gearbeitet. Ist ein stabiler Zwischenstand des Codes erreicht, für den ein Entwickler zuständig ist, publiziert er seine Servlets etc. auf den zentralen Testserver und repliziert seine Datenbanken. Arbeiten viele Entwickler gleichzeitig auf einer Datenbank, so empfiehlt sich das Design Locking, wobei der zentrale Testserver gleichzeitig als Administrationsserver (im lokalen Netz) und somit auch als zentraler Lockingserver (s. Kap. 9.7) zum Einsatz kommt. Während der Entwickler seine Arbeiten auf dem lokalen Domino-Server durchführt und auch z.B. JUnit Tests und kleinere funktionale Tests lokal durchführt, wird ein weiterer Entwickler – zuständig für Tests und Qualitätssicherung – über den zentralen Testserver regelmäßige Tests über die gesamte Anwendung durchführen. Hierzu gehören neben Lasttests optional auch Integrationstests, für die dann die zugehörige Infrastruktur dort abgebildet werden muss und die neuerliche Durchführung diesmal aller Unittests aller beteiligten Entwickler. Der Testarbeitsplatz benötigt keinen lokalen Domino-Server. Neben der somit erreichten Ausgliederung der Zuständigkeit für Tests könnte z.B. über weitere Arbeitsplätze die Administration der verschiedenen beteiligten Komponenten und Domino-Server herausgelöst werden.
18.3 Entwicklungsumgebungen
•
683
Szenario 3 – Erweiterung durch zusätzliche Testserver Die Komponenten aus Szenario 2 können nun nach den jeweiligen Bedürfnissen erweitert werden (Abb. 18-9). Durch die Replikation lassen sich sehr einfach zusätzliche Testserver aufsetzen, die für unterschiedliche Testaufgaben zuständig sind. Da sich Domino-Server leicht partitionieren lassen, können leicht auf einer Hardware mehrere logisch getrennte Systeme aufgebaut werden. In großen Installationen ist es hilfreich, einen Server vorzuhalten, der der Auslie-
Abb. 18-9 Erweiterte Entwicklungsumgebung mit mehreren Arbeitsplätzen und Entwicklungsservern
ferungsversion entspricht und der immer nur dann mit der Entwicklung repliziert wird, wenn eine freigegebene Version vorliegt. Von diesem Server werden die Schablonen der Domino-Datenbanken gezogen. Java-Code in Domino kann sich entweder in einer externen Jar-Datei im Filesystem befinden oder wird in einer Bibliothek in der Domino-Datenbank hinterlegt. Im letzteren Fall gestaltet sich die Verteilung des jeweils aktuellen Java-Codes logistisch einfach, birgt aber – dies gilt für alle Gestaltungselemente einer DominoDatenbank – das Risiko, dass zwei Entwickler gleichzeitig die Bibliothek aktualisieren und somit einer der Entwicklungsstände verloren geht. Da Designelemente keine Replikationskonflikte erzeugen, sondern sich die aktuellere Version durchsetzt, muss die Replikation einer Datenbank, die im Team entwickelt wird, sauber abgestimmt sein.
684
18 Domino-Projektentwicklung Das Designlocking schafft hier Abhilfe. Bei Aktualisieren der Jar-Dateien in der Domino-Java-Bibliothek sei noch darauf hingewiesen, dass natürlich sichergestellt sein muss, dass auch im Java-Code die Versionskontrolle funktioniert und alle beteiligten Entwickler nach einem zuvor abgestimmten Schema ihren Code regelmäßig im CVS einchecken. Dies ist insbesondere dann wichtig, wenn mit externen Jar-Dateien im Filesystem gearbeitet wird. Diese Dateien können leider nicht über die Replikation verteilt werden und müssen manuell ausgerollt werden. Bei einer sauber eingespielten Versionskontrolle fällt dafür nicht nur die Problematik überschriebener Designelemente weg, sondern der Rollout kann über ein Deployment Tool oder Builtskript durch make oder ant regelmäßig und kontrolliert auf die beteiligten Systeme verteilt werden.
18.4
Team
Die Java-Entwicklung für Domino lässt sich nicht auf die reine Java-Entwicklung beschränken. Ein Domino-Server ist ein komplexes System, dessen Administration und Einbindung in eine Infrastruktur beherrscht werden will. Für alle in diesem Buch vorgestellten Techniken wurde immer wieder auch auf die hierfür notwendigen administrativen Tasks eingegangen. Dennoch wird üblicherweise in einem größeren Team auch eine administrative Verantwortlichkeit delegiert werden, so dass die Entwickler sich weiterhin auf die Programmierung konzentrieren können, während das Administrationsteam die Domino-Server aufsetzt und konfiguriert und deren Betrieb sicherstellt. Die Notwendigkeit, neben der Java-Programmierung auch genügend Support für die Administration bereitzustellen, darf nicht unterschätzt werden. Nicht nur, dass insbesondere die Sicherheit im Domino-System eine komplexe Aufgabe ist, die eben von administrativer und Programmierseite genügend Berücksichtigung finden muss, es ist auch nicht zu unterschätzen, dass sich Motivation und Effizienz steigern lassen, wenn die Programmierung durch gut administrierte Systeme unterstützt wird. Hieraus ergeben sich in einem Entwicklungsteam mindestens folgende Rollen, die natürlich im Einzelfall auch in Personalunion Einzelner besetzt werden: Die JavaProgrammierung wird vom Java-Entwicklerteam übernommen. Hier steht das notwendige Know-how aus dem Java-Umfeld zur Verfügung. Ergänzt wird das Entwicklungsteam durch Domino-Entwickler, die die Bereitstellung von Domino-Designelementen übernehmen. Idealerweise wird dieses Know-how von allen Entwicklern geteilt. Während Unit Tests und funktionale Tests in der Entwicklungsabteilung selbst erledigt werden, können Acceptance-, Last- und Integrationstests ausgelagert werden. Unerlässlich ist die Bereitstellung von Domino-Administrations-Know-how. Die besondere Chance, die Java im Domino-Umfeld bietet, ist, dass hierdurch eine Entwicklungsplattform bereit steht, die von einer breiten Basis von Entwicklern beherrscht wird. So wird es leicht möglich, schnell effiziente und gute Teams für die Domino-Entwicklung zusammenzustellen.
18.5 Zusammenfassung
18.5
685
Zusammenfassung
Aufgrund der Server-Client-Umgebung und einiger Besonderheiten der DominoObjekt- und Speicherstruktur wird es notwendig, die Domino-Java-Entwicklung in einer vorkonfigurierten Infrastruktur aufzusetzen. Gleichzeitig lässt sich aber das benötigte Know-how leicht auf verschiedene Rollen verteilen, in denen Java-Programmierung, und Domino-Administration aufgeteilt werden. So bietet Java die Chance, im komplexen Domino-Umfeld als weit verbreitete und ausgereifte Programmiersprache schnell und effizient Anwendungen zu entwickeln, die sich gleichzeitig, durch breite Basis an Frameworks in komplexe Umgebungen einbinden lassen.
19
19 Debugging und Qualitätssicherung
In diesem Kapitel: Domino-Java-Anwendungen testen Domino-Java-Anwendungen und JUnit Agenten Remote Debugging Logging in lotus.domino.Log Logging mit Domino und log4j Domino-Debug-Parameter und Notes-INI-Variablen
688
19 Debugging und Qualitätssicherung
19.1
Notwendigkeit und verschiedene Typen von Tests
Die grundsätzliche Notwendigkeit, Anwendungen zu testen, wird kein Anwendungsentwickler verneinen, auch wenn die Praxis zeigt, dass Tests oft vernachlässigt werden. In einer komplexen und womöglich verteilten Infrastruktur von Domino sind – möglichst automatisierte – Tests unerlässlich. Sie stellen sicher, dass das gewünschte Businessmodell tatsächlich abgebildet wird und natürlich der Code keine Bugs enthält. Spätestens beim Debugging werden Techniken benötigt, die die Domino-Java-Objekte so zur Verfügung stellen, dass sie in externen Werkzeugen, wie z.B. einem Debugger, geladen werden können. Im Folgenden wird aufgezeigt, wie durch den Einsatz einfacher Designpattern Domino-Java-Anwendungen so aufgebaut werden, dass dies ermöglicht wird. Datenbankanwendungen sind an sich schon schwierig zu testen, da ein Test immer auch Testdaten benötigen werden und deren Konsistenz nach einer Operation überprüft werden muss. Zusätzlich kommt bei Domino noch die Schwierigkeit hinzu, dass alle Objekte an eine Domino-Session gebunden sind, die wiederum selbst Einfluss auf ein Testergebnis haben kann (z.B. durch Benutzerrechte). Daher werden eigene Strategien benötigt, um Java-Code für Domino-Anwendungen testen zu können, durch die insbesondere folgende Aufgabenstellungen gelöst werden müssen: 1
Bereitstellung einer (Test-) Domino-Session Je nachdem, um welche Art von Domino-Java-Anwendung es sich handelt, die getestet werden soll, werden auch die Tests unterschiedlich aussehen. Im Wesentlichen kann nach drei verschiedenen Anwendungsfällen unterschieden werden: Java-Agenten, die direkt im Designer erstellt wurden und eine Session über den AgentContext beziehen, eigenständige Java-Anwendungen, die über die NotesFactory ihre Session beziehen und in diesem Zusammenhang der Sonderfall eines Servlets, das ebenfalls über die NotesFactory eine Session erhält, aber als Webanwendung noch besondere Aufmerksamkeit im Zusammenhang mit Gleichzeitigkeit (MultiThreading) und Lastverhalten benötigt. Der Bezug der geeigneten Domino-Session für die verschiedenen Tests wird in den jeweiligen folgenden Kapiteln aufgezeigt.
2
Entkoppelung des Java-Codes vom Designer, um einen Java-Debugger einsetzen zu können. Werden Java-Anwendungen als Agenten im Domino-Designer entwickelt, um z.B. die besonderen Trigger eines Domino-Agenten nutzen zu können, stellt sich das Debugging besonders schwierig dar. Hieraus folgen Designpattern, die schon bei der Anlage einer solchen Anwendung berücksichtigt werden müssen, um Tests und Debugging zu ermöglichen (s. Kap. 19.2f.).
19.1 Notwendigkeit und verschiedene Typen von Tests 3
4
5
6
7
8
9
689
Umgang mit Remote-Anwendungen Dadurch, dass Domino immer auch eine Client-Server-Anwendung ist, wird jeder Test gleichzeitig (und ungewollt) die Remote-Situation der (Test-) Umgebung mittesten. Für jeden Test wird man also immer eine Testumgebung mit Client und Server benötigen und diese Tests entweder über die lokale Session oder eine IIOP-Verbindung testen müssen. Dies kann den Kontext einer Anwendung verändern und muss berücksichtigt werden, wie in den folgenden Beispielen gezeigt wird (s. Kap. 19.2f.). In-Container-Debugging von Java-Agenten innerhalb des Domino-Servers Domino stellt Mechanismen bereit, über die Domino-Java-Agenten direkt auf dem Server in ihrem Kontext debugged werden können (s. Kap. 19.4). Bereitstellung von Testdaten und Überprüfen dieser auf Konsistenz nach Ausführung der Tests Jede Datenbank ist nichts ohne ihre Daten, also wird ein Test auch nur so gut sein wie die hierbei verwendeten Testdaten. Diese müssen möglichst realitätsnah und automatisiert bereitgestellt werden. JUnit bietet hier die notwendigen Rahmenbedingungen (s. Kap. 19.5). Automatisieren von Tests, funktionale Tests und JUnit Auch Tests für Domino-Anwendungen lassen sich durch JUnit automatisieren. Hierbei liegt das Augenmerk sowohl auf Unit Tests als auch auf funktionalen Tests, die sich mit dem JUnit Framework abbilden lassen (s. Kap. 19.5). Testen von Benutzerrechten Benutzerrechte und die Kontrolle ihrer Funktionalität gehören mit zu dem schwierigsten, was die Domino-Entwicklung zu bieten hat. Werden für eine Domino-Anwendung unterschiedliche Rollen und Gruppen definiert, so müssen die hieraus abgeleiteten Erwartungen im Businessmodell auf alle hier beschriebenen Testmechanismen angewendet werden. Insbsondere müssen die Tests mit jeweils unterschiedlichen Sessions ausgeführt werden, die dann die jeweiligen Berechtigungen anhand von speziell eingerichteten Testusern erhalten. Folglich werden für Berechtigungs-Tests nicht nur speziell bereitgestellte Testdaten, sondern auch mit vordefinierten Rechten ausgestattete Testuser benötigt. Mindestens muss die Businesslogik einer Domino-(Java) Anwendung jeweils mit allen geplanten Rollen ausgeführt werden um die unterschiedlichen Funktionen zu kontrollieren. Logging Das Logging kann in einer Anwendung zwei Zwecke erfüllen. Zum einen wird ein gut eingerichtetes Logging – in verschieden ausführlichen Varianten – dem Endanwender, bzw. dem Administrator, der eine Anwendung betreut, ausführliche Informationen über den Ablauf der durch die Anwendung angestoßenen Businessabläufe geben. Zusätzlich kann das Logging die rudimentärste Art eines Tests sein, indem gezielt Verarbeitungsszenarien angestoßen und die Loggingausgaben manuell ausgewertet, bzw. nachträglich im Fehlerfall die über das Standardlogging ausgegebenen Meldungen analysiert werden (s. Kap 19.6). Testen von Multithreading-Situationen
690
19 Debugging und Qualitätssicherung
Egal, ob eine Domino-Anwendung als klassische externe Java-Anwendung oder über ein Servlet als Webanwendung ausgeführt wird, handelt es sich immer um eine Multithreaded-Umgebung, in der mit der Gleichzeitigkeit der Verarbeitung gerechnet werden muss. Domino-Anwendungen testen heißt immer auch Multithreaded-Anwendungen testen. Viele der hierbei zu berücksichtigenden Fragestellungen werden bereits durch Domino selbst abgefangen, so dass die sich aus dem Multithreading ergebenden Tests in der Regel auf die Businesslogik der Anwendung selbst beschränkt werden können (s. Kap. 19.7). 10 Lasttests Oft vernachlässigt, aber unerlässlich sind Lasttests auf einem typischen Multiuser-System wie Domino. Sie sind eng verbunden mit dem Multithreading und werden ebenfalls im Kapitel 19.7 behandelt. Als Vorbereitung auf die verschiedenen Tests wird auf dem Arbeitsplatzrechner eine Installation des Notes Clients und des Domino-Servers, der allerdings auch auf einer entfernten Maschine installiert sein kann, benötigt. Zusätzlich ist ein Java IDE mit integriertem Java-Debugger empfehlenswert und – wer es bequem mag – benötigt einen Test Runner für JUnit. Ein Java-Projekt sollte bereits eingerichtet sein und mit den für Domino notwendigen Classpath-Angaben ausgestattet sein (s. Kap. 4.2).
19.2
Agenten debuggen und testen
Um Domino-Java-Agenten gut debuggen und testen zu können, ist es zunächst nötig, dafür zu sorgen, dass der Agent einem typischen Grundraster entspricht. Sinnvollerweise besteht er lediglich aus einem Rumpf, der ein Startobjekt instanziert, über das die eigentliche Businesslogik abgewickelt wird. Dieses Startobjekt ist, ähnlich der main-Methode in einer autarken Java-Anwendung, der Einstiegspunkt für den Java-Agenten und natürlich dann auch für die Tests. Als Beispiel für einen typischen Java-Agenten soll eine Newsletter-Anwendung dienen. Das Beispiel ist einfach gehalten und besteht aus einem Newsletterdokument und einem Agenten, der den Versand abwickelt. Das Newsletterdokument enthält sowohl die Angaben, welche Benutzer den Newsletter erhalten sollen, als auch Subject, Absender und Inhalt der zu erzeugenden Newsletter. Das Beispiel verwendet als Basis eine Javaklasse, die die Besonderheiten des Domino-Mail-Routings berücksichtigt. Sie ist praxiserprobt und kann auch für viele andere Anwendungen als Basis dienen. Das eigentliche Beispiel, an dem ein typischer Test aufgezeigt werden soll, besteht aus folgenden Bestandteilen:
19.2 Agenten debuggen und testen •
691
Maske „FO_NLDoc_k19“ Über die Maske FO_NLDoc_k19 können neue Newsletterdokumente im Notes Client erstellt werden (s. Abb. 19-1). Ein Newsletterdokument ist einfach gehalten
Abb. 19-1 Newsletter-Maske
•
und stellt Felder für die Eingabe von Empfängern, Subject und Inhalt (RichText) bereit. Der Radiobutton isActive ermöglicht es, einen Newsletter zunächst inaktiv zu halten, so dass er nicht abgeschickt werden kann. Ansicht „V_NLDocs_k19“ Über die Ansicht V_NLDocs_k19 können alle vorliegenden Newsletterdokumente angezeigt werden. Inaktive werden durch ein Kreuz gekennzeichnet (s. Abb. 192). Agent „AG_NLVersenden“ Um einen Newsletterversand zu starten, wählt man in der Ansicht V_NLDocs_k19 ein Newsletterdokument aus und startet den Agenten AG_NLVersenden über die Menü-Aktion „Actions“ -> „Kapitel 19“ -> „1. Newsletter versenden“. Wie bereits in Kapitel 2 erläutert, werden Agenten, die als Trigger „Action menu selection“ erhalten, in diesem Menü angezeigt und können so gestartet werden. Dieser Agent enthält den Code, um die Businesslogik zur Abwicklung der Newsletter zu starten.
•
public class NewsletterAgent extends AgentBase
•
Der Code zum Agenten wurde direkt über den Domino Designer eingegeben und liegt zusätzlich dem Beispielcode als Klasse NewsletterAgent im Package djbuch.kapitel_19.Newsletter bei. Diese Klasse ist als typischer – und testfähiger – Agenten-Rumpf angelegt. Hier wird lediglich ein Newsletter-Objekt instanziert und die send-Methode gestartet, um die Ausführung der Newsletteraussendung zu starten.
692
19 Debugging und Qualitätssicherung
Abb. 19-2 Newsletter-Ansicht
•
public class Newsletter
Die Klasse Newsletter enthält die eigentliche Businesslogik des Newsletters. Die Aufgabe dieser Klasse ist es, das ausgewählte Newsletterdokument auszulesen und den Versand an die darin gefundenen Empfänger zu veranlassen. •
public class NLDocument extends DJDocument
Die Klasse NLDocument kapselt ein Newsletterdokument und ist ein alter Bekannter. Sie ist der in Kapitel 13.1.2 vorgestellten Klasse CMSDocument sehr ähnlich. Sie entspricht in der Funktion einem Domino-Document, wurde aber um die benötigten Funktionen erweitert, um Subject, Newsletterinhalt, Empfänger und Status abzufragen bzw. zu setzen. •
public class NewsletterStart implements Runnable
Erst die Klasse NewsletterStart ermöglicht das eigentliche Debugging der Klasse Newsletter. Durch Zuhilfenahme der ebenfalls in Kapitel 13.1.3 vorgestellten Klassen DJAgentContext wird es möglich, das Businessmodell aus NewsletterAgent von außen anzustoßen und im Debugger des IDE zu analysieren. Später wird gezeigt werden, dass diese Methode ebenfalls geeignet ist, Domino-JavaAgenten in einen JUnit Test einzubinden. Listing 19-1 zeigt den bekannten, typischen Rumpf eines Domino-Java-Agenten. Er wird hier nochmals gezeigt, um in den direkten Vergleich mit einer neuen Klasse gestellt zu werden, über die Debugging und Test ermöglicht wird. Lediglich drei Bestandteile sind entscheidend: Der AgentContext als Träger der Session, der aktuellen Datenbank und des DocumentContext, die Instanzierung des Newsletter-Objekts und das „Abfeuern“ der Ausführung durch den Start der Methode send.
19.2 Agenten debuggen und testen
693
public void NotesMain() { try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); Newsletter nl = new Newsletter (agentContext); int count = nl.send(); System.out.println("Anzahl der versendeten NL: " + (count == Newsletter.SEND_ERROR ? "FEHLER!" : "" + count)); } catch(NotesException e) { ... }
Listing 19-1 Klasse NewsletterAgent
Wesentlich länger sollte keiner Ihrer Agenten im Designer sein oder ein Refactoring wird notwendig werden. In manchen Situationen wird es erforderlich sein, dass das Objekt, in dem das Businessmodell abgebildet wird, dem Recycling zugeführt wird, in diesem Beispiel werden jedoch lediglich der AgentContext und die darin enthaltenen Objekte getCurrentDatabase und getDocumentContext verwendet, für dessen Recycling der Agent selbst (also Domino) zuständig ist. Enthält Ihr BusinessObjekt recyclingpflichtige Domino-Java-Objekte (s. Kap. 14.2, §10) muss im finally-Block des Agentenrumpfes noch das Recycling Ihres Businessobjekts angestoßen werden. Innerhalb des Domino-Java-Agenten stellt sich das Debugging oder ein Test schwierig dar. Der Java-Code ist im Agenten gekapselt und von außen nicht zugänglich. Lediglich über eine Ausgabe, z.B. auf die Domino-Java-Console, könnten rudimentäre Debugging-Möglichkeiten eröffnet werden. Auch das in Kapitel 19.4 vorgestellte Remote Debugging ist keine wirkliche Alternative. Der Agent würde nicht durch eine Menü-Aktion eines Benutzers gestartet, die das markierte Dokument als DocumentContext lieferte. Es müsste künstlich ein Document beigegeben und so der eigentliche Code manipuliert werden, um testen zu können, da dies direkt im Code des Agenten erfolgen müsste. Unser Businessobjekt Newsletter basiert auf einem AgentContext. Angenommen es gelänge, einen „künstlichen“ AgentContext zu erzeugen, so kann über diesen das auszuführende NLDocument injiziert werden. Methodenaufruf von send und vorherige Instanzierung des Newsletter-Objekts können dann in einer beliebigen Klasse erfolgen, die im Java-Debugger des IDE untersucht werden kann. Die Hilfsklasse NewsletterStart geht genau diesen Weg. Nicht unerwartet ähnelt sie dem Agenten wie ein Zwilling (s. Listing 19-2). Über getTestAgentContext (s. Listing 19-3) wird ein künstlicher AgentContext erzeugt, der auch hier als Basis für die Instanzierung von Newsletter dient und so eine Ausführung von send ermöglicht. Kleine Unterschiede ergeben sich dadurch, dass die Session natürlich nun nicht mehr „frei Haus“ vom Agenten geliefert, sondern über die NotesFactory bezogen werden muss. Die Änderung ist leicht durchgeführt, aber nicht ohne Risiken und Nebenwirkungen. Je nachdem, wie die Session aufgebaut wird (lokal oder über DIIOP) entsteht ein anderer Kontext, in dem nun das Newsletter-Objekt arbeitet.
694
19 Debugging und Qualitätssicherung
public void run () { Session session = null; DJAgentContext agentContext = null; try { session=NotesFactory.createSession (HOST, USER, PASSWORD); agentContext = getTestAgentContext(session); Newsletter nl = new Newsletter (agentContext); int count = nl.send(); System.out.println("Anzahl der versendeten NL: " + (count == Newsletter.SEND_ERROR ? "FEHLER!" : "" + count)); } catch (NotesException e) { [...] }
Listing 19-2 Klasse NewsletterStart – AgentContext künstlich injizieren private static DJAgentContext getTestAgentContext(Session session) throws NotesException { Database contextDB = null; NLDocument nlDoc = null; contextDB = session .getDatabase("Java/DJBUCH","djbuch/djbuch.nsf"); nlDoc = NLDocument.getInstance(contextDB); nlDoc.setActive(true); nlDoc.setBody("Hallo, ich bin ein NL."); Vector rec=new Vector(); rec.add ("
[email protected]"); rec.add ("
[email protected]"); nlDoc.setRecipients(rec); nlDoc.setSubject("DJBuch Newsletter"); nlDoc.save (true,false); return new DJAgentContext ("Anonymous",contextDB, nlDoc.getDoc()); }
Listing 19-3 Klasse NewsletterStart – Erzeugen eines künstlichen AgentContext
Ein manuell gestarteter Agent, wie in diesem Beispiel, unterliegt immer der lokalen Ausführung, da ein Benutzer diesen Agenten auf seinem Notes Client startet. Also wird für den Test dieses Agenten auch eine lokale Session benötigt. Soll z.B. ein periodischer Agent getestet werden, der auf dem Server startet, so ist es eine gute Idee, über DIIOP die Verbindung zum Server herzustellen. Dann nämlich werden die Objekte auf dem Server erzeugt und befinden sich im Serverkontext, wie für die DIIOP-Objekte üblich. Allerdings dürfen Sie nicht vergessen, dass ein periodischer Agent mit der ServerID betrieben wird. Ihr Test aber wird mit den Logindaten der DIIOP Session gestartet. Wertet der Agent den Benutzernamen der Session aus und erwartet hier den Namen des Servers (z.B. um serverabhängige Profile zu referenzieren), so sind Nebenwirkungen vorprogrammiert und müssen entsprechend berücksichtigt werden, indem solche serverabhängigen Konfigurationen zusätzlich für den in den Tests verwendeten IIOP User angelegt werden.
19.2 Agenten debuggen und testen
695
Die in der Testklasse verwendete Variable agentContext ist kein lotus. domino.AgentContext, sondern ein DJAgentContext , wie er in Kapitel 13.1.3 vorgestellt wurde. Er implementiert dieselbe Basisklasse, kann also (dank der sauberen Implementierung der Interfaces in den lotus.domino Klassen) ohne weitere Anpassungen verwendet werden. Der Bezug dieses DJAgentContext ist einfach. Es wird lediglich ein neues Domino-Document angelegt (gekapselt in dem NLDocument ), mit den benötigten Items ausgestattet und dieses Document als DocumentContext und eine Datenbank als aktuelle Datenbank dem DJAgentContext beigegeben . Da dies außerhalb des Agenten passiert, kann dessen Code unberührt bleiben und in das ebenfalls vom Agenten unabhängige Businessobjekt Newsletter injiziert werden. Natürlich muss man sich entscheiden, welche Datenbank als Basis der Anwendung fungieren soll, sinnvollerweise dieselbe Datenbank, in der sich der ursprüngliche Agent befindet, schließlich wird der Agent ebenfalls die Datenbank, in der er sich befindet, via getCurrentDatabase in AgentContext zurückgeben. Auch dieser Punkt ist entscheidend für den Kontext, in dem Ihr Businessobjekt später betrieben wird. Unser Newsletter soll später immer serverbasiert arbeiten, d.h. die Newsletterdatenbank muss sich auf einem Domino-Server befinden (und nicht etwa lokal auf einem Notes Client), damit die für den E-Mail-Versand benötigte Infrastruktur stets vorhanden ist. Dementsprechend wird die Datenbank auch auf dem Server geladen. In der vorgestellten Testklasse wird dies erreicht, indem der Server hart verdrahtet ist. Im Domino-Agenten wird dies erreicht, indem der Agent zwar in der lokalen Session des Notes Client gestartet wird, dort aber die Datenbank auf dem Server geöffnet wird. Dann wird der AgentContext auch das Datenbankobjekt auf dem Server zurückgeben. Würde der AgentContext sich auf eine lokale Datenbank beziehen, so würden (sowohl im eigentlichen Agenten, als auch in unserem Testfall) die erzeugten E-Mails in der lokalen Datenbank mail.box gespeichert, die für den Versand von E-Mails zuständig ist, wenn der Notes Client keine Verbindung zu einem Domino-Server hat120. Damit bei der Benutzung eines Newsletterdokuments keine Fehler passieren, wurden die einzelnen Items hinter entsprechenden setter- und getter-Methoden „versteckt“ und ganz analog zu dem in Kapitel 13.1.2 vorgestellten CMSDocument in dem sehr ähnlichen NLDocument gekapselt. Die Methode setActive ist sogar identisch. Neu sind setBody , setRecipients und setSubject , save kommt direkt aus dem Domino-Document121. Der DJAgentContext selbst wird einfach über dessen Konstruktor erzeugt . 120
121
Ein Versand über die lokale Datenbank mail.box erfordert eine spezielle Konfiguration des Notes Clients. In einer Standardinstallation können Sie die so genannte Arbeitsumgebung mit Namen „Internet“ auswählen. Weitere Informationen finden Sie in Kapitel 13.1.2 und im Beispielcode der Klasse NLDocument. Insbesondere die Methode setBody und getBody sind interessant, denn hier fließen einige Besonderheiten für RichTextItems ein. RichText wurde hier verwendet, damit der Benutzer in der Maske FO_NLDoc_k19 freier in der Gestaltung, aber auch der Länge des Textes ist.
696
19 Debugging und Qualitätssicherung
Nun steht der eigentlichen Verarbeitung nichts mehr im Wege. Die Klasse NewsletterStart kann über das Java IDE im Debugger gestartet werden. Der Newsletter wird nichts von dem „eingeschleusten“ AgentContext bemerken, denn er zieht sich aus diesem lediglich Datenbank und Kontextdokument – und bei 80% aller Agenten wird dies ebenfalls so sein, so dass sich diese Vorgehensweise leicht auf viele Anwendungsfälle übertragen lässt. public Newsletter (AgentContext context) throws NotesException { if (context==null) { throw new NotesException (NEWSLETTER_ERROR, "AgentContext war null."); } ac = context; db = ac.getCurrentDatabase(); nldoc = NLDocument.getInstance(ac.getDocumentContext()); if (nldoc == null || !nldoc.isActive()) { throw new NotesException (NEWSLETTER_ERROR, "Kein NL oder nicht aktiv."); } } public int send () { EmailNotes email = null; try { email = new EmailNotes(db.getParent(), db.getServer()); email.setSender(DEFAULT_SENDER); ... for (int i = 0; i < nldoc.getRecipients().size(); i++) { email.setRecipient((String)nldoc.getRecipients().elementAt(i)); boolean done=email.send(); ... } catch (NotesException e) { ... // finally - Recycling }
Listing 19-4 Klasse Newsletter – Businessobjekt zum Versand der Newsletter
Anhand des Codes für den eigentlichen Versand soll die Problematik unterschiedlicher Kontexte beim Testen und im Agenten veranschaulicht werden. Der Konstruktor von Newsletter (Listing 19-4) lädt aus dem AgentContext die benötigte Datenbank , das Newsletterdokument, das gleich in ein NLDocument umgewandelt wird, und erledigt eine Überprüfung, ob dieses erfolgreich geladen wurde und auf aktiv gesetzt ist. Das EmailNotes-Objekt benötigt eine Domino-Session und einen Servernamen, auf dem die E-Mails versendet werden sollen. Dort wird dieses Objekt Notes-E-Mails direkt über die serverbasierte Datenbank mail.box versenden. Dies hat einige Vorteile, da diese Klasse Besonderheiten des Domino-Mail-Routings bereits berücksichtigt. Sie sendet die E-Mails über die vorliegende Infrastruktur des Servers122. Da dieses Objekt reyclingpflichtige Objekte enthält, besitzt es eine Methode recycle und wird über das typische try-finally-Konstrukt verwendet (s. Kap. 14.2). 122
Selbstverständlich muss der gewählte Server für das Notes Mail Routing aktiviert sein, der zugehörige Router laufen und die entsprechenden Berechtigungen müssen vorliegen.
19.2 Agenten debuggen und testen
697
Der bei der Instanzierung des EmailNotes-Objektes angegebene Server ist letztlich auch ausschlaggebend für den Kontext, in dem die E-Mails erzeugt werden. Da dieser Servername direkt aus der Datenbank bezogen wurde, in der die Business-
Best Practice – Ausführungskontexte beherrschen Manuell gestartete Agenten in lokalen Datenbanken des Notes Clients haben einen ausschließlich lokalen Kontext. Die zugehörige Testklasse sollte ebenfalls mit einer lokalen Session und lokalen Datenbanken arbeiten. Der Agent und die Testklasse öffnen Datenbanken unter Angabe von session.getServerName () oder "". Manuell gestartete Agenten in Datenbanken auf einem Server nutzen zwar die lokale Session des Notes Clients, das angesprochene Datenbankobjekt liegt aber im Kontext des Servers. Die zugehörige Testklasse sollte mit einer lokalen Session gestartet werden und ebenfall die zum Einsatz kommende Datenbank auf dem Server öffnen. Der Agent erkennt seinen Servernamen über getCurrentDatabase().getServer() in AgentContext. Die Testklasse muss den gewünschten Servernamen hart codieren. Serverbasierte Agenten, die z.B. periodisch gestartet und auf dem Server ausgeführt werden, nutzen die Session des Servers. Dementsprechend wird auch die Server.id zur Authentifizierung genutzt. Die zugehörige Testklasse sollte über eine DIIOP Session als Remote-Verbindung aufgebaut werden. Die verwendeten Datenbanken können dann im lokalen Kontext des Servers geöffnet werden, da dies immer automatisch auch der Serverkontext für die Datenbank ist. Der Agent erkennt seinen Servernamen über getCurrentDatabase().getServer() in AgentContext, kann aber auch eine Datenbank im lokalen Kontext des Servers durch Angabe von "" als Servernamen öffnen. Die Testklasse erkennt den Servernamen über getServerName() in Session. Voraussetzung ist, dass die DIIOP Session zuvor zu dem gewünschten Server aufgebaut wurde. Serverbasierte Agenten, die auf einem Server ausgeführt werden und auf Datenbanken entfernter Server zugreifen, sind prinzipiell denkbar und benötigen spezielle Vertrauensstellungen zwischen den Servern (seit Version 6). Agent und Testklasse müssen den gewünschten entfernten Server beim Namen kennen, um ihn zu öffnen. Der Agent öffnet die Datenbank unter Angabe des gewünschten Servernamens. Die Testklasse baut die DIIOP-Verbindung zu dem primären Server auf und öffnet die Datenbank unter Angabe des gewünschten Servernamens.
Abb. 19-3 Serverkontexte beherrschen
698
19 Debugging und Qualitätssicherung
logik abgewickelt wird, ist sichergestellt, dass es sich einerseits um einen erreichbaren Server handelt, und andererseits eine enge Verzahnung von Anwendung (die in der Datenbank des AgentContext liegt) und Mailserver hergestellt wird. Abb. 19-3 zeigt eine Übersicht über die verschiedenen Kontexte, in denen Agenten laufen können, und die zugehörige Übersetzung für die Testklassen. Fazit Domino-Agenten können ohne weiteres über einen Java-Debugger debugged und getestet werden. Lediglich das gezeigte Design Pattern muss eingehalten und die Kontexte der Session müssen kontrolliert werden. Der nächste naheliegende Schritt ist natürlich die Überführung in einen JUnit Test, der automatisierte Tests erlaubt. Die Klasse NewsletterStart ist bereits so angelegt, dass sie sehr verwandt mit einem UnitTest ist. Bisher taugt sie aber nur als Einstiegspunkt für den Debugger. In Kapitel 19.5 wird sie als Basis für einen JUnit Test dienen.
19.3
Domino-Java-Anwendungen testen
Je nachdem, um welche Art von Anwendung es sich bei einer alleinstehenden JavaAnwendung, die Domino-Java-Objekte verwendet, handelt, werden sich spezifische Anforderungen an Tests und Debugging ergeben. Diese Anforderungen hängen jedoch nicht vom Einsatz der Domino-Java-Objekte ab, sondern lassen sich immer auf die Anforderungen zurückführen, die sich einerseits aus der Art des gewünschten Tests und andrerseits der eingesetzten Anwendung ergeben. Domino-Java-Objekten können in folgenden Umgebungen eingesetzt werden: • • • • • • •
Webanwendung mit Servlet Webanwendung mit JSP GUI Anwendung im Web, im Notes Client oder Stand-alone-Anwendung jeweils mit Applet GUI Anwendung (z.B. auf Basis von Swing o.ä.) mit Einsatz der Domino-JavaObjekte im Hintergrund durch Einsatz der NotesFactory GUI Anwendung im Notes Client durch Einsatz von manuell gestarteten Agenten Backendanwendung in Domino durch Einsatz von serverbasierten Agenten Backendanwendung in sonstigen Kontexten (z.B. als Java-Batch o.ä)
Demgegenüber stehen (unter anderem) folgende Tests, die für die Domino-Java-Programmierung relevant sein können: • • •
Unit Tests Funktionale Tests Lasttests
19.3 Domino-Java-Anwendungen testen
699
Je nachdem, welcher Test in welcher Umgebung zum Einsatz kommen soll, muss sich auch die Vorgehensweise unterscheiden. Die eigentlichen Testverfahren selbst sollen bis auf den Unit Test nicht Gegenstand dieses Buches sein. Die folgende Auflistung zeigt lediglich eine kurze Übersicht über die in Frage kommenden Testmethoden und den jeweils für die Domino-Java-Entwicklung relevanten Ansatz. Unit Tests Unit Tests eignen sich für alle oben genannten Umgebungen, wenn nur der Code sinnvoll gekapselt ist. Egal, ob Ihre Businesslogik in einem Servlet, einer JSP-Seite oder in einer sonstigen Anwendung zum Einsatz kommt, immer wird es möglich sein, jeweils die logischen Einheiten, die den Domino-Java-Code enthalten, zu kapseln und über einen Unit Test zu testen. Bereits im vorigen Kapitel wurde dieser Weg angedeutet und er wird im übernächsten Kapitel näher ausgeführt. Funktionale Tests Funktionale Tests kommen zum Einsatz, wenn größere Einheiten einer Anwendung, die eine komplette Funktion abbilden, getestet werden. Dies kann z.B. das Übermitteln und die Auswertung eines Formulars einer Webanwendung, die Auswahl eines Menübefehls einer GUI-Anwendung oder die Ausführung eines Agenten in Domino oder Notes sein. Ist die abzubildende Funktion in ein Objekt gekapselt, kann dieses wiederum durch einen Unit Test getestet werden. Müssen GUI-Funktionen getestet werden, so müssen andere Techniken zum Einsatz kommen. Müssen Webanwendungen getestet werden, die Domino-Java-Objekte einsetzen, können diese getestet werden, indem ein automatisierter Browserclient die Anwendung bedient und sowohl die Verfügbarkeit der erwarteten Funktionselemente als auch die Ausführung dieser und der erwarteten Responseobjekte getestet werden. Für derartige Tests gibt es Erweiterungen für das JUnit Framework, wie z.B. HttpUnit [HttpUnit] und htmlunit [htmlunit], die beide einen etwas unterschiedlichen Ansatz verfolgen. HttpUnit arbeitet aus der Perspektive des HTTP-Protokolls und kennt Requests und Response-Objekte. htmlunit arbeitet aus der Perspektive einer HTML-Seite und bildet diese auf Java-Objekte ab und erlaubt dementsprechend eine objektorientierte Auswertung und Bedienung. In beiden Ansätzen werden interaktive HTML-Anwendungen mit (automatierten) Benutzerdaten gefüllt und die jeweiligen Serverrückmeldungen zur Auswertung bereitgestellt. Die restliche Arbeit übernimmt dann JUnit. In der Regel werden derartige Benutzertransaktionen entsprechende Erwartungen in der Datenbank zur Folge haben, wenn z.B. durch Absenden eines Formulars Dokumente erzeugt oder gelöscht werden, oder wenn die Bereitstellung von bestimmten (Test-) Dokumenten in der Domino-Datenbank entsprechende Ansichten oder Rückmeldungen im Browser Client zur Folge hat. Die hierfür benötigte Domino-Session und Auswertungsmechanismen werden analog zu den Techniken der JUnit-Verarbeitung (s. Kap. 19.5) angewendet. Müssen z.B. Swing-basierte GUI Anwendungen funktional getestet werden, so kann z.B. jfcUnit [jfcUnit] eingesetzt werden.
700
19 Debugging und Qualitätssicherung
Funktionale Tests für Agenten sind letztendlich BlackBox-Tests. Durch runOnServer kann ein Agent auf dem Server gestartet werden und ein geeigneter Mechanismus muss anschließend testen, ob die gewünschte Verarbeitung erreicht wurde. Lasttests Lasttests kommen vor allem für Web- und GUI-Anwendungen zum Einsatz, da hier durch die Client-Server-Situation auch entsprechende Last zu erwarten ist. Näheres finden Sie in Kapitel 19.7.
19.4
Remote Debugging von Java-Agenten
Sollen serverbasierte Agenten direkt in ihrem originären Kontext auf dem DominoServer debugged werden, ohne den Umweg über die oben beschriebene Injektion eines künstlichen AgentContext, kann dies über das Remote Debugging erfolgen. Es ist recht unpraktisch zu handhaben und kommt nur zum Einsatz, wenn die übrigen beschriebenen Techniken nicht weiterführen. Der Einsatz kann vor allem dann wichtig werden, wenn die erwarteten Schwierigkeiten nicht im eigentlichen Code, sondern in dessen Zusammenspiel mit dem Server und dessen Infrastruktur erwartet oder beobachtet werden. Folgerichtig benötigen Sie einen Domino-Server, um das Remote Debugging nachzuvollziehen. Folgende Schritte müssen durchgeführt werden, um ein Remote Debugging durchzuführen: 1 2 3 4 5
Setup des Servers (Remote Debugging für JVM aktivieren) Codieren einer Klasse, die AgentBase erweitert Compilieren dieser Klasse im Eclipse IDE Erstellen eines periodischen Agenten mit importiertem Java. Import des durchs IDE compilierten Class Files Sobald der periodische Agent durch den Agent Manager des Servers gestartet wurde, Starten des Remote Debuggings im Java IDE.
Im Detail bedeutet dies: Damit der Remote Debugger der Domino Java Virtual Machine aktiviert wird, müssen Sie eine INI-Variable (s. Kap. 19.8) für die JVM setzen. Öffnen Sie auf Ihrem Server die Datenbank names.nsf und dort die Sektion „Servers“ -> „Configuration“ und markieren Sie den Eintrag „Configurations“. Nun können Sie, falls noch keine Serverkonfiguration für Ihren Server vorhanden ist, mit dem Button „Add Configuration“ eine neue Serverkonfiguration hinzufügen, um einen INI-Parameter zu setzen. Im Bereich „Notes INI Settings“ wählen Sie den Befehl „Set/ Modify Parameters“ und füllen Sie das Feld „Item“ mit dem Wert „JavaEnableDebug“ und das Feld „Value“ mit dem Wert „1“. Klicken Sie den Button „Add“, dann den Button „OK“ und speichern Sie das Server-Konfigurationsdokument.
19.4 Remote Debugging von Java-Agenten
701
Erstellen Sie nun in Eclipse ein neues Projekt und dort die in Kapitel 4.11 beschriebene Beispielklasse EclipseDebugJavaAgent im Package djbuch/kapitel_04. Compilieren und importieren Sie diese Klasse, wie im Beispiel beschrieben, in einen Agenten und nehmen Sie die beschriebenen Einstellungen vor. Setzen Sie einen Breakpoint in Eclipse an eine sinnvolle Stelle. Ohne einen Breakpoint kann keine erfolgreiche Verbindung zum Remote Debugger von Domino hergestellt werden. Im Remote Debugging können nur Agenten debugged werden, die periodisch ablaufen. Weder durch runOnServer noch durch den Konsolenbefehl tell amgr run "" '' gestartete Agenten können durch den Remote Debugger der JVM in Domino debugged werden. Ändern Sie daher den Trigger auf „on schedule / more than once a day“, stellen eine adäquate Periodik (z.B. alle 5 Minuten) ein und ändern den Target auf „all documents in Database“. Speichern und schließen Sie den neuen Agenten.
Abb. 19-4 Serverkonsole
Nun müssen Sie den Server neu starten, damit die Debug-Einstellung wirksam wird. Wenn Sie kurz nach dem Neustart in der Konsole den Befehl „tell amgr schedule“ eingeben, werden Sie sehen, dass der Agent AG_HelloWorldDebug in der Liste der zu verarbeitenden Agenten auftaucht (Abb. 19-4). Um mit Eclipse ein Remote Debugging durchführen zu können, müssen Sie einige Einstellungen vornehmen: Öffnen Sie die Klasse EclipseDebugJavaAgent in Eclipse und wählen Sie den Menübefehl „Run“ -> „debug ...“ und in dem neuen Dialog den Abschnitt „Remote Java Application“. Erstellen Sie mit dem Button „new“ eine neue Konfiguration und nehmen Sie folgende Einstellungen vor: Im Bereich Connection Properties wählen Sie die Server IP oder den Servernamen, mit dem Sie verbinden möchten, und tragen Sie als Port 9788 ein. Inzwischen sollte der Agent auf dem Domino-Server angelaufen sein, der Remote Debugger in Domino wird die JVM per Default auf den IDE Debugger warten lassen,
702
19 Debugging und Qualitätssicherung
d.h. der Agent wartet auf die Verbindung mit Ihrem Debugger. Sie erkennen dies durch erneute Eingabe des Befehls „tell amgr schedule“. Vor dem Agentennamen soll-
Abb. 19-5 Eclipse IDE mit laufendem Debugger
te ein „E“ für executing stehen. Wenn Sie nun in der zuvor erstellten Debug-Konfiguration in Eclipse auf den Button „debug“ klicken, öffnet sich die Debug Umgebung in Eclipse (Abb. 19-5) und Sie können schrittweise durch den Code gehen. Diese Methode ist eine einfache Möglichkeit Java-Agenten auf einem DominoServer ohne großen Setup zu debuggen, insbesondere, wenn das Verhalten eines Agenten in der produktiven Umgebung des Agent Managers getestet werden muss, aber es gibt einige Punkte, die zu bedenken sind: 1
2 3 4 5
Das Class File im IDE, mit dem der Debugger im IDE gestartet wird, muss ein und dasselbe sein, wie das in den Agenten importierte, d.h. nach jedem compilieren (!) muss das Class File neu in den Agenten importiert werden. Es können nur periodisch durch den Agent Manager gestartete Agenten debugged werden. Der Default Port ist 9788. Der Debugging Task in Eclipse muss disconnected werden, damit der Agent im Agent Manager beendet werden kann. Konnte aus irgendeinem Grund die Verbindung zwischen JVM des Domino-Servers und des Debuggers in Eclipse nicht hergestellt werden, muss der DominoServer neu gestartet werden.
19.5 JUnit Tests 6
703
Es sollte auf dem Server nur einen periodischen Task geben, sofern nicht die Option suspend=n gesetzt wurde, mit der erreicht werden kann, dass die JVM nicht auf den IDE Debugger wartet. In diesem Fall muss der Debugger gestartet sein, bevor der Agent auf dem Domino-Server startet.
Weitere Optionen können mit der INI-Variablen JavaDebugOptions gesetzt werden: JavaDebugOptions=transport=dt_socket,server=y,suspend= y,address=9877
19.5
JUnit Tests
Domino-Java-Anwendungen können leicht in JUnit Tests eingebunden werden. Hierfür sind lediglich einige Vorbereitungen notwendig und Besonderheiten zu beachten. Die Technik der JUnit Tests als solche ist nicht Thema dieses Kapitels, eine vorzügliche Einführung findet sich bei Massol u. Husted [Massol, JUnit]. Letztendlich wird jeder JUnit Test eine Domino-Session, eine Domino-Datenbank und in der Regel ein Kontextdokument benötigen. Damit diese Objekte nicht jedesmal neu erzeugt und verwaltet werden müssen, soll als Basis für die Tests eine Hilfsklasse dienen, die diese bereitstellt. Die Klasse NotesTestObjects (Listing 19-5, der Code befindet sich im Packapublic Session getSession() throws NotesException { if (session == null) { session = NotesFactory.createSession(HOST, USER, PASSWORD); } if (session == null) { throw new NotesException(NOTESEXCEPTION_NO, "Die Session konnte nicht erzeugt werden."); } return session; }
Listing 19-5 Hilfsklasse NotesTestObjects mit den am häufigsten benötigten Objekten (Beispiel getSession)
ge djbuch.kapitel_19 im Code zum Buch) stellt alle drei Objekte „auf Abruf“ bereit („lazy initialized“), d.h. die jeweiligen getter-Methoden laden die Objekte erst, wenn sie zum ersten mal benötigt werden. Sind sie erst einmal geladen, sorgt die Klasse dafür, dass immer wieder jeweils ein und dieselbe Instanz des Objekts bereitgestellt wird, indem einfach auf null überprüft wird. Sobald das Objekt einmal verwendet wurde, hat es einen Wert ungleich null und muss nicht mehr erzeugt werden. Insgesamt stehen die Methoden getDb, getDocumentContext und getSession zur Verfügung. Zusätzlich gibt es natürlich auch eine Methode recycle, die nicht nur die drei Domino-Java-Objekte Datenbank, Session und Document wieder aufräumt, sofern sie erzeugt wurden, sondern die auch gleich das Kontextdokument
704
19 Debugging und Qualitätssicherung
wieder löscht, sofern die Methode getDocumentContext angesprochen wurde, denn diese Methode erstellt implizit ein neues, leeres Domino-Document, das für die Tests dann frei verwendet werden kann. Ein JUnit Test kann dank dieser Klasse wie gewohnt aufgesetzt werden, Listing 19-6 zeigt einen einfachen Test, der nochmals die Newsletter-Anwendung aus dem public void test_01_send() { try { agentContext = getTestAgentContext(); Newsletter nl = new Newsletter (agentContext); int count = nl.send(); System.out.println("Anzahl der versendeten NL: " + (count == Newsletter.SEND_ERROR ? "FEHLER!" : "" + count)); assertEquals ("Fehlerhafte Anzahl von erfolgreichen E-Mails", 2, count); } catch (NotesException e) { DJLog.writeException(e); fail ("Unerwartete NotesException"); } }
Listing 19-6 JUnit Test für Domino-Objekte
Kapitel 19.2 testet. Basis ist die Klasse NewsletterStart, die nun um die eigentliche Test-Überprüfung erweitert werden konnte. Die wesentliche Vereinfachung besteht darin, dass die Datenbank und das Kontextdokument, das in getTestAgentContext benötigt wird, nicht mehr dort explizit erstellt werden müssen, sondern vom JUnit Test mit Hilfe der Klasse NotesTestObjects selbst zur Verfügung gestellt werden. Wichtig ist hierbei, dass die Vereinfachung nicht nur darin besteht, dass die Objekte leicht zu beziehen sind, sondern dass durch die zentrale Definition in NotesTestObjects erreicht wird, dass immer die gleiche Datenbank für die Tests zum Einsatz kommt und so die benötigte Stringenz eingehalten wird. Gelöst wird diese Aufgabe durch die von JUnit ohnehin bereitgestellten Methoden setUp und tearDown, die vor bzw. nach jedem einzelnen Test aufgerufen werden (s. Listing 19-7). Die TestKlasse verfügt über eine Instanzvariable base , die in setUp jeweils mit einem neuen NotesTestObjects-Objekt belegt wird, so dass über dieses Objekt jeweils neu eine Datenbank, eine Session und ein Kontextdokument zur Verfügung stehen. Zusätzlich benötigt unser Test für den Newsletter noch ein NLDocument, das natürlich auch gleich im setUp erzeugt werden kann. Etwas kniffeliger ist die Bereitstellung eines AgentContext, der benötigt wird, wenn Agenten getestet werden sollen. Schließlich wird jeder Test den zu testenden Agenten unter anderen Voraussetzungen untersuchen wollen, so dass durchaus der AgentContext selbst Modifikationen unterliegt.
19.5 JUnit Tests private private private private private
705
NotesTestObjects base = null; NLDocument nlDoc = null; DJAgentContext agentContext = null; int logstatus; boolean logtrace;
public void setUp() throws Exception { super.setUp(); base = new NotesTestObjects(); nlDoc = NLDocument.getInstance(base.getDb()); logstatus = DJLog.getLogLevel(); logtrace = DJLog.getTraceStatus(); if (debugging) { DJLog.setLogLevel(DJLog.DEBUG2); DJLog.setTraceStatus(showTrace); } else { DJLog.setLogLevel(DJLog.INFO); DJLog.setTraceStatus(showTrace); } //weitere Vorbereitung... } public void tearDown() throws Exception { if (doCleanup) { cleanUp(); } DJLog.setLogLevel(logstatus); DJLog.setTraceStatus(logtrace); if (agentContext!=null) { agentContext.recycle(); //erledigt implizit das nlDoc.recycle(); } base.recycle(); super.tearDown(); }
Listing 19-7 SetUp und tearDown zur Bereitstellung der zentralen Objekte
Folglich wird die Instanzvariable agentContext im setUp nicht erzeugt. Dies bleibt den Tests überlassen. Lediglich das Recycling wird übernommen und im tearDown angestoßen. Ein kleiner Vorgriff auf das nächste Kapitel stellt der Einsatz der Klasse DJLog dar. Diese Klasse bietet eine Domino-taugliche Kapselung des log4j Frameworks [Apache, log4j], die zusätzlich noch einige praktische Methoden zur Ausgabe komplexerer Objekte (natürlich auch des Domino-Document) bietet. Diese Klasse kennt einen maximalen Loglevel , der in setUp abhängig von zentralen Einstellungen der Testklasse NewsletterTest gesteuert und in tearDown wieder zurückgesetzt wird und einen trace-Mode, der für jeden Logging-Befehl die aktuelle Klasse anzeigt, ohne dass diese ausdrücklich angegeben werden müsste. Mit diesen wenigen Vorbereitungen lassen sich nun leicht JUnit Tests für Domino aufbauen. Die Klasse NewsletterTest (im Package djbuch.kapitel_19) kann als Basis für weitere Tests eingesetzt werden und bietet bereits alle benötigten Komponenten.
706
19 Debugging und Qualitätssicherung
In dieser Klasse müssen lediglich die bereits vorhandenen Methoden test_0* ausgetauscht werden und die vorbereiteten Konstanten debugging, showTrace und doCleanup gesetzt werden, über die das Verhalten des Tests noch gesteuert werden kann. Wird debugging auf true gesetzt, wird der LogLevel erhöht und zusätzliche Ausgaben generiert (sofern in den eingesetzten Klassen die DJLog-Klasse verwendet wurde), wird showTrace auf true gesetzt, liefert jede Log Ausgabe zusätzlich die das Log aufrufende Klasse und über doCleanup == true wird schließlich noch die private Methode cleanUp bei der Ausführung des tearDown angesteuert. Für manche Tests kann es hilfreich sein, zumindest während der Entwicklung, erzeugte Domino-Dokumente nicht automatisch wieder zu löschen, um deren Zustand nachträglich noch manuell untersuchen zu können. Die Methdoe cleanUp ist im Beispiel für das Löschen des privaten NLDocument zuständig und muss an eigene Tests angepasst werden. Die Umsetzung eigener Tests ist im Beispiel nur angedeutet. Es werden typische Szenarien und Erwartungen für den Newsletter abgeprüft, wie z.B. eine fehlende Aktivierung, fehlende Angaben wie Subject oder Empfänger, aber auch fehlerhafte EMail-Adressen, die sich in entsprechend veränderter Rückmeldung im count bemerkbar machen müssen. Eine letzte offene Frage muss natürlich noch beantwortet werden. Sie werden feststellen, dass der Test korrekt durchläuft, auch wenn keine der eigentlich erzeugten E-Mails seinen Empfänger erreicht (wenn z.B. der Router Task im Domino-Server nicht aktiv ist). Diese Art des Tests liegt jedoch außerhalb der Reichweite des JUnit Tests für den Newsletter. Schließlich wird nur getestet, ob ohne Fehlermeldung ein korrekt vorbereitetes Document in der mail.box platziert werden konnte. Für die Verteilung ist dann Domino zuständig und die Überprüfung dieser Vorgänge kann nicht die Aufgabe unseres Tests sein. Denkbar wäre noch, gezielt in der Datenbank mail.box nach den erzeugten Dokumenten zu suchen, aber auch dies geht eigentlich schon über die „Unit“ dieses Tests hinaus. Ergänzend sei darauf hingewiesen, dass ein wichtiger Punkt im Test noch nicht implementiert ist: Die Überprüfung der korrekten Vergabe der Berechtigungen. So ist es empfehlenswert bei Anwendungen, die z.B. auf Rollenkonzepten basieren, in Domino auch das korrekte Funktionieren unter verschiedenen Benutzern zu testen, damit Sie sicher sein können, dass Anwendungsteile, von denen Sie erwarten, dass sie nur bestimmten Rollen zur Verfügung stehen, auch nur von Benutzern mit diesen Rechten erreicht werden. So können Regressionen im Bereich der Sicherheit vermieden werden. Leicht sind solche Tests allerdings nicht zu implementieren. Am besten werden in der Klasse NotesTestObjects die zur Zeit noch statischen Konfigurationsparameter nach außen zugänglich gemacht, so dass leicht mehrere solche Objekte mit verschiedenen Sessions für verschiedene Benutzer erzeugt werden können.
19.6 Logging
19.6
707
Logging
Das Logging in einer Anwendung sollte zwei Aufgaben erfüllen können. Zum einen wird (in der Regel) Bedarf daran bestehen, bestimmte Ereignisse zu Zwecken der Nachvollziehbarkeit festzuhalten. Dies können z.B. Benutzerinteraktionen oder periodische Agentenausführungen sein, um später nachvollziehen zu können, welche Aktion von wem durchgeführt wurde oder ob bestimmte Änderungen an Daten vorgenommen wurden123. Neben diesem Anwendungslog ist es sinnvoll, ein Systemlog der Anwendung zu führen. Dort sollten Informationen über Prozesse, erwartete und unerwartete Ereignisse, Konfigurationsänderungen und allgemeine Ladevorgänge protokolliert werden. Idealerweise sind diese Meldungen unterschiedlichen Wichtigkeitsstufen zugeordnet, so dass deren Ausgabe stufenweise, am besten von außen über eine Konfiguration gesteuert werden kann. Domino sieht hierfür eine eigene Klasse (s. Kap. 19.6.1) vor, über die sich einfache Domino-Agenten-Logs erzeugen lassen. Wie es sich für eine Domino-Klasse gehört, benötigt sie eine Domino-Session. In einer großen Anwendung wird es unter Umständen viele Bereiche und zugehörige Klassen geben, die unabhängig von Domino agieren und daher auch unabhängig von einer Session sein sollten. Daher wird in Kapitel 19.6.2 eine Klasse vorgestellt, die einerseits viele praktische Methoden bereitstellt, um auch komplexere Objekte wie Vector, Enumeration und natürlich Document ins Log schreiben zu können. Die Methoden sind alle statisch und unabhängig von einer Session. In Kapitel 19.6.3 wird schließlich ein Appender für das log4j Framework vorgestellt, der es dieser Klasse ermöglicht, die zu loggenden Daten in eine Domino-Datenbank zu schreiben. Die log-Methoden bleiben aber dabei unabhängig von der Session.
19.6.1
Notes-Log-Klasse
Die Domino-Log-Klasse sieht verschiedene Arten von Log-Methoden vor. Das einfachste und am wenigsten mächtige ist das AgentLog. Es gehört direkt zu einem im Designer erstellten Agenten und kann nur dort eingesetzt werden. Abgerufen wird das AgentLog im Designer, indem der Agent markiert und der Befehl „Agent“ -> „Log“ gewählt wird. In einem Informationsfenster (s. Abb. 19-6) werden die Log-Meldungen der letzten Agentenausführung angezeigt. Folglich sind alle älteren Meldungen des AgentLog flüchtig. Wird das AgentLog außerhalb eines Domino-Agenten verwendet, so wird eine NotesException mit ID 4314 „NotesException: There is no current agent, can't open log“ geworfen. 123
Hierbei darf der Datenschutz nicht außer Acht bleiben, wenn persönliche Daten verarbeitet werden. Gleichzeitig kann ein (schlecht geführtes) Log eine erhebliche Sicherheitslücke darstellen, denn die falschen Informationen an einer leicht kompromittierbaren Stelle (z.B. das Log einer Webanwendung auf einem dem öffentlichen Netz ausgesetzten Webserver) können einem potentiellen Angreifer möglicherweise ungewollt wichtige Informationen mitgeben.
708
19 Debugging und Qualitätssicherung
Abb. 19-6 Agent Log
Listing 19-8 zeigt typische Log-Befehle, in diesem Beispiel für ein AgentLog. Jedes Domino-Log muss eingangs über die Session initialisiert und dann geöffnet werden. Beim Öffnen stehen vier verschiedene Log-Modelle zur Verfügung (AgentLog, MailLog, FileLog und NotesLog). Die eigentlichen Meldungen werden über logAction und logError an die jeweiligen Logs angehängt. Während das AgentLog nur den letzten Lauf des Agenten anzeigt und nur über den Designer angezeigt werden kann, werden in MailLog, FileLog oder NotesLog
public void NotesMain() { Log log = null; try { Session session = getSession(); AgentContext agentContext = session .getAgentContext(); log = session.createLog("DJBuch Agenten Log"); log.openAgentLog(); log.logAction("Information im Agenten Log"); log.logError(123, "Mein Fehler mit Code 123"); log.logAction("Noch mehr Information im Agenten Log"); log.logAction("Anzahl der Actions: " + log.getNumActions()); log.logAction("Anzahl der Fehler: " + log.getNumErrors()); log.logAction("Programm Name: " + log.getProgramName()); log.close(); } catch (Exception e) { e.printStackTrace(); } finally { GC.recycle (log); } }
Listing 19-8 NotesLog-Anwendung
19.6.1 Notes-Log-Klasse
709
durch die beiden Befehle logAction oder logError jeweils eine Zeile in der Mail oder der Datei im Dateisystem angehängt, bzw. ein neues Domino-Document in der Notes-Log-Datenbank angelegt. Der Name, mit dem das Log angelegt wird, wird im NotesLog als Kategorie (s. Abb. 19-7) verwendet und kann über getProgramName wieder abgefragt wer-
Abb. 19-7 NotesLog-Datenbank
den. Je nachdem, welche der beiden Methoden zur Ausgabe der Meldung verwendet wird, wird ein interner Zähler hochgezählt und kann jeweils über getNumActions bzw. log.getNumErrors wieder abgefragt werden. Insgesamt stehen folgende Methoden und Eigenschaften in Log zur Verfügung: • void openAgentLog() throws NotesException Öffnet das AgentenLog eines Domino-Agenten. Darf nicht außerhalb von Agenten verwendet werden. Für jede LogMeldung, die durch logAction oder logError erzeugt wird, wird eine Zeile im AgentLog erzeugt. Das AgentLog kann im Domino Designer über „Agent“ -> „Log“ abgerufen werden, zeigt jedoch immer nur die Meldungen des jeweils letzten Laufs an.
710 •
19 Debugging und Qualitätssicherung void openFileLog(String fullPath) throws NotesException
Öffnet ein DateiLog. Geschrieben werden die Log-Meldungen in die durch fullPath angegebene Datei. Über setOverwriteFile(boolean flag) wird festgelegt, ob das neu eröffnete Log die Meldungen an eine vorhandene Datei anhängen oder die Datei überschreiben soll. Für jede LogMeldung, die durch logAction oder logError erzeugt wird, wird eine Zeile in die durch fullPath festgelegte Datei geschrieben. •
void openMailLog(Vector mailRecipients, String mailSubject) throws NotesException
Öffnet ein Mail Log. Darf nicht außerhalb von Agenten verwendet werden. Empfänger der zu mailenden Log-Meldung(en) sind die durch den Vector mailRecipients bestimmten Domino- oder Internet-Mail-Empfänger. Die Mail erhält das Subject mailSubject. Gesendet wird die Mail, sobald das Objekt recycelt oder durch close() geschlossen wird. Für jede LogMeldung, die durch logAction oder logError erzeugt wird, wird eine Zeile in der Mail erzeugt. •
void openNotesLog(String server, String logDatabase) throws NotesException
Öffnet ein Datenbank-basiertes NotesLog. Über die Angabe der Parameter server und logDatabase wird die Datenbank bestimmt, in die geloggt werden soll. Die Datenbank sollte mit der Schablone „Agent Log (alog4.ntf)“ erstellt werden, damit die Meldungen richtig angezeigt werden können. Für jede LogMeldung, die durch logAction oder logError erzeugt wird, wird ein neues Dokument in der Log Datenbank erzeugt. Error-Dokumente enthalten ein zusätzliches Item mit der Fehlernummer (siehe logError). •
void close() throws NotesException
Schließt das Log. Bei MailLogs wird die erstellte Mail gesendet. •
void logAction(String logMessage) throws NotesException
Erstellt ein Document im NotesLog, eine Zeile im FileLog oder MailLog und einen Eintrag im AgentLog. Zählt den Counter für die Meldungen um eins hoch (s. getNumActions). •
void logError(int errorNumber, String errorMessage) throws NotesException
Fügt dem Log eine Fehlermeldung hinzu. Hierfür wird ein Document im NotesLog, eine Zeile im FileLog oder MailLog und ein Eintrag im AgentLog erstellt. Zählt den Counter für die Fehlermeldungen um eins hoch (s. getNumErrors). Neben der Meldung (errorMessage) kann ein (frei wählbarer) Fehlercode (errorNumber) angegeben werden, der im Log vermerkt wird. •
void logEvent(String eventMessage, String eventQueue, int kindOfEvent, int severityOfEvent) throws NotesException
Neben AgentLog, FileLog, MailLog und NotesLog gibt es die Möglichkeit so genannte Message Queues zu bedienen, die aber nur über entsprechende Tools wieder ausgelesen werden können. Hierfür wird kein spezielles Log geöffnet, sondern die Meldung über logEvent ins Netzwerk gemeldet. eventMessage gibt die Meldung an, eventQueue wählt eine vordefinierte Queue aus, wobei eventQueue auch der leere String "" sein darf, kindOfEvent spezifiziert über die Konstanten
19.6.2 Logging ohne Domino-Session, Die DJLog-Klasse
711
EV_UNKNOWN, EV_COMM, EV_SECURITY, EV_MAIL, EV_REPLICA, EV_RESOURCE, EV_MISC, EV_SERVER, EV_ALARM oder EV_UPDATE den
Typ des Events und severityOfEvent gibt die Schwere einer Ausnahme über die Konstanten SEV_UNKNOWN, SEV_FATAL, SEV_FAILURE, SEV_WARNING1, SEV_WARNING2 oder SEV_NORMAL an. •
String getProgramName() throws NotesException void setProgramName(String programName) throws NotesException
Setzt oder liest den programmatischen Namen des aktuellen Logs, der auch bei der Instanzierung über createLog in Session gesetzt wird. •
int getNumActions() throws NotesException
Anzahl der Meldungen zwischen open...Log und close. •
int getNumErrors() throws NotesException
Anzahl der Fehlermeldungen zwischen open...Log und close. •
Session getParent() throws NotesException
Session, in der das Log erzeugt wurde. •
boolean isLogActions() throws NotesException void setLogActions(boolean doLogActions) throws NotesException
Zeigt an bzw. legt fest, ob Meldungen, die über logAction erzeugt werden, ins Log geschrieben werden sollen. •
boolean isLogErrors() throws NotesException void setLogErrors(boolean doLogErrors) throws NotesException
Zeigt an bzw. legt fest, ob Meldungen, die über logError erzeugt werden, ins Log geschrieben werden sollen. •
boolean isOverwriteFile() throws NotesException void setOverwriteFile(boolean doOverwriteFile) throws NotesException
Zeigt an oder legt fest, ob in einem FileLog eine LogDatei fortgeführt oder überschrieben werden soll.
19.6.2
Logging ohne Domino-Session, Die DJLog-Klasse
Grundsätzlich ist die Log-Klasse in Domino für einige Zwecke durchaus eine Erleichterung, wer aber bereits mit log4j oder einem anderen Logging Framework gearbeitet hat, will nicht auf die dort vorgefundenen Vereinfachungen und Funktionen verzichten. Insbesondere die Bindung an eine Domino-Session in lotus.domino.Log ist oft nachteilig. Nicht nur, dass nicht immer eine Session zur Verfügung steht, sondern auch in wichtigen Situationen – z.B. im catch oder finally – sind die von den Methoden in Log geworfenen Exceptions hinderlich. Es versteht sich (mittlerweile) von selbst, dass ein Logging über System.out. println nicht angestrebt wird, da diese Ausgaben nicht zentral gesteuert oder abgeschaltet werden können. Es soll erreicht werden, dass in der Default-
712
19 Debugging und Qualitätssicherung
konfiguration die Meldungen in der Dominokonsole (und somit in der Datenbank log.nsf) landen und optional auf andere Ausgabemedien (z.B. eine eigenständige Domino-Datenbank – s. Kap. 19.6.3) umgeleitet werden können. Ziel ist es, eine Klasse einzuführen, die einerseits weiterhin in der Lage ist, mit Domino-Objekten umzugehen und gleichzeitig von den Möglichkeiten eines Log Frameworks profitiert. Die Klasse DJLog im package djbuch.kapitel_19 verfolgt hierbei folgende Ziele: •
•
•
•
•
•
Loslösung von der Domino-Session Für ein Logging, das an einer beliebigen Stelle des Codes eingesetzt werden kann, muss die Abhängigkeit von der Domino-Session aufgelöst werden. Vereinfachung des Logging schwerer Objekte, wie Document oder Enumeration Neben den Vorteilen einer zentralen Steuerung soll die Klasse Methoden erhalten, die es ermöglichen, auch schwere (Domino-) Objekte wie ein Document ausgeben zu können. Hierfür werden geeignete Methoden bereitgehalten. Einführung eines Log Levels („Severity“) Zur Laufzeit (!) soll festgelegt werden können, welche Meldungen ins Log gelangen und welche nicht. Die Meldungen werden hierfür über Konstanten ERROR, WARN, INFO, DEBUG, DEBUG2 und DEBUG3 klassifiziert. Die Level sind hierarchisch angeordnet. Nur wenn mindestens der verwendete Level auch im DJLog zugelassen ist, gelangen diese Meldungen ins Log. Kapselung des log4j Frameworks Im Java-Umfeld wird log4j inzwischen als Quasistandard gehandelt und soll auch hier zum Einsatz kommen. Beim Entwurf von DJLog wurde vor allem Wert auf Vereinfachung gelegt. Es soll lediglich einen einzigen Logger geben, der transparent und direkt aufgerufen werden kann, ohne jeweils ausdrücklich einen Logger erzeugen oder ansprechen zu müssen. Automatische Konfiguration des log4j Frameworks Eine der häufig beobachteten Hürden beim Einsatz von log4j ist die Konfiguration der Appender. Die DJLog-Klasse soll zumindest Defaulteinstellungen automatisch zur Verfügung stellen und somit den Einsatz nach außen völlig transparent (und unabhängig von der Anwendung, in der es eingesetzt wird) machen. Abbildung des Loggings auf statische Methoden Um die Vereinfachung des Loggings noch zu steigern, sollen statische Methoden zum Einsatz kommen. Hierdurch wird erreicht, dass jederzeit an jeder Stelle des Codes direkt Log-Meldungen ausgegeben werden können, ohne auf eine wie auch immer geartete Infrastruktur achten zu müssen. In sehr großen Anwendungen kann diese Vorgehensweise hinderlich sein, da sie unter anderem bedingt, dass es nur einen Logger geben kann. Da die Methoden der Klasse in der Lage sind, automatisch die aufrufende Klasse mit auszugeben, wird dies für die meisten Anwendungen reichen. Falls nicht, muss auf die statischen Wrapper verzichtet werden.
19.6.2 Logging ohne Domino-Session, Die DJLog-Klasse
713
Im Beispielcode zum Buch finden Sie die Klasse DJLog, die diese Bedingungen erfüllt und hierfür folgende Methoden zur Verfügung stellt: •
static static static static
void void void void
error(String msg) warn(String msg) info(String msg) debug(String msg)
Einfache String-Meldungen können über die Methoden error, warn, info und debug den jeweiligen Log Levels zugeordnet werden. Über setLogLevel wird der Level für die gesamte DJLog-Klasse eingestellt. Liegt der Level der Klasse unter der einer Meldung, so wird die Meldung nicht ausgegeben. •
static void write(int logLevel, String msg) static void write(int logLevel, String msg, boolean kurz) Für die Methode write kann der klassifizierende Log Level explizit über die Konstanten ERROR, WARN, INFO, DEBUG, DEBUG2 und DEBUG3 angegeben werden. Der optionale Parameter kurz erzwingt ein Ausblenden der sonst per
Default angezeigten Namen der aufrufenden Methoden. Das Anzeigen dieses Traces kann über setTraceStatus gesetzt werden. •
static void writeString(int logLevel, String msg) static void writePairs(int logLevel, Vector keys, Vector values) static void writeException(Throwable e) static void writeException(int logLevel, Throwable e) static void writeDocument(Document doc) static void writeEnumeration(int logLevel, Enumeration e) static void writeVector(int logLevel, Vector v)
Die verschiedenen Write-Methoden geben entsprechend ihrem Namen Objekte ins Log aus. Bei den meisten wird über den ersten Parameter der Log Level spezifiziert. writeDocument gibt die Items eines Document als Name-Value-Paare im Level INFO aus. writeException gibt ohne Angabe des LogLevels die Exception im Level ERROR aus. Alle Methoden teilen eine Meldung in mehrere einzelne Print-Methoden auf und zwar an optional vorgefundenen Carriage Return bzw. Newline-Zeichen. Dies ist für die Ausgabe im Domino-Log hilfreich, da Domino Carriage Return bzw. Linefeed nicht als Zeilenumbruch im Log anzeigt und die Anzahl der Zeichen pro Meldung beschränkt. •
static void setLogLevel(int newLogLevel) static int getLogLevel() Per Default ist die Log-Klasse im Level INFO eingestellt. Alle Meldungen mit Level INFO oder niedriger (also WARN oder ERROR) werden ausgegeben. über setLogLevel und getLogLevel kann diese Schranke gesetzt bzw. gelesen
werden. Soll der Default z.B. über eine Konfiguration geladen oder anders voreingestellt werden, so muss die private Methode loadLogLevel angepasst werden. Als Parameter für newLogLevel sind die Konstanten ERROR, WARN, INFO, DEBUG, DEBUG2 und DEBUG3 zugelassen, die bei der Entscheidung, welche Meldungen angezeigt werden, in der genannten Reihenfolge ausgewertet werden.
714 •
19 Debugging und Qualitätssicherung static void setTraceStatus(boolean newTraceStatus) static boolean getTraceStatus()
Per Default gibt die Klasse den Meldungen den Namen der aufrufenden Klasse und Methode aus. Zusätzlich wird für jeden geschachtelten Aufruf die Ausgabe um einen Punkt nach rechts gerückt. Beispiel: 2005-12-29 12:35:41,522 [ - ] .djbuch.kapitel_19.AgentLogDemo.run: MsgSub 2005-12-29 12:35:41,522 [ - ] djbuch.kapitel_19.AgentLogDemo.run: Msg
•
static final void setBasePath(String path) static String getBasePath()
Per Default legt die Klasse die automatisch erstellte Konfiguration für log4j im Pfad „>/java/“ an. Per setBasePath kann dieser Pfad geändert werden. Im durch getBasePath angezeigten Pfad wird, sofern noch nicht vorhanden, die Konfigurationsdatei log4j.conf neu erstellt und für das Logging in der Dominokonsole konfiguriert. Hierfür kommt ein einfacher ConsoleLogger zum Einsatz. Zusätzlich wird ein Rotating File Logger vorbereitet, der seine Log-Dokumente ebenfalls in dieses Verzeichnis schreibt. Darüber hinaus ist ein Domino-Logger vorbereitet, der es ermöglicht, die zu loggenden Informationen in eine Domino-Datenbank zu schreiben, ohne dass hierfür beim eigentlichen Log-Vorgang eine Session übermittelt werden müsste (sie wird im Hintergrund aufgebaut). •
static final Level internalToLog4jLevel(int source)
Ermöglicht die Umwandlung der Konstanten ERROR...DEBUG3 in einen log4jkonformen Level. •
static String logSymbol(int level)
Jedem LogLevel ist ein „MarkierungsString“ zugeordnet. Für ERROR ist dies z.B. „[!!!]“, der eine Meldung im Log hervorhebt. logSymbol liefert diesen String zu einem Log Level. Die DJLog-Klasse ermöglicht ein einfaches und universell einsetzbares Logging auf Basis von log4j. Wurde die Datei log4j.conf beim ersten Aufruf erstellt, so kann diese Konfigurationsdatei den eigenen Wünschen angepasst werden. Die Verwendung ist äußerst einfach. An jeder beliebigen Stelle des Codes können die statischen Methoden verwendet werden, z.B.: DJLog.warn("Achtung, dies ist eine Warnung"); DJLog.info("Nur zur Information"); DJLog.writeDocument(doc);
Diese Methoden erzeugen die Ausgabe: [ ! ] . djbuch.kapitel_19.AgentLogDemo.run:
19.6.3 Apache log4J und Domino
715
Achtung, dies ist eine Warnung [ - ] . djbuch.kapitel_19.AgentLogDemo.run: Nur zur Information [ - ] . djbuch.kapitel_19.AgentLogDemo.run: Document: F_Titel=[Dokumenten Titel] F_Datum=[12.03.2006 12:57:20 CET] Form=[FO_logDemo_k19]
Hierbei wurde die Angabe von Datum, Uhrzeit und Threadname weggelassen. Diese Angaben sind in der automatischen Konfiguration von log4j vorgesehen. Die Funktion der Methode logSymbol, die die Schwere der Meldung jeweils kennzeichnet, ist gut zu erkennen. Bei der Ausgabe des Document werden die Name-Value-Paare in einer neuen Zeile ausgegeben. NotesExceptions sind gekapselt, so dass auch das Logging von Domino-Objekten außerhalb von try-catch-Blöcken erfolgen kann.
19.6.3
Apache log4J und Domino
Mit der DJLog Klasse haben Sie nun ein Werkzeug an der Hand, das sich flexibel an jeder Stelle Ihres Codes einsetzen lässt und das gleichzeitig unabhängig von Domino auch in beliebigen anderen Nicht-Domino-Klassen eingesetzt werden kann, ohne gleichzeitig auf die Möglichkeit verzichten zu müssen, Domino-Objekte in ein Log ausgeben zu können. Sollte es gelungen sein, Sie mit Domino zu infizieren, so werden Sie jetzt sagen: „Schade, dass ich nur in die Konsole oder ein Rotating-FileLog loggen kann. Domino hat so praktische Replikationseigenschaften, die mir meine LoggingInformationen von allen Servern an eine zentrale Stelle replizieren könnten.“ Hier kann geholfen werden. Dank log4j kann leicht ein so genannter Appender [Apache, log4j] definiert werden, der anstatt oder sogar zusätzlich zur Ausgabe in die Konsole die Logging-Informationen in eine Domino-Datenbank schreibt. Wird dieser Appender geschickt angelegt, so kann dies wie alle übrigen Appender transparent und ohne das einfache Framework zu verlassen, erfolgen. Um in eine Datenbank schreiben zu können, wird natürlich eine Session benötigt und für diese natürlich ein Login. Da der Appender über die Konfigurationsdatei von log4j konfigurierbar sein soll, müssen dort diese Informationen gespeichert werden. Um Passwörter nicht im Klartext hinterlegen zu müssen, wird daher empfohlen, mit einer lokalen Session zu arbeiten, die dann auf die Server-ID (sofern der zu loggende Task im Server läuft) zurückgreift. Auch Anwendungen, die nicht im Serverkontext auf einem Client mit lokaler Domino- oder Notes Client-Installation laufen, können so eine Domino-Session erzeugen. Der im Folgenden vorgestellte Appender sieht vier Parameter vor, über die die Session aufgebaut wird: •
log4j.appender.NOTESLOGGER.notesDb=djbuch/djbuch.nsf
Bezeichnet die Datenbank, in die die Logs geschrieben werden sollen. Der Name wird als vollständiger Pfad, relativ zum Dataverzeichnis, einschließlich eventueller Verzeichnisse angegeben. Dieser Parameter muss immer gesetzt werden. •
log4j.appender.NOTESLOGGER.dominoServer=Java/DJBUCH
716
19 Debugging und Qualitätssicherung Bezeichnet den Domino-Server, auf dem sich diese Datenbank befindet. Dieser Parameter muss immer gesetzt werden. Handelt es sich nicht um den Server, in dem die Session aufgebaut wird (siehe unten – iiopServer), so müssen entsprechende Vertrauensstellungen eingerichtet werden.
•
log4j.appender.NOTESLOGGER.password=secret
Optional kann ein Passwort angegeben werden. Bei lokalen Sessions (iiopServer bleibt leer) wird dieses Passwort für die lokal vorliegende ID verwendet. Bei DIIOP-Verbindungen (iiopServer hat einen gültigen Wert) wird dieses Passwort für die Internet-Session verwendet. Soll das Passwort leer bleiben, kann diese Zeile durch # auskommentiert werden. •
log4j.appender.NOTESLOGGER.iiopUser=user
Optionaler Benutzername für den Aufbau einer DIIOP Session. Das zugehörige Passwort wird über log4j.appender.NOTESLOGGER.password gesetzt. Soll kein DIIOP verwendet werden, kann diese Zeile durch # auskommentiert werden. •
log4j.appender.NOTESLOGGER.iiopServer=127.0.0.1
IP oder Hostname des zu verwendenden DIIOP Servers. Soll kein DIIOP verwendet werden, kann diese Zeile durch # auskommentiert werden. Die Konfiguration ist bereits über die Klasse DJLog vorbereitet und wird duch die automatische Konfiguration erzeugt. Der zugehörige Logger ist allerdings per Default auskommentiert und muss aktiviert werden. Auf der Basis der oben beschriebenen Parameter kann der Appender nun eine Session aufbauen und die Domino-Datenbank mit Log-Daten befüllen. Damit das Logging nicht zum performancebestimmenden Faktor wird, wurde der Appender als Erweiterung des RollingFileAppenders implementiert, der als (nicht flüchtiger) Cache verwendet wird. Erst wenn eine Datei (entsprechend den Vorgaben in der Konfiguration) eine Maximalgröße erreicht hat, wird sie in ein Domino-Document in der Log-Datenbank gespeichert, so dass nicht bei jeder einzelnen Meldung ein Dokument erzeugt werden muss, wie das für den NotesLogger in lotus.domino.Log der Fall ist. Der Appender erweitert den org.apache.log4j.RollingFileAppender und überschreibt lediglich die Methode rollOver() (siehe Beispielcode zum Buch) und stellt die oben definierten Parameter zur Verfügung. Die Klasse NotesLogWriter (s. Listing 19-9) stellt die eigentliche Logik zur Verfügung, mit der die DominoLogs geschrieben werden. Der Aufbau ist klassisch als Erweiterung von NotesThread implementiert. Allerdings wird nach dem Start des NotesThread auf die Fertigstellung gewartet , da sonst der Fall eintreten kann, dass der RollingLogAppender eine Datei bereits wieder löscht, während der DominoLogWriter noch versucht, die Daten in ein Dokument zu schreiben. Die statische Methode writeToDomino kapselt den gesamten Vorgang, um die Log-Daten in die Domino-Datenbank zu schreiben. Das private Objekt NotesLogWriter erhält bei der Initialisierung alle benötigten Parameter, um eine Domino-Session aufzubauen und um die Datenbank zu öffnen .
19.6.3 Apache log4J und Domino public class NotesLogWriter extends lotus.domino.NotesThread { //... private fields private NotesLogWriter (String dServer, String db, String server, String user, String pwd, String fileName) { //... set private fields } public static synchronized void writeToDomino (String dServer, String db, String server, String user, String pwd, String fileName) { NotesLogWriter nlw = new NotesLogWriter (dServer, db, server, user, pwd, fileName); nlw.run(); try { nlw.join(); } catch (InterruptedException e) { System.err.println ("Fatal: " + e.toString()); } } public void runNotes () { Session session = null; Database db = null; //..init domino variables session, doc, br, rti, rts = null ... try { session = NotesFactory .createSession(iiopServer, iiopUser, dominoPwd); db = session.getDatabase(dominoServer,dominoDb); doc = db.createDocument(); doc.replaceItemValue("Form", "FO_LogFileJava"); doc.replaceItemValue("F_Servername",db.getServer()); doc.replaceItemValue("F_NotesLogWriter", "1"); doc.replaceItemValue("F_Created", session .createDateTime(new Date())); br = new BufferedReader (new FileReader (logFileName)); String tmp = br.readLine(); rti = doc.createRichTextItem("F_Events"); rts = session.createRichTextStyle(); rts.setFont(RichTextStyle.FONT_COURIER); rts.setFontSize(8); rti.appendStyle(rts); boolean errorfound=false; while (tmp!=null) { if (!errorfound && tmp.indexOf(DJLog .logSymbol(DJLog.ERROR)) != -1) { doc.replaceItemValue("F_Error","1"); errorfound=true;//spart spätere indexOf Evaluierungen... } rti.appendText(tmp); rti.addNewLine(1); tmp = br.readLine(); } doc.save (true,false); } ...//catch Exceptions } ...//finally close br; finally recycle domino objects } } }
Listing 19-9 NotesLogWriter
717
718
19 Debugging und Qualitätssicherung
Je nachdem, ob die Parameter iiopServer und iiopUser gesetzt oder null sind, wird entsprechend der Definition von createSession eine lokale oder RemoteSession geöffnet. Damit die Logdateien etwas besser lesbar sind, werden die Informationen in ein RichTextItem geschrieben und formatiert , und schließlich beim Übertragen wird erkannt, ob sich in den Meldungen solche vom Level „ERROR“ befinden. Ist dies der Fall, wird das Log-Dokument um das Item F_Error mit dem Wert "1" ergänzt. Die Datenbank, in die die Log-Dokumente geschrieben werden, sollte eine Ansicht besitzen, die die Dokumente der Maske „FO_LogFileJava“ anzeigt und ein Warnsymbol anzeigt, wenn das Item F_Error vorhanden ist. Die Ansicht V_log in der Beispieldatenbank erfüllt diese Bedingungen und kategorisiert die Meldungen auch gleich nach Server anhand des von DJLog erstellten Items F_Servername . Zusätzlich muss die Log-Datenbank über die Maske FO_LogFileJava verfügen, die ebenfalls der Beispieldatenbank entnommen werden kann. Die DJLog-Klasse zusammen mit dem Domino-Log-Appender erfüllt also zwei Aufgaben. Einerseits bietet DJLog die nötigen Methoden, um ein sehr einfaches Logging an jeder beliebigen Stelle des Codes unabhängig von jeder Infrastruktur zu ermöglichen, einschließlich der Möglichkeit, Domino-Objekte ins Log zu dumpen. Gleichzeitig bietet der Domino-Log-Appender, das Logging von DJLog auf eine Domino-Datenbank umzuleiten, so dass Log-Informationen in Domino zur Verfügung stehen und über die Replikationsmechanismen an zentraler Stelle sichtbar gemacht werden können. So ist zum Beispiel denkbar, eine Log-Datenbank über alle benötigten Server als Repliken zu verteilen und immer in diese Datenbank zu loggen. Da DJLog immer auch den Servernamen, auf dem die Log-Informationen entstanden, in die Datenbank schreibt, bleiben diese sauber getrennt. So können Log-Informationen von vielen Servern in einer Datenbank zur Verfügung gestellt werden.
19.7
Multithreading und Lasttests
Lasttests hängen stark vom Charakter einer Anwendung ab. Webbasierte Anwendungen, z.B. ein Servlet, das die Domino-Java-API verwendet, werden am besten direkt über ihr Frontend – also einen Robot, der sich als Browser benimmt – getestet, während reine Domino-Anwendungen, also zum Beispiel eine Java-Anwendung oder ein Applet, das die Domino-Java-API verwendet, entweder über ein für den Anwendungstyp vorgesehenes Lasttestprogramm oder durch eine eigene Anwendung getestet werden können. Bereits in Kapitel 13.3.7 und 13.3.8 wurden die Klassen DJStressRunner und DJStressTest bzw. DJStressRunnerOneSession und DJStressTestExternalSession vorgestellt, die einen einfachen Stresstest simulieren, abhängig davon, ob viele Zugriffe über eine Domino-Session oder viele Domino-Sessions simmuliert werden sollen. Neben vielen kommerziellen Angeboten, die insbesondere für den Test von Webanwendungen am Markt zur Verfügung stehen, bietet das JMeter-Projekt der Apache Foundation neben den ursprünglichen Testmöglichkeiten für WebAnwendungen inzwischen eine Vielzahl an Möglichkeiten [Apache, JMeter]. Insbesondere die Mög-
19.8 Notes-INI-Variablen
719
lichkeit, auch Java-Objekte einem Lasttest zu unterziehen, ist interessant, da hiermit weit über die in Kapitel 13.3.7 und 13.3.8 gezeigten Möglichkeiten hinausgegangen wird. In jedem Fall sollten Domino-Java-Anwendungen, egal ob Web- oder alleinstehende Anwendugen, einem Lasttest unterzogen werden, um neben der Einschätzung des Lastverhaltens auch den korrekten Umgang mit Threads, Ressourcen und Recycling unter Last zu verifizieren.
19.8
Notes-INI-Variablen
Domino bietet viele Parameter, mit denen sich insbesondere Debugginginformationen aktivieren lassen. Diese werden über so genannte Notes-INI-Parameter, also Einträge in die Datei notes.ini, eingestellt. Die Datei notes.ini befindet sich für den Domino-Server und den Notes Client im jeweiligen Programmverzeichnis, wobei bei partitionierten Servern sich diese Datei im jeweiligen Dataverzeichnis jeder Partition befindet. Sie wird in der Regel als normale Textdatei editiert, jedoch könnnen Parameter zum Teil auch temporär über die Serverkonsole gesetzt werden. Wird die Textdatei notes.ini editiert, ist zu beachten, dass die auf dem jeweiligen System üblichen Zeilentrenner eingehalten werden und die letzte Zeile der Datei leer bleibt. Zusätzlich ist es möglich, in der Datenbank names.nsf in den so genannten Server-Konfigurationen („Servers“ -> „Configurations“ -> „“) Notes-INI-Parameter zu setzen. Diese überschreiben eventuell identische Einträge, die manuell in der Datei notes.ini vorgenommen wurden. Viele der Parameter haben eine zum Teil erhebliche Erweiterung der Ausgabe im Console Log zur Folge. Daher sollten diese Parameter nur zu Debugging-Zwecken und temporär eingesetzt werden. Im Zweifelsfall sollten die Parameter nicht auf produktiven Servern, sondern zunächst im Labor eingesetzt und für die jeweilige Infrastruktur und Anwendungsfälle untersucht werden, ob sie den gewünschten Zweck erfüllen. Die folgende Liste hat keinen Anspruch auf Vollständigkeit, bietet aber einen guten Überblick über wichtige Parameter, die bei der Programmierung mit dem Domino-Java-API eine gute Hilfe leisten können. Alle Parameter sind, bis auf die gekennzeichneten, die auch auf dem Notes Client verwendet werden können, für den Einsatz auf dem Domino-Server gedacht. Java In Domino ist die JVM in den Server bzw. Notes Client eingebettet und kommt für serverseitige Agenten oder Servlets und für clientseitigen Java-Code zum Einsatz. Diese JVM kann nicht wie gewohnt über Startparameter beeinflusst werden, sondern wird über Notes-INI-Variablen gesteuert. •
JavaDebugOptions=transport=dt_socket,server=y|n,suspend=y| n,address=
720
19 Debugging und Qualitätssicherung Ist das Debugging für den Server durch JavaEnableDebug=1 aktiviert, so können über diesen Parameter zusätzliche Einstellungen für die JVM vorgenommen werden. „suspend=n“ verhindert, dass der Domino-Server auf den Debugger wartet, d.h. dieser muss rechtzeitig gestartet sein. Über „address=“ kann der TCP Port angepasst werden, über den die Verbindung hergestellt wird.
•
JavaEnableDebug=1|0
Aktiviert das Serverseitige Debugging (s. Kap. 19.4) •
JavaEnableJIT=1
Aktiviert den Just In Time Compiler für Java. •
JavaJITName=
Legt den Namen des JIT fest und aktiviert ihn. •
JavaMaxHeapSize=
Bestimmt die Maximalgröße des Java Heaps der JVM in Byte (Kilobyte oder Megabyte bei Angabe von KB oder MB). Bei großen Anwendungen ist es empfehlenswert, diesen Wert zu setzen. Je nach Einsatz des Domino-Java-API wird auf einem Domino-Server die JVM im Kontext des HTTP-Tasks oder des Agent Managers betrieben. Diesen muss dementsprechend mehr Hauptspeicher zur Verfügung stehen, wenn dieser Wert gesetzt wird. Wird dieser Wert zu hoch gesetzt, wird Domino nicht mehr starten, da bereits beim Start geprüft wird, ob dieser Wert prinzipiell zugewiesen werden kann, auch wenn JavaMinHeapSize niedriger gesetzt ist. Die JVM startet mit dem Wert aus JavaMinHeapSize und alloziert bei Bedarf bis zum Maximalwert aus JavaMaxHeapSize. Um unnötige Nachallozierung von Hauptspeicher zu vermeiden, ist es empfehlenswert beide Parameter auf denselben Wert zu setzen, der allerdings auf die jeweilige Anwendung abgestimmt sein muss. Bei stark frequentierten Anwendungen (z.B. Webanwendugen auf Basis von Servlets) kann es nötig sein, diesen Wert auf große Werte (256MB..512MB) zu setzten, wobei diese natürlich auch wesentlich von der Anwendung abhängen. Unter AIX muss über das dataseg Utility sichergestellt werden, dass der HTTP bzw. Agent Manager Task (diese werden im System als Prozess abgebildet) genügent Hauptspeicher belegen dürfen und gleichzeitig dem gesamten Domino-Server (der – zur Zeit – nicht mehr als 2GB belegen kann) genügend Hauptspeicher zur Verfügung steht. Kann auch auf dem Notes Client verwendet werden. •
JavaMinHeapSize=
Bestimmt die Minimalgröße des Java Heaps der JVM in Byte (Kilobyte oder Megabyte bei Angabe von KB oder MB). Dieser beträgt per Default 16 MB. Ist JavaMaxHeapSize größer als JavaMinHeapSize, so wird die JVM den Heap bei Bedarf auf JavaMaxHeapSize ausdehnen. Kann auch auf dem Notes Client verwendet werden. •
JavaNoAsyncGC=1
Verhindert die sonst per Default aktive asynchrone Garbage Collection. Für das Debugging von JVM-internen GC-Problemen gedacht.
19.8 Notes-INI-Variablen •
721
JavaNoClassGC=1
Verhindert die Garbage Collection von Klassen. Normalerweise sollte eine Anwendung ohne diesen Parameter auskommen. Kann für falsch programmierte statische Klassen notwendig werden. Sollte nur unter Vorbehalt eingesetzt werden. •
JavaNoVerify=1
Verhindert die Verifizierung des Java Byte Codes beim Laden der Klassen. Dies ist schneller, stellt aber ein Sicherheitsrisiko dar. Sollte nur unter Vorbehalt eingesetzt werden. •
JavaStackSize=512000
Größe des Stacks der JVM. Per Default sind dies 400 KiloByte. Normalerweise sollte dieser Wert ausreichen. Er muss für starke Rekursionen vergrößert werden. •
JavaUserClasses=
Klassen, die über JavaUserClasses geladen werden, können von Domino-JavaAnwendungen im Kontext von Notes / Domino verwendet werden. Der Pfad muss entweder relativ zum Programmverzeichnis (Windows) oder Dataverzeichnis (Unix) oder absolut angegeben werden. Die Gesamtlänge des Pfades darf maximal 256 Zeichen betragen. Einzelne Einträge werden unter Windows mit Semikolon „;“ und unter Unix/Linux mit einem Doppelpunkt „:“ getrennt. Kann auch auf dem Notes Client verwendet werden. •
JavaVerbose=1
Aktiviert die erweiterte Ausgabe der JVM. •
JavaVerboseGC=1
Aktiviert die erweiterte Ausgabe der Garbage Collection. •
JavaVerboseJNI=1
(java native interface debug meldungen)
Aktiviert die erweiterte Ausgabe bei der Verwendung des Java Native Interface. •
JavaVerifyRemote=1 (ist schon default)
Aktiviert die Verifizierung des Java Byte Codes beim Laden entfernter RemoteKlassen. Siehe JavaNoVerify. Per Default ist dieser Wert aktiviert (=1). Console Log Die Domino-Serverkonsole (das Console Log) kann parallel zu der Live-Anzeige in einer Datei aufgezeichnet werden. Diese Aufzeichnung kann über Notes-INI-Parameter gesteuert werden. •
CONSOLE_LOG_ENABLED=1
Aktiviert das Console Log. Ist das Console Log aktiviert, so schreibt Domino die Log Ausgaben der Serverkonsole zusätzlich in eine Textdatei, üblicherweise (Domino R6) im Verzeichnis „IBM_TECHNICAL_SUPPORT“, je nach Installation, innerhalb des Programm- oder Dataverzeichnisses. •
Console_Loglevel=
Legt den Level der Log-Ausgaben fest. Der Log Level kann die Werte 0 bis 4 annehmen und steht jeweils für:
722
19 Debugging und Qualitätssicherung 0 1 2 3 4
•
Keine Ausgabe Ausgabe von Fehlern Zusammengefasste Ausgaben über Prozesse Detaillierte Ausgaben über Prozesse Ausgabe aller Informationen
Console_Log_Max_Kbytes=
Limitiert die Größe der Console-Log Datei. Wird die Größe überschritten, beginnt Domino den Anfang der Datei wieder zu überschreiben. Wird der Wert auf 0 gesetzt, ist die Größe nicht limitiert. •
Debug_Outfile=
Name der Console-Log-Datei. Dieser Parameter wird seit Domino R6 nicht mehr benötigt, da die Datei console.log automatisch geschrieben wird. •
LogFile_Dir=
Verzeichnis, in dem die Console-Log-Datei abgelegt wird. Seit Domino R6 nicht mehr notwendig. Debug Debug-Parameter sind mit besonderer Vorsicht zu behandeln und in jedem Fall nur zum Debugging und nicht für den dauerhaften Einsatz auf produktiven Servern gedacht. Dennoch liefern sie oft sehr hilfreiche Informationen, die es ermöglichen, Fehler zu beheben. •
Debug_ThreadID=1
Wird dieser Parameter aktiviert, so schreibt Domino zu jeder Ausgabe auch die Thread ID des jeweiligen Prozesses ins Log, der dieses Meldung generiert. Diese können dann verwendet werden, um in einem NSD Dump (s.u.) festzustellen, welcher Thread möglicherweise einen Absturz verursacht hat. •
Debug_Capture_Timeout=1 Debug_Show_Timeout=1
Mit den beiden Parametern Debug_Capture_Timeout und Debug_Show_Timeout können so genannte Semaphoren Locks (AIX) erkannt werden. Sind beide Parameter gesetzt, wird die Datei SEMDEBUG.TXT geschrieben, die vom Lotus Support ausgewertet werden kann. In der Datei werden die betroffenen Speicherbereiche angezeigt. Wird zur Untersuchung von Server Hangs eingesetzt. •
DebugShowJavaAgentTID=1
Ist dieser Parameter aktiviert, zeigt das Console Log die Thread IDs von JavaAgenten und -Prozessen. Kann zur Untersuchung im Vergleich mit den Ausgaben eines NSD Files herangezogen werden.
19.8 Notes-INI-Variablen •
723
Debug_Directory_Assistance=1
Aktiviert die Ausgabe von Debugging-Informationen beim Einsatz der Directory Assistance, durch die mehrere Adressbücher mit dem zentralen Adressbuch „names.nsf“ (Domino Directory) verbunden werden können. Die Directory Assistance ermöglicht es, auch LDAP-Verzeichnisse als sekundäre Adresssbücher für die Authentifizierung einzusetzen. Um die Directory Assistance zu aktivieren, muss eine DA-Datenbank aus der Schablone da50.ntf erzeugt und im Serverdokument in der names.nsf angemeldet werden. Dort können dann die sekundären Adressbücher verbunden werden. Näheres hierzu finden Sie im Domino-Administrationshandbuch, der Hilfe im Domino-Administrator. •
Debug_NameLookup=1
Aktiviert die Ausgabe von Debuggging-Informationen während Address-Nachschlage-Operationen. Diese Einstellung ist hilfreich bei Problemen mit der Authentifizierung oder dem Routing. •
DebugRouterLookUp=1..3
Aktiviert die Ausgabe von Debugging-Informationen während des Routings. Der verwendete Wert bestimmt die Ausführlichkeit der Ausgabe. Fault Recovery Fault-Recovery-Parameter sind geeignet, das Verhalten des Servers bei Serverausfällen zu steuern. Sie werden nur in Ausnahmefällen benötigt. •
NSDEnabled=1
Aktiviert die Ausführung eines NSD Dumps nach einem Serverabsturz. Diese Einstellung kann auch im Serverdokument vorgenommen werden. NSD Dumps enthalten Debugging-Informationen und werden im Verzeichnis IBM_TECHNICAL_SUPPORT erstellt. •
FaultRecovery=1
Aktiviert einen automatischen Neustart nach einem Serverabsturz. Diese Einstellung kann auch im Serverdokument vorgenommen werden. •
FaultRecoveryFromIni=1
Im Normalfall lädt Domino die Notes-INI-Parameter zunächst aus der Datei notes.ini, lädt dann die optional vorhandenen Einstellungen aus dem ServerKonfigurationsdokument in names.nsf, speichert diese in der notes.ini-Datei und startet schließlich mit diesen Parametern. Wenn sich im Serverkonfigurationsdokument nun Einstellungen befinden, die den Server am Neustart hindern, entsteht ein Deadlock, der nicht behoben werden kann, da die Datenbank names.nsf wegen des nicht gestarteten Servers nicht erreicht werden kann. Der Parameter FaultRecoveryFromIni unterbindet das Lesen der Einstellungen in der names.nsf nach einem Serverabsturz. Server Domino kennt sehr viele allgemeine serverseitige Einstellungen und Notes-INI-Parameter. Die folgende Auswahl ist wichtig beim Einsatz von HTTP und DIIOP.
724
•
19 Debugging und Qualitätssicherung
TCPIP_TcpipAddress=0,: (z.B.: TCPIP_TcpipAddress=0,192.168.1.1:1352)
Auf einem Domino-Server mit mehreren Interfaces, insbesondere auf einem partitionierten Server, wird es in der Regel erwünscht sein, den Server an eine bestimmte IP zu binden. Der Parameter TCPIP_TcpipAddress ermöglicht dies und erlaubt gleichzeitig die Angabe eines Ports, unter dem Domino erreichbar ist. Der Default Port ist 1352. •
ServerTasks=,HTTP,DIIOP
Für jeden Domino-Server sind die beim Startup zu ladenden Tasks im NotesINI-Parameter ServerTasks gelistet. Unter anderem muss hier HTTP und optional DIIOP gelistet sein, wenn diese Tasks in den Beispielen genutzt werden sollen. HTTP Der Domino-eigene HTTP-Task kennt Debugging- und Logging-Einstellungen, die über die Notes-INI-Parameter aktiviert werden. Insbesondere beim Einsatz von Servlets können diese sehr hilfreich bei der Fehlersuche sein. Allerdings sind aufgrund der Multithreading-Natur des HTTPs einige darunter, die insbesondere in produktiven Umgebungen extrem umfangreiche Ausgaben generieren und dementsprechend vorsichtig eingesetzt werden müssen. •
HTTPDisableMethods= (z.B.: HTTPDisableMethods=TRACE)
Deaktiviert einzelne Methoden (GET, POST, TRACE etc.) des HTTP Protokolls. Dies kann z.B. für die TRACE-Methode aus Sicherheitsgründen sinnvoll sein. Seit den in Domino R6 verfügbaren Internet-Site-Dokumenten in names.nsf kann diese Einstellung auch dort vorgenommen werden. •
HTTPEnableThreadDebug=1
Wird dieser Parameter aktiviert, erzeugt Domino für jeden HTTP Request einen (sehr ausführlichen) Log-Eintrag in separate Log-Dateien. Für jeden Worker des HTTP-Tasks (deren Anzahl im den HTTP-Einstellungen des Serverdokuments geändert werden kann) wird eine eigene Datei angelegt und fortgeschrieben. Dort wird neben dem Request der gesamte Header und Responsecode des Servers aufgezeichnet. Dieser Parameter erzeugt sehr große Mengen von Ausgaben und kann nur vorübergehend und nur unter gleichzeitiger Kontrolle des Dateisystems in produktiven Umgebungen eingesetzt werden. •
HTTPDebugLogDirectory=
Legt das Verzeichnis fest, in das die durch HTTPEnableThreadDebug erzeugten Daten geschrieben werden sollen. •
HTTPUseNotesMemory=0
Domino verwendet für seine internen Tasks ein eigenes Memory Management, vor allem um plattformübergreifenden Code bereitstellen zu können.
19.8 Notes-INI-Variablen
725
In Einzelfällen kann es für den HTTP-Task (unter starker Last) erwünscht sein, auf das Memory Management des Systems zurückzugreifen. Durch HTTPUseNotesMemory=0 wird für den HTTP-Task das Domino-Memory-Management abgeschaltet und auf die Systemroutinen zurückgegriffen. Hierdurch stehen im Falle eines Crashes keine Debugging-Informationen für das HTTP Memory im NSD zur Verfügung. Da die JVM unter Domino innerhalb des HTTP-Tasks betrieben wird, kann unter bestimmten Umständen diese Einstellung unter Performance-Gesichtspunkten von Vorteil sein, insbesondere wenn Servlets und Webagenten stark zum Einsatz kommen. •
HTTPLogUnauthorized=1
Wird dieser Parameter aktiviert, werden unerlaubte Zugriffe und 401-Fehler nicht nur in das Access Log, sondern auch in die Dominokonsole geschrieben. •
HTTP_Pwd_Change_Cache_Hours=
Wird dieser Parameter gesetzt, so wird Domino ein HTTP-Passwort eines Benutzers nach einer Änderung cachen, so dass während der angegebenen Zeitperiode beide Passwörter zugelassen werden. •
NLCACHE_UPDATE_SUPPRESS_TIME=
Der NLCache (Name Lookup Cache) speichert Benutzerinformationen angemeldeter Benutzer. Ändert sich (z.B.) das Internet-Passwort eines Benutzers, so wird dieses (per Default) nach 5 Minuten im Server bekannt, da nach Ablauf dieses Intervalls der NLCache aktualisiert wird. Dies hat zur Folge, dass Benutzer ein über ein Browser Frontend geändertes HTTP-Passwort erst nach 5 Minuten nutzen können. Über den Parameter NLCACHE_UPDATE_SUPPRESS_TIME kann dieser Wert in 100stel Sekunden eingestellt werden, betrifft aber auch andere Bereiche, die Namen und Benutzerinformationen nachschlagen. Wird dieser Wert zu niedrig eingestellt, kann dies die Performance erheblich negativ beeinflussen. Durch den Konsolenbefehl „show nlcache reset“ kann der Name Lookup Cache gelöscht werden. •
NLCACHE_DISABLE=1
Durch den Parameter NLCACHE_DISABLE kann der NLCache komplett deaktiviert werden. Dies macht nur auf reinen Directory Servern (z.B. wenn Domino als reiner LDAP-Server fungiert) Sinn, also auf Servern, die lediglich Benutzerinformationen (durch Deaktivieren des NLCaches sehr aktuell) vorhalten und nicht selbst Benutzerinteraktionen (regelmäßig) authentifizieren müssen. •
Debug_SSO_Trace_Level=
Aktiviert Debug-Informationen für das Single Sign On. Für den Wert 1 werden allgemeine Debugging-Informationen über den Austausch und die Verifizierung von Tokens ausgegeben. Für den Wert 2 werden zusätzlich detaillierte Informationen über die Tokens und deren Inhalt ausgegeben.
726
19 Debugging und Qualitätssicherung
Fulltext Search •
FT_Max_Search_Results=>
der
Ergebnisse
einer
Erlaubt die Einstellung der Maximalzahl zurückgegebener Ergebnisse aus einer FTSearch (s. Kap. 15.5). Default ist 5000. Maximalwert ist 2^31 - 1 =2147483647. Dieser Parameter kann auf einem Domino-Server und einem Notes Client eingesetzt werden. LDAP Bei der Programmierung von Java-Anwendungen ist der Einsatz von Web-Standards wie LDAP sehr naheliegend, aber auch beim Einsatz zum Beispiel des SSO (s. Kap. 16.3) kann Domino sehr gut als LDAP-Server fungieren. Durch den Einsatz einiger Notes-INI-Parameter können hilfreiche Logging-Ausgaben helfen, LDAP-Authentifizierungen transparent zu machen. •
LDAPBatchAdds=0|1
Wird der Parameter LDAPBatchAdds auf 1 gesetzt, so wird nach Schreiboperationen im LDAP nur die Ansicht ($LDAPRDNHier) aktualisiert. Dies kann die Performance von batchgestützten LDAP-Updates mit vielen Änderungen beschleunigen. Der Parameter sollte unmittelbar nach dem Batch-Lauf wieder auf 0 gesetzt werden, damit wieder alle Ansichten nach LDAP-Schreiboperationen aktualisiert werden, um unzuverlässige LDAP-Operationen zu verhindern. •
LDAPConfigUpdateInterval=
Legt die Häufigkeit fest, in der LDAP-Konfigurationen aktualisiert werden. Default ist 3 Minuten. Einige Einstellungen erfordern immer einen Neustart des Servers: „Felder, die anonyme Benutzer im LDAP abfragen dürfen“, „Schreibzugriff für LDAP-User erlauben“ und LDAP-Einstellungen im Serverdokument im Abschnitt „Ports“ -> „Internet Ports“ -> „Directory“. •
LDAPGroupMembership=1|2
Beeinflusst das Verhalten bei Gruppenauflösungen bei LDAP-Suchen. Für den Wert 1 werden alle Gruppen, auch vom Typ „Mail only“ oder ohne Typspezifikation, berücksichtigt. In diesem Fall ist ein Volltextindex auf dem Domino Directory (names.nsf) dringend empfohlen. Für den Wert 2 werden Gruppen vom Typ „Mail only“ oder ohne Typenspezifikation nicht berücksichtigt. •
LDAPNotesPort=
Erlaubt, den Port des LDAP-Dienstes zu beeinflussen. Kann auch im Serverdokument eingestellt werden. •
LDAPPre55Outlook=1
Sollen (vornehmlich) Outlook-Benutzer mit einer Outlook-Version kleiner als 5.5 auf ein Domino Directory per LDAP zugreifen, so muss dieser Parameter gesetzt werden. •
LDAPDebug=0..7
19.8 Notes-INI-Variablen
727
Aktiviert die Ausgabe von LDAP-Debugging-Angaben. Für den Wert 7 werden sehr viele Ausgaben geschrieben, so dass auch für diesen Parameter gilt, dass er nur zu Debugging-Zwecken und nicht (dauerhaft) in produktiven Umgebungen eingesetzt werden kann. •
LDAP_Enforce_Schema=1
Erzwingt, dass Schreiboperationen konform zum LDAP-Schema sind. Andernfalls können Schreiboperationen auch Attribute in einem Datensatz erzeugen oder ändern, die nicht im Schema beschrieben sind. Default ist LDAP_Enforce_Schema=0. Für Domino ist dieser Default sinnvoll, da Domino-Dokumente ebenfalls nur auf einem losen Schema basieren und sich hinter den LDAP-Datensätzen im Domino Directory Domino-Dokumente befinden. •
WebAuth_Verbose_Trace=1
Aktiviert Debugging-Informationen für Domino-LDAP-Web-Authentifzierungsoperationen. DIIOP Wird mit Remote-Domino-Java-Anwendungen gearbeitet, kommt DIIOP zum Einsatz. Die folgenden Notes-INI-Parameter ermöglichen einige erweiterte Einstellungen und erweitertes Logging. •
DIIOPConfigUpdateInterval=
Gibt das Aktualisierungsintervall an, nach dem der DIIOP-Dienst seine Konfiguration aus dem Domino Directory aktualisiert. Default sind 3 Minuten. •
DIIOPCookieCheckAddress=1
Aktiviert Cookies, mit denen die IP-Adresse des Clients, der ein Applet vom HTTP Server geladen hat, gegen die IP-Adresse der eigentlichen Anfrage geprüft wird. Werden NAT oder Proxies eingesetzt, wird die IP des Clients in der Regel nicht identisch, mit der IP-Adresse der Anfrage sein. In diesem Fall kann der Parameter nicht eingesetzt werden. •
DIIOPCookieTimeout=
Zeitintervall, für das ein Cookie gültig ist, das beim Download von Applets gesetzt wird. •
DIIOPDNSLookup=1
Aktiviert DNS Lookups für Clients, die sich mit dem Domino-IIOP-Dienst verbinden. Die aufgelösten Namen werden in der Konsole angezeigt. •
DIIOPIgnorePortLimits=1
Aktiviert Port Limits für den DIIOP-Task auf (einigen) Linux-Plattformen. Normalerweise ist der Dienst unter den Ports 63148 und 63149 verfügbar. Unter den betroffenen Linux-Plattformen werden stattdessen per Default die Ports 60148 und 60149 verwendet. Durch Setzen des Parameters DIIOPIgnorePortLimits=1, werden die höheren Ports auch unter Linux verwendet. •
DIIOPIORHost=
728
19 Debugging und Qualitätssicherung Gibt den Namen des DIIOP-Hosts an, unter dem sich dieser meldet. Andernfalls wird der Name aus dem Serverdokument im Feld „Fully qualified Internet host name:“ im Abschnitt „Basics“ verwendet. Dieser Wert kann auch in den DIIOPEinstellungen des Serverdokuments vorgenommen werden. Er ist insbesondere wichtig beim Einsatz von SSL.
•
DIIOPLogLevel=0..4
Aktiviert die Ausgabe von Logging-Informationen für DIIOP. 0 1 2 3 4 •
Ausgabe von Fehlern und Warnungen Zusätzlich Ausgaben vom Level „Information“ Zusätzlich Ausgabe von Initialisierungs- und Terminierungsinformationen Zusätzlich Ausgabe von statistischen Informationen Zusätzlich Ausgabe von Informationen zu Transaktionen
DIIOP_Debug_Invoke=1|2
Erweitert die Debugging-Ausgaben im DIIOP. Zusätzlich werden Object IDs und Session IDs ausgegeben. Agenten Beim Einsatz periodischer Agenten kann mit Hilfe des folgenden Parameters ein erweitertes Logging aktiviert werden, das hilfreiche Informationen über die Agentenausführung liefert. •
Debug_AMGR=
Durch den Einsatz von Debug_AMGR wird das Logging für den Agent Manager aktiviert, so dass erweiterte Meldungen für Domino-Agenten ins Console Log geschrieben werden. c Zeigt Steuerparamter des Agenten an e Zeigt Agenten-Events an l Zeigt das Laden von Agenten an m Zeigt Speicherwarnungen bei Agenten an p Zeigt Performanceinformationen für Agenten an r Zeigt Ausführungsberichte für Agenten an v Erweiterte Debugging-Anzeige für Agenten * Zeigt alle obigen Informationen für Agenten an Date / Time •
ClockType=12_HOUR | 24_HOUR
Definiert die Uhr für den Domino-Server oder Domino-Client und gibt an, ob eine 12-Stunden- oder 24-Stunden-Uhr zum Einsatz kommen soll. •
DateOrder=DMY|YMD|MDY
Definiert die Anordnung von Tag, Monat und Jahr in Datumsangaben. •
DateSeparator=
19.9 Zusammenfassung
729
(z.B. DateSeparator=.)
Trennzeichen für Datumsangaben •
TimeSeparator= (z.B. TimeSeparator=:)
Trennzeichen für Zeitangaben
19.9
Zusammenfassung
Debugging und Logging sind aus der Softwareentwicklung und deren Qualitätssicherung nicht wegzudenken. Durch den Einsatz von fest vordefinierten Tests, insbesondere Unit Tests, funktionale und Lasttests, wird die dauerhafte und langfristige Stabilität von Anwendungen gesichert. Auch Domino-Java-Anwendungen lassen sich diesen Techniken unterziehen, erfordern jedoch besonderes Augenmerk, um dem transaktionalen Charakter der Session gerecht zu werden. Leicht konnte gezeigt werden, dass es möglich ist, einfach JUnit auch für Domino-Java-Objekte zum Einsatz zu bringen und erfolgreich einzusetzen. Neben Tests ist das Logging ein wichtiger Faktor bei der Überwachung, aber auch beim Debugging von Anwendungen. Das Apache-log4j-Framework kann durch die DJLog-Klasse gekapselt werden und ermöglicht – ohne selbst von einer Domino-Session abhängig zu sein – das einfache und von jeglicher Infrastruktur unabhängige Logging in Domino-Java-Anwendungen. Die Einführung eines Domino-Database Appenders für das log4j Framework ermöglicht schließlich, Logging-Informationen – auch von Nicht-Domino-Anwendungen – in Domino-Datenbanken zu speichern und der Replikation und Infrastruktur von Domino zur Verfügung zu stellen. Neben dem Loggging in eigenen Anwendungen ist es oft notwendig auch das Zusammenspiel mit Domino selbst zu überwachen. Mit Test und Qualitätssicherung stehen nun als Abrundung die notwendigen Werkzeuge der Javaentwicklung für Domino zur Verfügung, die es erlauben, auch große und komplexe Anwendungen mit Erfolg, stabil und sicher fertigzustellen.
20
20 Anhang
732
20 Anhang
20.1
Bekannte Speicherlimits in Domino-Datenbanken
•
•
• • • •
•
•
• • •
Maximale Datenbankgröße 64 GB, bzw. limitiert durch das Betriebssystem (2GB bei einigen Unix-Derivaten. Macintosh ebenfalls 2GB). Größe eines Items 32 KB im Dokument für Items mit der Eigenschaft „summary“ (s. Kap. 7.1.3) – Summary Items können in einer Ansichten-Spalte angezeigt werden. Das Item darf jedoch maximal 15 KB enthalten, damit es noch in einer Ansichtenspalte angezeigt wird. Non-summary Items können bis zu 64 KB groß sein. Gesamtgröße aller Items (ohne RichText Items und ohne Anhänge) 64 KB Größe eines RichText Items Limitiert durch den vorhandenen Plattenplatz, jedoch maximal 1 GB Kategoriehierarchien in einer kategorisierten Ansicht Maximal 31 Ebenen und maximal 300.000 Dokumente als Antwortdokumente. Zeichenlängen der Namen von Designelementen Datenbanktitel – 96 Byte Dateiname – entsprechend dem Betriebssystem Itemnamen – maximal 32 Byte Ansichtennamen – maximal 64 Byte Maskennamen – maximal 32 Byte Agentennamen – maximal 32 Byte Maximalzahl an Items in einem Dokument Maximal rund 3000. Maximal darf die Länge alle Itemnamen 64 KB nicht überschreiten. Die Datenbankeigenschaft „Allow more fields in database“ erweitert dieses Limit auf rund 22.000 Items mit eindeutigem Namen. Anzahl der Ansichten in einer Datenbank Grundsätzlich nur durch die maximale Datenbankgröße limitiert. Allerdings sollte berücksichtigt werden, dass mit jedem View auch dessen Ansichtenindex aktuell gehalten werden muss, so dass bei einer zu großen Zahl von Ansichten Performanceeinbußen hinzunehmen sind. Anzahl der Masken in einer Datenbank Grundsätzlich nur durch die maximale Datenbankgröße limitiert. Spalten in einer Ansicht Die Gesamtzahl aller Spaltenbreiten darf 2890 nicht überschreiten. Anzahl der Dokumente einer Ansicht Grundsätzlich nur durch die maximale Datenbankgröße limitiert.
20.2 Übersicht über die Domtags Tag Library
20.2
Übersicht über die Domtags Tag Library
action accesskey class debug enabled lang name onfailure showdisabled style tabindex text time title
N N N N N J N N N N N N N
attachmentlist class debug enableconfirm enabledelete format lang name onfailure style tabindex target time title
N N N N N N N N N N N N N
attachments bgcolor codepath debug draw height locale maxsize name
733
N N N N N N N N
onfailure time width
onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onselect onselectstart readonly style tabindex time title value
N N N
authors debug format id locale onfailure time
N N N N N N
checkbox accesskey bundle class datatype debug default disabled id itemname label labelbefore labelkey locale multivalue name onafterupdate onbeforeupdate onblur onchange onclick ondblclick ondragstart onfailure onfocus onhelp onkeydown
N N N N N N N N N N N N N N J N N N N N N N N N N N
N N N N N N N N N N N N N N J
created debug format id locale onfailure time timezone
N N N N N N N
dbname dbserver debug dojkey host id onfailure password time user
N N N N N N N N N N
db
dbfill dbfield debug
J N
734
20 Anhang
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder onfailure target time
N J N
dbselect accesskey align category datasrc dbname dbserver debug disabled displayitem format formula host itemname key locale max multiple name onafterupdate onbeforeupdate onblur onchange onclick ondblclick ondragstart onfailure onfocus onhelp onkeydown onkeypress onkeyup onmousedown onmousemove
N N N N N N N N N N N N N N N N N J N N N N N N N N N N N N N N N
onmouseout onmouseover onmouseup onresize onrowenter onrowexit onselectstart password query size tabindex time user valueitem view
N N N N N N N N N N N N N N N
default bundle dbname dbserver debug host locale onfailure password responseto scope sessionduration sessionmgrkey time timezone unid user viewname
N N N N N N N N N N N N N N N N N
deleteattachment accesskey class debug
N N N
lang name onfailure showdisabled style tabindex text time title
N N N N N N N N N
deletedoc accesskey afterdelete class debug lang name onfailure showdisabled style tabindex text time title validhref
N N N N N N N N N N N N N N
docloop count debug direction id onfailure start time
N N N N N N N
docnavimg accesskey afterdelete alttext asresponse
N N N N
20.2 Übersicht über die Domtags Tag Library
735
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder border class debug disabled enabled lang name onfailure showdisabled style tabindex time title type validhref
N N N N N N N N N N N N N J N
document dbname dbserver debug dojkey host id onfailure password responseto schema time unid user
N N N N N N N N N N N N N
editdoc accesskey class debug lang name onfailure showdisabled
N N N N N N N
style tabindex text time title
N N N N N
debug onfailure target time value
N N J N N
fill
form accept accept_charset class clientvalidate counter dbname dbserver debug dir editmode host id lang language name onclick ondblclick ondragstart onfailure onhelp onkeydown onkeypress onkeyup onmousedown onmousemove
N N N N N N N N N N N N N N J N N N N N N N N N N
onmouseout onmouseover onmouseup onreset onselectstart onsubmit password responseto schema style target time title unid user validhref viewname
N N N N N N N N N N N N N N N N N
formlink accesskey asresponse class debug href id lang name onblur onclick ondblclick onfailure onfocus onhelp onkeydown onkeypress onkeyup onmousedown onmousemove
N N N N J N N N N N N N N N N N N N N
736
20 Anhang
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder onmouseout onmouseover onmouseup onselectstart style tabindex target text time title
N N N N N N N N N N
formula debug format id locale onfailure time timezone
N N N N N N N
ftsearch dbname dbserver debug host id max onfailure options password query sort time user
debug dominoversion is onfailure tag taglibversion time
N N N N N N N
ifcategoryentry debug is onfailure time
N N N N
ifconflictentry debug is onfailure time
N N N N
ifdbrole N N N N N N N N N J N N N
ifauthor debug is onfailure time
ifcapability
N N N N
debug is name onfailure time
N N J N N
ifdepositor debug is onfailure time
N N N N
ifdesigner debug is onfailure time
N N N N
ifdisplaymode debug is
N N
onfailure time
N N
ifdocauthor debug is onfailure time
N N N N
ifdocreader debug is onfailure time
N N N N
ifdocumententry debug is onfailure time
N N N N
ifeditmode debug is onfailure time
N N N N
ifeditor debug is onfailure time
N N N N
ifmanager debug is onfailure time
N N N N
ifnoaccess debug is onfailure time
N N N N
20.2 Übersicht über die Domtags Tag Library
737
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder ifreader debug is onfailure time
N N N N
ifserverevent debug event for id onfailure time
N J N N N N
iftotalentry debug is onfailure time
N N N N
indent debug onfailure time
N N N
input accesskey class clientvalidate datatype debug defaultformula defaultvalue dir disabled editformat format id itemname lang language
N N N N N N N N N N N N N N N
locale maxlength multivalue mvdisplayseparator mveditseparator name onafterupdate onbeforeupdate onblur onchange onclick ondblclick ondragstart onfailure onfocus onhelp onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onselect onselectstart readonly size src style tabindex time timezone title type
N N N N N J N N N N N N N N N N N N N N N N N N N N N N N N N N N N N
item debug
N
format formattedid id locale name onfailure time timezone
N N N N N N N N
lastaccessed debug format id locale onfailure time timezone
N N N N N N N
lastmodified debug format id locale onfailure time timezone
N N N N N N N
mailto bcc cc dbname dbserver debug deliveryreport encoding host importance onfailure password priority
N N N N N N N N N N N N
738
20 Anhang
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder returnreceipt saveonsend subject time to user
N N J N J N
msg arg0 arg1 arg2 arg3 arg4 bundle debug id locale msgkey onfailure time
N N N N N N N N N J N N
newdoc accesskey asresponse class debug lang name onfailure showdisabled style tabindex text time title
N N N N N N N N N N N N N
nextdoc accesskey class debug
N N N
lang name onfailure showdisabled style tabindex text time title
N N N N N N N N N
nodocument text
N
novalues debug onfailure time
N N N
option class debug dir disabled display id label lang language onfailure selected style time title value
N N N N N N N N N N N N N N N
page debug direction evaluateonnorows id max
N N N N N
name onfailure rows start time viewasdocuments
N N N N N N
pagebar accesskey class debug lang navstyle nextblockimg onfailure pages prevblockimg style tabindex target time title
N N N N N N N N N N N N N N
pagefirst accesskey class debug id lang name onblur onclick ondblclick onfailure onfocus onhelp onkeydown onkeypress onkeyup
N N N N N N N N N N N N N N N
20.2 Übersicht über die Domtags Tag Library
739
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder onmousedown onmousemove onmouseout onmouseover onmouseup onselectstart style tabindex target text time title
N N N N N N N N N N N N
pagelast accesskey class debug id lang name onblur onclick ondblclick onfailure onfocus onhelp onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onselectstart style tabindex target
N N N N N N N N N N N N N N N N N N N N N N N N
text time title
N N N
pagenext accesskey class debug id lang name onblur onclick ondblclick onfailure onfocus onhelp onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onselectstart showdisabled style tabindex target text time title
N N N N N N N N N N N N N N N N N N N N N N N N N N N N
pageprev accesskey class debug id
N N N N
lang name onblur onclick ondblclick onfailure onfocus onhelp onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onselectstart showdisabled style tabindex target text time title
N N N N N N N N N N N N N N N N N N N N N N N N
parentlink accesskey class debug href id lang name onblur onclick ondblclick onfailure onfocus
N N N N N N N N N N N N
740
20 Anhang
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder onhelp onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onselectstart showdisabled style tabindex target text time title
N N N N N N N N N N N N N N N N N
preserve debug name onfailure time value
N J N N N
preset bundle dbname dbserver debug host host locale onfailure password responseto scope sessionduration sessionmgrkey
N N N N N N N N N N N N N
time timezone unid user viewname
N N N N N
prevdoc accesskey class debug lang name onfailure showdisabled style tabindex text time title
N N N N N N N N N N N N N N N N N N N N N N N N N
profileitem debug format formattedid id
N N N N N
profilesetitem
profiledocument dbname dbserver debug host id onfailure password profilename profileuser schema time unid user
locale name onfailure time timezone
N N N N
datatype debug locale mvseparator name onfailure time timezone value
N N N N J N N N J
radio accesskey bundle class clientvalidate datatype debug default disabled id itemname label labelbefore labelkey locale name onafterupdate onbeforeupdate onblur onchange onclick ondblclick
N N N N N N N N N N N N N N J N N N N N N
20.2 Übersicht über die Domtags Tag Library
741
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder ondragstart onfailure onfocus onhelp onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onselect onselectstart readonly style tabindex time title value
N N N N N N N N N N N N N N N N N N N J
responses debug id max onfailure time
N N N N N
richtext accesskey clientvalidate codepath cols control debug height itemname locale name
N N N N N N N N N N
onblur onfailure rows tabindex time title width
N N N N N N N
runagent dbname dbserver debug host name onfailure password time unid user
N N N N J N N N N N
saveclosedoc accesskey class debug lang name onfailure showdisabled style tabindex text time title validhref
N N N N N N N N N N N N N
savedoc accesskey class debug lang
N N N N
name onfailure showdisabled style tabindex text time title
N N N N N N N N
savenow debug onfailure time
N N N
select accesskey align datatype debug defaultformula defaultvalue disabled format itemname locale multivalue mvdisplayseparator mveditseparator name onafterupdate onbeforeupdate onblur onchange onclick ondblclick ondragstart onfailure onfocus onhelp
N N N N N N N N N N N N N J N N N N N N N N N N
742
20 Anhang
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onresize onrowenter onrowexit onselectstart size tabindex time timezone
N N N N N N N N N N N N N N N N
selectedloop count dbname dbserver debug host id onfailure password start time user viewname
N N N N N N N N N N N N
selectentry debug onfailure time
N N N
session debug dojkey duration
N N N
host id onfailure password sessionmgrkey time user
N N N N N N N
setitem datatype debug locale mvseparator name onfailure time timezone value
N N N N J N N N J
textarea accesskey align class clientvalidate cols datatype debug defaultformula defaultvalue dir disabled editformat format id itemname lang language locale multivalue
N N N N N N N N N N N N N N N N N N N
mvdisplayseparator mveditseparator name onafterupdate onbeforeupdate onblur onchange onclick ondblclick ondragstart onfailure onfocus onhelp onkeydown onkeypress onkeyup onmousedown onmousemove onmouseout onmouseover onmouseup onresize onrowenter onrowexit onselect onselectstart readonly rows style tabindex time timezone title wrap
N N J N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N
unid debug id
N N
20.2 Übersicht über die Domtags Tag Library
743
Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder onfailure time
N N
user debug id onfailure time
N N N N
validatecc alert bundle class debug locale message msgkey name onalert onfailure style time
N N N N N N N J N N N N
validatedate alert bundle class debug locale max message min msgkey name onalert onfailure style time type
validatelength
N N N N N N N N N J N N N N N
alert bundle class debug locale max message min msgkey name onalert onfailure style time
N N N N N N N N N J N N N N
validatepattern alert bundle class debug locale message msgkey name onalert onfailure pattern style time
N N N N N N N J N N J N N
validaterange alert bundle class debug locale max message min
N N N N N N N N
msgkey name onalert onfailure style time type
N J N N N N N
validaterequired alert bundle class debug locale message msgkey name nonspace onalert onfailure style time
N N N N N N N J N N N N N
validatesummary bundle caption captionkey class debug format locale onfailure style time
N N N N N N N N N N
validatewith alert bundle casesensitive class
N N N N
744
20 Anhang
debug locale message msgkey name onalert onfailure operation otherfield style time type value
N N N N J N N N N N N N N
view category dbname dbserver debug defaultname depth dojkey enableselect ftsearch host id key keyexact keyseparator keytype max onfailure password time toponly user viewname
viewitem
N N N N N N N N N N N N N N N N N N N N N N
col debug format formattedid id locale name onfailure time timezone
N N N N N N N N N N
viewloop count debug direction id onfailure start time
N N N N N N N
20.3 Literatur und Links
20.3
Literatur und Links
[AndyKhan, JExcel]
[Apache, cocoon] [Apache, JMeter] [Apache, log4j] [Apache, mod_proxy] [Apache, Struts] [Apache, XML] [Batutis, LMBCS] [OMG, CORBA] [developerWorks] [Domino Eclipse Plugin] [Domino Exceptions] [Domino JBoss SSO] [Domino Redirector] [Domino Tomcat SSO]
[Domino, ODBC] [Eclipse] [Heise, News] [Hibernate, In Action] [Hibernate, URL] [htmlunit] [HttpUnit] [Hunter, Servlet]´ [ISO]
745
Java Excel API - A Java-API to read, write and modify Excel spreadsheets - http://www.andykhan.com/jexcelapi/ The Apache cocoon Project - http://cocoon.apache.org/ Apache JMeter - http://jakarta.apache.org/jmeter Logging Services Project @ Apache - http:// logging.apache.org Apache Module mod_proxy - http://httpd.apache.org/ docs/2.1/mod/mod_proxy.html The Apache Struts Project - http://struts.apache.org/ The Apache XML Project - http://xml.apache.org Lotus Multi Byte Character Set - LMBCS - http:// www.batutis.com/i18n/papers/lmbcs/LMBCS.html Common Object Request Broker Architecture - http:// www.omg.org/gettingstarted/specintro.htm#CORBA developerWorks - Downloads - http://www.ibm.com/ developerworks/lotus/downloads/ Domino Eclipse Plugin - http://www.domiclipse.com Java Access to Domino Objects. http://www.ibm.com/ developerworks/lotus/library/ls-Java_access_2/#N10206 http://www.keysolutions.com/webhome/domino_jboss_sso.html Domino Tomcat Redirector - http://free.tagish.net/domino-tomcat/index.jsp Lotus Domino and Apache Tomcat - Single Sign On (SSO) - http://www.automatedlogic.com/domblog.nsf/ dx/DominoTomcatSSOIntegration Homepage von Lotus NotesSQL - http://lotus.com/notessql Eclipse Tool Platform IDE - http://www.eclipse.org RSS News Feed der WebSite www.heise.de - http:// www.heise.de/newsticker/heise.rdf Hibernate in Action, Manning Publications, ISBN 193239415X O/R-mapping Framework für Java mit Hibernate -http://www.hibernate.org Htmlunit Framework - Russell Gold - http:// htmlunit.sourceforge.net HttpUnit Framework - Mike Bowler, et. al. - http:// httpunit.sourceforge.net Servlet Programmierung, Hunter, Crawford, ISBN: 389721282X, O'Reilly, 2001 International Organization for Standardization - http:// www.iso.org
746
20 Anhang
[LDAP Browser] [Lotus, Designer]
[Lotus, DesignerR6]
[Lotus, DesignerR65]
[Lotus, DesignerR7]
[Lotus, DGW]
[Lotus, DTD]
[Lotus, History] [Lotus, LDD]
[Lotus, R6Formula]
[Massol, JUnit] [OMG, CORBA]
[OMG] [Programming Bible]
LDAP Browser/Editor - http://www-unix.mcs.anl.gov/ ~gawor/ldap/index.html Domino R5 Designer Handbuch mit XML - http:// www.lotus.com/ldd/doc/domino_notes/5.0.3/XML/ help5_designer_XML.nsf. Auch als offline Version als Datenbank help5_designerXML.nsf als Bestandteil der R5 Distribution verfügbar. Domino R6 Designer Hilfe - http://www.lotus.com/ldd/ doc/domino_notes/Rnext/help6_designer.nsf. Auch als offline Version als Datenbank help6_designer.nsf als Bestandteil der R6 Distribution verfügbar. Domino R65.x Designer Hilfe - http://www.lotus.com/ ldd/doc/domino_notes/6.5.1/help65_designer.nsf. Auch als offline Version als Datenbank help65_designer.nsf als Bestandteil der R65x Distribution verfügbar. Domino R7 Designer Hilfe - http://www.lotus.com/ldd/ doc/domino_notes/7.0/help7_designer.nsf/Main?OpenFrameSet. Auch als offline Version als Datenbank help7_designer.nsf als Bestandteil der R7 Distribution verfügbar. LDD Today - What is a multilangual Application - http:// www.lotus.com/ldd/today.nsf/lookup/multilanguage_apps Domino R6 Designer Hilfe - Domino DTD - http:// www.lotus.com/ldd/doc/domino_notes/Rnext/ help6_designer.nsf/ b3266a3c17f9bb7085256b870069c0a9/f6841beedeeceac485256c54004ba87a?OpenDocument LDD Today - The history of Notes and Domino - http:// www.lotus.com/ldd/today.nsf/lookup/NDHistory Lotus Domino Developer Domain - http:// www.ibm.com/developerworks/lotus/products/notesdomino/ Enhancements to the formula language in Domino 6 http://www.lotus.com/ldd/today.nsf/lookup/formula_enhancements JUnit in Action, Vincent Massol u. Ted Husted, Manning Publications Co., Greenwich, 2004 Common Object Request Broker Architecture: Core Specification - http://www.omg.org/docs/formal/04-0301.pdf Object Management Group - http://www.omg.org/ Lotus Notes and Domino 6 Programming Bible. Brian Benz und Rocky Oliver. Wiley Publishing. Inc. 2003, Indianapolis, Indiana, USA.
20.3 Literatur und Links [QuantumDB]
747
Homepage des Quantum Software Plugins - http:// quantum.sourceforge.net [Redbook, Domino WAS] Integrating Lotus Domino 6 and WebSphere Express V5 on the IBM e-server iSeries Server, Redbook SG24-699800, Deb Landon, Jim Ahrens, Tony Austin, Lu Cao, Paulo Meira, ISBN 0738498564, IBM, 2003 [Redbook, Integration] Patterns: Custom Designs for Domino and WebSphere Integration, Redbook SG24-6903-00, Tommi Tulisalo, Edward Cawthorne, Jonathan Czernel, Bradley Hertenstein, Kenneth Reed, ISBN 0738429201, IBM, 2003 [Redbook, XML] XML Powered By Domino, Redbook SG24-6207-00, David Morrison, Brian Benz, Paul Calhoun, Yusuke Murakami, ISBN 073842109X, IBM, 2000 [Reske, Domino JSP] Using the Domino JSP Custom Tags, Domino redpaper 3902, Thomas Reske, IBM 12/2004 [RFC 2068, HTTP 1.1] RFC 2068 - http://www.faqs.org/rfcs/rfc2068.html [RFC 2109, Cookie] RFC 2109 - http://www.faqs.org/rfcs/rfc2109.html [RFC 2965, Cookie2] RFC 2965 - http://www.faqs.org/rfcs/rfc2965.html [RFC 821, SMTP] Simple Mail Transfer Protocol. RFC 821 - http:// www.faqs.org/rfcs/rfc821.html [RFC 822, ARPA Messages] Standard for the format of ARPA Internet text messages. RFC 822 - http://www.faqs.org/rfcs/rfc822.html [ShiftOne] ShiftOne joCache - a Java Object Cache www.shiftone.org und http://jocache.sourceforge.net [Sun, JDK] Java 2 Platform, Standard Edition (J2SE) - http:// java.sun.com/j2se/ und http://java.sun.com/products/archive/j2se/1.3.1_12/index.html [Tomcat, Apache] Tomcat FAQ. Connectors - http://tomcat.apache.org/ faq/connectors.html [Tomcat, Connectors] The Apache Jakarta Tomcat Connector - http:// tomcat.apache.org/connectors-doc [Tomcat, DSAPI Kurzinfo] Connecting Tomcat to Domino http:// home.exetel.com.au/dangela/technical_docs/tomcatdomino.html [W3C, WebService] Web Services Architecture. W3C Working Group Note 11. February 2004. David Booth et.al. - http:// www.w3.org/TR/ws-arch [WAS, Infocenter] „Migrating Common Object Request Broker Architecture programmatic login to Java Authentication and Authorization Service“ - http://publib.boulder.ibm.com/ infocenter/ws51help/index.jsp. Dort Suche nach „CORBA Security API“.
748
20 Anhang
20.4
Disclaimer
Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutzgesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Alle Warenzeichen und Markennamen sind Eigentum ihrer jeweiligen Inhaber. Text, Abbildungen, Beispiele und Sourcecode wurden mit größter Sorgfalt erarbeitet. Verlag und Autor können jedoch für eventuell verbliebene fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Die Urheberrechte an den Sourcecodes, die diesem Buch als Beispiele beiliegen, liegen bei Thomas Ekert, Hamburg. Alle Rechte vorbehalten. Trotz sorgfältiger Kontrolle sind Fehler in Softwareprodukten nie vollständig auszuschließen. Die Sourcecodes werden in ihrem Originalzustand ausgeliefert. Ansprüche auf Anpassung, Weiterentwicklung, Fehlerbehebung, Support oder sonstige wie auch immer geartete Leistungen oder Haftung sind ausgeschlossen. Die Sourcecodes dürfen kommerziell genutzt, weiterverarbeitet oder weitervertrieben werden. Voraussetzung hierfür ist, dass für jeden beteiligten Entwickler, jeweils mindestens ein Exemplar dieses Buches in seiner aktuellen Version als gekauftes Exemplar vorliegt. Trotz sorgfältiger inhaltlicher Kontrolle übernehmen weder Verlag noch Autor Haftung für die Inhalte von Internetseiten, deren Links in diesem Werk genannt werden. Für den Inhalt der verlinkten Seiten sind deren Betreiber verantwortlich.
20.5 Index
20.5
749
Index
Neben den allgemeinen Einträgen enthält der Index ein vollständiges Klassen- und Methodenverzeichnis der Domino-Java-API-Klassen mit Verweisen auf die Fundstellen der Methoden bzw. der zugehörigen Klasse im Text. Eine Übersicht über alle Klassen befindet sich in Tabelle 6-1 in Kapitel 6.1.5. A A 464 abbreviated 465 Abschnitt Abschnitt 91, 426, 434 Twistie 434 Abstract 262 Access Control List 355, 356 Access to data denied 290 ACL ACL 155, 302, 355, 526 public interface ACL extends Base 355 ACLEntry createACLEntry(String s, int i) throws NotesException 355 ACLEntry getEntry(String s) throws NotesException 358 ACLEntry getFirstEntry() throws NotesException 358 ACLEntry getNextEntry() throws NotesException 358 ACLEntry getNextEntry(ACLEntry aclentry) throws NotesException 358 boolean isAdminNames() throws NotesException 355 boolean isAdminReaderAuthor() throws NotesException 355 boolean isExtendedAccess() throws NotesException 355 boolean isUniformAccess() throws NotesException 355 Database getParent() throws NotesException 355 int getInternetLevel() throws NotesException 359 String getAdministrationServer() throws NotesException 319 Vector getRoles() throws NotesException 356 void addRole(String s) throws NotesException 356 void deleteRole(String s) throws NotesException 356 void removeACLEntry(String s) throws NotesException 355 void renameRole(String s, String s1) throws NotesException 356 void save() throws NotesException 358 void setAdministrationServer(String s) throws NotesException 319 void setAdminNames(boolean flag) throws NotesException 355 void setAdminReaderAuthor(boolean flag) throws NotesException 355 void setExtendedAccess(boolean flag) throws NotesException 355 void setInternetLevel(int i) throws NotesException 359 void setUniformAccess(boolean flag) throws NotesException 355 ACLEntry public interface ACLEntry extends Base 358 ACL getParent() throws NotesException 358 boolean isAdminReaderAuthor() throws NotesException 358 boolean isAdminServer() throws NotesException 358 boolean isCanCreateDocuments() throws NotesException 358 boolean isCanCreateLSOrJavaAgent() throws NotesException 358 boolean isCanCreatePersonalAgent() throws NotesException 358 boolean isCanCreatePersonalFolder() throws NotesException 358 boolean isCanCreateSharedFolder() throws NotesException 358 boolean isCanDeleteDocuments() throws NotesException 358 boolean isCanReplicateOrCopyDocuments() throws NotesException 358 boolean isGroup() throws NotesException 358 boolean isPerson() throws NotesException 358 boolean isPublicReader() throws NotesException 358 boolean isPublicWriter() throws NotesException 358
750
20 Anhang
boolean isRoleEnabled(String s) throws NotesException 356, 357 boolean isServer() throws NotesException 358 int getLevel() throws NotesException 358 int getUserType() throws NotesException 358 Name getNameObject() throws NotesException 358 String getName() throws NotesException 358 Vector getRoles() throws NotesException 358 void disableRole(String s) throws NotesException 356 void enableRole(String s) throws NotesException 356 void remove() throws NotesException 358 void setAdminReaderAuthor(boolean flag) throws NotesException 358 void setAdminServer(boolean flag) throws NotesException 358 void setCanCreateDocuments(boolean flag) throws NotesException 358 void setCanCreateLSOrJavaAgent(boolean flag) throws NotesException 358 void setCanCreatePersonalAgent(boolean flag) throws NotesException 358 void setCanCreatePersonalFolder(boolean flag) throws NotesException 358 void setCanCreateSharedFolder(boolean flag) throws NotesException 358 void setCanDeleteDocuments(boolean flag) throws NotesException 358 void setCanReplicateOrCopyDocuments(boolean flag) throws NotesException 358 void setGroup(boolean flag) throws NotesException 358 void setLevel(int i) throws NotesException 358 void setName(Name name) throws NotesException 358 void setName(String s) throws NotesException 358 void setPerson(boolean flag) throws NotesException 358 void setPublicReader(boolean flag) throws NotesException 358 void setPublicWriter(boolean flag) throws NotesException 358 void setServer(boolean flag) throws NotesException 358 void setUserType(int i) throws NotesException 358 Action 83 ActionListener 135 ADMD 464 Admin Prozess 677 admin4.nsf 674 Administration Management Domain Name 464 Administration Requests 674 AdministrationProcess public interface AdministrationProcess extends Base 331 boolean isCertificateAuthorityAvailable() throws NotesException 331 boolean isUseCertificateAuthority() throws NotesException 331 DateTime getCertificateExpiration() throws NotesException 331 Session getParent() throws NotesException 331 String addGroupMembers(String s, Vector vector) throws NotesException 331 String addInternetCertificateToUser(String s, String s1, String s2) throws NotesException 331 String addInternetCertificateToUser(String s, String s1, String s2, DateTime datetime) throws NotesException 331 String addServerToCluster(String s, String s1) throws NotesException 331 String approveDeletePersonInDirectory(String s) throws NotesException 331 String approveDeleteServerInDirectory(String s) throws NotesException 331 String approveDesignElementDeletion(String s) throws NotesException 331 String approveHostedOrgStorageDeletion(String s) throws NotesException 331 String approveMailFileDeletion(String s) throws NotesException 331 String approveMovedReplicaDeletion(String s) throws NotesException 331 String approveNameChangeRetraction(String s) throws NotesException 331 String approveRenamePersonInDirectory(String s) throws NotesException 331 String approveRenameServerInDirectory(String s) throws NotesException 331 String approveReplicaDeletion(String s) throws NotesException 331 String approveResourceDeletion(String s) throws NotesException 331 String changeHTTPPassword(String s, String s1, String s2) throws NotesException 331 String configureMailAgent(String s, String s1) throws NotesException 331 String configureMailAgent(String s, String s1, boolean flag, boolean flag1) throws NotesException 331 String createReplica(String s, String s1, String s2) throws NotesException 331
20.5 Index
751
String createReplica(String s, String s1, String s2, String s3, boolean flag, boolean flag1) throws NotesException 331 String deleteGroup(String s, boolean flag) throws NotesException 331 String deleteGroup(String s, boolean flag, boolean flag1) throws NotesException 331 String deleteReplicas(String s, String s1) throws NotesException 331 String deleteServer(String s, boolean flag) throws NotesException 331 String deleteUser(String s, boolean flag, int i, String s1) throws NotesException 331 String deleteUser(String s, boolean flag, int i, String s1, boolean flag1) throws NotesException 331 String findGroupInDomain(String s) throws NotesException 331 String findServerInDomain(String s) throws NotesException 331 String findUserInDomain(String s) throws NotesException 331 String getCertificateAuthorityOrg() throws NotesException 331 String getCertifierFile() throws NotesException 331 String getCertifierPassword() throws NotesException 331 String moveMailUser(String s, String s1, String s2) throws NotesException 331 String moveMailUser(String s, String s1, String s2, boolean flag, Vector vector, boolean flag1) throws NotesException 331 String moveReplica(String s, String s1, String s2) throws NotesException 331 String moveReplica(String s, String s1, String s2, String s3, boolean flag, boolean flag1) throws NotesException 331 String moveRoamingUser(String s, String s1, String s2) throws NotesException 331 String moveUserInHierarchyComplete(String s) throws NotesException 331 String moveUserInHierarchyComplete(String s, String s1, String s2, String s3, String s4, String s5, String s6, String s7, boolean flag) throws NotesException 331 String moveUserInHierarchyRequest(String s, String s1) throws NotesException 331 String moveUserInHierarchyRequest(String s, String s1, boolean flag) throws NotesException 331 String recertifyServer(String s) throws NotesException 331 String recertifyUser(String s) throws NotesException 331 String removeServerFromCluster(String s) throws NotesException 331 String renameGroup(String s, String s1) throws NotesException 331 String renameNotesUser(String s, String s1, String s2, String s3, String s4) throws NotesException 331 String renameNotesUser(String s, String s1, String s2, String s3, String s4, String s5, String s6, String s7, boolean flag) throws NotesException 331 String renameWebUser(String s, String s1, String s2, String s3, String s4, String s5, String s6) throws NotesException 331 String setServerDirectoryAssistanceSettings(String s, String s1) throws NotesException 331 String setUserPasswordSettings(String s, Integer integer, Integer integer1, Integer integer2, Boolean boolean1) throws NotesException 331 String signDatabaseWithServerID(String s, String s1) throws NotesException 331 String signDatabaseWithServerID(String s, String s1, boolean flag) throws NotesException 331 String upgradeUserToHierarchical(String s) throws NotesException 331 String upgradeUserToHierarchical(String s, String s1, String s2, String s3, String s4) throws NotesException 331 void setCertificateAuthorityOrg(String s) throws NotesException 331 void setCertificateExpiration(DateTime datetime) throws NotesException 331 void setCertifierFile(String s) throws NotesException 331 void setCertifierPassword(String s) throws NotesException 331 void setUseCertificateAuthority(boolean flag) throws NotesException 331 Administrationsserver 319, 682 Administrator 670 Adressbuch 350 Agent Agent 82, 105, 475 public interface Agent extends Base 475 boolean isActivatable() throws NotesException 478 boolean isEnabled() throws NotesException 476 boolean isNotesAgent() throws NotesException 477 boolean isPublic() throws NotesException 477 boolean isWebAgent() throws NotesException 477 boolean lock() throws NotesException 323, 478 boolean lock(boolean flag) throws NotesException 320, 323, 478
752
20 Anhang
boolean lock(String s) throws NotesException 323, 478 boolean lock(String s, boolean flag) throws NotesException 323, 478 boolean lock(Vector vector) throws NotesException 323, 478 boolean lock(Vector vector, boolean flag) throws NotesException 478 boolean lock(Vector vector, boolean flag) throws NotesException 323 boolean lockProvisional() throws NotesException 320, 323, 478 boolean lockProvisional(String s) throws NotesException 323, 478 boolean lockProvisional(Vector vector) throws NotesException 323, 478 Database getParent() throws NotesException 477 DateTime getLastRun() throws NotesException 476 int getTarget() throws NotesException 477 int getTrigger() throws NotesException 477 int runOnServer() throws NotesException 148, 149, 476 int runOnServer(String noteID) throws NotesException 149, 476 Manuell gestartete Agenten 697 Serverbasierte Agenten 697 String getComment() throws NotesException 477 String getCommonOwner() throws NotesException 476 String getHttpURL() throws NotesException 274, 477 String getName() throws NotesException 476 String getNotesURL() throws NotesException 273, 477 String getOnBehalfOf() throws NotesException 478 String getOwner() throws NotesException 476 String getParameterDocID() throws NotesException 478 String getQuery() throws NotesException 477 String getServerName() throws NotesException 476 String getURL() throws NotesException 273, 477 Vector getLockHolders() throws NotesException 323, 478 void remove() throws NotesException 476 void run() throws NotesException 153, 475 void run(String noteID) throws NotesException 475 void save() throws NotesException 476 void setEnabled(boolean enable) throws NotesException 476 void setServerName(String serverName) throws NotesException 476 Agent 137, 161 void unlock() throws NotesException 323, 478 Agent anlegen 34 Agent done 233 Agent Manager 141, 160, 677 AgentBase AgentBase 68, 248 public class AgentBase extends NotesThread 249 AgentBase() 249 boolean isRestricted() 250 final void runNotes() throws NotesException 250 final void startup(AgentInfo agentinfo) 250 OutputStream getAgentOutputStream() 250 PrintWriter getAgentOutput() 249 Session getSession() 250 void dbgMsg(String s) 249 void dbgMsg(String s, PrintStream printstream) 249 void dbgMsg(String s, PrintWriter printwriter) 249 void NotesMain() 250 void setDebug(boolean flag) 250 void setTrace(boolean flag) 250 AgentContext AgentContext 140, 331, 493, 494, 695 public interface AgentContext extends Base 138 Agent getCurrentAgent() throws NotesException 139, 152, 493, 494 Database getCurrentDatabase() throws NotesException 138, 144, 493
20.5 Index DateTime getLastRun() throws NotesException 138 Document getDocumentContext() throws NotesException 138, 276, 493 Document getSavedData() throws NotesException 138 DocumentCollection getUnprocessedDocuments() throws NotesException 106, 110, 276, 401 DocumentCollection unprocessedFTSearch(String s, int i) throws NotesException 276, 409, 413 DocumentCollection unprocessedFTSearch(String s, int i, int j, int k) throws NotesException 276, 409, 413 DocumentCollection unprocessedFTSearchRange(String s, int i, int j) throws NotesException 276, 409, 413 DocumentCollection unprocessedFTSearchRange(String s, int i, int j, int k, int l) throws NotesException 276, 409, 413 DocumentCollection unprocessedSearch(String s, DateTime datetime, int i) throws NotesException 276, 409, 413 int getLastExitStatus() throws NotesException 138 String getEffectiveUserName() throws NotesException 138, 493 void updateProcessedDoc(Document document) throws NotesException 110 AgentContext implementieren 493 Agenten-Rumpf 691 Agentenausführung 728 AgentLog 707, 708 Aktion 83, 88 Aktualisieren 418 Alias 28, 31 Alignment 455 anonym 181, 188 Ansicht 82, 97, 368 Ansicht anlegen 32 Ansichtenindex 369 Ansichtenspalte Ansichtenspalte 310, 370 Feldverschlüsselung 310 Antwortdokument 279, 284–287, 373, 376, 382, 387, 398 Anwendung 123, 143, 161, 243 Anzeige 418 Apache-Server 622 Appender 715 Applet 128 Applet einbinden 136 AppletBase AppletBase 248 class AppletBase extends java.applet.Applet implements DominoAppletBase 129 AppletBase() 250 boolean isNotesLocal() 130, 251 class AppletBase extends java.applet.Applet implements DominoAppletBase 250 final void destroy() 130, 251 final void init() 130, 251 final void start() 130, 252 final void stop() 130, 252 NotesAppletContext getContext(Session session) throws NotesException 132, 251 Object newInstance(String s) 129, 251 Session getSession() throws NotesException 129, 251 Session getSession(String s, String s1) throws NotesException 129, 251 Session openSession() throws NotesException 130, 251 Session openSession(String s, String s1) throws NotesException 130, 251 void closeSession(Session session) throws NotesException 132, 251 void notesAppletDestroy() 129, 251 void notesAppletInit() 129, 251 void notesAppletStart() 129, 251 void notesAppletStop() 129, 251 Applets 84 Application Server 202, 616
753
754
20 Anhang
APPLICATION_ SCOPE 633 ASCII 56 AST 471 AT-Formel 559 Atlantic Standard Time 471 Attachment Attachment 259, 288, 298 Übersicht über die Methoden 298 Attribute 631 Aufgaben 670 Ausführungskontexte 697 Ausführungskontrollliste 76 Ausführungsrechte 156 Ausführungsstart 139 Ausgabe 418 Authentifizierung 75 Authentifizierungsmethode 215 AUTHOR 357 AUTHORS 259 Automatisieren von Tests 689 Autorenfeld 268, 506 Autorisierung 75 AWT 130
B Base Base 244 public interface Base 244, 248 void recycle() throws NotesException 570 void recycle(Vector vector) throws NotesException 582 Basic HTTP Authentication 181 Batch 105 Bearbeitbares Feld 86 Befehl 243 Benutzer 560 Benutzermapping 529 Benutzername 464 Berechnetes Feld 86 Berechtigung 75, 356, 525, 529 Berechtigung bei Internet Zugriffen 359 Berechtigungsklasse 214 Bering Standard Time 471 Bezug des Datenbankobjekts 340 Bibliothek 111 Bilder 84 Binärdaten 524, 590 BlackBox-Test 700 Body 27 Bookmark 80 Browser 92, 168 BST 471 Businesslogik 420, 654
C C 210, 464 C++ 68, 184, 517, 570 CA 199 CA Administration 675 CA Prozess 675 Cache 499
20.5 Index Cast 484, 509 catalog.nsf 674 CEDT 472 Central Standard Time 471 CERL 11 certserv.nsf 675 CET 471, 472 CGI-Variable 170, 180 Chat 670 ClassCastException 484 ClassLoader 172, 178 Classpath 119, 173, 234, 235, 551 Client-Server-Anwendungen 184 CLP 541 Cluster 342 CMSDocument CMSDocument 488, 520 public static CMSDocument getInstance (Document initialDoc) 488 CN 198, 210, 464 Codepage 56, 543 ColorObject ColorObject 329 public interface ColorObject extends Base 329 int getBlue() throws NotesException 329 int getGreen() throws NotesException 329 int getHue() throws NotesException 329 int getLuminance() throws NotesException 329 int getNotesColor() throws NotesException 329 int getRed() throws NotesException 329 int getSaturation() throws NotesException 329 int setHSL(int i, int j, int k) throws NotesException 329 int setRGB(int i, int j, int k) throws NotesException 329 void setNotesColor(int i) throws NotesException 329 Column 83, 555 Commit 524 Common Name 198, 210, 464 compact 365 Conflict 279 Connection Pooling 224 Connection refused 233 Console Log 721 CONTAINS 604, 605 Content Management System 482 Content-Type 167 Controller 652 Cookies 167, 182, 204 CORBA 185, 220 CORBA-Authentifizierungskontext 207 Could not get IOR from Domino Server 233 Could not open Notes session 233 Country 464 Cross Site Scripting 204 CSS Stylesheets 88 CST 471 CURRENCY 652 Cursor 407, 410, 436 CustomData 265, 266 CustomDataBytes 265, 266 CVS Repository 681
755
756
20 Anhang
D da.nsf 675 Data Connections 84 Database Database 68, 240, 247 public interface Database extends Base 340 ACL getACL() throws NotesException 359 Agent getAgent(String s) throws NotesException 362 boolean getFolderReferencesEnabled() throws NotesException 364 boolean getListInDbCatalog() throws NotesException 354 boolean getOption(int i) throws NotesException 349 boolean isClusterReplication() throws NotesException 347 boolean isConfigurationDirectory() throws NotesException 354 boolean isCurrentAccessPublicReader() throws NotesException 360 boolean isCurrentAccessPublicWriter() throws NotesException 360 boolean isDB2() throws NotesException 350 boolean isDelayUpdates() throws NotesException 351 boolean isDesignLockingEnabled() throws NotesException 323, 365, 401 boolean isDirectoryCatalog() throws NotesException 354 boolean isDocumentLockingEnabled() throws NotesException 320, 365 boolean isFTIndexed() throws NotesException 353, 600 boolean isInMultiDbIndexing() throws NotesException 354 boolean isInService() throws NotesException 350 boolean isLink() throws NotesException 350 boolean isMultiDbSearch() throws NotesException 354 boolean isOpen() throws NotesException 340 boolean isPendingDelete() throws NotesException 344 boolean isPrivateAddressBook() throws NotesException 350 boolean isPublicAddressBook() throws NotesException 350 boolean open() throws NotesException 340 boolean openByReplicaID(String s, String s1) throws NotesException 341 boolean openIfModified(String s, String s1, DateTime datetime) throws NotesException 341 boolean openWithFailover(String s, String s1) throws NotesException 341 boolean replicate(String s) throws NotesException 343 Database createCopy(String s, String s1) throws NotesException 342 Database createCopy(String s, String s1, int i) throws NotesException 342 Database createFromTemplate(String s, String s1, boolean flag) throws NotesException 342 Database createFromTemplate(String s, String s1, boolean flag, int i) throws NotesException 342 Database createReplica(String s, String s1) throws NotesException 343 DateTime getCreated() throws NotesException 346 DateTime getLastFixup() throws NotesException 366 DateTime getLastFTIndexed() throws NotesException 353 DateTime getLastModified() throws NotesException 346 Document createDocument() throws NotesException 276, 345 Document FTDomainSearch(String s, int i, int j, int k, int l, int i1, String s1) throws NotesException 354 Document getDocumentByID(String s) throws NotesException 271, 345, 591 Document getDocumentByUNID(String s) throws NotesException 272, 345, 591 Document getDocumentByURL(String s, boolean flag) throws NotesException 273, 345 Document getDocumentByURL(String s, boolean flag, boolean flag1, boolean flag2, String s1, String s2, String s3, String s4, String s5, boolean flag3) throws NotesException 272, 345 Document getProfileDocument(String s, String s1) throws NotesException 282, 345 DocumentCollection FTSearch(String s) throws NotesException 407, 592, 601, 647 DocumentCollection FTSearch(String s, int i) throws NotesException 601 DocumentCollection FTSearch(String s, int i, int j, int k) throws NotesException 352, 601 DocumentCollection FTSearch(String searchQuery) throws NotesException 352 DocumentCollection FTSearch(String searchQuery, int max) throws NotesException 352 DocumentCollection FTSearchRange(String s, int i, int j, int k, int l) throws NotesException 352, 601 DocumentCollection getAllDocuments() throws NotesException 275, 345 DocumentCollection getModifiedDocuments() throws NotesException 275, 410
20.5 Index
757
DocumentCollection getModifiedDocuments(DateTime datetime) throws NotesException 275 DocumentCollection getModifiedDocuments(DateTime datetime, int i) throws NotesException 275 DocumentCollection getProfileDocCollection(String s) throws NotesException 282, 345 DocumentCollection search(String s) throws NotesException 352, 592, 600 DocumentCollection search(String s, DateTime datetime) throws NotesException 352, 600 DocumentCollection search(String s, DateTime datetime, int i) throws NotesException 352, 600 double getLimitRevisions() throws NotesException 351 double getLimitUpdatedBy() throws NotesException 351 double getPercentUsed() throws NotesException 349 double getSize() throws NotesException 349 Form getForm(String s) throws NotesException 362 int compact() throws NotesException 365 int compactWithOptions(int i) throws NotesException 366 int compactWithOptions(int i, String s) throws NotesException 366 int compactWithOptions(String s) throws NotesException 366 int getCurrentAccessLevel() throws NotesException 359 int getFileFormat() throws NotesException 350 int getFTIndexFrequency() throws NotesException 353 int getSizeQuota() throws NotesException 349 int getType() throws NotesException 350 int getUndeleteExpireTime() throws NotesException 282, 351 int queryAccess(String s) throws NotesException 360 int queryAccessPrivileges(String s) throws NotesException 360 long getMaxSize() throws NotesException 349 long getSizeWarning() throws NotesException 349 NoteCollection createNoteCollection(boolean flag) throws NotesException 345, 656 Outline createOutline(String s) throws NotesException 364 Outline createOutline(String s, boolean flag) throws NotesException 364 Outline getOutline(String s) throws NotesException 364 Replication getReplicationInfo() throws NotesException 344 Session getParent() throws NotesException 347 String getCategories() throws NotesException 355 String getDB2Schema() throws NotesException 351 String getDesignTemplateName() throws NotesException 343 String getFileName() throws NotesException 347, 348 String getFilePath() throws NotesException 346, 348 String getHttpURL() throws NotesException 274, 350 String getNotesURL() throws NotesException 273, 350 String getReplicaID() throws NotesException 346 String getServer() throws NotesException 346 String getTemplateName() throws NotesException 346 String getTitle() throws NotesException 349 String getURL() throws NotesException 273, 350 String getURLHeaderInfo(String s, String s1, String s2, String s3, String s4, String s5) throws NotesException 351 Vector getACLActivityLog() throws NotesException 359 Vector getAgents() throws NotesException 362 Vector getForms() throws NotesException 362 Vector getManagers() throws NotesException 359 Vector getViews() throws NotesException 363 Vector queryAccessRoles(String s) throws NotesException 360 View createView() throws NotesException 363 View createView(String s) throws NotesException 363 View createView(String s, String s1) throws NotesException 363 View createView(String s, String s1, View view) throws NotesException 363 View createView(String s, String s1, View view, boolean flag) throws NotesException 363 View getView(String s) throws NotesException 362 void createFTIndex(int i, boolean flag) throws NotesException 352, 369 void enableFolder(String s) throws NotesException 364 void fixup() throws NotesException 366 void fixup(int i) throws NotesException 366
758
20 Anhang
void grantAccess(String s, int i) throws NotesException 360 void markForDelete() throws NotesException 344 void remove() throws NotesException 344 void removeFTIndex() throws NotesException 352 void revokeAccess(String s) throws NotesException 360 void setCategories(String s) throws NotesException 355 void setDelayUpdates(boolean flag) throws NotesException 351 void setDesignLockingEnabled(boolean flag) throws NotesException 323, 365 void setDocumentLockingEnabled(boolean flag) throws NotesException 320, 365 void setFolderReferencesEnabled(boolean flag) throws NotesException 364 void setFTIndexFrequency(int i) throws NotesException 353, 369 void setInMultiDbIndexing(boolean flag) throws NotesException 354 void setInService(boolean flag) throws NotesException 350 void setLimitRevisions(double d) throws NotesException 351 void setLimitUpdatedBy(double d) throws NotesException 351 void setListInDbCatalog(boolean flag) throws NotesException 354 void setOption(int i, boolean flag) throws NotesException 347 void setSizeQuota(int i) throws NotesException 349 void setSizeWarning(int i) throws NotesException 349 void setTitle(String s) throws NotesException 349 void setUndeleteExpireTime(int i) throws NotesException 282, 351 void sign() throws NotesException 365 void sign(int i) throws NotesException 365 void sign(int i, boolean flag) throws NotesException 365 void sign(int i, boolean flag, String s) throws NotesException 365 void sign(int i, boolean flag, String s, boolean flag1) throws NotesException 365 void updateFTIndex(boolean createOnFail) throws NotesException 352 void updateFTIndex(boolean flag) throws NotesException 369 Database already contains a document with this ID (UNID) 277 Database does not support folder references 318 Date 506 Dateianhang Dateianhang 288, 298, 348, 353 Dateianhang ablösen 295 Übersicht über die Methoden 298 Dateien 84 Dateiformat 525 Dateiname 346, 347 Dateipfad 346 Datenbank 340 Datenbank anlegen 25 Datenbank öffnen 64 Datenbankgrenzen 561 Datenbankschablone 377 Datenstrukturen 524 Datentypen 50, 55, 259, 264, 526 Datenverbindungen 84 DateRange DateRange 330 public interface DateRange extends Base 469 DateTime getEndDateTime() throws NotesException 473 DateTime getStartDateTime() throws NotesException 473 Session getParent() throws NotesException 473 String getText() throws NotesException 473 void setEndDateTime(DateTime datetime) throws NotesException 473 void setStartDateTime(DateTime datetime) throws NotesException 473 void setText(String s) throws NotesException 473 DateTime DateTime 57, 330 public interface DateTime extends Base 469 boolean isDST() throws NotesException 472
20.5 Index
759
Date toJavaDate() throws NotesException 473 double timeDifferenceDouble(DateTime datetime) throws NotesException 471 int getTimeZone() throws NotesException 472 int timeDifference(DateTime datetime) throws NotesException 471 Session getParent() throws NotesException 473 String getDateOnly() throws NotesException 473 String getGMTTime() throws NotesException 472 String getLocalTime() throws NotesException 472 String getTimeOnly() throws NotesException 473 String getZoneTime() throws NotesException 472 void adjustDay(int i) throws NotesException 470 void adjustDay(int i, boolean flag) throws NotesException 470 void adjustHour(int i) throws NotesException 470 void adjustHour(int i, boolean flag) throws NotesException 470 void adjustMinute(int i) throws NotesException 470 void adjustMinute(int i, boolean flag) throws NotesException 470 void adjustMonth(int i) throws NotesException 470 void adjustMonth(int i, boolean flag) throws NotesException 470 void adjustSecond(int i) throws NotesException 470 void adjustSecond(int i, boolean flag) throws NotesException 470 void adjustYear(int i) throws NotesException 470 void adjustYear(int i, boolean flag) throws NotesException 470 void convertToZone(int i, boolean flag) throws NotesException 470 void setAnyDate() throws NotesException 471 void setAnyTime() throws NotesException 471 void setLocalDate(int i, int j, int k) throws NotesException 471 void setLocalDate(int i, int j, int k, boolean flag) throws NotesException 471 void setLocalTime(Calendar calendar) throws NotesException 472 void setLocalTime(Date date) throws NotesException 472 void setLocalTime(int i, int j, int k, int l) throws NotesException 472 void setLocalTime(String s) throws NotesException 472 void setNow() throws NotesException 471 DateTime 265, 266, 500, 506 DATETIMES 259 Datum 57, 469, 728 DAV 526, 545 DB2 DB2 7, 523 Benutzermapping 529 Berechtigung 525 DB2 Access Server 538 DB2 ACCESS SET 539 DB2 Access View 84, 526, 545, 554 DB2 INFO 539 DB2 Username 529 DB2-Benutzer 545 Dbdirectory DbDirectory 63, 64, 344 public interface DbDirectory extends Base 331, 340 Database createDatabase(String s) throws NotesException 331, 340 Database createDatabase(String s, boolean flag) throws NotesException 331, 340 Database getFirstDatabase(int i) throws NotesException 64, 332, 340 Database getNextDatabase() throws NotesException 64, 332, 340 Database openDatabase(String s) throws NotesException 64, 332, 340 Database openDatabase(String s, boolean flag) throws NotesException 64, 332, 340 Database openDatabaseByReplicaID(String s) throws NotesException 63, 333, 340, 344, 345 Database openDatabaseIfModified(String s, DateTime datetime) throws NotesException 332, 340 Database openMailDatabase() throws NotesException 332, 340 Session getParent() throws NotesException 332, 340 String getName() throws NotesException 332, 340 Debug-Parameter 722
760
20 Anhang
Debugger 701 Debugging 688 DECS 523 Default 631 Delegationsberechtigungen 156 Deletionstub 281 DEPOSITOR 357 Design Locking 401 Designelemente 361, 684 Designer 357, 670 developerWorks 117 DIIOP 185, 508, 724, 727 DIIOP SSL 199 dircat5.ntf 354 Directory Assistance 191, 202, 675, 723 Directory Catalog 354 Directory Indexer 678 DJAgentContext 494, 695 DJCacheDocument DJCacheDocument 499, 509, 556, 643 public class DJCacheDocument 509 boolean hasItem(String fieldName) 511 Database getParentDatabase(Session session) throws NotesException 510 DJCacheDocument(Document doc) throws NotesException 500, 509 Document createDocument (Database db) throws NotesException 510 Document getDocument (Session session) throws NotesException 510 double getItemValueDouble(String fieldName) 509, 510 int getItemValueInteger(String fieldName) 509, 510 String getItemValueString(String fieldName) 508, 510 String getParentDatabaseFileName() 506, 510 String getParentDatabaseFilePath() 506, 511 String getParentDocumentUNID() 505, 506, 511 String getType (String fieldName) 511 String getUniversalID() 505, 506, 511 String toString() 511 Vector getAllItemNames () 500, 510 Vector getItemValue(String fieldName) 499, 510 Vector getItemValueDateTimeArray (String fieldName, Session session) throws NotesException 509, 510 void appendItemValue(String fieldName) 499, 509 void appendItemValue(String fieldName, double value) 509 void appendItemValue(String fieldName, int value) 510 void appendItemValue(String fieldName, Object value) 510 void recycle() 511 void removeItem (String fieldName) 511 void removeItem(String s) throws NotesException 508 void replaceItemValue(String fieldName, Object value) 499, 511 DJLog DJLog 713 public class DJLog 713 static boolean getTraceStatus() 714 static final Level internalToLog4jLevel(int source) 714 static final void setBasePath(String path) 714 static int getLogLevel() 713 static String getBasePath() 714 static String logSymbol(int level) 714 static void debug(String msg) 713 static void error(String msg) 713 static void info(String msg) 713 static void setLogLevel(int newLogLevel) 713 static void setTraceStatus(boolean newTraceStatus) 714
20.5 Index static void warn(String msg) 713 static void write(int logLevel, String msg) 713 static void write(int logLevel, String msg, boolean kurz) 713 static void writeDocument(Document doc) 713 static void writeEnumeration(int logLevel, Enumeration e) 713 static void writeException(int logLevel, Throwable e) 713 static void writeException(Throwable e) 713 static void writePairs(int logLevel, Vector keys, Vector values) 713 static void writeString(int logLevel, String msg) 713 static void writeVector(int logLevel, Vector v) 713 DJStressRunner 718 DJStressRunnerOneSession 718 DJStressTest 718 DJStressTestExternalSession 718 Do-mänensuche 593 Document Document 43, 68, 240, 247, 256, 271, 482, 485, 499 public interface Document extends Base 256 boolean closeMIMEEntities() throws NotesException 244 boolean closeMIMEEntities(boolean flag) throws NotesException 244 boolean closeMIMEEntities(boolean flag, String s) throws NotesException 244 boolean computeWithForm(boolean flag, boolean flag1) throws NotesException 277 boolean hasEmbedded() throws NotesException 289, 299 boolean hasItem(String s) throws NotesException 261, 262 boolean isDeleted() throws NotesException 281 boolean isEncrypted() throws NotesException 307 boolean isEncryptOnSend() throws NotesException 315 boolean isNewNote() throws NotesException 276 boolean isProfile() throws NotesException 282 boolean isResponse() throws NotesException 285 boolean isSaveMessageOnSend() throws NotesException 315 boolean isSentByAgent() throws NotesException 315 boolean isSigned() throws NotesException 313 boolean isSignOnSend() throws NotesException 315 boolean isValid() throws NotesException 281 boolean lock() throws NotesException 323 boolean lock(boolean flag) throws NotesException 320, 323 boolean lock(String s) throws NotesException 323 boolean lock(String s, boolean flag) throws NotesException 323 boolean lock(Vector vector) throws NotesException 323 boolean lock(Vector vector, boolean flag) throws NotesException 323 boolean lockProvisional() throws NotesException 320, 323 boolean lockProvisional(String s) throws NotesException 323 boolean lockProvisional(Vector vector) throws NotesException 323 boolean remove(boolean flag) throws NotesException 281 boolean removePermanently(boolean flag) throws NotesException 282 boolean renderToRTItem(RichTextItem richtextitem) throws NotesException 317 boolean save() throws NotesException 278 boolean save(boolean flag) throws NotesException 279 boolean save(boolean flag, boolean flag1) throws NotesException 279 boolean save(boolean flag, boolean flag1, boolean flag2) throws NotesException 280 byte[] getItemValueCustomDataBytes(String s, String s1) throws IOException, NotesException 259, 260, 265, 266 Database getParentDatabase() throws NotesException 316 DateTime getCreated() throws NotesException 315 DateTime getLastAccessed() throws NotesException 316 DateTime getLastModified() throws NotesException 316 Document copyToDatabase(Database database) throws NotesException 276 Document createReplyMessage(boolean flag) throws NotesException 315 DocumentCollection getResponses() throws NotesException 285 double getItemValueDouble(String s) throws NotesException 260, 265, 266
761
762
20 Anhang
EmbeddedObject getAttachment(String s) throws NotesException 289, 299 int getFTSearchScore() throws NotesException 318, 408, 412, 605 int getItemValueInteger(String s) throws NotesException 260, 265, 266 int getSize() throws NotesException 260, 261, 316 Item appendItemValue(String s) throws NotesException 261, 262, 507 Item appendItemValue(String s, double d) throws NotesException 258, 261 Item appendItemValue(String s, int i) throws NotesException 258, 261 Item appendItemValue(String s, Object obj) throws NotesException 258, 261 Item copyItem(Item item) throws NotesException 258 Item copyItem(Item item, String s) throws NotesException 258 Item getFirstItem(String s) throws NotesException 257, 258, 260 Item replaceItemValue(String s, Object obj) throws NotesException 258, 262, 508 Item replaceItemValueCustomData(String s, Object obj) throws IOException, NotesException 261 Item replaceItemValueCustomData(String s, String s1, Object obj) throws IOException, NotesException 261 Item replaceItemValueCustomDataBytes(String s, String s1, byte abyte0[]) throws IOException, NotesException 261 MIMEEntity createMIMEEntity() throws NotesException 244 MIMEEntity createMIMEEntity(String s) throws NotesException 244 MIMEEntity getMIMEEntity() throws NotesException 244 MIMEEntity getMIMEEntity(String s) throws NotesException 244 Object getItemValueCustomData(String s) throws IOException, ClassNotFoundException, NotesException 259, 260, 265, 266 Object getItemValueCustomData(String s, String s1) throws IOException, ClassNotFoundException, NotesException 259, 260 RichTextItem createRichTextItem(String s) throws NotesException 317 String generateXML() throws NotesException 316, 661 String getHttpURL() throws NotesException 274 String getItemValueString(String s) throws NotesException 260, 265, 266 String getKey() throws NotesException 283 String getNameOfProfile() throws NotesException 283 String getNoteID() throws NotesException 63, 272 String getNotesURL() throws NotesException 273 String getParentDocumentUNID() throws NotesException 285 String getSigner() throws NotesException 313 String getUniversalID() throws NotesException 63, 272, 277 String getURL() throws NotesException 273 String getVerifier() throws NotesException 313 Vector getAuthors() throws NotesException 315 Vector getColumnValues() throws NotesException 318 Vector getEmbeddedObjects() throws NotesException 289, 299 Vector getEncryptionKeys() throws NotesException 306 Vector getFolderReferences() throws NotesException 318 Vector getItems() throws NotesException 257, 260, 261, 500, 564 Vector getItemValue(String s) throws NotesException 257–260, 265, 266, 508 Vector getItemValueDateTimeArray(String s) throws NotesException 259, 260, 265, 266 Vector getLockHolders() throws NotesException 323 Vector getReceivedItemText() throws NotesException 263 View getParentView() throws NotesException 318 void copyAllItems(Document document, boolean flag) throws NotesException 258 void encrypt() throws NotesException 306 void generateXML(Object obj, XSLTResultTarget xsltresulttarget) throws IOException, NotesException 316, 661 void generateXML(Writer writer) throws NotesException, IOException 316, 661 void makeResponse(Document document) throws NotesException 285 void putInFolder(String s) throws NotesException 98, 318 void putInFolder(String s, boolean flag) throws NotesException 318 void removeFromFolder(String s) throws NotesException 319 void removeItem(String s) throws NotesException 261, 262 void send() throws NotesException 314 void send(boolean flag) throws NotesException 314
20.5 Index void send(boolean flag, String s) throws NotesException 315 void send(boolean flag, Vector vector) throws NotesException 315 void send(String s) throws NotesException 314 void send(Vector vector) throws NotesException 314 void setEncryptionKeys(Vector vector) throws NotesException 306 void setEncryptOnSend(boolean flag) throws NotesException 315 void setSaveMessageOnSend(boolean flag) throws NotesException 315 void setSignOnSend(boolean flag) throws NotesException 315 void setUniversalID(String s) throws NotesException 277 void sign() throws NotesException 312 void unlock() throws NotesException 323 Document has been removed or recycled 488 Document ID 64 Document is from a different database 516 Document Locking Document Locking 278, 319 Provisorisches Locking 320 DocumentCollection DocumentCollection 72, 97, 240, 275, 402, 405, 420, 576 public interface DocumentCollection extends Base 401 boolean isSorted() throws NotesException 409 Database getParent() throws NotesException 407 DateTime getUntilTime() throws NotesException 409, 410 Document getDocument(Document document) throws NotesException 407 Document getFirstDocument() throws NotesException 72, 275, 403, 407, 413, 423, 576 Document getLastDocument() throws NotesException 407 Document getNextDocument() throws NotesException 275, 403, 407, 413, 423, 576 Document getNextDocument(Document document) throws NotesException 407 Document getNthDocument(int i) throws NotesException 72, 407 Document getPrevDocument() throws NotesException 407 Document getPrevDocument(Document document) throws NotesException 407 int getCount() throws NotesException 406 String getQuery() throws NotesException 407 void addDocument(Document document) throws NotesException 408 void addDocument(Document document, boolean flag) throws NotesException 408 void deleteDocument(Document document) throws NotesException 408 void FTSearch(String s) throws NotesException 407, 408, 592, 603 void FTSearch(String s, int i) throws NotesException 408, 603 void putAllInFolder(String s) throws NotesException 98, 408, 413 void putAllInFolder(String s, boolean flag) throws NotesException 408 void removeAll(boolean flag) throws NotesException 281, 409, 413 void removeAllFromFolder(String s) throws NotesException 409, 413 void stampAll(String s, Object obj) throws NotesException 402, 409, 413 void updateAll() throws NotesException 409 Dokument 593 Dokument in ein RichTextItem rendern 317 Dokument sperren 320 Dokumente erstellen 276 Dokumente löschen 280 Dokumente speichern 278 Domain Search 605, 674 Domino Access Server 533 Domino Administrator 670, 673 Domino Adressbuch 527, 545 Domino Catalog 674 Domino Designer 80, 670, 673 Domino Directory 17, 132, 180, 181, 183, 191, 202, 206, 212, 315, 350, 527, 545, 560, 675, 723, 726 Domino Global Workbench 28 Domino Log 675 Domino Tomcat Redirector 619 Domino WAS Plugin 617
763
764
20 Anhang
Domino-LDAP-Schema 675 Domino-Mail-Routing 690 domino:db 635, 637 domino:default 632 domino:docloop 637 domino:document 635, 641 domino:form 634 domino:formlink 634 domino:ftsearc 647 domino:item 635, 650 domino:nextdoc 635, 645 domino:prevdoc 645 domino:runagent 648 domino:saveclosedoc 635, 645 domino:savenow 635 domino:selectedloop 639 domino:selectentry 638 domino:session 635 domino:validaterequired 634, 645 domino:view 634, 637 domino:viewitem 634, 637 domino:viewloop 634, 637 Domtags 634 domutil:browsertag 650 domutil:case 651 domutil:condition 650 domutil:default 651 domutil:elseif 651 domutil:format 652 domutil:if 650 domutil:switch 650 Double 265, 266, 506 DSAPI Plugin 203, 614 DTD 655 dumpDoc 418 dumpView 418 Durchgangs 491 DXL Utilities 361 DxlExporter DXLExporter 361 DxlExporter 333, 655, 663 public interface DxlExporter extends Base 655 boolean getConvertNotesBitmapsToGIF() throws NotesException 664 boolean getExitOnFirstFatalError() throws NotesException 663 boolean getForceNoteFormat() throws NotesException 664 boolean getOutputDOCTYPE() throws NotesException 664 String exportDxl(Database database) throws NotesException 663 String exportDxl(Document document) throws NotesException 656, 663 String exportDxl(DocumentCollection documentcollection) throws NotesException 663 String exportDxl(NoteCollection notecollection) throws NotesException 657, 663 String getDoctypeSYSTEM() throws NotesException 664 String getLog() throws NotesException 664 String getLogComment() throws NotesException 664 void setConvertNotesBitmapsToGIF(boolean flag) throws NotesException 664 void setDoctypeSYSTEM(String s) throws NotesException 664 void setExitOnFirstFatalError(boolean flag) throws NotesException 663 void setForceNoteFormat(boolean flag) throws NotesException 664 void setLogComment(String s) throws NotesException 664 void setOutputDOCTYPE(boolean flag) throws NotesException 664 DxlImporter DxlImporter 333, 655
20.5 Index public interface DxlImporter extends Base 655 boolean getCreateFTIndex() throws NotesException 667 boolean getExitOnFirstFatalError() throws NotesException 665 boolean getReplaceDbProperties() throws NotesException 667 boolean getReplicaRequiredForReplaceOrUpdate() throws NotesException 667 int getAclImportOption() throws NotesException 665 int getDesignImportOption() throws NotesException 665 int getDocumentImportOption() throws NotesException 665 int getImportedNoteCount() throws NotesException 665 int getInputValidationOption() throws NotesException 666 int getUnknownTokenLogOption() throws NotesException 666 String getFirstImportedNoteID() throws NotesException 665 String getLog() throws NotesException 667 String getLogComment() throws NotesException 667 String getNextImportedNoteID(String s) throws NotesException 665 void importDxl(RichTextItem richtextitem, Database database) throws NotesException 664 void importDxl(Stream stream, Database database) throws NotesException 664 void importDxl(String s, Database database) throws NotesException 664 void setAclImportOption(int i) throws NotesException 665 void setCreateFTIndex(boolean flag) throws NotesException 667 void setDesignImportOption(int i) throws NotesException 665 void setDocumentImportOption(int i) throws NotesException 665 void setExitOnFirstFatalError(boolean flag) throws NotesException 665 void setInputValidationOption(int i) throws NotesException 666 void setLogComment(String s) throws NotesException 667 void setReplaceDbProperties(boolean flag) throws NotesException 667 void setReplicaRequiredForReplaceOrUpdate(boolean flag) throws NotesException 667 void setUnknownTokenLogOption(int i) throws NotesException 666
E E-Mail 466, 670 EAR-Implementierungsdeskriptor 213 Eastern Standard Time 471 eckige Klammern 343 ECL 155 ECL-Sicherheit 76 EDITOR 357 eindeutige ID 590 Eingabevalidierung 87 EMBEDDEDOBJECT 259 EmbeddedObject EmbeddedObject 288, 300 public interface EmbeddedObject extends Base 300 InputSource getInputSource() throws NotesException 300 InputStream getInputStream() throws NotesException 301 int activate(boolean flag) throws NotesException 300 int getFileSize() throws NotesException 300 int getObject() throws NotesException 300 int getType() throws NotesException 290, 300 org.w3c.dom.Document parseXML(boolean flag) throws IOException, NotesException 301 org.w3c.dom.Document parseXML(boolean validateDTD) 301 Reader getReader() throws NotesException 300 RichTextItem getParent() throws NotesException 300 String getClassName() throws NotesException 300 String getName() throws NotesException 295, 296, 300 String getSource() throws NotesException 296, 300 Vector getVerbs() throws NotesException 297, 300 void doVerb(String s) throws NotesException 300 void extractFile(String s) throws NotesException 289, 295, 300 void remove() throws NotesException 300
765
766
20 Anhang
void transformXML(Object obj, XSLTResultTarget xsltresulttarget) throws NotesException 301 Encryption 302 Entry is not from this collection 410 Entschlüsseln 307 Entwicklungsteam 684 Environment-Variablen 336 Erweiterung 481, 488 EST 471 Event 106 Eventhandler 134 EXACTCASE 353, 604, 605 exakte Übereinstimmung 382, 388 Excel 445 Exception Access to data denied 290 ClassCastException 484 Connection refused 233 Could not get IOR from Domino Server 233 Could not open Notes session 233 Database already contains a document with this ID (UNID) 277 Database does not support folder references 318 Document has been removed or recycled 488 Document is from a different database 516 File cannot be created 290 InputStream does not contain a serialized object 266 Invalid date Exception 57 Invalid object type for method argument 266 Invalid universal id 280 Invalid user name/password 234 Item value is not a date type 265, 266 Not implemented 221 not yet implemented 494 Note is already locked by someone else 321 NullPointerException 265, 266 Object has been removed or recycled 572 Operation timed out 233 Position is not valid 438 Reference to createSession is ambigous 235 SSL implementation not available 39 Supplied Data type name does not match stored CustomData type 259, 265, 266 The document is not locked by you 323 The version of the IBM Universal JDBC driver in use is not licensed for connectivity to QDB2/NT databases 551 There is no certificate in the Address Book 311 There is no current agent, can't open log 707 Unable to lock - cannot connect to Master Lock Database 321 Unable to write to database because database would exceed its disk quota 349 Unknown value type to convert 265 UnsatisfiedLinkError: Can't find library nlsxbe (nlsxbe.dll) in java.library.path 235 UnsatisfiedLinkError: NCreateSession 235 User username is not a server 235 You are not authorized to perform that operation 280 You don't have any of the specified encryption keys 310 You must have permission to encrypt documents for server based agents 310 Execution Control List 76 Export 663 Exportvorschriften 305
20.5 Index F Factory Methode 488 Farbe 329, 444 Fault-Recovery 723 Federated Data 527 Feld 49, 83, 86, 263, 267 Feld-Mapping 546 Feldvalidierung 277 Feldverschlüsselung 304, 310 Field 83 FieldNames 500 File cannot be created 290 File I/O 156 FileLog 708 Files 84 Filter 213 Filterung 384, 418 finally 577 Fixup 366 Folder 82 Font 372, 433, 459 Form Form 43, 44, 47, 82, 85, 362, 526 public interface Form extends Base 362 boolean isProtectReaders() throws NotesException 47, 362 boolean isProtectUsers() throws NotesException 47, 362 boolean isSubForm() throws NotesException 46, 47, 362 boolean lock() throws NotesException 323 boolean lock(boolean flag) throws NotesException 320, 323 boolean lock(String s) throws NotesException 323 boolean lock(String s, boolean flag) throws NotesException 323 boolean lock(Vector vector) throws NotesException 323 boolean lock(Vector vector, boolean flag) throws NotesException 323 boolean lockProvisional() throws NotesException 320, 323 boolean lockProvisional(String s) throws NotesException 323 boolean lockProvisional(Vector vector) throws NotesException 323 Database getParent() throws NotesException 340 int getFieldType(String s) throws NotesException 47 String getHttpURL() throws NotesException 274, 362 String getName() throws NotesException 47 String getNotesURL() throws NotesException 273, 362 String getURL() throws NotesException 273, 362 Vector getAliases() throws NotesException 47 Vector getFields() throws NotesException 47, 362 Vector getFormUsers() throws NotesException 47, 362 Vector getLockHolders() throws NotesException 323 Vector getReaders() throws NotesException 47, 362 void remove() throws NotesException 362 void setFormUsers(Vector vector) throws NotesException 362 void setProtectReaders(boolean flag) throws NotesException 362 void setProtectUsers(boolean flag) throws NotesException 362 void setReaders(Vector vector) throws NotesException 362 void unlock() throws NotesException 323 Formeln 82 Formelsprache 335 Fragmentierung 365 Frameset 81 FTSearch 384 Funktionale Tests 698 FUZZY 601
767
768
20 Anhang
G G 464 Garbage Collection 570, 721 GC 585 Gegenzertifikat 313 gemeinsame Ansicht 368 Gemeinsame Ressourcen 84 Gemeinsamer Code 82 Generation Qualifier 464 Generationskennzeichen 464 Geschwister 382 GET 633 getter Methoden 481 Given Name 464 GMT 469, 471 GRANT 531 Groß- und Kleinschreibung 99, 174, 353, 382, 388, 594, 600, 603 Größenbeschränkung 429 Gültigkeitsbereiche 235
H Hash 312 Hauptspeicherbedarf 403 Hawaii Standard Time 471 Header 167 HeapSize 720 Hide-When 29 Hierarchie 284, 286 HSL 329 HST 471 HTML 427 htmlunit 699 HTTP 168, 189, 670, 678, 724 HTTP Header 167 HTTP-Session 177 HTTP-Task 166, 725 HttpUnit 699
I I 464 IBM-HTTP-Server 622 ICL Datenbank 675 ID 303 ID-File 126, 333 IDE 679 IIOP 670 Images 84 IMAP 670 implementieren implementieren 484, 490, 493 AgentContext implementieren 493 implizit gespeichert 409, 413 Import 664 importieren 141 Index 369 Indizierung 604 Infrastruktur 670 Initial 464 Input Validation 87
20.5 Index InputStream does not contain a serialized object 265, 266 Installation 118 Instanzvariable 482 Integer 265, 266, 506 Integrationstest 682 Interface 480, 484, 490 International International 330, 474 public interface International extends Base 474 boolean isCurrencySpace() throws NotesException 475 boolean isCurrencySuffix() throws NotesException 475 boolean isCurrencyZero() throws NotesException 475 boolean isDateDMY() throws NotesException 475 boolean isDateMDY() throws NotesException 475 boolean isDateYMD() throws NotesException 475 boolean isDST() throws NotesException 475 boolean isTime24Hour() throws NotesException 475 int getCurrencyDigits() throws NotesException 474 int getTimeZone() throws NotesException 474 Session getParent() throws NotesException 475 String getAMString() throws NotesException 474 String getCurrencySymbol() throws NotesException 474 String getDateSep() throws NotesException 474 String getDecimalSep() throws NotesException 474 String getPMString() throws NotesException 474 String getThousandsSep() throws NotesException 474 String getTimeSep() throws NotesException 474 String getToday() throws NotesException 474 String getTomorrow() throws NotesException 474 String getYesterday() throws NotesException 474 Internet Zugriff 359 Internetkennwort 191 Internetname 465 Internetpasswort 335 Interoperable Object Reference 193 Invalid date Exception 57 Invalid object type for method argument 266 Invalid universal id 280 Invalid user name/password 234 IOR 193, 195 Iris Associates Inc. 12 ISO/IEC 10021 464 Issued Certificate Lis 675 Item Item 49, 50, 86, 241, 256, 257, 304, 500 public interface Item extends Base 257 boolean containsValue(Object obj) throws NotesException 260, 262 boolean isAuthors() throws NotesException 268 boolean isEncrypted() throws NotesException 269, 306 boolean isNames() throws NotesException 269 boolean isProtected() throws NotesException 269 boolean isReaders() throws NotesException 269 boolean isSaveToDisk() throws NotesException 269 boolean isSigned() throws NotesException 270 boolean isSummary() throws NotesException 270 byte[] getValueCustomDataBytes(String s) throws IOException, NotesException 260 DateTime getDateTimeValue() throws NotesException 260 DateTime getLastModified() throws NotesException 270 Document getParent() throws NotesException 270 double getValueDouble() throws NotesException 260 InputSource getInputSource() throws NotesException 270, 662
769
770
20 Anhang
InputStream getInputStream() throws NotesException 270, 662 int getType() throws NotesException 259, 260, 262 int getValueInteger() throws NotesException 260 int getValueLength() throws NotesException 260, 262 Item copyItemToDocument(Document document) throws NotesException 258 Item copyItemToDocument(Document document, String s) throws NotesException 258 MIMEEntity getMIMEEntity() throws NotesException 270 Object getValueCustomData() throws IOException, ClassNotFoundException, NotesException 260 Object getValueCustomData(String s) throws IOException, ClassNotFoundException, NotesException 260 org.w3c.dom.Document parseXML(boolean flag) throws IOException, NotesException 271, 662 Reader getReader() throws NotesException 270, 662 String abstractText(int i, boolean flag, boolean flag1) throws NotesException 260, 262 String getName() throws NotesException 270 String getText() throws NotesException 260, 262 String getText(int i) throws NotesException 260, 262 String getValueString() throws NotesException 260 String toString() 260, 262 Vector getValueDateTimeArray() throws NotesException 260 Vector getValues() throws NotesException 260 void appendToTextList(String s) throws NotesException 261 void appendToTextList(Vector vector) throws NotesException 261 void recycle() throws NotesException 261, 263 void remove() throws NotesException 261, 262 void setAuthors(boolean flag) throws NotesException 268 void setDateTimeValue(DateTime datetime) throws NotesException 261 void setEncrypted(boolean flag) throws NotesException 269, 306 void setNames(boolean flag) throws NotesException 269 void setProtected(boolean flag) throws NotesException 269 void setReaders(boolean flag) throws NotesException 269 void setSaveToDisk(boolean flag) throws NotesException 269 void setSigned(boolean flag) throws NotesException 270 void setSummary(boolean flag) throws NotesException 270 void setValueCustomData(Object obj) throws IOException, NotesException 261 void setValueCustomData(String s, Object obj) throws IOException, NotesException 261 void setValueCustomDataBytes(String s, byte abyte0[]) throws IOException, NotesException 261 void setValueDouble(double d) throws NotesException 261 void setValueInteger(int i) throws NotesException 261 void setValues(Vector vector) throws NotesException 261 void setValueString(String s) throws NotesException 261 void transformXML(Object obj, XSLTResultTarget xsltresulttarget) throws NotesException 271, 662 Item value is not a date type 265, 266 Items durchsuchen 603
J J2EE 616 JAppletBase class JAppletBase extends JApplet implements DominoAppletBase 129 JAppletBase 248 boolean isNotesLocal() 130, 251 class JAppletBase extends JApplet implements DominoAppletBase 250 final void destroy() 130, 251 final void init() 130, 251 final void start() 130, 252 final void stop() 130, 252 JAppletBase() 250 NotesAppletContext getContext(Session session) throws NotesException 132, 251 Object newInstance(String s) 129, 251 Session getSession() throws NotesException 129, 251 Session getSession(String s, String s1) throws NotesException 129, 251
20.5 Index Session openSession() throws NotesException 130, 251 Session openSession(String s, String s1) throws NotesException 130, 251 void closeSession(Session session) throws NotesException 132, 251 void notesAppletDestroy() 129, 251 void notesAppletInit() 129, 251 void notesAppletStart() 129, 251 void notesAppletStop() 129, 251 Java 82 Java Agent 679 Java Heap 583 Java IDE 80, 679 Java in Masken 97 Java-Backend-Objekte 68 Java-Debugger 693 Java-Entwickler 684 JavaScript 82 JavaUserClasses 234, 235 JAVA_HOME 118 JBoss 616, 622, 625 JDBC 550 JDK 117 JExcel 445 jfcUnit 699 JOIN 527 Joker-Symbole 604 JSP 628 JSP-Container 623 JUnit 511, 682, 699, 703 JVM 172, 719 j_security_check 216
K Kalender 376, 670 kanonischer Name 464, 465 Kapselung 482 Kapselung von Schleifen 420 Karteikartenreiter 432 Kategorie 398, 400 kategorisierte Ansicht 385, 400 kategorisierte Spalte 375 Kategorisierung 287, 375, 386 Klassen 244 Klon 276 komprimieren 365 Konfliktdokument 67, 279, 319, 387 konsole 190, 334, 539, 701, 721 Kontext 94, 632, 697 Kontextdokument 138, 139
L L 198 LAN 18 Land 464 Ländercode 468 Lasttests 690, 698 Lastverteilung 615 Layout-Regionen 85 LDAP 202, 210, 670, 726 LDAP-Schema 210 LDAP-Suche 726
771
772
20 Anhang
LDAP-Verzeichnis 625, 675 least recently used 514 LEI 523 Len Kawell 12 Leserfelder 269, 379, 507 Lifecycle 271, 517 Link 435, 453 Liste 57, 59, 257, 506 Listenfeld 546 LMBCS 56 Locale 652 Location 198 Lock Holder 323 Locking Locking 320, 401 Provisorisches Locking 320 Lockingserver 319, 682 Log Log 333, 707 public interface Log extends Base 707 boolean isLogActions() throws NotesException 711 boolean isLogErrors() throws NotesException 711 boolean isOverwriteFile() throws NotesException 711 int getNumActions() throws NotesException 709, 711 int getNumErrors() throws NotesException 709, 711 Session getParent() throws NotesException 711 String getProgramName() throws NotesException 709, 711 void close() throws NotesException 710 void logAction(String s) throws NotesException 708, 710 void logError(int i, String s) throws NotesException 708, 710 void logEvent(String s, String s1, int i, int j) throws NotesException 710 void openAgentLog() throws NotesException 709 void openFileLog(String s) throws NotesException 710 void openMailLog(Vector vector, String s) throws NotesException 710 void openNotesLog(String s, String s1) throws NotesException 710 void setLogActions(boolean flag) throws NotesException 711 void setLogErrors(boolean flag) throws NotesException 711 void setOverwriteFile(boolean flag) throws NotesException 711 void setProgramName(String s) throws NotesException 711 log.nsf 675 log4j 711 Logging 689, 707 Login 179 Logische Operatoren 604 lokale Internet-Session 193 lokale Notes-Session 120 Löschen 418 Lotus Domino Toolkit for WebSphere 630 lotus.domino.local 186 lotus.domino.cso 186, 484 lotus.domino.cso.Session 480 lotus.domino.local 484 lotus.domino.local.Session 480 LotusScript 68, 82, 297 lotusxsl.jar 663 LRU Cache 514 LTPA 222 LTPA Token 204, 206, 615 LTPA-Authentifizierungsverfahren 211 LZ1-Kompression 348
20.5 Index M Mail-Routing 674, 690 mail.box 674, 706 MailLog 708 MANAGER 357 Manipulation einer Ansicht 370 Manipulation von Ansichtenspalten 370 Manuell gestartete Agenten 697 Mapping 527 Margin 455 Maske 45, 82, 85, 267 Maske anlegen 26 Master Template 343 Mime 60 MIMEEntity MIMEEntity 249, 270, 427, 428, 584, 661 public interface MIMEEntity extends Base 244, 270 InputSource getInputSource() throws NotesException 270, 662 InputStream getInputStream() throws NotesException 270, 662 int getEncoding() throws NotesException 270 MIMEEntity createChildEntity() throws NotesException 270 MIMEEntity createChildEntity(MIMEEntity mimeentity) throws NotesException 270 MIMEEntity createParentEntity() throws NotesException 270 MIMEEntity getFirstChildEntity() throws NotesException 270 MIMEEntity getNextEntity() throws NotesException 270 MIMEEntity getNextEntity(int i) throws NotesException 270 MIMEEntity getNextSibling() throws NotesException 270 MIMEEntity getParentEntity() throws NotesException 270 MIMEEntity getPrevEntity() throws NotesException 270 MIMEEntity getPrevEntity(int i) throws NotesException 270 MIMEEntity getPrevSibling() throws NotesException 270 MIMEHeader createHeader(String s) throws NotesException 270 MIMEHeader getNthHeader(String s) throws NotesException 270 MIMEHeader getNthHeader(String s, int i) throws NotesException 270 org.w3c.dom.Document parseXML(boolean flag) throws IOException, NotesException 270, 662 Reader getReader() throws NotesException 270, 662 String getBoundaryEnd() throws NotesException 270 String getBoundaryStart() throws NotesException 270 String getCharset() throws NotesException 270 String getContentAsText() throws NotesException 270 String getContentSubType() throws NotesException 270 String getContentType() throws NotesException 270 String getHeaders() throws NotesException 270 String getPreamble() throws NotesException 270 String getSomeHeaders() throws NotesException 270 String getSomeHeaders(Vector vector) throws NotesException 270 String getSomeHeaders(Vector vector, boolean flag) throws NotesException 270 Vector getHeaderObjects() throws NotesException 270 void decodeContent() throws NotesException 270 void encodeContent(int i) throws NotesException 270 void getContentAsBytes(Stream stream) throws NotesException 270 void getContentAsBytes(Stream stream, boolean flag) throws NotesException 270 void getContentAsText(Stream stream) throws NotesException 270 void getContentAsText(Stream stream, boolean flag) throws NotesException 270 void getEntityAsText(Stream stream) throws NotesException 270 void getEntityAsText(Stream stream, Vector vector) throws NotesException 270 void getEntityAsText(Stream stream, Vector vector, boolean flag) throws NotesException 270 void remove() throws NotesException 270 void setContentFromBytes(Stream stream, String s, int i) throws NotesException 270 void setContentFromText(Stream stream, String s, int i) throws NotesException 270
773
774
20 Anhang
void setPreamble(String s) throws NotesException 270 void transformXML(Object obj, XSLTResultTarget xsltresulttarget) throws NotesException 270, 662 MIMEHeader MIMEHeader 427 public interface MIMEHeader extends Base 427 boolean addValText(String s) throws NotesException 427 boolean addValText(String s, String s1) throws NotesException 427 boolean setHeaderVal(String s) throws NotesException 427 boolean setHeaderValAndParams(String s) throws NotesException 427 boolean setParamVal(String s, String s1) throws NotesException 427 String getHeaderName() throws NotesException 427 String getHeaderVal() throws NotesException 427 String getHeaderVal(boolean flag) throws NotesException 427 String getHeaderVal(boolean flag, boolean flag1) throws NotesException 427 String getHeaderValAndParams() throws NotesException 427 String getHeaderValAndParams(boolean flag) throws NotesException 427 String getHeaderValAndParams(boolean flag, boolean flag1) throws NotesException 427 String getParamVal(String s) throws NotesException 427 String getParamVal(String s, boolean flag) throws NotesException 427 void remove() throws NotesException 427 Modell View Controller Pattern 652 Modell-Komponente 654 mod_jk 623 mod_proxy 622, 623 mod_proxy_ajp 623 Monitoring und Events 676 Mountain Standard Time 471 MST 471 Multi-Value-Item 257 Multimedia 524 Multimediadaten 590 Multithreaded 514
N Nachname 464 Name Name 56, 210, 330, 464 public interface Name extends Base 464 @Name 102 boolean isHierarchical() throws NotesException 468 Session getParent() throws NotesException 468 String getAbbreviated() throws NotesException 467 String getAddr821() throws NotesException 466 String getAddr822Comment1() throws NotesException 466 String getAddr822Comment2() throws NotesException 466 String getAddr822Comment3() throws NotesException 466 String getAddr822LocalPart() throws NotesException 466 String getAddr822Phrase() throws NotesException 466 String getADMD() throws NotesException 468 String getCanonical() throws NotesException 467 String getCommon() throws NotesException 467 String getCountry() throws NotesException 467 String getGeneration() throws NotesException 467 String getGiven() throws NotesException 467 String getInitials() throws NotesException 467 String getKeyword() throws NotesException 468 String getLanguage() throws NotesException 468 String getOrganization() throws NotesException 468 String getOrgUnit1() throws NotesException 468 String getOrgUnit2() throws NotesException 468
20.5 Index String getOrgUnit3() throws NotesException 468 String getOrgUnit4() throws NotesException 468 String getPRMD() throws NotesException 468 String getSurname() throws NotesException 467 Name-Value-Paar 256 Namensfelder 506 NAMES 259 names.nsf 17, 75, 83, 121, 132, 173, 183, 187, 193, 211, 212, 234, 350, 466, 478, 527, 545, 560, 673, 723 NCSO.jar 137 Newsletter Newsletter 257, 333 public interface Newsletter extends Base 333 boolean isDoScore() throws NotesException 333 boolean isDoSubject() throws NotesException 333 Document formatDocument(Database database, int i) throws NotesException 333 Document formatMsgWithDoclinks(Database database) throws NotesException 333 Session getParent() throws NotesException 333 String getSubjectItemName() throws NotesException 333 void setDoScore(boolean flag) throws NotesException 333 void setDoSubject(boolean flag) throws NotesException 333 void setSubjectItemName(String s) throws NotesException 333 NOACCESS 357 Not implemented 221 not yet implemented 494 Note 6 Note ID 63, 271, 560, 593 Note is already locked by someone else 321 NoteCollection Note 41 NoteCollection 243, 345, 656, 657, 663 public interface NoteCollection extends Base 657 boolean getSelectAcl() throws NotesException 661 boolean getSelectActions() throws NotesException 660 boolean getSelectAgents() throws NotesException 660 boolean getSelectDatabaseScript() throws NotesException 660 boolean getSelectDataConnections() throws NotesException 660 boolean getSelectDocuments() throws NotesException 659 boolean getSelectFolders() throws NotesException 660 boolean getSelectForms() throws NotesException 660 boolean getSelectFramesets() throws NotesException 660 boolean getSelectHelpAbout() throws NotesException 660 boolean getSelectHelpIndex() throws NotesException 660 boolean getSelectHelpUsing() throws NotesException 660 boolean getSelectIcon() throws NotesException 660 boolean getSelectImageResources() throws NotesException 660 boolean getSelectJavaResources() throws NotesException 660 boolean getSelectMiscCodeElements() throws NotesException 660 boolean getSelectMiscFormatElements() throws NotesException 660 boolean getSelectMiscIndexElements() throws NotesException 660 boolean getSelectNavigators() throws NotesException 660 boolean getSelectOutlines() throws NotesException 660 boolean getSelectPages() throws NotesException 660 boolean getSelectProfiles() throws NotesException 659 boolean getSelectReplicationFormulas() throws NotesException 661 boolean getSelectScriptLibraries() throws NotesException 660 boolean getSelectSharedFields() throws NotesException 660 boolean getSelectStylesheetResources() throws NotesException 660 boolean getSelectSubforms() throws NotesException 660 boolean getSelectViews() throws NotesException 660 Database getParent() throws NotesException 658 DateTime getLastBuildTime() throws NotesException 657
775
776
20 Anhang
DateTime getSinceTime() throws NotesException 657 int getCount() throws NotesException 657 String getFirstNoteID() throws NotesException 657 String getNextNoteID(String s) throws NotesException 657 String getSelectionFormula() throws NotesException 658 void add(Agent agent) throws NotesException 658 void add(Document document) throws NotesException 658 void add(DocumentCollection documentcollection) throws NotesException 658 void add(Form form) throws NotesException 658 void add(int i) throws NotesException 658 void add(NoteCollection notecollection) throws NotesException 658 void add(String s) throws NotesException 658 void add(View view) throws NotesException 658 void buildCollection() throws NotesException 346, 657, 658 void clearCollection() throws NotesException 657 void intersect(Agent agent) throws NotesException 659 void intersect(Document document) throws NotesException 659 void intersect(DocumentCollection documentcollection) throws NotesException 659 void intersect(Form form) throws NotesException 659 void intersect(int i) throws NotesException 658 void intersect(NoteCollection notecollection) throws NotesException 659 void intersect(String s) throws NotesException 658 void intersect(View view) throws NotesException 659 void remove(Agent agent) throws NotesException 658 void remove(Document document) throws NotesException 658 void remove(DocumentCollection documentcollection) throws NotesException 658 void remove(Form form) throws NotesException 658 void remove(int i) throws NotesException 658 void remove(NoteCollection notecollection) throws NotesException 658 void remove(String s) throws NotesException 658 void remove(View view) throws NotesException 658 void selectAllAdminNotes(boolean flag) throws NotesException 661 void selectAllCodeElements(boolean flag) throws NotesException 660 void selectAllDataNotes(boolean flag) throws NotesException 659 void selectAllDesignElements(boolean flag) throws NotesException 659 void selectAllFormatElements(boolean flag) throws NotesException 659 void selectAllIndexElements(boolean flag) throws NotesException 660 void selectAllNotes(boolean flag) throws NotesException 658 void setSelectAcl(boolean flag) throws NotesException 661 void setSelectActions(boolean flag) throws NotesException 660 void setSelectAgents(boolean flag) throws NotesException 660 void setSelectDatabaseScript(boolean flag) throws NotesException 660 void setSelectDataConnections(boolean flag) throws NotesException 660 void setSelectDocuments(boolean flag) throws NotesException 659 void setSelectFolders(boolean flag) throws NotesException 660 void setSelectForms(boolean flag) throws NotesException 660 void setSelectFramesets(boolean flag) throws NotesException 660 void setSelectHelpAbout(boolean flag) throws NotesException 660 void setSelectHelpIndex(boolean flag) throws NotesException 660 void setSelectHelpUsing(boolean flag) throws NotesException 660 void setSelectIcon(boolean flag) throws NotesException 660 void setSelectImageResources(boolean flag) throws NotesException 660 void setSelectionFormula(String s) throws NotesException 658 void setSelectJavaResources(boolean flag) throws NotesException 660 void setSelectMiscCodeElements(boolean flag) throws NotesException 660 void setSelectMiscFormatElements(boolean flag) throws NotesException 660 void setSelectMiscIndexElements(boolean flag) throws NotesException 660 void setSelectNavigators(boolean flag) throws NotesException 660 void setSelectOutlines(boolean flag) throws NotesException 660 void setSelectPages(boolean flag) throws NotesException 660 void setSelectProfiles(boolean flag) throws NotesException 659
20.5 Index
777
void setSelectReplicationFormulas(boolean flag) throws NotesException 661 void setSelectScriptLibraries(boolean flag) throws NotesException 660 void setSelectSharedFields(boolean flag) throws NotesException 660 void setSelectStylesheetResources(boolean flag) throws NotesException 660 void setSelectSubforms(boolean flag) throws NotesException 660 void setSelectViews(boolean flag) throws NotesException 660 void setSinceTime(DateTime datetime) throws NotesException 657 NoteID 528, 557 NOTELINKS 259 NOTEREFS 259 Notes Client 670 Notes Storage Facility 42 Notes-Adressbuch 315 Notes-Farben 329 Notes-Mail 302, 314 notes.ini 719 NotesAppletContext NotesAppletContext 131, 135, 248, 251 public class NotesAppletContext 248 Database getDatabase() 131, 135 Document getDocument() 248 String getServer() 131 NotesError NotesError 248, 252 String getErrorString(int i) 252 NotesException NotesException 252 public final class NotesException extends UserException 252 NotesException() 252 NotesException(int i, String s) 252 NotesException(int i, String s, Throwable throwable) 252 String toString() 252, 253 NotesFactory NotesFactory 123, 175, 195, 203, 229, 248, 269, 614, 628 public class NotesFactory 229 ORB createORB() 220, 232 ORB createORB(Properties properties) 232 ORB createORB(String args[]) 232 Session createSession() throws NotesException 229 Session createSession(Applet applet, ORB orb, String user, String pwd) throws NotesException 230 Session createSession(Applet applet, String user, String pwd) throws NotesException 230 Session createSession(String host) throws NotesException 230 Session createSession(String host, Credentials credentials) throws NotesException 203, 206, 207, 230 Session createSession(String host, HttpServletRequest req) throws NotesException 203, 204, 231, 625 Session createSession(String host, ORB orb, Credentials credentials) throws NotesException 231, 232 Session createSession(String host, ORB orb, HttpServletRequest req) throws NotesException 220, 231, 232 Session createSession(String host, ORB orb, String token) throws NotesException 220, 231, 232 Session createSession(String host, ORB orb, String user, String pwd) throws NotesException 230–232 Session createSession(String host, String args[], Credentials credentials) throws NotesException 231 Session createSession(String host, String args[], HttpServletRequest req) throws NotesException 231 Session createSession(String host, String args[], String token) throws NotesException 231 Session createSession(String host, String args[], String user, String pwd) throws NotesException 197, 230, 231 Session createSession(String host, String token) throws NotesException 203, 204, 230 Session createSession(String host, String user, String pwd) throws NotesException 229, 230 Session createSessionWithFullAccess() throws NotesException 175, 229, 269 Session createSessionWithFullAccess(String pwd) throws NotesException 175, 229, 269 Session createSessionWithIOR(String ior) throws NotesException 230 Session createSessionWithIOR(String ior, Credentials credentials) throws NotesException 230 Session createSessionWithIOR(String ior, HttpServletRequest req) throws NotesException 230
778
20 Anhang
Session createSessionWithIOR(String ior, ORB orb, Credentials credentials) throws NotesException 230 Session createSessionWithIOR(String ior, ORB orb, HttpServletRequest req) throws NotesException 230 Session createSessionWithIOR(String ior, ORB orb, String token) throws NotesException 230 Session createSessionWithIOR(String ior, ORB orb, String user, String pwd) throws NotesException 230, 232 Session createSessionWithIOR(String ior, String args[], Credentials credentials) throws NotesException 230 Session createSessionWithIOR(String ior, String args[], HttpServletRequest req) throws NotesException 230 Session createSessionWithIOR(String ior, String args[], String token) throws NotesException 230 Session createSessionWithIOR(String ior, String args[], String user, String pwd) throws NotesException 230, 231 Session createSessionWithIOR(String ior, String token) throws NotesException 230 Session createSessionWithIOR(String ior, String user, String pwd) throws NotesException 195, 230 String getIOR(String host) throws NotesException 195, 232 String getIOR(String host, String args[]) throws NotesException 232 String getIOR(String host, String args[], String user, String pwd) throws NotesException 232 String getIOR(String host, String user, String pwd) throws NotesException 175, 232 NotesFactory 517 NotesLog 708 NotesTestObjects 704 NotesThread NotesThread 68, 121, 123, 127, 193, 205, 514, 515, 519 public class NotesThread extends Thread 121 final void run() 122, 128 Multithreading 522 NotesThread erweitern 127 Runnable implementieren 128 static void sinitThread() 122–125, 127, 128, 134, 515 static void stermThread() 122–125, 134, 515 void runNotes() 122, 127 NotesUIDatabase 68 NotesUIDocument 68 NotesUIView 68 NotesUIWorkspace 68 NRPC-Protokoll 304 NSD Dump 723 NSF 6 NSFDB2 525, 528 NullPointerException 265, 266, 373 Number 506 NUMBERS 259
O O 198, 210, 464 O/R Framework 256 Object has been removed or recycled 132, 572 Object Request Broker 220 Objekte 240 Objekthierarchie 244 Objektstruktur 240 Oder-Verknüpfung 592 Öffnen von Datenbanken 328, 340, 344 OLE 288, 426 OMG 185 OnDiskStructure 350 OpenAgent 170 Operation timed out 233
20.5 Index
779
ORB 193, 220 ORBEnableSSLSecurity 205 Ordner 82, 97, 409 Organisation 198, 464 Organisations(unter)einheit 464 Organization 464 Organizational Unit 210, 464 OTHEROBJECT 259 OU 210, 464 Outline Outline 83, 364 public interface Outline extends Base 364 Database getParentDatabase() throws NotesException 364 int save() throws NotesException 364 OutlineEntry createEntry(OutlineEntry outlineentry) throws NotesException 364 OutlineEntry createEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1) throws NotesException 364 OutlineEntry createEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1, boolean flag) throws NotesException 364 OutlineEntry createEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1, boolean flag, boolean flag1) throws NotesException 364 OutlineEntry createEntry(String s) throws NotesException 364 OutlineEntry createEntry(String s, OutlineEntry outlineentry) throws NotesException 364 OutlineEntry createEntry(String s, OutlineEntry outlineentry, boolean flag) throws NotesException 364 OutlineEntry createEntry(String s, OutlineEntry outlineentry, boolean flag, boolean flag1) throws NotesException 364 OutlineEntry getChild(OutlineEntry outlineentry) throws NotesException 364 OutlineEntry getFirst() throws NotesException 364 OutlineEntry getLast() throws NotesException 364 OutlineEntry getNext(OutlineEntry outlineentry) throws NotesException 364 OutlineEntry getNextSibling(OutlineEntry outlineentry) throws NotesException 364 OutlineEntry getParent(OutlineEntry outlineentry) throws NotesException 364 OutlineEntry getPrev(OutlineEntry outlineentry) throws NotesException 364 OutlineEntry getPrevSibling(OutlineEntry outlineentry) throws NotesException 364 String getAlias() throws NotesException 364 String getComment() throws NotesException 364 String getName() throws NotesException 364 void addEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1) throws NotesException 364 void addEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1, boolean flag) throws NotesException 364 void addEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1, boolean flag, boolean flag1) throws NotesException 364 void moveEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1) throws NotesException 364 void moveEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1, boolean flag) throws NotesException 364 void moveEntry(OutlineEntry outlineentry, OutlineEntry outlineentry1, boolean flag, boolean flag1) throws NotesException 364 void removeEntry(OutlineEntry outlineentry) throws NotesException 364 void setAlias(String s) throws NotesException 364 void setComment(String s) throws NotesException 364 void setName(String s) throws NotesException 364 OutlineEntry OutlineEntry 364 public interface OutlineEntry extends Base 364 boolean getKeepSelectionFocus() throws NotesException 364 boolean getUseHideFormula() throws NotesException 364 boolean hasChildren() throws NotesException 364 boolean isHidden() throws NotesException 364 boolean isHiddenFromNotes() throws NotesException 364 boolean isHiddenFromWeb() throws NotesException 364
780
20 Anhang
boolean isInThisDB() throws NotesException 364 boolean isPrivate() throws NotesException 364 boolean setAction(String s) throws NotesException 364 boolean setNamedElement(Database database, String s, int i) throws NotesException 364 boolean setNoteLink(Database database) throws NotesException 364 boolean setNoteLink(Document document) throws NotesException 364 boolean setNoteLink(View view) throws NotesException 364 boolean setURL(String s) throws NotesException 364 Database getDatabase() throws NotesException 364 Document getDocument() throws NotesException 364 int getEntryClass() throws NotesException 364 int getLevel() throws NotesException 364 int getType() throws NotesException 364 Outline getParent() throws NotesException 364 String getAlias() throws NotesException 364 String getFormula() throws NotesException 364 String getFrameText() throws NotesException 364 String getHideFormula() throws NotesException 364 String getImagesText() throws NotesException 364 String getLabel() throws NotesException 364 String getNamedElement() throws NotesException 364 String getURL() throws NotesException 364 View getView() throws NotesException 364 void setAlias(String s) throws NotesException 364 void setFrameText(String s) throws NotesException 364 void setHidden(boolean flag) throws NotesException 364 void setHiddenFromNotes(boolean flag) throws NotesException 364 void setHiddenFromWeb(boolean flag) throws NotesException 364 void setHideFormula(String s) throws NotesException 364 void setImagesText(String s) throws NotesException 364 void setKeepSelectionFocus(boolean flag) throws NotesException 364 void setLabel(String s) throws NotesException 364 void setUseHideFormula(boolean flag) throws NotesException 364
P P 464 Pacific Standard Time 471 Page 81 PARAGRAPH 353, 384, 604 Paragraphenwechsel 429 Parameter 148, 719 Passwort 126 PATH 118 PDF 353 PERCENT 652 Performance 178, 223, 236, 345, 369, 403, 418, 483, 496, 498, 512, 582, 594, 595, 608 periodisch 105, 107, 160 periodischer Agent 694 Perl 615 persistent 498 Personendokument 180, 181, 335, 545 persönliches Adressbuch 350 PHP 615 PLATO 11 POJO 499 POP3 670 Port 132, 184, 189, 195, 233 Position is not valid 438 POST 633 preset 631
20.5 Index
781
Preview in Browser 168 Private Management Domain Name 464 PRMD 464 Profildokument 282, 514, 518 Programmatische Sicherheit 155 Programmierung 80 Provisorisches Locking 320 PST 471 Public-/Private Key-Infrastruktur 302
Q Q 464 Qualitätssicherung 682 QuantumDB 550 Query 554 Query View 527, 558, 565 QuerySave 679 Quota 349
R RAD 207 Rahmengruppe 81 Rational Application Developer 207 Ray Ozzie 12 RDBMS-Datenbank 14, 523 READER 357 READERS 259 recycle() 335 Recycling 335, 570 recyclingpflichtig 421, 579 recyclingpflichtige Felder 577 Redundanz 524 Reference to createSession is ambigous 235 Referenz 286, 419, 574, 577 Registration public interface Registration extends Base 333 Registration 333 boolean addCertifierToAddressBook(String s) throws NotesException 333 boolean addCertifierToAddressBook(String s, String s1) throws NotesException 333 boolean addCertifierToAddressBook(String s, String s1, String s2, String s3) throws NotesException 333 boolean addServerToAddressBook(String s, String s1, String s2) throws NotesException 333 boolean addServerToAddressBook(String s, String s1, String s2, String s3) throws NotesException 333 boolean addServerToAddressBook(String s, String s1, String s2, String s3, String s4, String s5, String s6, String s7, String s8) throws NotesException 333 boolean addUserToAddressBook(String s, String s1, String s2) throws NotesException 333 boolean addUserToAddressBook(String s, String s1, String s2, String s3) throws NotesException 333 boolean addUserToAddressBook(String s, String s1, String s2, String s3, String s4, String s5, String s6, String s7, String s8, String s9, String s10) throws NotesException 333 boolean crossCertify(String s) throws NotesException 333 boolean crossCertify(String s, String s1) throws NotesException 333 boolean crossCertify(String s, String s1, String s2) throws NotesException 333 boolean getCreateMailDb() throws NotesException 333 boolean getStoreIDInAddressBook() throws NotesException 333 boolean getUpdateAddressBook() throws NotesException 333 boolean isEnforceUniqueShortName() throws NotesException 333 boolean isMailCreateFTIndex() throws NotesException 333 boolean isNoIDFile() throws NotesException 333 boolean isNorthAmerican() throws NotesException 333 boolean isRoamingUser() throws NotesException 333
782
20 Anhang
boolean isStoreIDInMailfile() throws NotesException 333 boolean isSynchInternetPassword() throws NotesException 333 boolean isUseCertificateAuthority() throws NotesException 333 boolean recertify(String s) throws NotesException 333 boolean recertify(String s, String s1) throws NotesException 333 boolean recertify(String s, String s1, String s2) throws NotesException 333 boolean registerNewCertifier(String s, String s1, String s2) throws NotesException 333 boolean registerNewCertifier(String s, String s1, String s2, String s3) throws NotesException 333 boolean registerNewServer(String s, String s1, String s2, String s3) throws NotesException 333 boolean registerNewServer(String s, String s1, String s2, String s3, String s4) throws NotesException 333 boolean registerNewServer(String s, String s1, String s2, String s3, String s4, String s5, String s6, String s7, String s8, String s9) throws NotesException 333 boolean registerNewUser(String s, String s1, String s2) throws NotesException 333 boolean registerNewUser(String s, String s1, String s2, String s3, String s4, String s5) throws NotesException 333 boolean registerNewUser(String s, String s1, String s2, String s3, String s4, String s5, String s6, String s7, String s8, String s9, String s10) throws NotesException 333 boolean registerNewUser(String s, String s1, String s2, String s3, String s4, String s5, String s6, String s7, String s8, String s9, String s10, String s11, String s12) throws NotesException 333 DateTime getExpiration() throws NotesException 333 int getIDType() throws NotesException 333 int getMailOwnerAccess() throws NotesException 333 int getMailQuotaSizeLimit() throws NotesException 333 int getMailQuotaWarningThreshold() throws NotesException 333 int getMailSystem() throws NotesException 333 int getMinPasswordLength() throws NotesException 333 int getRoamingCleanupPeriod() throws NotesException 333 int getRoamingCleanupSetting() throws NotesException 333 Session getParent() throws NotesException 333 String getCertifierIDFile() throws NotesException 333 String getCertifierName() throws NotesException 333 String getMailACLManager() throws NotesException 333 String getMailInternetAddress() throws NotesException 333 String getMailTemplateName() throws NotesException 333 String getOrgUnit() throws NotesException 333 String getPolicyName() throws NotesException 333 String getRegistrationLog() throws NotesException 333 String getRegistrationServer() throws NotesException 333 String getRoamingServer() throws NotesException 333 String getRoamingSubdir() throws NotesException 333 String getShortName() throws NotesException 333 String switchToID(String s, String s1) throws NotesException 333 Vector getAltOrgUnit() throws NotesException 333 Vector getAltOrgUnitLang() throws NotesException 333 Vector getGroupList() throws NotesException 333 Vector getMailReplicaServers() throws NotesException 333 void addUserProfile(String s, String s1) throws NotesException 333 void deleteIDOnServer(String s, boolean flag) throws NotesException 333 void getIDFromServer(String s, String s1, boolean flag) throws NotesException 333 void getUserInfo(String s, StringBuffer stringbuffer, StringBuffer stringbuffer1, StringBuffer stringbuffer2, StringBuffer stringbuffer3, Vector vector) throws NotesException 333 void setAltOrgUnit(Vector vector) throws NotesException 333 void setAltOrgUnitLang(Vector vector) throws NotesException 333 void setCertifierIDFile(String s) throws NotesException 333 void setCertifierName(String s) throws NotesException 333 void setCreateMailDb(boolean flag) throws NotesException 333 void setEnforceUniqueShortName(boolean flag) throws NotesException 333 void setExpiration(DateTime datetime) throws NotesException 333 void setGroupList(Vector vector) throws NotesException 333 void setIDType(int i) throws NotesException 333
20.5 Index void setMailACLManager(String s) throws NotesException 333 void setMailCreateFTIndex(boolean flag) throws NotesException 333 void setMailInternetAddress(String s) throws NotesException 333 void setMailOwnerAccess(int i) throws NotesException 333 void setMailQuotaSizeLimit(int i) throws NotesException 333 void setMailQuotaWarningThreshold(int i) throws NotesException 333 void setMailReplicaServers(Vector vector) throws NotesException 333 void setMailSystem(int i) throws NotesException 333 void setMailTemplateName(String s) throws NotesException 333 void setMinPasswordLength(int i) throws NotesException 333 void setNoIDFile(boolean flag) throws NotesException 333 void setNorthAmerican(boolean flag) throws NotesException 333 void setOrgUnit(String s) throws NotesException 333 void setPolicyName(String s) throws NotesException 333 void setRegistrationLog(String s) throws NotesException 333 void setRegistrationServer(String s) throws NotesException 333 void setRoamingCleanupPeriod(int i) throws NotesException 333 void setRoamingCleanupSetting(int i) throws NotesException 333 void setRoamingServer(String s) throws NotesException 333 void setRoamingSubdir(String s) throws NotesException 333 void setRoamingUser(boolean flag) throws NotesException 333 void setShortName(String s) throws NotesException 333 void setStoreIDInAddressBook(boolean flag) throws NotesException 333 void setStoreIDInMailfile(boolean flag) throws NotesException 333 void setSynchInternetPassword(boolean flag) throws NotesException 333 void setUpdateAddressBook(boolean flag) throws NotesException 333 void setUseCertificateAuthority(boolean flag) throws NotesException 333 Reihenfolge 386 Relational 286 relationale Datenbank 523 Remote Computing 184 REMOTE_USER 180 reorganisieren 366 Replication public interface Replication extends Base 344 Replication 62, 243, 343, 344, 347 boolean getDontSendLocalSecurityUpdates() throws NotesException 344 boolean isAbstract() throws NotesException 344 boolean isCutoffDelete() throws NotesException 344 boolean isDisabled() throws NotesException 344 boolean isIgnoreDeletes() throws NotesException 344 boolean isIgnoreDestDeletes() throws NotesException 344 DateTime getCutoffDate() throws NotesException 344 int clearHistory() throws NotesException 344 int getPriority() throws NotesException 344 int reset() throws NotesException 344 int save() throws NotesException 344 long getCutoffInterval() throws NotesException 344 ReplicationEntry getEntry(String s, String s1) throws NotesException 344 ReplicationEntry getEntry(String s, String s1, boolean flag) throws NotesException 344 void setAbstract(boolean flag) throws NotesException 344 void setCutoffDelete(boolean flag) throws NotesException 344 void setCutoffInterval(long l) throws NotesException 344 void setDisabled(boolean flag) throws NotesException 344 void setDontSendLocalSecurityUpdates(boolean flag) throws NotesException 344 void setIgnoreDeletes(boolean flag) throws NotesException 344 void setIgnoreDestDeletes(boolean flag) throws NotesException 344 void setPriority(int i) throws NotesException 344 ReplicationEntry public interface ReplicationEntry extends Base 344 ReplicationEntry 344
783
784
20 Anhang
boolean isIncludeACL() throws NotesException 344 boolean isIncludeAgents() throws NotesException 344 boolean isIncludeDocuments() throws NotesException 344 boolean isIncludeForms() throws NotesException 344 boolean isIncludeFormulas() throws NotesException 344 int remove() throws NotesException 344 int save() throws NotesException 344 String getDestination() throws NotesException 344 String getFormula() throws NotesException 344 String getSource() throws NotesException 344 String getViews() throws NotesException 344 void setFormula(String s) throws NotesException 344 void setIncludeACL(boolean flag) throws NotesException 344 void setIncludeAgents(boolean flag) throws NotesException 344 void setIncludeDocuments(boolean flag) throws NotesException 344 void setIncludeForms(boolean flag) throws NotesException 344 void setIncludeFormulas(boolean flag) throws NotesException 344 void setViews(String s) throws NotesException 344 Replik ID 62, 344 Replikation 5, 524, 671 Replikationskonflikt 67, 281 REQUEST_SCOPE 633 Ressourcenplanung 670 Resultset 98, 527, 556 Reverse Proxy 623 reyclingpflichtige Objekte 696 RFC 821 464 RFC 822 464 RGB 329 RichText 61, 259, 316, 529 RichTextDoclink public interface RichTextDoclink extends Base 453 RichTextDoclink 426, 428, 453 RichTextStyle getHotSpotTextStyle() throws NotesException 455 String getDBReplicaID() throws NotesException 453 String getDisplayComment() throws NotesException 455 String getDocUnID() throws NotesException 454 String getHotSpotText() throws NotesException 455 String getServerHint() throws NotesException 455 String getViewUnID() throws NotesException 454 void remove() throws NotesException 454 void setDBReplicaID(String s) throws NotesException 453 void setDisplayComment(String s) throws NotesException 455 void setDocUnID(String s) throws NotesException 454 void setHotSpotText(String s) throws NotesException 455 void setHotSpotTextStyle(RichTextStyle richtextstyle) throws NotesException 455 void setServerHint(String s) throws NotesException 455 void setViewUnID(String s) throws NotesException 454 RichTextItem public interface RichTextItem extends Item 426 RichTextItem 28, 45, 51, 61, 69, 241, 288, 289, 298, 314, 317, 426, 428, 429 EmbeddedObject embedObject(int i, String s, String s1, String s2) throws NotesException 298, 431 EmbeddedObject getEmbeddedObject(String s) throws NotesException 299, 431 InputSource getInputSource() throws NotesException 662 InputStream getInputStream() throws NotesException 662 int getNotesFont(String s, boolean flag) throws NotesException 433 org.w3c.dom.Document parseXML(boolean flag) throws IOException, NotesException 662 Reader getReader() throws NotesException 662 RichTextNavigator createNavigator() throws NotesException 433, 435 RichTextRange createRange() throws NotesException 433 String getFormattedText(boolean flag, int i, int j) throws NotesException 431
20.5 Index
785
String getUnformattedText() throws NotesException 432 Vector getEmbeddedObjects() throws NotesException 299 void addNewLine() throws NotesException 429 void addNewLine(int i) throws NotesException 429 void addNewLine(int i, boolean flag) throws NotesException 429 void addPageBreak() throws NotesException 430 void addPageBreak(RichTextParagraphStyle richtextparagraphstyle) throws NotesException 430, 455 void addTab() throws NotesException 429 void addTab(int i) throws NotesException 429 void appendDocLink(Database database) throws NotesException 430 void appendDocLink(Database database, String s) throws NotesException 430 void appendDocLink(Database database, String s, String s1) throws NotesException 430 void appendDocLink(Document document) throws NotesException 430 void appendDocLink(Document document, String s) throws NotesException 430 void appendDocLink(Document document, String s, String s1) throws NotesException 430 void appendDocLink(View view) throws NotesException 430 void appendDocLink(View view, String s) throws NotesException 430 void appendDocLink(View view, String s, String s1) throws NotesException 430 void appendParagraphStyle(RichTextParagraphStyle richtextparagraphstyle) throws NotesException 431, 455 void appendRTItem(RichTextItem richtextitem) throws NotesException 431 void appendStyle(RichTextStyle richtextstyle) throws NotesException 431 void appendTable(int i, int j) throws NotesException 432 void appendTable(int i, int j, Vector vector) throws NotesException 432 void appendTable(int i, int j, Vector vector, int k, Vector vector1) throws NotesException 432, 455 void appendText(String s) throws NotesException 431 void beginInsert(Base base) throws NotesException 433 void beginInsert(Base base, boolean flag) throws NotesException 433 void beginSection(String s) throws NotesException 434 void beginSection(String s, RichTextStyle richtextstyle) throws NotesException 434 void beginSection(String s, RichTextStyle richtextstyle, ColorObject colorobject, boolean flag) throws NotesException 434 void compact() throws NotesException 434 void endInsert() throws NotesException 433 void endSection() throws NotesException 434 void transformXML(Object obj, XSLTResultTarget xsltresulttarget) throws NotesException 662 void update() throws NotesException 434 RichTextNavigator public interface RichTextNavigator extends Base 435 RichTextNavigator 241, 288, 289, 291, 299, 426, 428, 433, 435, 437, 441, 445, 451 Base getElement() throws NotesException 436, 438 Base getFirstElement(int i) throws NotesException 299, 438 Base getLastElement(int i) throws NotesException 438 Base getNextElement() throws NotesException 438 Base getNextElement(int i) throws NotesException 438 Base getNextElement(int i, int j) throws NotesException 438 Base getNthElement(int i, int j) throws NotesException 438 boolean findFirstElement(int i) throws NotesException 299, 436, 437 boolean findFirstString(String s) throws NotesException 438 boolean findFirstString(String s, int i) throws NotesException 438 boolean findLastElement(int i) throws NotesException 437 boolean findNextElement() throws NotesException 438 boolean findNextElement(int i) throws NotesException 436, 438 boolean findNextElement(int i, int j) throws NotesException 438 boolean findNextString(String s) throws NotesException 439 boolean findNextString(String s, int i) throws NotesException 439 boolean findNthElement(int i, int j) throws NotesException 437 RichTextNavigator Clone() throws NotesException 439 RTELEM_TYPE_DOCLINK 427 RTELEM_TYPE_FILEATTACHMENT 299, 427 RTELEM_TYPE_OLE 299, 427
786
20 Anhang
RTELEM_TYPE_SECTION 427 RTELEM_TYPE_TABLE 427 RTELEM_TYPE_TABLECELL 427 RTELEM_TYPE_TEXTPARAGRAPH 427 RTELEM_TYPE_TEXTPOSITION 427 RTELEM_TYPE_TEXTRUN 427 RTELEM_TYPE_TEXTSTRING 427 void setCharOffset(int i) throws NotesException 439 void setPosition(Base base) throws NotesException 439 void setPositionAtEnd(Base base) throws NotesException 439 RichTextParagraphStyle public interface RichTextParagraphStyle extends Base 455 RichTextParagraphStyle 330, 426, 428, 455 int getAlignment() throws NotesException 456 int getFirstLineLeftMargin() throws NotesException 457 int getInterLineSpacing() throws NotesException 456 int getLeftMargin() throws NotesException 456 int getPagination() throws NotesException 457 int getRightMargin() throws NotesException 457 int getSpacingAbove() throws NotesException 456 int getSpacingBelow() throws NotesException 456 RichTextParagraphStyle createRichTextParagraphStyle() throws NotesException 455 Vector getTabs() throws NotesException 458 void clearAllTabs() throws NotesException 459 void setAlignment(int i) throws NotesException 456 void setFirstLineLeftMargin(int i) throws NotesException 455, 457 void setInterLineSpacing(int i) throws NotesException 456 void setLeftMargin(int i) throws NotesException 455, 456 void setPagination(int i) throws NotesException 457 void setRightMargin(int i) throws NotesException 457 void setSpacingAbove(int i) throws NotesException 456 void setSpacingBelow(int i) throws NotesException 456 void setTab(int i, int j) throws NotesException 458 void setTabs(int i, int j, int k) throws NotesException 458 void setTabs(int i, int j, int k, int l) throws NotesException 458 RichTextRange public interface RichTextRange extends Base 439 RichTextRange 426, 428, 429, 433, 439 int findandReplace(String s, String s1) throws NotesException 441 int findandReplace(String s, String s1, long l) throws NotesException 441 int getType() throws NotesException 441 RichTextNavigator getNavigator() throws NotesException 441 RichTextRange Clone() throws NotesException 441 RichTextStyle getStyle() throws NotesException 440 String getTextParagraph() throws NotesException 441 String getTextRun() throws NotesException 441 void remove() throws NotesException 440 void reset(boolean flag, boolean flag1) throws NotesException 441 void setBegin(Base base) throws NotesException 440 void setEnd(Base base) throws NotesException 440 void setStyle(RichTextStyle richtextstyle) throws NotesException 440 RichTextSection public interface RichTextSection extends Base 442 RichTextSection 91, 426, 428, 442 boolean isExpanded() throws NotesException 442 ColorObject getBarColor() throws NotesException 442 RichTextStyle getTitleStyle() throws NotesException 442 String getTitle() throws NotesException 442 void remove() throws NotesException 442 void setBarColor(ColorObject colorobject) throws NotesException 442 void setExpanded(boolean flag) throws NotesException 442
20.5 Index void setTitle(String s) throws NotesException 442 void setTitleStyle(RichTextStyle richtextstyle) throws NotesException 442 RichTextStyle public interface RichTextStyle extends Base 459 RichTextStyle 329, 330, 426, 428, 459 boolean isDefault() throws NotesException 461 int getBold() throws NotesException 460 int getColor() throws NotesException 459 int getEffects() throws NotesException 460 int getFont() throws NotesException 460 int getFontSize() throws NotesException 460 int getItalic() throws NotesException 460 int getPassThruHTML() throws NotesException 461 int getStrikeThrough() throws NotesException 460 int getUnderline() throws NotesException 460 RichTextStyle createRichTextStyle() throws NotesException 459 Session getParent() throws NotesException 461 void setBold(int i) throws NotesException 460 void setColor(int i) throws NotesException 459 void setEffects(int i) throws NotesException 460 void setFont(int i) throws NotesException 460 void setFontSize(int i) throws NotesException 460 void setItalic(int i) throws NotesException 460 void setPassThruHTML(int i) throws NotesException 461 void setStrikeThrough(int i) throws NotesException 460 void setUnderline(int i) throws NotesException 461 RichTextTab public interface RichTextTab extends Base 452 RichTextTab 426, 428, 452 int getPosition() throws NotesException 453 int getType() throws NotesException 453 void clear() throws NotesException 453 RichTextTable public interface RichTextTable extends Base 442 RichTextTable 426, 428, 442, 446, 448 boolean isRightToLeft() throws NotesException 444 ColorObject getAlternateColor() throws NotesException 443 ColorObject getColor() throws NotesException 443 int getColumnCount() throws NotesException 444 int getRowCount() throws NotesException 444 int getStyle() throws NotesException 444 Vector getRowLabels() throws NotesException 444 void addRow() throws NotesException 443 void addRow(int i) throws NotesException 443 void addRow(int i, int j) throws NotesException 443 void remove() throws NotesException 443 void removeRow() throws NotesException 443 void removeRow(int i) throws NotesException 443 void removeRow(int i, int j) throws NotesException 443 void setAlternateColor(ColorObject colorobject) throws NotesException 443 void setColor(ColorObject colorobject) throws NotesException 443 void setRightToLeft(boolean flag) throws NotesException 444 void setRowLabels(Vector vector) throws NotesException 444 void setStyle(int i) throws NotesException 444 Rolle 76, 268, 357 RollingFileAppender 716 RSA 303 RSA-Infrastruktur 9 RSA-Public-Key-Infrastruktur 4 Run as web user 180 run on behalf of 233
787
788
20 Anhang
Runnable 128, 515
S S 198, 464 S/MIME 302 SaveOptions 491 Schema 4, 6, 256, 523 schema.nsf 675 Schleife 413, 419 Schleifenverarbeitung 420 Schlüssel 269, 303, 590 Schlüsselring 197 Schrift 372, 426, 459 Script Libraries 84, 111 search 407 SearchView 607 Security 191 Security Exception 159, 172 Seite 81 Seitenränder 455 Seitenwechsel 430 sekundäres Verzeichnis 675 SELECT 98, 102 SELECT-Formel 379, 413, 600 Selektion 369 SENTENCE 384, 604 Server access denied 234 Server Certificate Request 197, 199 Server-ID 303, 531 Serverausfälle 723 Server-basierte Agenten 151, 697 Serverdokument 155, 173, 187 Serverkontext 175 Service-Anwendungen 243 Servlet 161, 171, 174, 679 Servlet Manager 172 servlets.properties 174, 234 Session public interface Session extends Base 328 Session 19, 37, 68, 119, 126, 142, 184, 193, 229, 240, 247, 328, 635 AdministrationProcess createAdministrationProcess(String s) throws NotesException 331 AgentContext getAgentContext() throws NotesException 331 Base resolve(String s) throws NotesException 335 boolean isConvertMime() throws NotesException 337 boolean isOnServer() throws NotesException 337 boolean isValid() 337 boolean verifyPassword(String s, String s1) throws NotesException 335 ColorObject createColorObject() throws NotesException 329 Database getDatabase(String s, String s1) throws NotesException 64, 328, 344, 345 Database getDatabase(String s, String s1, boolean flag) throws NotesException 328 Database getURLDatabase() throws NotesException 328 DateRange createDateRange() throws NotesException 330, 469 DateRange createDateRange(Date date, Date date1) throws NotesException 330 DateRange createDateRange(DateTime datetime, DateTime datetime1) throws NotesException 330 DateTime createDateTime(Calendar calendar) throws NotesException 330, 469 DateTime createDateTime(Date date) throws NotesException 330 DateTime createDateTime(String s) throws NotesException 330, 474, 475 DbDirectory getDbDirectory(String s) throws NotesException 331 Document getUserPolicySettings(String s, String s1, int i) throws NotesException 336 Document getUserPolicySettings(String s, String s1, int i, String s2) throws NotesException 336
20.5 Index
789
DxlExporter createDxlExporter() throws NotesException 333, 663 DxlImporter createDxlImporter() throws NotesException 333, 663 International getInternational() throws NotesException 330, 474 Log createLog(String s) throws NotesException 333, 708 Name createName(String s) throws NotesException 330, 464 Name createName(String s, String s1) throws NotesException 330 Name getUserNameObject() throws NotesException 337, 464 Newsletter createNewsletter(DocumentCollection documentcollection) throws NotesException 333 Object getEnvironmentValue(String s) throws NotesException 336 Object getEnvironmentValue(String s, boolean flag) throws NotesException 336 Registration createRegistration() throws NotesException 333 RichTextParagraphStyle createRichTextParagraphStyle() throws NotesException 330 RichTextStyle createRichTextStyle() throws NotesException 330 Stream createStream() throws NotesException 333 String getCommonUserName() throws NotesException 337 String getEnvironmentString(String s) throws NotesException 336 String getEnvironmentString(String s, boolean flag) throws NotesException 336 String getHttpURL() throws NotesException 274, 335 String getNotesVersion() throws NotesException 338 String getOrgDirectoryPath() throws NotesException 338 String getPlatform() throws NotesException 338 String getServerName() throws NotesException 338 String getSessionToken() throws NotesException 336 String getSessionToken(String s) throws NotesException 336 String getURL() throws NotesException 273, 335 String getUserName() throws NotesException 337 String hashPassword(String s) throws NotesException 335 String sendConsoleCommand(String s, String s1) throws NotesException 334 String toString() 337 Vector evaluate(String s) throws NotesException 334 Vector evaluate(String s, Document document) throws NotesException 334 Vector freeTimeSearch(DateRange daterange, int i, Object obj, boolean flag) throws NotesException 335 Vector getAddressBooks() throws NotesException 337 Vector getUserGroupNameList() throws NotesException 337 Vector getUserNameList() throws NotesException 337, 464 void recycle() throws NotesException 335 void recycle(Vector objects) throws NotesException 335 void setConvertMime(boolean flag) throws NotesException 337 void setEnvironmentVar(String s, Object obj) throws NotesException 336 void setEnvironmentVar(String s, Object obj, boolean flag) throws NotesException 336 Session 181 Session authentication 181 Session Cookies 182 Session Sharing 636 SESSION_SCOPE 633 setter-Methoden 481 setUp 234, 704 Shared Code 82, 90 Shared Resources 84, 88 ShiftOne 513 Show response documents in a hierarchy 284 show stat agent 160 Sibling 382 Sicherheit 355 Sicherheitseinstellungen 156, 191, 213 Sicherheitsrisiko 204 Signatur 303, 311–313 Signieren 312 Signing 302 Single Sign On 182, 202, 234, 624
790
20 Anhang
Skript-Bibliotheken 84 SMTP 670 Softdeletion 282, 348 Sommerzeit 469 Sortieroptionen 99, 601–603 sortierte Ergebnisse 592 sortierte Spalte 594 sortiertes Resultset 402 Spalte 83, 318, 370, 373, 443, 455, 527 Spaltenberechnung 99, 370, 405 Spaltenwert 318, 562 Speicherbedarf 369 Speicherkonflikt 387, 518 Speicherleak 571 Speicherung unstrukturierter Daten 590 Speicherverbrauch 582 SQL 98, 527 SQL Datenbank 523 SSL 197 SSL implementation not available 39 SSL-Verschlüsselung 304 SSO 181, 193, 202, 209, 615 Start eines Agenten auf einem Server 158 Start eines Agenten im Notes Client 158 Starten eines Agenten 148 State 198 Statuszeile 430 Stream public interface Stream extends Base 333 Stream 333 boolean isEOS() throws NotesException 334 boolean isReadOnly() throws NotesException 333 boolean open(String s) throws NotesException 334 boolean open(String s, String s1) throws NotesException 334 byte[] read() throws NotesException 333 byte[] read(int i) throws NotesException 333 int getBytes() throws NotesException 333 int getPosition() throws NotesException 334 int write(byte abyte0[]) throws NotesException 333 int writeText(String s) throws NotesException 334 int writeText(String s, int i) throws NotesException 334 String getCharset() throws NotesException 333 String readText() throws NotesException 334 String readText(int i) throws NotesException 334 String readText(int i, int j) throws NotesException 334 void close() throws NotesException 333 void getContents(OutputStream outputstream) throws NotesException 334 void getContents(Writer writer) throws NotesException 334 void setContents(InputStream inputstream) throws NotesException 334 void setContents(Reader reader) throws NotesException 334 void setPosition(int i) throws NotesException 334 void truncate() throws NotesException 333 Stresstest 519 String 265, 266 Style Sheets 84 Subform 83 Suche Suche 70, 369, 383, 388, 590, 594, 599–601 DATETIME 599 NUMBER 599 Suche über Ansichten 594
20.5 Index TEXT 599 Suche 726 Suche über Ansichten 592 Summenspalte 386 Supplied Data type name does not match stored CustomData type 259, 265, 266 Surname 464 Swing 129
T Tabelle Export 445 Import 445 Konvertierung 445 Tabelle 442 Tabulator 430, 452 Target 105, 108 Team 683 tearDown 704 Teilmaske 83, 85 tell amgr schedule 160, 701 Template 343 temporäre Dateien 584 Test 688 Testen von Benutzerrechten 689 Testserver 681 Text 55, 259, 506 Textliste 55 Textstil 426 The document is not locked by you 323 The version of the IBM Universal JDBC driver in use is not licensed for connectivity to QDB2/N databases 551 There is no certificate in the Address Book 311 There is no current agent, can't open log 707 Thread 516 Tim Halvorsen 12 Tomcat 616, 619 Totals 386 Transaktion 523, 524 Transaktionslog 348, 535 Transformation 301, 663 Trigger 105–107 Twistie 100, 434
U übergeordnetes Dokument 380 UI-Klassen 15 Umbruch 455 Unable to lock - cannot connect to Master Lock Database 321 Unable to write to database because database would exceed its disk quota 349 Und-Verknüpfung 592 UndeleteExpireTime 282 Unique ID 64 Unit Test 682, 698 Universal ID 63, 64, 272, 593 Unknown value type to convert 265 UnsatisfiedLinkError: Can't find library nlsxbe (nlsxbe.dll) in java.library.path 235 UnsatisfiedLinkError: NCreateSession 235 Unsortierte Ergebnisse 592 Unterschied zwischen Servlet und Web Agent 172, 177 URL 24, 274, 429
791
792
20 Anhang
User username is not a server 235 User-ID 303
V Validierung 644, 666 VARCHAR 526 Vector 257, 507 Verbindungsversuch 233 Vergleichsoperatoren 604 verschlüsselte Felder 353 verschlüsselte Items 505 Verschlüsselung 155, 269, 302, 507 Verschlüsselungsschlüssel Verschlüsselungsschlüssel 304, 311 geheime 304 öffentliche 311 verteilte Systeme 523 Vertrauensstellung 186, 328 Verzeichnis 350, 625, 675 View public interface View extends Base 368 View 17, 68, 70, 82, 97, 240, 244, 247, 362, 363, 368, 402, 419, 524, 526, 527, 576, 594, 637 boolean isAutoUpdate() throws NotesException 383, 402, 416 boolean isCalendar() throws NotesException 376 boolean isCategorized() throws NotesException 375 boolean isConflict() throws NotesException 378 boolean isDefaultView() throws NotesException 376 boolean isFolder() throws NotesException 376 boolean isHierarchical() throws NotesException 376 boolean isModified() throws NotesException 378 boolean isPrivate() throws NotesException 376 boolean isProhibitDesignRefresh() throws NotesException 377 boolean isProtectReaders() throws NotesException 380 boolean isQueryView() throws NotesException 378 boolean lock() throws NotesException 323, 401 boolean lock(boolean flag) throws NotesException 320, 323, 401 boolean lock(String s) throws NotesException 323, 401 boolean lock(String s, boolean flag) throws NotesException 323, 401 boolean lock(Vector vector) throws NotesException 323, 401 boolean lock(Vector vector, boolean flag) throws NotesException 323, 401 boolean lockProvisional() throws NotesException 320, 323, 401 boolean lockProvisional(String s) throws NotesException 323, 401 boolean lockProvisional(Vector vector) throws NotesException 323, 401 Database getParent() throws NotesException 378 DateTime getCreated() throws NotesException 377 DateTime getLastModified() throws NotesException 377 Document getChild(Document document) throws NotesException 381 Document getDocumentByKey(Object obj) throws NotesException 382, 402, 591, 594 Document getDocumentByKey(Object obj, boolean flag) throws NotesException 382 Document getDocumentByKey(Vector vector) throws NotesException 382 Document getDocumentByKey(Vector vector, boolean flag) throws NotesException 382 Document getFirstDocument() throws NotesException 381, 413, 564 Document getLastDocument() throws NotesException 381 Document getNextDocument(Document document) throws NotesException 381, 413, 414, 416, 419 Document getNextSibling(Document document) throws NotesException 382 Document getNthDocument(int i) throws NotesException 381, 419 Document getParentDocument(Document document) throws NotesException 381 Document getPrevDocument(Document document) throws NotesException 381 Document getPrevSibling(Document document) throws NotesException 382
20.5 Index
793
DocumentCollection getAllDocumentsByKey(Object obj) throws NotesException 71, 73, 383, 402, 591, 594 DocumentCollection getAllDocumentsByKey(Object obj, boolean flag) throws NotesException 383 DocumentCollection getAllDocumentsByKey(Vector vector) throws NotesException 383 DocumentCollection getAllDocumentsByKey(Vector vector, boolean flag) throws NotesException 383 Feldverschlüsselung 310 int FTSearch(String s) throws NotesException 384, 410, 416, 592, 602 int FTSearch(String s, int i) throws NotesException 384, 602 int getBackgroundColor() throws NotesException 374 int getColumnCount() throws NotesException 374 int getEntryCount() throws NotesException 389 int getHeaderLines() throws NotesException 374 int getRowLines() throws NotesException 374 int getSpacing() throws NotesException 374 int getTopLevelEntryCount() throws NotesException 388 String getHttpURL() throws NotesException 274, 380 String getName() throws NotesException 377 String getNotesURL() throws NotesException 273, 380 String getSelectionFormula() throws NotesException 379 String getUniversalID() throws NotesException 378 String getURL() throws NotesException 273, 380 String getViewInheritedName() throws NotesException 377 Vector getAliases() throws NotesException 376 Vector getColumnNames() throws NotesException 374 Vector getColumns() throws NotesException 373 Vector getLockHolders() throws NotesException 323, 401 Vector getReaders() throws NotesException 379 ViewColumn copyColumn(int i) throws NotesException 373 ViewColumn copyColumn(int i, int j) throws NotesException 373 ViewColumn copyColumn(String s) throws NotesException 373 ViewColumn copyColumn(String s, int i) throws NotesException 373 ViewColumn copyColumn(ViewColumn viewcolumn) throws NotesException 373 ViewColumn copyColumn(ViewColumn viewcolumn, int i) throws NotesException 373 ViewColumn createColumn() throws NotesException 373 ViewColumn createColumn(int i) throws NotesException 373 ViewColumn createColumn(int i, String s) throws NotesException 373 ViewColumn createColumn(int i, String s, String s1) throws NotesException 373 ViewColumn getColumn(int i) throws NotesException 370 ViewEntry getEntryByKey(Object obj) throws NotesException 387, 402, 591, 594 ViewEntry getEntryByKey(Object obj, boolean flag) throws NotesException 387 ViewEntry getEntryByKey(Vector vector) throws NotesException 387 ViewEntry getEntryByKey(Vector vector, boolean flag) throws NotesException 387 ViewEntryCollection getAllEntries() throws NotesException 388, 402, 416, 418, 564 ViewEntryCollection getAllEntriesByKey(Object obj) throws NotesException 73, 388, 402, 591, 594 ViewEntryCollection getAllEntriesByKey(Object obj, boolean flag) throws NotesException 388 ViewEntryCollection getAllEntriesByKey(Vector vector) throws NotesException 388 ViewEntryCollection getAllEntriesByKey(Vector vector, boolean flag) throws NotesException 388 ViewNavigator createViewNav() throws NotesException 389, 392, 394, 398 ViewNavigator createViewNav(int i) throws NotesException 392 ViewNavigator createViewNavFrom(Object obj) throws NotesException 392, 395, 396, 399 ViewNavigator createViewNavFrom(Object obj, int i) throws NotesException 392, 399 ViewNavigator createViewNavFromCategory(String s) throws NotesException 393, 394, 400 ViewNavigator createViewNavFromCategory(String s, int i) throws NotesException 393, 400 ViewNavigator createViewNavFromChildren(Object obj) throws NotesException 392, 396, 399, 400 ViewNavigator createViewNavFromChildren(Object obj, int i) throws NotesException 392, 400 ViewNavigator createViewNavFromDescendants(Object obj) throws NotesException 393, 396, 399, 400 ViewNavigator createViewNavFromDescendants(Object obj, int i) throws NotesException 400 ViewNavigator createViewNavMaxLevel(int i) throws NotesException 392, 394 ViewNavigator createViewNavMaxLevel(int i, int j) throws NotesException 392 void clear() throws NotesException 384 void refresh() throws NotesException 383, 414, 416, 419, 598
794
20 Anhang
void remove() throws NotesException 379 void removeColumn() throws NotesException 374 void removeColumn(int i) throws NotesException 374 void removeColumn(String s) throws NotesException 374 void setAliases(String s) throws NotesException 376 void setAliases(Vector vector) throws NotesException 376 void setAutoUpdate(boolean flag) throws NotesException 369, 383, 402, 414, 416, 419 void setBackgroundColor(int i) throws NotesException 374 void setDefaultView(boolean flag) throws NotesException 376 void setName(String s) throws NotesException 377 void setProhibitDesignRefresh(boolean flag) throws NotesException 377 void setProtectReaders(boolean flag) throws NotesException 380 void setReaders(Vector vector) throws NotesException 379 void setSelectionFormula(String s) throws NotesException 379 void setSpacing(int i) throws NotesException 374 void unlock() throws NotesException 323, 401 View-Komponente 654 ViewColumn public interface ViewColumn extends Base 371 ViewColumn 371 boolean isAccentSensitiveSort() throws NotesException 371 boolean isCaseSensitiveSort() throws NotesException 371 boolean isCategory() throws NotesException 371 boolean isField() throws NotesException 371 boolean isFontBold() throws NotesException 371 boolean isFontItalic() throws NotesException 371 boolean isFontStrikethrough() throws NotesException 371 boolean isFontUnderline() throws NotesException 371 boolean isFormula() throws NotesException 371 boolean isHeaderFontBold() throws NotesException 371 boolean isHeaderFontItalic() throws NotesException 371 boolean isHeaderFontStrikethrough() throws NotesException 371 boolean isHeaderFontUnderline() throws NotesException 371 boolean isHidden() throws NotesException 372 boolean isHideDetail() throws NotesException 371 boolean isIcon() throws NotesException 371 boolean isNumberAttribParens() throws NotesException 371 boolean isNumberAttribPercent() throws NotesException 371 boolean isNumberAttribPunctuated() throws NotesException 371 boolean isResize() throws NotesException 371 boolean isResortAscending() throws NotesException 371 boolean isResortDescending() throws NotesException 371 boolean isResortToView() throws NotesException 371 boolean isResponse() throws NotesException 372 boolean isSecondaryResort() throws NotesException 371 boolean isSecondaryResortDescending() throws NotesException 371 boolean isShowTwistie() throws NotesException 371 boolean isSortDescending() throws NotesException 372 boolean isSorted() throws NotesException 371 int getAlignment() throws NotesException 371 int getDateFmt() throws NotesException 371 int getFontColor() throws NotesException 372 int getFontPointSize() throws NotesException 372 int getFontStyle() throws NotesException 372 int getHeaderAlignment() throws NotesException 371 int getHeaderFontColor() throws NotesException 371 int getHeaderFontPointSize() throws NotesException 371 int getHeaderFontStyle() throws NotesException 371 int getListSep() throws NotesException 371 int getNumberAttrib() throws NotesException 371 int getNumberDigits() throws NotesException 371
20.5 Index int getNumberFormat() throws NotesException 371 int getPosition() throws NotesException 371 int getSecondaryResortColumnIndex() throws NotesException 371 int getTimeDateFmt() throws NotesException 371 int getTimeFmt() throws NotesException 371 int getTimeZoneFmt() throws NotesException 371 int getWidth() throws NotesException 371 String getFontFace() throws NotesException 372 String getFormula() throws NotesException 371 String getHeaderFontFace() throws NotesException 371 String getItemName() throws NotesException 371 String getResortToViewName() throws NotesException 371 String getTitle() throws NotesException 371 View getParent() throws NotesException 371 void setAccentSensitiveSort(boolean flag) throws NotesException 371 void setAlignment(int i) throws NotesException 371 void setCaseSensitiveSort(boolean flag) throws NotesException 371 void setDateFmt(int i) throws NotesException 371 void setFontBold(boolean flag) throws NotesException 371 void setFontColor(int i) throws NotesException 372 void setFontFace(String s) throws NotesException 372 void setFontItalic(boolean flag) throws NotesException 371 void setFontPointSize(int i) throws NotesException 372 void setFontStrikethrough(boolean flag) throws NotesException 371 void setFontStyle(int i) throws NotesException 372 void setFontUnderline(boolean flag) throws NotesException 371 void setFormula(String s) throws NotesException 371 void setHeaderAlignment(int i) throws NotesException 371 void setHeaderFontBold(boolean flag) throws NotesException 371 void setHeaderFontColor(int i) throws NotesException 371 void setHeaderFontFace(String s) throws NotesException 371 void setHeaderFontItalic(boolean flag) throws NotesException 371 void setHeaderFontPointSize(int i) throws NotesException 371 void setHeaderFontStrikethrough(boolean flag) throws NotesException 371 void setHeaderFontStyle(int i) throws NotesException 371 void setHeaderFontUnderline(boolean flag) throws NotesException 371 void setHidden(boolean flag) throws NotesException 372 void setHideDetail(boolean flag) throws NotesException 371 void setListSep(int i) throws NotesException 371 void setNumberAttrib(int i) throws NotesException 371 void setNumberAttribParens(boolean flag) throws NotesException 371 void setNumberAttribPercent(boolean flag) throws NotesException 371 void setNumberAttribPunctuated(boolean flag) throws NotesException 371 void setNumberDigits(int i) throws NotesException 371 void setNumberFormat(int i) throws NotesException 371 void setPosition(int i) throws NotesException 371 void setResize(boolean flag) throws NotesException 371 void setResortAscending(boolean flag) throws NotesException 371 void setResortDescending(boolean flag) throws NotesException 371 void setResortToView(boolean flag) throws NotesException 371 void setResortToViewName(String s) throws NotesException 371 void setSecondaryResort(boolean flag) throws NotesException 371 void setSecondaryResortColumnIndex(int i) throws NotesException 371 void setSecondaryResortDescending(boolean flag) throws NotesException 371 void setShowTwistie(boolean flag) throws NotesException 371 void setSortDescending(boolean flag) throws NotesException 372 void setSorted(boolean flag) throws NotesException 372 void setTimeDateFmt(int i) throws NotesException 371 void setTimeFmt(int i) throws NotesException 371 void setTimeZoneFmt(int i) throws NotesException 371 void setTitle(String s) throws NotesException 371
795
796
20 Anhang
void setWidth(int i) throws NotesException 371 ViewEntry public interface ViewEntry extends Base 385 ViewEntry 97, 240, 244, 380, 385, 390, 565, 605 boolean isCategory() throws NotesException 385, 395 boolean isConflict() throws NotesException 385, 387, 395 boolean isDocument() throws NotesException 385, 395 boolean isTotal() throws NotesException 385, 395 boolean isValid() throws NotesException 281 Document getDocument() throws NotesException 387 int getChildCount() throws NotesException 385 int getColumnIndentLevel() throws NotesException 385 int getDescendantCount() throws NotesException 385 int getFTSearchScore() throws NotesException 408, 412, 605 int getIndentLevel() throws NotesException 385 int getSiblingCount() throws NotesException 385 Object getParent() throws NotesException 385 String getNoteID() throws NotesException 385 String getPosition(char c) throws NotesException 385 String getUniversalID() throws NotesException 385 Vector getColumnValues() throws NotesException 386, 397, 564, 565, 593 ViewEntryCollection public interface ViewEntryCollection extends Base 401 ViewEntryCollection 73, 97, 240, 387, 401, 402, 405, 413, 564 int getCount() throws NotesException 410 String getQuery() throws NotesException 410 View getParent() throws NotesException 410 ViewEntry getEntry(Object obj) throws NotesException 411 ViewEntry getFirstEntry() throws NotesException 387, 410, 413, 564 ViewEntry getLastEntry() throws NotesException 387, 410 ViewEntry getNextEntry() throws NotesException 387, 410, 413, 564 ViewEntry getNextEntry(ViewEntry viewentry) throws NotesException 410 ViewEntry getNthEntry(int i) throws NotesException 411 ViewEntry getPrevEntry() throws NotesException 387, 411 ViewEntry getPrevEntry(ViewEntry viewentry) throws NotesException 411 void addEntry(Object obj) throws NotesException 411 void addEntry(Object obj, boolean flag) throws NotesException 411 void deleteEntry(ViewEntry viewentry) throws NotesException 411 void FTSearch(String s) throws NotesException 410, 412, 592, 603 void FTSearch(String s, int i) throws NotesException 412, 603 void putAllInFolder(String s) throws NotesException 98, 412 void putAllInFolder(String s, boolean flag) throws NotesException 412 void removeAll(boolean flag) throws NotesException 281, 412, 414 void removeAllFromFolder(String s) throws NotesException 412 void stampAll(String s, Object obj) throws NotesException 402, 413 void updateAll() throws NotesException 413 ViewNavigator public interface ViewNavigator extends Base 389 ViewNavigator 389, 402 boolean gotoChild() throws NotesException 389 boolean gotoChild(ViewEntry viewentry) throws NotesException 390 boolean gotoEntry(Object obj) throws NotesException 390 boolean gotoFirst() throws NotesException 389 boolean gotoFirstDocument() throws NotesException 389 boolean gotoLast() throws NotesException 389 boolean gotoLastDocument() throws NotesException 389 boolean gotoNext() throws NotesException 389 boolean gotoNext(ViewEntry viewentry) throws NotesException 390 boolean gotoNextCategory() throws NotesException 389 boolean gotoNextDocument() throws NotesException 389 boolean gotoNextSibling() throws NotesException 389
20.5 Index boolean gotoNextSibling(ViewEntry viewentry) throws NotesException 390 boolean gotoParent() throws NotesException 389, 390 boolean gotoParent(ViewEntry viewentry) throws NotesException 390 boolean gotoPos(String s, char c) throws NotesException 389 boolean gotoPrev() throws NotesException 389 boolean gotoPrev(ViewEntry viewentry) throws NotesException 390 boolean gotoPrevCategory() throws NotesException 389 boolean gotoPrevDocument() throws NotesException 389 boolean gotoPrevSibling() throws NotesException 389, 390 boolean gotoPrevSibling(ViewEntry viewentry) throws NotesException 390 int getCacheSize() 391 int getCount() 391 int getMaxLevel() 391 View getParentView() 391 ViewEntry getChild() throws NotesException 389, 390 ViewEntry getChild(ViewEntry viewentry) throws NotesException 390 ViewEntry getCurrent() throws NotesException 389, 390 ViewEntry getFirst() throws NotesException 389, 390, 395 ViewEntry getFirstDocument() throws NotesException 390 ViewEntry getLast() throws NotesException 390 ViewEntry getLastDocument() throws NotesException 390 ViewEntry getNext() throws NotesException 389, 390, 395 ViewEntry getNext(ViewEntry viewentry) throws NotesException 390 ViewEntry getNextCategory() throws NotesException 390 ViewEntry getNextDocument() throws NotesException 390 ViewEntry getNextSibling() throws NotesException 389, 390 ViewEntry getNextSibling(ViewEntry viewentry) throws NotesException 390 ViewEntry getNth(int i) throws NotesException 389, 390 ViewEntry getParent() throws NotesException 389, 390 ViewEntry getParent(ViewEntry viewentry) throws NotesException 390 ViewEntry getPos(String s, char c) throws NotesException 390 ViewEntry getPrev() throws NotesException 390 ViewEntry getPrev(ViewEntry viewentry) throws NotesException 390 ViewEntry getPrevCategory() throws NotesException 390 ViewEntry getPrevDocument() throws NotesException 390 ViewEntry getPrevSibling() throws NotesException 389, 390 ViewEntry getPrevSibling(ViewEntry viewentry) throws NotesException 390 void setCacheSize(int i) throws NotesException 391 void setMaxLevel(int i) throws NotesException 391 Volltextindex 369, 524, 600, 601 Volltextindex aktualisieren 353 Volltextindex löschen 353 Volltextsuche 70, 318, 384, 590, 600, 601 Vorlage 343 Vorname 464
W WAN 18 WAS 209 Web Service 83 Web-Agent 157, 166 WEB-Implementierungsdeskriptor 214 Webanwendung 202 WebQueryOpen 147, 166, 491 WebQuerySave 147, 166 WebSphere 174, 203, 204, 207, 611, 616, 617, 625 WebSphere Apache Plugin 622 Webuser 168 While-Schleife 423 Winterzeit 469
797
798
20 Anhang
Word 353 Workflow 524 WYSIWYG 6
X X.400 464 X.509-Zertifikat 303 Xalan 662 XML 6, 54, 171, 256, 301, 316, 361, 452, 654 XML-Schema 655 xml4j.jar 663 XSL Stylesheet 301, 316, 361, 655 XSLTResultTarget class XSLTResultTarget 663 XSLTResultTarget 249, 316, 661, 663 org.w3c.dom.Node getNode() 663 OutputStream getByteStream() 662 String getEncoding() 663 String getFileName() 662 void setByteStream(OutputStream outputstream) 662 void setCharacterStream(Writer writer) 662 void setFileName(String s) 662 void setNode(org.w3c.dom.Node node) 663 Writer getCharacterStream() 662 XSLTResultTarget() 661 XSLTResultTarget(org.w3c.dom.Node node) 661 XSLTResultTarget(OutputStream outputstream) 661 XSLTResultTarget(String s) 661 XSLTResultTarget(Writer writer) 661 XSLTResultTarget 316
Y You are not authorized to perform that operation 280 You don't have any of the specified encryption keys 310 You must have permission to encrypt documents for server based agents 310 You must have permission to sign documents for server based agents 313 YST 471 Yukon Standard Time 471
Z ZE2 471 Zeile 443 Zeilenabstand 455 Zeilenwechsel 429 Zeit 57, 469, 729 Zeitberechnungen in Spalten 403 Zeitzone 471 Zelle 443 Zertifikat 197 Zertifizierer 312 Zertifizierungsstelle 197, 313 Zone East 471 Zone West 471 Zugriffsberechtigung 341 Zugriffskontrollliste 75, 155, 302, 356 Zugriffssteuerung 155 ZW1 471 ZW12 471
20.6 Abbildungsverzeichnis
20.6 Abb. 1-1 Abb. 2-1 Abb. 2-2 Abb. 2-3 Abb. 2-4 Abb. 2-5 Abb. 2-6 Abb. 2-7 Abb. 2-8 Listing 2-1 Abb. 2-9 Listing 2-2 Tabelle 2-1 Tabelle 2-2 Tabelle 2-3 Listing 2-3 Listing 2-4 Abb. 2-10 Listing 2-5 Abb. 2-11 Abb. 2-12 Abb. 2-13 Abb. 2-14 Listing 2-6 Abb. 3-1 Abb. 3-2 Tabelle 3-1 Abb. 3-3 Abb. 3-4 Abb. 3-5 Abb. 3-6 Abb. 3-7 Tabelle 3-2 Abb. 3-8 Abb. 3-9 Abb. 3-10 Abb. 3-11 Listing 3-1 Tabelle 4-1 Tabelle 4-2 Listing 4-1 Listing 4-2 Listing 4-3 Listing 4-4 Listing 4-5 Listing 4-6 Tabelle 4-3
Abbildungsverzeichnis Domino-Sicherheit Neue Datenbank erstellen Maskeneigenschaften Berechnetes Textfeld @Formel „@command ([ToolsRunMacro])“ startet einen Java-Agenten Fertiggestellte Maske und Hide-When-Formeln Neue Ansicht erstellen Spaltenwert ändern Basisgerüst eines Java-Agenten URL-Loader-Java-Agent – Sourcecode URL-Loader-Anwendung Form-Objekte und -Eigenschaften anzeigen Die Note-Typen Die Note-Klassen Wichtige Datentypen und Arten von Items und Feldern Items eines Dokuments anzeigen Date / Time Items in einem Dokument erzeugen Time-Range-Wähler-Feld Datenbanken öffnen Datenbanken öffnen Java-Debug-Konsole Replikationskonflikt Domino-Objektmodell (vereinfacht) Ansicht durchsuchen Domino Designer Feld-Events Feld-Events im Client und Browser Aktionsleiste im Designer Aktionsleiste im Client Im Browser geöffnete Maske Maske im Browser verbergen Maskeneigenschaften Ansichtenspalten berechnen Spalteneigenschaften Java-Agent im Domino Designer Agenteneigenschaften Suchkriterien für Agenten Agent verarbeitet unverarbeitete Dokumente Systemvariablen für lokale Domino-Verbindungen Systemvariablen für lokale Domino-Verbindungen auf einem Server Lokale Session mit statisch erzeugtem Thread Lokale Session durch Erweitern von NotesThread Lokale Session durch Implementation von Runnable Einfaches Applet mit AWT Applet mit Swing und Eventhandler (eigener Thread) Java-Agent und AgentContext Masken-Events, in denen @Formeln verwendet werden können
799
800 Listing 4-7 Abb. 4-1 Abb. 4-2 Abb. 4-3 Abb. 4-4 Abb. 4-5 Abb. 4-6 Abb. 4-7 Abb. 4-8 Listing 4-8 Abb. 4-9 Abb. 4-10 Tabelle 4-4 Tabelle 4-5 Tabelle 4-6 Listing 5-1 Abb. 5-1 Abb. 5-2 Abb. 5-3 Listing 5-2 Tabelle 5-1 Abb. 5-4 Listing 5-3 Listing 5-4 Abb. 5-5 Listing 5-5 Abb. 5-6 Tabelle 5-2 Abb. 5-7 Abb. 5-8 Abb. 5-8-1 Listing 5-6 Listing 5-7 Listing 5-8 Listing 5-9 Listing 5-10 Tabelle 6-1 Tabelle 7-1 Tabelle 7-2 Tabelle 7-3 Tabelle 7-4 Abb. 7-1 Abb. 7-2 Abb. 7-3 Abb. 7-4 Tabelle 7-5 Listing 7-1 Listing 7-1 Fortsetzung Abb. 7-5
20 Anhang Aufrufender Agent startet serverbasierten Agenten Domino-Java-Anwendung als Agent, NotesThread oder Runnable Notes Applet im Notes Client Notes Applet im Browser Java-Agent im Domino Designer Java importieren Java Console nach einem Aufruf des Starter-Agenten auf einem Server Items Java Console nach einem lokalen Aufruf des Starter-Agenten Aufgerufener serverbasierter Agent verändert Parameterdokument Sicherheit in der Agentenausführung Agenten-Sicherheit im Serverdokument (R6) in der Datenbank names.nsf Programmatische Ausführungseinschränkungen Delegationserlaubnis „sign on behalf of“ Vergleich zw. Java-Anwendung und Java-Agent Einfacher Domino-Web-Agent in Java Ausgabe der CGI-Variablen aus dem AgentContext Domino-Daten als XML-Feed über einfachen Web-Agenten Servlet Manager im Serverdokument Einfaches Domino-Servlet Web-Agent und Servlet im Vergleich DIIOP Einstellungen im Serverdokument Öffnen einer DIIOP Session getSession() liefert DIIOP Session bei Bedarf. Domino-Certificate-Administration DIIOP Session – Single Sign On – SSO und SSL WAS-Verwaltungskonsole aktivieren LDAP für WAS-Benutzer-Registry konfigurieren Zusammenhang zwischen Berechtigungsklassen und Constraints Sicherheitseinstellungen in WSAD Sicherheitseinstellungen in RAD Gesichertes Servlet mit SSO zwischen WAS und Domino Einfacher Connection Pool Connection Pool mit Funktionsuser für die Anmeldung Verwendung des Connection Pools ConnectionPool-Klasse für Connection Pooling mit verbesserter Synchronisation Übersicht über die Domino-Klassen Methoden zum Lesen von Item-Werten in Item und Document Methoden zum Schreiben von Item-Werten in Item und Document Verhalten der getItemValue-Methoden beim Lesen nicht erwarteter Datentypen (lokal Session) Verhalten der getItemValue-Methoden beim Lesen nicht erwarteter Datentypen (DIIOP-Session) Notes URL Konfliktdokument (Speicherkonflikt) Antwortdokument (Beispiel) Dateianhänge in Richtext und Dokument Methoden und Properties mit Zugriff auf EmbeddedObject Demonstration verschiedener Attachment-Operationen Demonstration verschiedener Attachment-Operationen Eigenschaften eines OLE EmbeddedObject im Java-Agenten
20.6 Abbildungsverzeichnis Abb. 7-6 Abb. 7-7 Abb. 7-8 Listing 7-2 Listing 7-3 Abb. 7-9 Listing 7-4 Abb. 7-10 Listing 7-5 Abb. 7-11 Tabelle 9-1 Tabelle 9-2 Abb. 9-1 Abb. 9-2 Tabelle 9-3 Tabelle 9-4 Abb. 9-3 Abb. 10-1 Abb. 10-2 Listing 10-1 Abb. 10-3 Tabelle 10-1 Listing 10-2 Abb. 10-4 Listing 10-3 Listing 10-4 Listing 10-5 Tabelle 11-1 Tabelle 11-2 Listing 11-1 Listing 11-2 Abb. 11-1 Abb. 11-2 Listing 13-1 Listing 13-2 Listing 13-3 Listing 13-4 Listing 13-5 Abb. 13-1 Abb. 13-2 Listing 13-6 Listing 13-7 Listing 13-8 Abb. 13-3 Abb. 13-4 Listing 13-9 Listing 13-10 Tabelle 13-1 Listing 13-11 Listing 13-12
Eigenschaften eines OLE EmbeddedObject im LotusScript-Agenten Verschlüsselungsschlüssel Neuen Verschlüsselungsschlüssel erstellen Beispiel für Feldverschlüsselung Fehlerhafte Feldverschlüsselung Verschlüsselte und unverschlüsselte Items Verschlüsselte und signierte Mail versenden Dokument als XML Beispiel für Document Locking Document Locking in verteilter Umgebung openDatabaseByReplicaID und getDatabase getFileName und getFilePath Datenbankeigenschaften ACL Zugriffslevel in der ACL Optionale Berechtigungen in der ACL Eigenschaft „Default design for new folders and views“ Kategorisierte Ansicht $Readers-Feld im Design-Dokument für View Verhalten verschiedener ViewNavigator-Objekte Kategorisierte Ansicht im Domino Designer DocumentCollection und ViewEntryCollection im Vergleich Verarbeitung von Dokumenten in Schleifen Klassendiagramm DJLoopCommand und DJLoopExecutor Gekapselte Schleife Erweiterung von DJLoopCommand DJLoopExecutor Verschiedene Elemententypen in RichTextItem RichText Framework – Unterstützende Klassen Lesen und Schreiben von Tabellen im RichTextItem Import und Export von RichTextTable Dynamisch erzeugte Tabelle im RichText Dynamisch erzeugte Doclinks im RichText Einfaches Document mit erweiterter Funktionalität Einfache Verarbeitung des erweiterten Document Pseudoimplementierung von Document CMSDocument als Erweiterung der Pseudoimplementierung Komfortable und transparente Verarbeitung durch die Pseudoimplementierung Die Maske FO_RSSNews Der WebQueryOpen Agent und dessen Anzeige im Browser über die Maske FO_RSSNews Code des WebQueryOpen-Agenten AgentContext als Eigenimplementierung In Servlet umgewandelter Agent Ausgabe des RSSNews Servlets Lokaler Zugriff auf eine NSFDB2-Datenbank ist nicht möglich Das DJCacheDocument Die Verwendung des DJCacheDocument in einem Cache Performance Test – DJCacheDocument und Document im Vergleich Domino-Multithreaded-Stresstest – Aufruf Domino-Multithreaded-Stresstest – Test
801
802 Abb. 13-5 Abb. 13-6 Abb. 13-7 Abb. 13-8 Abb. 13-9 Abb. 13-10 Abb. 13-11 Abb. 13-12 Abb. 13-13 Abb. 13-14 Abb. 13-15 Abb. 13-16 Abb. 13-17 Abb. 13-18 Abb. 13-19 Abb. 13-20 Abb. 13-21 Abb. 13-22 Abb. 13-23 Listing 13-13 Listing 13-14 Abb. 13-24 Abb. 13-25 Abb. 13-26 Abb. 13-27 Abb. 13-28 Listing 13-15 Abb. 13-29 Listing 14-1 Abb. 14-1 Abb. 14-2 Listing 14-2 Listing 15-1 Abb. 15-1 Tabelle 15-1 Abb. 16-1 Abb. 16-2 Listing 17-1 Abb. 17-1 Listing 17-2 Abb. 17-2 Listing 17-3 Listing 17-4 Listing 17-5 Listing 17-6 Abb. 17-3 Listing 17-7 Listing 17-8 Listing 17-9 Listing 17-10 Listing 17-11
20 Anhang Mapping zwischen Domino und DB2-Benutzern DB2-Benutzer mit Rechten ausstatten Neuen Benutzer in DB2 anlegen DB2-Super-User – Anmeldung des Domino-Servers an DB2 Domino-Transaktionsprotokollierung DB2 Access Server registrieren DB2 Access Server: Eigenschaften Domino-Serverdokument – DB2-Eigenschaften Domino-DB2-Enablement-Prozess Test: DB2 Access Test: DB2 Access Server-Verbindung Formatwechsel zwischen NSF und NSFDB2 DB2 Access View – Felderdefinitionen DB2 Access View – Typendefinitionen DB2 Access View – Listenwerte bei Zahlen DB2 Access View – Dokumentenzuordnung DB2 Access View in DB2 aktualisieren SQL Plugin Java ClassPath für DB2-JDBC-Anwendungen DB2 Access View per JDBC und SQL anzeigen Klasse Column Zahl Liste –per SQL Insert erzeugt Query View erstellen Query View im Domino Designer Voraussetzungen für die Verwendung von Query Views DB2 und Domino – Ansicht des Query View in Domino und DB2 Query View per Java auslesen Ergebnisse eines Query View aus reinen SQL-Daten und aus einem DAV Recycling von Objekten mit recyclingpflichtigen internen Domino-Objekten Java-Speicherverbrauch, bei sofortigem Recycling Java-Speicherverbrauch bei nachgelagertem Recycling GC Klasse – Recycling vereinfacht Suchkonzepte im Einsatz – ToyStoreAdvanced Suche mit URL-Parametern Performancevergleich der verschiedenen Suchmethoden in Domino Integrationsszenarien mit Domino als primärem Server Integrationsszenarien mit WAS oder Apache als primärem Server Einfache aber unschöne Domino-JSP-Einbindung Lotus Domino Toolkit for WebSphere Hierarchie der Domtags Serverseitige Validierung mit domino:validaterequired domino:session domino:db domino:view Beispielanwendung domino:view Ausgabe des domino:view-Beispiels Beispielanwendung domino:document domino:document Beispielanwendung domino:form domino:form domino:ftsearch
20.6 Abbildungsverzeichnis
803
domino:runagent Domutil – Beispiele Übersicht Domutil – Attribute und Pflichtfelder Modell View Controller und Domino DXL Werkzeuge Domino View als XML Dokument in XML umwandeln Design in XML umwandeln Einfache Domino-Infrastruktur Domino-Infrastruktur mit Replikation, Redaktions- und Display Server Notes Client Domino-Administrator und Taskliste Filesystem-Berechtigung für HTTP-Zugriff Typische Anwendungsfälle für Domino-Java-Entwicklung Einfache Entwicklungsumgebung auf alleinstehendem Arbeitsplatz Erweiterte Entwicklungsumgebung mit mehreren Arbeitsplätzen Erweiterte Entwicklungsumgebung mit mehreren Arbeitsplätzen und Entwicklungsser vern Newsletter-Maske Abb. 19-1 Newsletter-Ansicht Abb. 19-2 Klasse NewsletterAgent Listing 19-1 Klasse NewsletterStart – AgentContext künstlich injizieren Listing 19-2 Klasse NewsletterStart – Erzeugen eines künstlichen AgentContext Listing 19-3 Klasse Newsletter – Businessobjekt zum Versand der Newsletter Listing 19-4 Hilfsklasse NotesTestObjects mit den am häufigsten benötigten Objekten (Beispiel get Listing 19-5 Session) JUnit Test für Domino-Objekte Listing 19-6 SetUp und tearDown zur Bereitstellung der zentralen Objekte Listing 19-7 Serverkontexte beherrschen Abb. 19-3 Serverkonsole Abb. 19-4 Eclipse IDE mit laufendem Debugger Abb. 19-5 Agent Log Abb. 19-6 NotesLog-Anwendung Listing 19-8 NotesLog-Datenbank Abb. 19-7 NotesLogWriter Listing 19-9 Tabelle Anhang-1 Übersicht Domtags – Attribute und Pflichtfelder Listing 17-12 Listing 17-13 Tabelle 17-1 Abb. 17-4 Abb. 17-5 Abb. 17-6 Listing 17-14 Listing 17-15 Abb. 18-1 Abb. 18-2 Abb. 18-3 Abb. 18-4 Abb. 18-5 Abb. 18-6 Abb. 18-7 Abb. 18-8 Abb. 18-9