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

Parameter an Hintergrundthread übergeben mit finalen Variablen

Ein oft übersehenes Feature in Java ist die Verwendung des Schlüsselwortes final bei der Definition einer Variablen. Prinzipiell weist die Verwendung von final den Java Compiler an, dass es sich bei der Variable um eine Konstante handelt, die nicht mehr geändert werden kann.

z.B.

 
final String test="Test";
test="test2";


Dieser Code erzeugt beim Compilieren einen Fehler, dass test nicht geändert werden darf.

Das ist sicher schon einmal ein Vorteil, da es Fehler vermeiden hilft, aber der ganz große Vorteil ist, dass wenn eine Variable final ist diese vom Compiler anders behandelt werden kann. Dies kann man sich beim Übergeben von Variablen an einen Hintergrundthread zu nutze machen.

Wir wollen eine Methode schreiben, die einen beliebigen Text beliebig oft an die Standardausgabe ausgibt und dies soll in einen Hintergrundthread passieren. Dies könnte man ungeführ wie unten codieren.

 
/**
* Druckt einen String mehrmals in einem Backgroundthread
* 
* @param string
* @param count
* @return
*/
public static void printHelloWorld(String string, int count) {
  
Thread thread = new Thread() {
    public void run() {
      for (int i = 0; i < count; i++) { //Compilerfehler, da count nicht sichtbar.
 System.out.println(string);  //Compilerfehler, da string nicht sichtbar.
      }
    }
  };
  thread.start();
}
Hier wird der Compiler aber sofort anmerken, dass die Variablen "string" und "count" innerhalb des Threads nicht sichtbar sind. Wenn man die Variablen aber final definiert, dann weiß der Compiler, dass die Variablen nicht mehr geändert werden können und kann diese auch im Hintergrundthread verwenden.
 
/**
* Druckt einen String mehrmals in einem Backgroundthread
* 
* @param string
* @param count
* @return
*/
public static void printHelloWorld(final String string, final int count) {
  
Thread thread = new Thread() {
    public void run() {
      for (int i = 0; i < count; i++) { //Jetzt ist count sichtbar, da eine finale Variable
 System.out.println(string); //Jetzt ist string sichtbar, da eine finale Variable
      }
    }
  };
  thread.start();
}

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.

ad