Showing posts with label Notes. Show all posts
Showing posts with label Notes. Show all posts

Thursday, October 25, 2012

Start 64bit vbscript von Lotus Notes

Wir verwenden unter Lotus Notes eine Inventorydatenbank, die auf jeden Client von Notes aus ein vbscript ausführt, dass diverse Infos aus der Registry ausliest und dann in Notes auf dem Client importiert. Das hat solange wunderbar funktioniert, wie wir nur 32 bit Clients gehabt haben. Auf 64 bit Windows hatte das skript, wenn es von Lotus aufgerufen wurde nur Zugriff auf die 32bit Zweige der Registry, es konnte aber keine Einträge aus den 64 bit Bereichen der Registry aufrufen.

Schuld daran ist, dass Prozesse die von einer 32 bit Applikation aus gestartet werden standardmäßig auf das windir\SysWow64 Verzeichnis umgeleitet werden, wenn Sie eigentlich auf windir\system32 zugreifen wollen. Dadurch starten Sie den vbscript Host als 32 bit Applikation und nicht als 64 bit.

Es gibt aber eine einfache Lösung: Zumindest unter Windows 7. Windows stellt einen Alias bereit der sich "sysnative" nennt und auch unter 32 bit Prozessen Zugriff auf das 64 bit system32 Verzeichnis erlaubt. Man kann also ein 64 bit vbscript in einem Javaagenten einfach auf folgende Art aufrufen:

// Aufruf mit dem sysnative bewirkt, dass Script in
// 64 bit ausgeführt wird auch wenn der Hostprozess ein
// 32bitiger ist.
Process p = Runtime.getRuntime().exec(System.getenv("WINDIR") + "\\sysnative\\cscript.exe\script.vbs")
p.waitFor();
 
Genauere Infos erhält man auf MSDN

Monday, October 22, 2012

Internationale Sonderzeichen in Lotus Notes plugins

Wenn man in seinem Javacode internationale Sonderzeichen wie z.B. Umlaute in String Literalen verwendet, kann es passieren dass nach dem Export des Plugins die Sonderzeichen falsch angezeigt werden. Dies passiert dadurch, dass beim Kompilieren von Plugins standardmäßig nicht UTF-8 sondern ein nicht Unicode fähiger Characterset verwendet wird. Man kann das Problem einfach lösen, in dem man in die build.properties seines Plugins den Eintrag "javacDefaultEncoding.. = UTF-8" ergänzt.

Eine Beispiel "build.properties" Datei sieht dann folgendermaßen aus:

source.. = src/
output.. = bin/
javacDefaultEncoding.. = UTF-8
bin.includes = META-INF/,\
               .,\
               plugin.xml,\
               icons/


Dann sollten String Literale in diesem Plugin richtig angezeigt werden.

Friday, October 19, 2012

Selektives Auto Deployment von Widgets

Das Verpacken von Eclipse Features und Plugins in Widget ist wirklich eine praktische Sache und über den Widgetkatalog kann man diese Widgets auch gut anderen Benutzern zur Verfügung stellen. Weniger elegant sind aber die Möglichkeiten mit denen man die Installation von Widgets automatisieren kann. Standardmäßig kann man einem Widget mehrere Kategorien zuweisen. Man kann dann bei einem Benutzer eine oder mehrere dieser Kategorien in den Benutzervorgaben einstellen (oder per Desktop Policy setzen) und es werden alle Widgets dieser Kategorien bei dem Benutzer installiert und im Bedarfsfall aktualisiert.

So weit so gut, aber bei intensiverer Verwendung dieser Technik zeigen sich doch ein paar Schwachstellen:

  • Man kann zwar über Desktoppolicy festlegen welche Kategorien von Widgets bei einem Benutzer installiert werden sollen, ich kann aber nicht verhindern, dass sich ein Benutzer andere Widgetkategorien selber nachinstalliert. Das ist natürlich problematisch, wenn man Widgets hat die nicht jeder Benutzer installieren können soll.
  • Wenn man mehr als eine handvoll Widgets und Gruppen von Personen die unterschiedliche Arten von Widgets benötigen hat dann braucht man sehr viele verschiedene Desktoppolicy Dokumente und das ganze wird schnell unübersichtlich.
  • Die zur Verfügungstellung von neueren Versionen von Widgets zum Testen ist schwer oder gar nicht möglich.

Da ich in meiner Umgebung mittlerweile von allen oben angeführten Problemen betroffen bin, habe ich eine Möglichkeit zur Lösung der Probleme gesucht die ich kurz erklären möchte.

Ich habe das Design des Widgetkatalogs dahingehend erweitert, dass ich ein Mehrfachwert Leserfeld zu der Widgetmaske hinzugefügt habe. Weiters habe ich das bereits bestehende berechnete Leser Feld in der Maske von "" auf "LocalDomainServers" geändert, damit es keine Probleme bei der Replizierung von Widgets zwischen den Servern gibt.


Nun habe ich die Kategorie aller Widgets auf eine Kategorie gesetzt die bei allen Benutzern installiert wird und kann aber über das Leserfeld ganz exakt steuern wer dieses Widget bekommen soll oder nicht. Natürlich lassen sich in dem Leserfeld auch wieder Gruppen eintragen.

2 Nachteile dieser Lösung:

  • Designänderungen von Systemdatenbanken bergen immer wieder das Risiko, dass es bei Updates zu Problemen kommt. 
  • Wenn man einen Benutzer aus dem Leserfeld entfernt, wird zwar das Widgetdokument aus seinem lokalen Widgetkatalog entfernt aber leider der Provisioningprozess nicht angestartet. Das heißt das Widget wird nicht sofort entfernt, sondern erst bei der nächsten Änderung eines Widgets das auf den Rechner des Benutzers repliziert wird. Ich löse das Problem in dem ich ein Dummywidget habe, dass ich bei einer Entfernung eines Benutzers neu speichere. Dann erkennt der Notesclient die Änderung und entfernt auch das verschwundene Widget aus dem Client.

Sunday, August 26, 2012

Zeilennummern in Stacktraces im Notesclient anzeigen

Meiner Meinung nach hat es die IBM bei der JVM Optimierung für Notes etwas übertrieben. Denn die IBM hat in den JVM.Properties eine Einstellung gesetzt, die die Anzeige von Zeilennummern in Stacktraces verhindert.Die Zeilennummern in den Stacktraces sind aber eine der wichtigsten Hilfen um Fehlern auf die Spur zu kommen. z.B. sagt ein typischer Stacktrace wie in Notes anzeigt augrund der fehlenden Nummern nicht wirklich viel aus.

java.io.IOException: Cannot run program ""C:\Program": CreateProcess error=2, Das System kann die angegebene Datei nicht finden. 
at java.lang.ProcessBuilder.start(Unknown Source) 
at java.lang.Runtime.exec(Unknown Source) 
at java.lang.Runtime.exec(Unknown Source) 
at java.lang.Runtime.exec(Unknown Source) 
at mycopmany.eclipse.teamviewercontroller.TeamViewerControllerView$5.run(Unknown Source) 
at mycopmany.eclipse.teamviewercontroller.TeamViewerControllerView$2.widgetDefaultSelected(Unknown Source) 
at mycopmany.ui.widgets.SimpleTable.onDefaultSelection(Unknown Source) 
at mycopmany.ui.widgets.SimpleTable$5.handleEvent(Unknown Source)

Ich weiß jetzt zwar, dass irgendwo in meiner run Methode der TeamViewerControllerView ein Fehler aufgetreten ist, aber ich habe keine Anhnung wo. Bei einer längeren Methode die oft native Programme aufruft ein Ding der Unmöglichkeit den Fehler zu finden.

Um diese ungünstige Optimierung der JVM für Notes zu deaktivieren sind zwei Schritte notwendig.

Schritt 1: In der Datei JVM.Properties in "NotesProgDir\framework\rcp\deploy den Eintrag "vmarg.Xnolinenumbers=-Xnolinenumbers" mit einem "#" auskommentieren. Dies verhindert, dass die Zeilennummern bei neuen Klassen die die JVM ausführt entfernt werden. Für Klassen die sich bereits im sogennanten Shared Class Cache der JVM befinden, hilft dieser erste Schritt nicht und man muss den Cache wie in Schritt 2 beschrieben löschen.

Schritt 2: Notes verwendet einen eigenen Shared Class Cache der im Verzeichnis "NotesDataDir\workspace\.config\org.eclipse.osgi" gespeichert wird. Um den Cache zu löschen muss man auf der Commandline in das Verzeichnis der Notes jvm "NotesProgDir\jvm\bin" wechseln und dort den Befehl

java -Xshareclasses:name=xpdplat_.jvm,controlDir="NotesDataDir\workspace\.config\org.eclipse.osgi",destroy

eigeben. NotesDataDir muss natürlich durch den richtigen Pfad ersetzt werden.

Danach bekommt man in Notes vernünftige Stacktraces

java.io.IOException: Cannot run program ""C:\Program": CreateProcess error=2, Das System kann die angegebene Datei nicht finden.
at java.lang.ProcessBuilder.start(ProcessBuilder.java:471)
at java.lang.Runtime.exec(Runtime.java:604) at java.lang.Runtime.exec(Runtime.java:442)
at java.lang.Runtime.exec(Runtime.java:339)
at mycompany.eclipse.teamviewercontroller.TeamViewerControllerView$5.run(TeamViewerControllerView.java:178)
at mycompany.eclipse.teamviewercontroller.TeamViewerControllerView$2.widgetDefaultSelected(TeamViewerControllerView.java:113) at mycompany.ui.widgets.SimpleTable.onDefaultSelection(SimpleTable.java:287)
at mycompany.ui.widgets.SimpleTable$5.handleEvent(SimpleTable.java:142)

und kann den Fehler in Zeile 178 beheben.

Thursday, August 23, 2012

Eclipse Befehlszeilenparameter an notes.exe übergeben.

Eclipse als Basis für Lotus Notes ab der Version 8 bietet jede Menge praktischer Befehlszeilenparameter. Normalerweise kann man diese bei einer RCP Anwendung einfach hinter die ausführbare Datei anhängen.

z.B. "eclipse.exe -console" um die OSGI Console mit eclipse mitzustarten.

Bei Lotus Notes geht das nicht so einfach, da Notes eigene Befehlszeilenparameter wie z.B. den "-sa" Parameter verwendet. Es gibt aber Abhilfe, in dem man den  Parameter "-RPARAMS" + den eclipse RCP Befehlszeilenparameter voranstellt.

z.B. um die OSGI Console mit Notes mitzustarten kann man "notes.exe -RPARAMS -console" aufrufen.

Ein anderer praktischer Befehlszeilenparamter ist "-clean" Es weist Eclipse an nicht den Extension Registry Cache zu verwenden, sondern die Plugins neu einzulesen. Dies muss man z.B. machen, wenn man neue Plugins in die Verzeichnisstruktur von Lotus Notes einkopiert hat und diese beim start von Notes verwendet werden sollen.


Eine Übersicht über alle Befehlszeilenparameter die Eclipse und damit auch Lotus Notes unterstützt findet man in der Eclipse Doku.

Wednesday, August 22, 2012

Automatisches Upgrade der ODS am Client

In jeder grösseren Notes/Domino Version wird die sogenannte ODS (On Disk Structure) verändert. Die ODS Version ist das Dateiformat in dem Notes/Domino die Daten der betreffenden Replik lokal speichert. Die ODS Version wird nicht repliziert. Das heißt eine Replik am Server kann eine andere ODS Version als der Client haben. Bei einem Upgrade auf eine neue Version wird die ODS Version am Server meistens mit einem copy style compact auf die aktuelle Version gehoben. Diese Änderungen wird aber dann nicht auf lokale Repliken übertragen.

Um auch die lokalen Repliken auf den neuesten Stand zu bringen, gibt es aber seit 8.5.2 den Notes.ini Parameter NSF_UpdateODS=1. Wenn dieser Parameter gesetzt ist, versucht Notes alle Datenbanken auf die neueste ODS Version zu konvertieren. Ein Teil der Datenbanken wird als Hintergrundtask konvertiert. Der Rest wird beim nächsten Starten des Client abgearbeitet. Ab Version 8 aufwärts muß man zusätzlich noch die Verwendung  der neuen ODS mit der Notes.ini Variable Create_R85_Databases=1 (oder für ODS 4.8 Create_R8_Databases=1) erlauben.

Der Parameter  NSF_UpdateODS=1 kann auch über eine Desktop Policy (Mail Tab->Alle lokalen NSF-Datenbanken auf die neueste ODS-Version aktualisieren) gesetzt werden.

 Für Details zu dieser neuen Funktion gibt es auch eine Technote der IBM
 

Tuesday, August 21, 2012

Ersatz für @Explode in Java

In Notesanwendungen hat man oft die Aufgabe Text, der durch Trennzeichnen getrennt ist in eine Liste sprich Array zu verwandeln. Dazu verwendet man die Funktion @Explode.

z.B. @Explode("a,b,c") gibt eine Liste mit den Elementen a,b und c zurück.

Etwas ganz ähnliches kann man in Java mit der split Methode der String Klasse machen. Die gleiche Funktion wie oben kann in Java so codiert werden:

 z. B. "a,b,c".split(",") gibt ein Array mit den Elementen "a", "b", "c" zurück.

Doch da das Trennzeichen nicht einfach nur ein Zeichen sondern eine Regular Expression sein kann ist die Funktion noch viel mächtiger. Stellen wir uns z.B. vor wir haben einen String der verschiedene Trennzeichen haben kann.

z.B. "a;b:c,d".split("[,:;]") gibt ein Array mit den Elementen "a", "b", "c", "d" zurück.

Genauso kann man mit einer anderen Regularexpression vor und nach dem Trennzeichen Whitespace entfernen.

z.B.  "a, b, c".split(",") ergibt ein Array mit den Elementen "a"," b"," c" was nicht gewünscht ist aber "a, b, c".split(""\\s*,\\s*") ergibt das korrekte Array mit den Elementen "a", "b","c".

Aufpassen beim split muss man nur, dass standardmäßig leere Elemente am Anfang nicht in das Ergebnisarray aufgenommen werden.

z.B. ",a,b".split(",") ergibt das Array "a","b" und nicht "","a", "b". Um dies zu vermeiden kann man als zweiten Paramter ein -1 angeben. ",a,b".split(",",-1) mit dem man das erwartete Ergebnis erhält.

Tuesday, May 22, 2012

Workspace Pfad mit Einstellungen für Plugins in Notes/Eclipse

Jedes Plugin in Notes/Eclipse kann Einstellungen haben, die bestimmte Funktionalitäten des Plugins steuern.

Diese Einstellungen werden in dem Pfad "workspace\.metadata\.plugins\org.eclipse.core.runtime\.settings" abgelegt. Für jedes Plugin, dass Einstellungen (Preferences) verwendet, wird ein eigenes Prefs File erstellt. Diese PrefsFiles können mit einem Texteditor problemlos editiert werden und sind als Schlüsselwort Wert Paare aufgebaut. Normalerweise ist es so, dass wenn man die Prefsdatei löscht, wird das File beim nächsten Laden des Plugins wieder mit Defaulteinstellungen erstellt. Das kann auch helfen, wenn in Lotus Notes irgendwas nicht so funktioniert wie es soll. Statt des Löschen des gesamten workspace Ordner kann man zielgerichtet einzelne Einstellung rauslöschen.

Sunday, May 20, 2012

Adresse mit Hilfe der Formelsprache auf Google Maps Karte zeigen.

Ich bin ja nicht gerade ein Fan der Formelsprache von Lotus Notes, aber manche Dinge lassen sich damit sehr elegant und einfach lösen. z.B. wenn man die Anforderung hat eine Adresse mittels Google maps nachzuschlagen, dann kann man folgende einfache Formel verwenden.:

@URLOpen("http://www.google.com/maps?f=q&hl=de&q="

+ @URLEncode("Platform" ; Strasse)

+ "+"
+ @URLEncode("Platform" ; Ort)+
"+"+
@URLEncode("Platform" ; Land))


Wen das aktuelle Dokument die Felder Strasse, Ort und Land (falls im Dokument nur das Länderkürzel steht muss hier noch mit einem kleinen DBLookup nachgeholfen werden) wird bei der Durchführung der Formel z.B. in einer Aktion im Defaultbrowser eine Googlemaps Karte mit einem Pin auf der gewünschten Adresse aufgemacht. Die Urlencode Formel ist wichtig, damit Sonderzeichen oder Umlaute richtig in die URL übersetzt werden. Natürlich kann man auf diese Art beliebige Urls zusammenbauen. Ich verwende das z.B. in einer Literaturdatenbank zum Anzeigen des Buches auf Amazon.


Thursday, May 3, 2012

Lösung für Probleme mit belegten Zeiten in der busytime.nsf

Ab und zu kann es in einer Dominoumgebung passieren, dass die belegten Zeiten einer Ressource oder Person nicht mehr stimmen. z.B. das ein Besprechungsraum zu einer bestimmten Zeit als belegt gekennzeichnet ist, obwohl gar keine Reservierung in der Ressource.nsf vorliegt. Ich habe zwar den Grund dafür noch nicht gefunden, dass Problem lässt sich aber mit folgenden Befehl auf der Serverkonsole beheben:

tell rnrmgr check Testroom/TestOrganisation

Testroom ist natürlich der hierachische Name des Raums oder der Person bei der es Probleme mit der belegten Zeit gibt.

In den Version <8.0 muss der Begriff rnrmgr durch "sched" ersetzen.

Für Details siehe die IBM Knowledgebase

Wednesday, April 25, 2012

Bilder von iPhone als Anhang im Lotus Notes Client

Seit einem Notes Update (8.5.3) hatten wir das Problem, dass Bilder die per mail von iPhones gesendet wurden nicht mehr als Dateianhang angezeigt wurden, sondern inline angezeigt wurden. Dies ist auch vom Notesclient soweit korrekt, da das iPhone die Bilder wirklich als inline schickt. Im Notes sind aber inline Bilder eher unpraktisch, da man diese nicht öffnen oder speichern kann, ausser über den Umweg der Zwischenablage.


Man kann das Anzeigen von Bildern als Inline Image in Notes aber einfach über die Einstellung


verhindern. Das bewirkt auch soweit ich bis jetzt gesehen habe keine anderen Seiteneffekte, da Bilder in HTML e-mails nach wie vor inline angezeigt werden. Der Haken im UI setzt die Notes.ini Variable ShowMIMEImagesAsAttachments=1. Man kann diese Variable natürlich auch über eine Desktop Policy auf allen Clients ausrollen.

Diese kleine Änderung kann die Userakzeptanz des Notes Client stark verbessern.

Tuesday, April 24, 2012

"Recycle" ein Versuch einer Erklärung 2. Teil

Im Teil 1 haben wir geklärt, warum man Notes Objekte überhaupt recyceln muß und wie man die Session am Ende eines Programms ordentlich beendet. Bei ganz kleinen Programmen würde das auch toll funktionieren, aber sobald Programme mehr Daten verarbeiten und seien Sie sich sicher, auch wenn die Datenmenge derzeit noch überschaubar ist, irgendwann wird sie so groß, dass die verfügbaren handles oder der Speicherplatz ausgehen.

Deshalb gilt folgender Merksatz: Jedes Notesobjekt soll so rasch als möglich wieder recycelt werden. Spätestens jedoch bevor es derefenziert wird.

Ein Beispiel:
if(flag) 
 Document doc=view.getFirstDocument(); 
 System.out.println(doc.getItemValueString("Test"); 
} 

Die Variable doc ist nur innerhalb des ifs gültig und muss deshalb noch innerhalb des if's recycelt werden.

if(flag){
  Document doc=view.getFirstDocument();
  System.out.println(doc.getItemValueString("Test");
  doc.recycle();
}

Es gibt beim Recycle jedoch eine Erleichterung, die einem einige Arbeit spart. Das recyceln von übergeordneten Objekten recycelt alle abgeleiteten Objekte mit.

Auf unser Beispiel übertragen:

View view=cdb.getView("Test");
if(flag){
  Document doc=view.getFirstDocument();
  System.out.println(doc.getItemValueString("Test");
  // doc.recycle(); kann man sich sparen, da das Dokument beim Recyclen der View mitrecycelt wird

}
else{
  Document doc=view.getLastDocument();
  System.out.println(doc.getItemValueString("Test");
   // doc.recycle(); kann man sich sparen, da das Dokument beim Recyclen der View mitrecycelt wird
}
view.recycle();

Man sollte dieses implizite Recycle aber nur dann verwenden, wenn man wie in unseren Beispiel gewährleisten kann, dass nur sehr wenige Objekte erzeugt werden und diese auch nicht mit der Datenmenge mehr werden.

Fatal wäre folgender Code:
View view=cdb.getView("Test");
Document doc=view.getFirstDocument();
while(doc!=null){
  //Mach was mit doc
  doc=view.getNextDocument();
}
view.recycle();

Dieser Code wird spätestens, bei ein paar Hundert Dokumenten in der View crashen.

Man muß also doc bei jedem Schleifendurchlauf recyceln. Das ist jedoch nicht ganz einfach, da doc ja nach getNextDocument nicht mehr da ist und vorher darf ich es nicht recyclen, da sonst getNextDocument() nicht mehr funktioniert, wenn das aktuelle Dokument recycelt wird. Folgendes Pattern hat sich bewährt:

View view=cdb.getView("Test");
Document doc=view.getFirstDocument();
while(doc!=null){
  //Mach was mit doc
  Document tempdoc=doc;
  doc=view.getNextDocument();
  tempdoc.recycle();
}
view.recycle();

 Aufpassen muß man natürlich auch darauf, dass man nicht Objekte implizit bereinigt die man noch gerne verwenden möchte:

View view=cdb.getView("Test");
Document doc=view.getFirstDocument();
view.recycle(); //Hier wird doc implizit mitrecycelt. Man darf doc nicht mehr verwenden.
System.out.println(doc.getItemValueString("form")); //Hier wird das Programm crashen.

Ich hoffe etwas Licht in die Sache gebracht zu haben. Würde mich auch über Kommentare zu spezifischen Problemen freuen, die ich dann im 3. Teil behandeln werde.

Thursday, April 19, 2012

Stacküberlauf beim Debuggen von Notesplugins

Wenn man Lotus Notes aus Eclipse zum Debuggen von selbsterstellten Plugins aufruft, wird das UI von Lotus Notes nicht richtig angezeigt und man bekommt einen sehr langen Stacktrace der mit

at java.util.regex.Pattern$Curly.match1(Pattern.java:3808) 
at java.util.regex.Pattern$Curly.match(Pattern.java:3757)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4179)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4179)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3800)
at java.util.regex.Pattern$Curly.match(Pattern.java:3755)
at java.util.regex.Pattern$Begin.match(Pattern.java:3131)
at java.util.regex.Matcher.search(Matcher.java:1116)
at java.util.regex.Matcher.find(Matcher.java:546)
at com.ibm.css.parser.CSSParserImpl.parse(CSSParserImpl.java:106)
at com.ibm.css.parser.CSSParserImpl.parse(CSSParserImpl.java:59)
at com.ibm.rcp.ui.css.StyleManager.handleRules(StyleManager.java:269)
at com.ibm.rcp.ui.css.StyleManager.handleRules(StyleManager.java:275)
at com.ibm.rcp.ui.css.StyleManager.parse(StyleManager.java:218)
at com.ibm.rcp.ui.internal.themes.ThemeManager.setCurrentTheme(ThemeManager.java:303)
at com.ibm.rcp.ui.internal.themes.ThemeManager.<init>(ThemeManager.java:187)
at com.ibm.rcp.ui.internal.themes.ThemeManager.getInstance(ThemeManager.java:216)
at com.ibm.rcp.ui.internal.themes.ThemeServiceFactory.getService(ThemeServiceFactory.java:30)
at org.eclipse.osgi.framework.internal.core.ServiceUse$1.run(ServiceUse.java:117)
at java.security.AccessController.doPrivileged(AccessController.java:202)
at org.eclipse.osgi.framework.internal.core.ServiceUse.getService(ServiceUse.java:115)

 .. 51 more

endet.


Der Grund dafür ist, dass im DebugMode der Speicher auf dem Stack ausgeht. Dieses Problem kann man einfach umgehen in dem man in der Launch Configuration von Lotus Notes in Eclipse den JVM Parameter Xss1m setzt.

Folgende Schritte sind für die Änderung durchzuführen:

Den Debug Configurations Dialog über das Menü aufrufen


In der Launchconfiguration von Lotus Notes auf dem zweiten Tab "Arguments" den zusätzlichen Paramter Xss1m setzen.

Dann auf Apply drücken und die Debug Configuration starten. Dann sollte Notes ohne UI Probleme auch im Debug Mode laufen.

Monday, April 16, 2012

Den Öffnen Knopf im Lotus Notes Client erweitern

Die Dokumentation zu Expeditor ist ja eher unübersichtlich, daher hier eine kleine Anleitung wie man ein bestehendes Plugin erweitern muß, damit man im Launcher (Öffnen Knopf) eigene Einträge platzieren kann.

Die Voraussetzungen für diese Beschreibung ist, dass in Eclipse bereits die Targetplattform für Notes eingerichtet ist und sich Notes aus Eclipse heraus starten lässt. Entweder mittels Expeditor Toolkit oder der Beschreibung von Mikkel Heisterberg. 

Dann legen wir mit dem entsprechenden Assistenen in Eclipse ein neues Pluginprojekt an.

In diesem neuen Plugin müssen wir folgende zusätzliche Abhängigkeiten hinzufügen:
 
Diese sind notwendig, damit man das UI von Lotus Notes erweitern kann.

Danach kann man in seinem Plugin den Erweiterungspunkt "com.ibm.rcp.ui.launcherSet"erweitern.



Dann sollte man einen LaunchItemType anlegen. Ein LaunchItemType kann für mehrere LaunchItems zuständig sein. Die Klasse MyLaunchHandler müssen wir später noch selbst anlegen.


Jetzt können wir die von uns gewünschten LaunchItems anlegen. Zuerst "Test1"

Natürlich kann man wenn gewünscht auch noch Icons angeben.

und dann als Übung einfach noch ein zweites mit "Test2" anlegen.


Dann muss man die Klasse MyLaunchHandler erstellen. Normalerweise kann man das bequem über den Link "class*:" beim LaunchItemType machen. Aber die IBM hat obwohl Lotus Expeditor noch ziemlich neu ist schon ein ziemliches Chaos angerichtet und deshalb gibt es die Basisklasse LaunchItemContribution bereits in zwei verschiedenen Paketen. In der Extensionpointbeschreibung ist blöderweise die falsche angegeben. Aber kein Problem einfach über den Eclipse Assistenten eine neue Klasse erstellen.


Wichtig ist, dass der Name übereinstimmt mit dem Klassennamen den wir im Type angegeben haben. In Echt wird man das ganze natürlich in ein vernünftiges Paket packen. Weiters ist wichtig, dass man als Superclass die LauncherContributionItem Klasse aus com.ibm.rcp.ui.launcher und nicht die aus jfacex nimmt.

In die Neuerstellte Klasse muss dann nur noch eine Launchmethode eingefügt werden.

public void launch(int arg0) {
        super.launch(arg0);
        MessageBox msg = new           MessageBox(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell());
        if (getId().equals("org.startmenuitem2")) {
            msg.setMessage("Es wurde Test2 ausgewählt");
        }
        if (getId().equals("org.startmenuitem1")) {
            msg.setMessage("Es wurde Test1 ausgewählt");
        }
        msg.open();
    }


Diese wird aufgerufen jedes Mal wenn ein Launchitem des von uns erstellten Typs aufgerufen wird. Anhand von getId() kann man dann abfragen welches item konkret der Benutzer aufgerufen hat. Wir zeigen dann mal die entsprechende Messagebox an. In meinen Plugins wird meist ein Assistent aufgerufen, der von Benutzern einige Eingaben erfordert und dann z.B. eine neuen Reiter mit einem Eclipse UI im Notes öffnet.

Sunday, April 15, 2012

"Recycle" Ein Versuch einer Erkärung... Teil1

Die meisten Notesforen sind voll von Fragen, warum Javaagenten die in einer Testumgebung so gut funktioniert haben auf dem Echtserver plötzlich die Performance in den Keller ziehen, oder sogar den Agentmanager oder gleich den ganzen Server crashen. Praktisch in jedem dieser Fälle lässt sich das Problem auf ein Problem mit der Noteseigenheit des recycelns von Notesobjekten zurückführen. Also was ist diese geheimnisvolle recycle() Methode die es für jedes Objekt des Notes Java API gibt und warum ist es so wichtig Sie zu verwenden.

Um recycle zu verstehen, muss man wissen, wie das Notes Java API funktioniert.  In diesem Post gehe ich ausschließlich auf den sogenannten lokalen Zugriff ein. Die andere Art DIIOP (funktioniert meiner Meinung nach nicht stabil und vor allem nicht performant) vergesse ich mal. Lokaler Zugriff heißt dabei nicht, dass man nicht auf den Server zugreifen kann. Es heißt nur, dass auf der Maschine entweder ein Notes Client oder ein Server lokal installiert sein muss, damit man diese Methode verwenden kann.

Die Notes Java API besteht einerseits aus der "Notes.jar" die nur kleine Methodenhüllen enthält, die nichts anderes tun als die entsprechenden Funktion in der nativen C++ Bibliothek nlsxbe.dll aufzurufen. Wie der Name lsx schon andeutet, ist das der selbe Code der auch bei der Verwendung des Notes API in Lotus script verwendet wird. Nun ist es so, dass wenn man ein NotesObjekt instantiert wird im Hintergrund auch immer ein natives C++ Objekt erzeugt. Während das Javaobjekt nur minimale Systemressourcen wie Hauptspeicher und vor allem Zugriffshandels belegt, benötigt das native C++ Objekt wesentlich mehr Ressourcen des Betriebssysstems.

Ein kleines Beispiel:

NotesThread.sinitThread(); //Thread für Noteszugriff initialisieren.
try {
   Session ses = NotesFactory.createSession();
} catch (NotesException e) {
   e.printStackTrace();
}
NotesThread.stermThread(); //Thread für Noteszugriff beenden.



Und schon hat man ein klassisches Speicherloch produziert.


In der Zeile

Session ses=NotesFactory.createSession();

wird sowohl ein leichtgewichtiges Java Objekt als auch ein ziemlich schweres C++ Objekt im Hintergrund erzeugt. Jetzt könnte man einwenden sobald die Ausführung den try catch Block verlässt, gibt es keine Referenz mehr auf "ses" und der Garbage Collector wird sich um die Speicherbereinigung kümmern, wie man es von Standardjava Objekten gewohnt ist. Das macht der GC auch brav, aber er kann  nur das leichtgewichtige Javaobjekt freigeben. Das schwergewichtige C++ Objekt bleibt im Hintergrund bestehen und mangels einer Referenz auf dieses Objekt kann dieses auch nie mehr bereinigt werden. Jetzt könnte man einwenden, dass die IBM die Bereinigung des C++ Objekts in die finalize() Methode jeder Klasse implementieren könnte die sich um die Bereinigung des C++ Objekts kümmert. Ich habe das auch lange nicht verstanden, bis ich mich selber ein wenig mit den Internas der GC von Java beschäfigt habe. Ohne zu tief ins Detail zu gehen sollte man sich auf keinen Fall auf finalize verlassen. Laut JVM Spezifikation kann die JVM den Aufruf von finalize auf Später verschieben, bzw. ist es nicht mal Pflicht die finalizer bei Beendigung der JVM aufzurufen. Deshalb hat die IBM diese finalize Methode berechtigterweise nicht verwendet und  bei jeder Klasse des API die recycle Methode implementiert. Beim Aufruf dieser Methode bleibt das leichtgewichtige Javaobjekt erhalten und das native C++ Objekt wird bereinigt.( Das heißt der Speicher wird freigegeben und Zugriffshandels werden geschlossen.) Wichtig ist, dass man ein Notesjavaobjekt nach dem recycle nicht mehr verwenden darf.

Wir müssen also ein ses.recylce() in unseren Code einfügen. Die Frage ist nur Wo?

Falsch wäre es das recycle einfach im Try catch einzubauen. wie z.B.

NotesThread.sinitThread(); //Thread für Noteszugriff initialisieren.
try {

     Session ses = NotesFactory.createSession();
     //Irgendwas mit der Session machen.
     ses.recycle(); //Dies ist ein schwerer Fehler

} catch (NotesException e) {
      e.printStackTrace();

}
NotesThread.stermThread(); //Thread für Noteszugriff beenden.

Das ses.recycle() ist problematisch, da wenn im Codeteil "irgendwas" ein Fehler auftritt in das Catch verzweigt wird und dann das recycle nie ausgeführt wird. Für die verlässliche Ressourcenfreigabe gibt es beim try catch die die finally Klausel.

NotesThread.sinitThread(); //Thread für Noteszugriff initialisieren.
Session ses=null;

try {
     ses = NotesFactory.createSession();
     //Irgendwas mit der Session machen.

     ses.recycle(); //Dies ist ein schwerer Fehler

} catch (NotesException e) {
      e.printStackTrace();

}
} finally {
     if (ses != null) {
         try {
              ses.recycle();
         } catch (NotesException e) {
               e.printStackTrace();
         }
     }

}
NotesThread.stermThread(); //Thread für Noteszugriff beenden.

Nun wird das recycle auf jeden Fall ausgeführt. Egal was im "Irgendwas" Codeteil passiert oder Welche Fehler auftritt das finally wird auf jeden Fall ausgeführt und der Code wird ausgeführt. Ich würde sogar das NotesThread.stermThread() in den finally Bereich verschieben, damit falls eine andere Exception als NotesException innerhalb des try catches Blocks auftritt auch das Beenden der Notesumgebung zuverlässig durchgeführt wird. Dieses minimale Codepattern mit zuverlässigen recycle der Session beim Verlassen des Thread/Programms sollte jedes Standaloneprogramm oder RCP Plugin oder Servlet beinhalten. Alles andere führt über über kurz oder lang zu Instabilitäten. Die einzige Ausnahme zum recyclen von Session gilt für Agents. In Agents wird eine Session vom Agentmanager zur Verfügung gestellt und diese darf man nicht recyclen, da sich um das Recyclen der Agentmanager selber kümmert.

Hier ein Beispiel für einen Agent:

public class JavaAgent extends AgentBase {

    public void NotesMain() {
      Database otherDatabase=null;
      try {
          Session session = getSession();
          AgentContext agentContext = session.getAgentContext();
          Database currentDatbase=session.getCurrentDatabase();          //Diese Datenbank sollte man nicht recyclen.
          otherDatabase=session.getDatabase("", "test.nsf"); //Diese Datenbank sollte man recyclen.
         
       } catch(Exception e) {
          e.printStackTrace();
       }
      finally{
          if(otherDatabase!=null){
            try {
                otherDatabase.recycle();
            } catch (NotesException e) {
                e.printStackTrace();
            }
          }
      }
   }
}


Wir haben in diesem Post einmal gesehen, wie man zuverlässig die Session beim verlassen seines Codes bereinigt und auch ein wenig geschaut wie das ganze bei Agents funktioniert. Wenn man diese Ratschläge beherzigt ist man schon vor einigen Problemen gefeit, da das recyclen der Session sämtliche anderen erzeugte C++ Objekte die von der Session abgeleitet sind auch recycelt Im nächsten Teil meines Erklärungsversuch von recycle kümmern wir uns dann um Objekte die nicht so lange warten können bis die Session recycelt werden.


Performanceproblem bei getCount des ViewNavigators

In meinem letzten Post habe ich über die großen Performancegewinne bei Verwendung eines ViewNavigators bei iterieren über eine View geschrieben. (Link) Gerade bei RCP Anwendungen gibt es da jedoch einen kleinen Fallstrick.

Für die Anzeige des Fortschritts des Jobs benötigt man am Anfang die Gesamtanzahl der Dokumente die  man verarbeiten möchte. Da würde einen als erstes die Methode getCount() von ViewNavigator ins Auge springen. Diese ist aber sehr langsam. Wenn man wirklich alle Dokumente der View verarbeitet sollte man lieber die getEntryCount() Methode der View verwenden. Falls man diese nicht verwenden kann, sollte man eventuell darüber nachdenken lieber einen unendlich Fortschrittsbalken zu verwenden.

Peformance Trick beim Durchlesen von Views


Eines vorweg für alle die mit Java auf Kriegsfuß stehen. Der Beispielcode ist zwar in Java. Das selbe sollte jedoch auch in anderen Sprachen z.B. Lotusscript funktionieren.  Ich zeige dann am Ende des Posts den Code auch in Lotusscript versprochen

In vielen meiner Programme oder Agenten kommt Code vor, der alle Dokumente einer bestimmten View lesen und verarbeiten muss.  wie z.B.


...
Document doc = view.getFirstDocument();
while (doc != null) {
     Document tempdoc = doc;
    
//Mach irgendwas mit diesem doc

     doc = view.getNextDocument(doc);
     tempdoc.recycle();
}

...

Ausführungsdauer  dieses Code bei unserer CRM Datenbank ca. 43 Sekunden.

Dabei war ich bis jetzt immer sehr enttäuscht über die Performance die Notes hier bietet. Das lesen von größeren Datenmengen dauert einfach ewig. Was hauptsächlich daran liegt, dass jeder Lesevorgang in der View eine Transaktion auslöst. Viel effizienter wäre es wenn Notes gleich ganze Blöcke lesen würde. Bisher gab es dazu keine Möglichkeit im Domino API.  Bei der view gibt es das auch nach wie vor nicht. Aber im Rahmen der Performanceverbesserungen bei xPages wurden neue Features beim ViewNavigator eingeführt und die kann man sich hier zunutze machen.

Hier der selbe Code wie oben nur umgebaut auf die Verwendung eines ViewNavigators:
...
ViewNavigator navigator = view.createViewNav();
ViewEntry entry = navigator.getFirstDocument();
while (entry != null) {
   Document doc = entry.getDocument();
 
//Mach irgendwas mit diesem doc
   ViewEntry tempEntry = entry;
   entry = navigator.getNextDocument();
   tempEntry.recycle();
}

...
Das bringt natürlich noch keine Verbesserung des Laufzeitverhalten, sondern bringt sogar eine Verschlechterung auf 53 Sekunden.

Aber der ViewNavigator besitzt neue Methoden die ein geblocktes Lesen erlauben. Die Voraussetzung dafür ist einmal, dass man die View aus dem man den Navigator erstellt das Autoupdate abgewöhnt mit view.setAutoUpdate(false).

Dann kann man den ViewNavigator mit navigator.setBufferMaxEntries(x) mitteilen, dass man gerne x Sätze auf einmal lesen will. Dies verbessert die Performance enorm. (Update laut Domino Wiki darf der Wert x zwischen (2 und 400) sein. Ich habe festgestellt, dass höhere Werte als 400 eine leicht bessere performance bringen. Siehe auch Kommentag von Ulrich Krause)

Folgender Code von oben ergänzt mit Blocking läuft auf meiner Maschine in unter 10 Sekunden statt der 43 Sekunden im usprünglichen Code ohne Blocking. 

...
view.setAutoUpdate(false);
ViewNavigator navigator = view.createViewNav();
navigator.setBufferMaxEntries(1024);
ViewEntry entry = navigator.getFirstDocument();
while (entry != null) {
   Document doc = entry.getDocument();
 
//Mach irgendwas mit diesem doc
   ViewEntry tempEntry = entry;
   entry = navigator.getNextDocument();
   tempEntry.recycle();
}

...
Das ist ja schon mal nicht schlecht.

Es gibt aber noch 2 zusätzliche Optionen mit denen man unter bestimmten Umständen noch zusätzliche Performance erzielen kann.

Wenn man wie in den obigen Beispiel die viewEntrys Spalten nicht benötigt, dann kann man das laden der Spaltenwerte unterdrücken. Je nach dem wie viele Spalten die View enthält, habe ich in meinem Tests nocheinmal eine Laufzeitreduktion um 10% erreichen können. Diese Option kann man mit navigator.setEntryOptions(ViewNavigator.VN_ENTRYOPT_NOCOLUMNVALUES) setzen.

Bei manchen Arten von Views kann es auch etwas bringen, die Entryoption navigator.setEntryOptions(ViewNavigator.VN_ENTRYOPT_NOCOUNTDATA); zu setzen. Laut meinen Informationen verhindert es die automatische Zählung bei kategorisierten Ansichten. Ich habe es aber noch nicht praktisch verwendet.

Noch einen zusätzlichen Performancegewinn, kann man erzielen, wenn man nur bestimmte Dokumente aus der View verarbeiten will, in dem man in der View die Selektionsfelder als Spalten aufnimmt, und dann das Dokument nur dann liest, wenn im Viewentry die Spaltenwerte den Selektionskriterien entsprechen. Hier sind die Performancegewinne noch stärker ausgeprägt als bei der klassischen Methode. Außerdem kann man natürlich noch die ganzen anderen Vorteile eines ViewNavigators verwenden. z.B. einen ViewNavigator mit allen Dokumenten einer bestimmten Kategorie einer kategorisierten View zu erstellen.

Hier noch wie am Anfang versprochen das oben angeführte Beispiel in Lotus script:

...
view.Autoupdate=false
Set navigator=view.Createviewnav()
navigator.Buffermaxentries=1024
navigator.Entryoptions=1  ' keine Spaltenwerte lesen.
Set entry=navigator.Getfirst()
While Not entry Is Nothing
    Set doc=entry.Document
    'Mach was mit dem Dokument
    Set entry=navigator.Getnext(entry)
Wend

... 

Würde mich über Kommentare zu den Performanceverbesserungen die Ihr in euren Anwendungen erzielen konntet freuen.

ad