Monday, October 29, 2012

Aufruf einer 32bit JVM auf Windows 64 bit

Wenn in Windows 64 bit sowohl eine 32 bittige als auch eine 64 bittige JVM installiert sind, wird standardmäßig bei Aufruf mittels java oder javaw die 64 bittige JVM gestartet. Dies ist normalerweise kein Problem, da Javaprogramme in beiden funktionieren. Wenn man aber z.B. ein 32 bit SWT in seinem Programm verwenden möchte, dann braucht man unbedingt die 32 bit JVM. Leider funktioniert der Befehlzeilenparamter -d32 bei der Windows JVM nicht. Deshalb habe ich mir einen kleines vbs script, dass erkennt ob es in einer 64 Umgebung läuft und wenn ja sich nocheinmal als 32bit Prozess startet. Wenn es dann in einer 32 bit Umgebung läuft, kann es java aufrufen.


 
Set WShell = WScript.CreateObject("WScript.Shell")
' Falls Script in einer 64bit Umgebung ausgeführt wird, dann starte das Script nocheinmal als 32 bit script
If WShell.ExpandEnvironmentStrings("%PROCESSOR_ARCHITECTURE%")="AMD64" Then
WShell.Run WShell.ExpandEnvironmentStrings("%windir%\SysWOW64\wscript.exe SollistvergleichExportImport.vbs")
Else
WShell.run "javaw.exe  example.class"
End If

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

Wednesday, October 24, 2012

Probleme mit dem Zugriff auf den eigenen Host

Zur Eröffnung eines sockets für den Zugriff auf den Localhost habe ich bisher immer die Methode getLocalHost() der InetAddress Klasse verwendet.
          Socket socket=newSocket(InetAddress.getLocalHost(),5146);
          PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
          BufferedReader in = new BufferedReader(new inputStreamReader(socket.getInputStream()));
          out.println("Test");
          parameter=in.readLine();

Bisher hat das auch immer relativ gut funktioniert, bis der Code auf einem Client gelaufen ist der den Cisco VPN Client verwendet hat. Sobald ein Cryptotunnel aufgebaut wurde, wird die lokale Adresse des Clients auf eine Adresse aus dem VPN Pool umgestellt und bei Ausführung des oben genannten Codes kommt es zu keiner Verbindung mehr, sondern es kommt ein Timeout. Der Grund ist, dass getLocalHost() nicht etwa das loopback (127.0.0.1) Interface zurück gibt, sondern die IP Adresse der Netzwerkkarte. Diese wechselt nach VPN Einwahl und schon verbindet man sich mit der falschen Adresse. Eine einfache Möglichkeit das Problem zu lösen ist folgenden Code zu verwenden.
          Socket socket=new Socket(InetAddress.getByName("localhost"),5146);
          PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          out.println("Test");
          parameter=in.readLine();
Das bietet aber noch immer die Gefahr das in der Hostsdatei localhost nicht richtig definiert ist und es dann wieder zu Problemen kommt. Nach einiger Recherche in der Dokumentation der Klasse Intnetadress habe ich dann den richtigen Weg gefunden. Die Methode getByName() darf man mit einem null Wert aufrufen und dann gibt sie automatisch und immer das loopback interface zurück.
          Socket socket=new Socket(InetAddress.getByName(null),5146);
          PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          out.println("Test");
          parameter=in.readLine();

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.

Tuesday, August 28, 2012

String Prüfung mit Regularexpressions in Java

Eine oft mühsame und aufwendige Aufgabe ist die Prüfung von Strings auf Gültigkeit. Gott sei Dank bietet die String Klasse viele Methoden (z.B. contains(), startsWith() usw.) die eine solche Aufgabe vereinfachen. Die aber wohl Mächstigste ist die .matches(String regex) Methode. Diese Methode gibt den Wert "true" zurück wenn die übergebene RegularExpression mit dem String übereinstimmt.

Ein Beispiel wir wollen prüfen, ob ein String ausschließlich die Zahlen "0-9" enthält.

//Prüfe diverse String Literale ob Sie nur die Ziffern 0-9 enthalten.
System.out.println("145T".matches("[0-9]*")); //ergibt False
System.out.println("145".matches("[0-9]*"));//ergibt true

Wenn wir uns das Regex genauer anschauen gibt der Bereich in den eckigen Klammern den Bereich an, der erlaubt ist und der * sagt, dass eine beliebige Anzahl Zeichen aus der eckigen Klammer erlaut sind.


Um beispielsweise auf eine Kundennummer zu prüfen, die 5 stellig numerisch sein muss, kann man folgendermaßen vorgehen .

// Prüfen ob 5 stellig numerisch
System.out.println("1452434".matches("[0-9]{5}")); // ergibt False da zu lange
System.out.println("RALFP".matches("[0-9]{5}"));   // ergibt False da nicht numerisch
System.out.println("12345".matches("[0-9]{5}"));   // ergibt true

Natürlich kann man auch viel komplexere Gültigkeitsprüfungen mit .matches() und Regular Expression durchführen.

z.B. Prüfung auf eine gültige IP Adresse

// Prüfen eines String ob er eine gültige IP Adresse ist.
System.out.println("172.16.2.3"
    .matches("(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)")); // ergibt true, da es eine gültige IP Adresse ist														
System.out.println("256.256.256.256"
    .matches("(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)")); // ergibt false, da keine gültige IP Adresse.
Alles in allem sieht man, dass .matches und Regular Expressions wirklich eine sehr mächtige Kombination sind. Und das schöne an Regular Expressions ist, dass es sehr viele fix fertige Beispiele zur Prüfung auf diversen Beispielwebseiten gibt. Das heißt man muss sich nicht alles selber zusammenbauen, sondern kann auf erprobte Regular Expressions zurückgreifen.

zum Beispiel

http://www.regular-expressions.info/examples.html

http://www.mkyong.com/regular-expressions/10-java-regular-expression-examples-you-should-know/

http://regexlib.com/DisplayPatterns.aspx

und viele mehr lassen sich über google finden



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.
ad