Performance-Tuning (2): Das Favicon

Das Favicon, daß sich normalerweiseim Root-Verzeichnis einer Webseite befindet, ist unter Lotus Notes für jede Datenbank individuell vergebbar, und aus Gründen der Performance sollte von dieser Möglichkeit auch unbedingt gebraucht gemacht werden: Das auf den Domino Servern hinterlegte Favicon ist nämlich ein wahrer Alptraum in diesem Zusammenhang.

Die allgemeine Empfehlung geht dahin, daß das Icon möglichst kleiner als 1KB* sein sollte, denn es wird vom Client in den unpassensten Momenten geladen. So z.B. wird es, wenn im Onload-Event des Internet Explorers zusätzliche Komponenten geladen werden sollen, vor den eigentlichen Komponenten geladen.

Die IBM hingegen sieht diesen Umstand wohl etwas gelassener, denn immerhin ist das Standardicon sage und schreibe 42166 Bytes (41 KB) groß, also mehr als das 40-fache der Empfehlung.

Hier ein Link auf das Icon direkt bei der IBM.

*: Das Favicon dieser Seite ist 1,4KB groß. Shame on me!

Veröffentlicht unter Allgemein, HTML, Performance, Server, Web, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

Partial Refresh: Get vs. Post (2)

In bestimmten Konstellationen kann es vorkommen, daß ein Aufruf von XSP.partialRefreshPost nicht ausgeführt wird; der gleiche Aufruf in der GET-Variante hingegen funktioniert einwandfrei.

Das kann an der Implementierung der beiden Funktionen liegen, denn hier gibt es einen kleinen aber feinen Unterschied zu beachten: Wird ein POST-basierter Partial Refresh außerhalb einer Form ausgeführt (z.B. indem man mittels <xp:form>-Tag mehrere Forms in einer XPage definiert hat), wird er nicht ausgeführt, da die JS-Funktion XSP.findForm dann den Wert null zurück liefert.

Die GET-Variante ist anders implementiert, wie man im folgenden sehen kann:

 this.partialRefreshGet = function x_prfs(refreshId, options) {
   // Find the action URL
   var form = document.forms[0];
   if(form==null || !this.canSubmit()) {
      return false;
   }
   options = options||new Object()
   this._partialRefresh("get",form,refreshId,options)
}

[Fett: Es wird überprüft, ob die erste bzw. eine Form im DOM-Baum existiert]

Hier wird einfach nur die Existens irgendeines Formulars abgefragt bzw. das erste Formular im DOM-Baum herangezogen (Eine andere Fehlerquelle, denn dies kann bei mehreren Formularen zu Fehlern führen, wenn man z.B. explizit ein anderes Formular verarbeiten möchte).

Im Vergleich dazu die POST-Variante:

this.partialRefreshPost = function x_prfs(refreshId, options) {
   var form = this.findForm(refreshId);
   if(form==null || !this.canSubmit()) {
      return false;
   }
   options = options||new Object()
   if(options.immediate) options.valmode=0; // For compatibility only
   if (this._doFireEvent(null, form, refreshId, null, true,
      options.valmode, options.execId)){
      this._partialRefresh("post",form,refreshId,options)
   } else {
      this.allowSubmit()
   }
}

[Fett: Die Form mit der angegebenen „RefreshId“ wird vorausgesetzt. Existiert sie nicht, wird der Vorgang abgebrochen.]

Bei der POST-basierten Variante wird das Formular gesucht, in daß das HTML-Element mit der übergebenen RefreshID eingebettet ist – liegt das Element jedoch nicht in einem Formular sonder ist direkt im BODY des DOM-Baumes eingebettet, liefert die Funktion null zurück, und der Partial Refresh wird nicht verarbeitet.

Der Workaround liegt adaher darin, das zu refreshende Element in ein Formular zu betten. Dann kann es auch via XSP.partialRefreshPost aktualisiert werden.

Veröffentlicht unter Allgemein, Dojo Toolkit, Java Script, Web, XPages, XSP | Verschlagwortet mit , , , , , , , , , | Schreib einen Kommentar

XSP Command Manager & die Serverkonsole

Ich bin über ein interessantes Kommando für die Domino Serverkonsole gestossen, mit der sich der XSP Command Manager manuell steuern läßt und so interessante Optionen bietet:

tell http xsp

Bisher konnte ich praktisch kaum näheren Informationen dazu finden, daher betrachte ich das Kommando als undokumentiertes Feature. Google liefert nicht wirklich etwas zu dem Thema, bei der IBM findet man kaum verwertbare Informationen und zur Administrator-Hilfe brauche ich wohl kaum etwas zu sagen… Falls jemand näheres weiß, bitte melden!

Das liefert die Konsole zurück, wenn man keine Optionen angibt:

tell http xsp [option]

---Extension Registry Commands---
ns [-v] [name] - display extension points in the namespace; add -v
   to display extensions
pt [-v] uniqueExtensionPointId - display the
   extension point and extensions; add -v to display config elements

---Eclipse Runtime commands---
diag - Displays unsatisfied constraints for the specified bundle(s).
enableBundle - enable the specified bundle(s)
disableBundle - disable the specified bundle(s)
disabledBundles - list disabled bundles in the system

---Controlling the OSGi framework---
launch - start the OSGi Framework
shutdown - shutdown the OSGi Framework
close - shutdown and exit
exit - exit immediately (System.exit)
init - uninstall all bundles
setprop <key>=<value> - set the OSGi property

---Controlling Bundles---
install - install and optionally start bundle from
          the given URL
uninstall - uninstall the specified bundle(s)
start - start the specified bundle(s)
stop - stop the specified bundle(s)
refresh - refresh the packages of the specified bundles
update - update the specified
   bundle(s)

---Displaying Status---
status [-s [<comma separated list of bundle states>]
   [<segment of bsn>]] - display installed bundles and
   registered services
ss [-s [<comma separated list of bundle states>]  [<segment of bsn>]]
   - display installed bundles (short status)
services [filter] - display registered service details
packages [<pkgname>|<id>|<location>] - display imported/exported
   package details
bundles [-s [<comma separated list of bundle states>]
   [<segment of bsn>]] - display details for all installed bundles
bundle (<id>|<location>) - display details for the specified bundle(s)
headers (<id>|<location>) - print bundle headers
log (<id>|<location>) - display log entries

---Extras---
exec <command> - execute a command in a separate process and wait
fork <command> - execute a command in a separate process
gc - perform a garbage collection
getprop  [ name ] - displays the system properties with the given
   name, or all of them.

---Controlling Start Level---
sl [<id>|<location>] - display the start level for the specified
   bundle, or for the framework if no bundle specified
setfwsl <start level> - set the framework start level
setbsl <start level> (<id>|<location>) - set the start level for the
   bundle(s)
setibsl <start level> - set the initial bundle start level

---Controlling the Profiling---
profilelog - Display & flush the profile log messages

---Controlling the Console---
more - More prompt for console output

Gibt man den Parameter „/?“ bzw. „help“ an, liefert die Konsole noch folgende Optionen:

show data directory
show program directory
show settings
show modules
show version
refresh
help
heapdump
javadump
systemdump

[Optionen von „tell http xsp“ unter Domino 8.5.2 FP2]

EDIT:

Unter 8.5.1 exisitieren nur folgende Optionen, die Kommandos wie z.B. „gc“ sind noch nicht implementiert.

show xsp directory
show lib directory
show data directory
show program directory
show settings
show modules
show version
refresh
help
heapdump
javadump

[Optionen von „tell http xsp“ unter Domino 8.5.1 FP3 // Fett: Kommandos, die unter 8.5.2 nicht mehr existieren]

Veröffentlicht unter Allgemein, Infrastruktur, Server, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

Partial Refresh: Get vs. Post

Wenn man die Wahl hat, einen Partial Refresh via HTTP GET oder via HTTP POST auszulösen, sollte aus Gründen der Performance stets die GET-Variante bevorzugt werden. Das läßt sich durch zwei Umstände begründen:

1. Handling des XMLHttpRequests im Browser

Wird ein XMLHttpRequest mittels POST abgeschickt, geschieht dies in zwei Schritten: Erst wird der Header gesendet, daraufhin im zweiten Schritt die Daten; es werden also mindestens zwei TCP-Pakete verschickt. Da die Daten bei GET-Requests auf eine maximale URL-Länge von 2K begrenzt sind, daher passen sie meistens in ein TCP-Paket, was die Kommunikation beschleunigt und den Server entlastet. Ausnahme hierbei: Man hat zuviele Cookies, denn diese werden im Request mitübertragen und vergrößern so die Datenmenge.

2. Handling des Requests im JSF-Lifecycle

Bei einem GET Request werden nur zwei Phasen des JSF-Lifecycles ausgeführt: Die „Restore View„-Phase und die „Render Response„-Phase. Das beschleunigt die Serverseitige Bearbeitung um ein vielfaches. POST Requests durchlaufen den kompletten JSF-Lifecycle.

Fazit

Solange die Datenmenge also gering bleibt bzw. sich die Absicht des Partial Refreshs nur auf den Abruf von Daten bzw. das Ausführen von SSJS beschränkt (z.B. beim Updaten eines Seitenbereiches, o.ä.), gewinnt ein XSP.partialRefreshGet immer vor einem XSP.partialRefreshPost. Dies sollte konsequent bei der Entwicklung berücksichtigt werden. Denn meistens ist die Notwendigkeit, einen Partial Refresh mittels POST durchzuführen, nicht wirklich gegeben.

Übrigens:
Ein GET Request encodiert die zu übertragenen Daten in die URL des Requests. Überschreitet die Datenmenge die maximale Länge einer URL, führt das unweigerlich zu Fehlern auf dem Server.

Veröffentlicht unter Allgemein, Java Script, JSF, Performance, ServerSide JavaScript, Web, XPages, XSP | Verschlagwortet mit , , , , , , , , , , | Schreib einen Kommentar

Mögliche Optionen bei Partial Refreshs

Beim Studieren des XSP-Objektes bin ich auf folgende mögliche Optionen gestossen, die den Funktionen XSP.partialRefreshGet und XSP.partialRefreshPost mitgegeben werden können:

  • params: parameters to add to the request
  • onStart: piece of code to execute before it start
  • onComplete: piece of code to execute when the execution is completed
  • onError piece of code to execute in case of an error
  • refreshTargetId:  the target id to refresh
  • refreshInner: indicate that the inner HTML should be refreshed  (boolean)
  • valmode: validation mode (0=none, 1=converters only, 2=converters+validators) bzw. true (für Versionen vor 8.5.2)
  • execId: partial execution id

Ich versuche z.Zt. zu verstehen, was man mit der Option  „execId“ im Detail anfangen kann. Auch „refreshInner“ gibt mir momentan noch Rätsel auf…

Nachforschungen in meinen JSF-Ressourcen erklären mir zwar, was prinzipiell mit der „execId“ möglich ist, doch im Zusammenspiel mit XPages habe ich bisher keine Informationen finden bzw. selbst ausprobieren können, was bei Angabe einer „execId“ intern auf dem Domino-Server passiert (Unterschiede im direkten Vergleich des reinen JSFs und XPages gibt es z.B. bei der Angabe von Id’s: Bei XPages ist nur eine Id pro Refresh erlaubt, gemäß JSF-Spezifikation sind mehrere Id’s auf einmal möglich – es wäre sehr nett, wenn das gehen könnte…).

Veröffentlicht unter Allgemein, Java Script, JSF, Web, XPages, XSP | Verschlagwortet mit , , , , , , , , , , | Schreib einen Kommentar

Abfangen einer fehlerhaften „documentId“

Öffnet man eine XPage, die ein Dokument über eine Datasource einbindet, kann man die UNID des Dokumentes über den „documentId“-Parameter angeben.

Problematisch ist allerdings, wenn die UNID falsch ist (z.B. wenn das Dokument gelöscht wurde), denn dies führt zu hässlichen 500er-Fehlern auf der XPage bzw. zu dem Fehler „Could not open the document“:

Um dieses Problem in den Griff zu bekommen, muß die Datasource so modifiziert werden, daß der Parameter „documentId“ nicht mehr als Vorgabewert verwendet wird, gleiches gilt für den Parameter „action“.

Dazu wird in der Datasource die Option „ignoreRequestParams“ auf „true“ gesetzt. Nun müssen nur noch die beiden Angaben manuell berechnet werden:

<xp:dominoDocument var="document1" ignoreRequestParams="true">
<xp:this.documentId>
<![CDATA[#{javascript:var unid = getHTTPQueryParam( "documentId" );
var isOK = false;
try{
   doc = database.getDocumentByUNID( unid );
   if( doc !=null )
      isOK = true;
}catch(e){}
if( isOK ){
    return unid;
}
return "";
}]]></xp:this.documentId> <xp:this.action>
<![CDATA[#{javascript:var unid = getHTTPQueryParam( "documentId" );
   var isOK = false;
   try{
      doc = database.getDocumentByUNID( unid );
      if( doc !=null )
         isOK = true;
   }catch(e){}

   if( isOK ){ return "editDocument"; }
      else{ return "newDocument" };

}]]>
</xp:this.action>
</xp:dominoDocument>

[In Fett: Die beiden berechneten Parameter „documentId“ und „action“ & der „Ignore“-Parameter // In Rot: Die jeweilige berechnete Aktion]

Durch den Code wird erreicht, daß das angegebene Dokument im Edit-Mode geöffnet wird; ist dies nicht möglich, wird ein neues Dokument erstellt. Die Fehlermeldung bei Öffnen wird hierdurch verhindert und die XPage kann weiter verarbeitet werden. Für ein erweitertes Errorhandling (um z.B. eine Fehlermeldung auszugeben) könnte eine Variable im ViewScope gesetzt werden, die auf der XPage ausgewertet werden kann.

Hier noch die Funktion zum Auslesen der HTTP-Queryparameter:

function getHTTPQueryParam( pParam ){
   q = facesContext.getExternalContext()
      .getRequest().getQueryString();
   if (q.indexOf(pParam+"=")>-1) {
      v = q.substring(q.indexOf(pParam+"=")+pParam.length()+1,
         q.length());
      v = (v.indexOf("&")>-1?v.substring(0,v.indexOf("&")):v);
      if ((typeof v !== "undefined") && !v.equalsIgnoreCase("")) {
         return (v);
      } else {
         return false;
      }
    }
}

[Funktion liefert Wert des abgefragten URL-Parameters zurück, oder false]

Veröffentlicht unter Allgemein, Errorhandling, Java Script, ServerSide JavaScript, Web, XPages | Verschlagwortet mit , , , , , , , | Schreib einen Kommentar

Code im HTML-Header direkt einbetten

Um Javascript-Code im HTML-Header einzubetten, besteht „offiziell“ nur die Möglichkeit, diesen über eine JS-Ressource einzubinden.

Das ist manchmal etwas umständlich, z.B. wenn es sich nur um eine kleine Funktion oder sogar nur eine Variable handelt, und außerdem bedeutet dies eine Performance-Einbuße und zusätzliche Last auf dem Server (ein weiterer HTTP-Request wird dadurch ausgelöst).

Eine eingebundene Ressource muß jedoch keine Quellangabe enthalten. Statt dessen kann der Inhalt einer Ressource programmatisch gefüllt werden:

<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:this.resources>
      <xp:script clientSide="true">
         <xp:this.contents>
            <![CDATA[alert("HUHU!")]]>
         </xp:this.contents>
      </xp:script>
   </xp:this.resources>
</xp:view>

[Fett: Der Tag für den Inhalt der eingebundenen Ressource // In Rot: Der Javascript-Code]

Natürlich kann hier auch SSJS verwendet werden, um den Code dynamisch zu generieren. Das Ergebnis für diese Beispiel sieht wie folgt aus:

<!DOCTYPE HTML PUBLIC 
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="de">
   <head>
      <title></title>
      [.....]
      <script type="text/javascript">
         alert("HUHU!")
      </script>
   </head>
   <body>
      [.....]
   </body>
</html>

[Fett: Der generierte Javascript-Block // In Rot: der definierte Content]

Veröffentlicht unter Allgemein, HTML, Java Script, XPages | Verschlagwortet mit , , , , , | Schreib einen Kommentar

Einfacher Datenaustausch zwischen SSJS & JS

Im XPages-Developer-Forum wurde die Frage gestellt, wie man auf einer XPage einen Button dazu bringen kann, erst ein Serverseitiges JavaScript auszuführen, um danach dessen Rückgabewert in einem  Clientseitigen Javascript zu verarbeiten.

Meine Lösung hierzu sieht so aus, daß der Datenaustausch über eine Javascript-Variable durchgeführt wird. Ein Partialrefresh lädt dabei ein Outputscript nach, und das Clientseitige Javascript wird durch das „onComplete“-Ereignis der Aktion angestoßen. Das Outputscript wird dynamisch generiert.

Hier der dazugehörige Code:

<xp:button value="Label" id="button2">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="partial" refreshId="refreshMe">
      <xp:this.onComplete>
         <![CDATA[alert(test)]]>
      </xp:this.onComplete>
   </xp:eventHandler>
</xp:button>

<xp:div id="refreshMe">
   <xp:scriptBlock id="scriptBlock1">
      <xp:this.value>
         <![CDATA[#{javascript:"var test='" +
         java.lang.System.currentTimeMillis() +
         "'";}]]>
      </xp:this.value>
   </xp:scriptBlock>
</xp:div>

[In Fett: Das Ziel des Partialrefresh // In Rot: Die „Austausch“-Variable]

Natürlich läßt sich auf diese Weise auch mehr als nur eine Varable austauschen: Es kann beliebiger Code nachgeladen werden (komplette Javascript-Objekte uvm.). Der Scriptblock selbst ist nicht referenzierbar für den Partialrefresh, so daß der Block in ein anderes Element eingebettet sein muß, damit der Trick funktioniert.

Veröffentlicht unter Allgemein, HTML, Java Script, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , | Schreib einen Kommentar

XPages & InMemory-Agents

Mit InMemory-Agents lassen sich Agenten wunderbar in XPage-Applikationen integrieren. Die XPage wartet, bis der Agent durchgelaufen ist, und über den Documentcontext kann eine bidirektionale Kommunikation zwischen den beiden  stattfinden. Ohne irgendwelche mehr oder minder dubiose Workarounds für das Zwischenspeichern von Werten (z.B. Speichern des Dokumentes, Arbeiten mit Profildokumenten, usw.) können auf diesem Weise bestehende LotusScript-Agenten in eine XPage integriert werden:

Hier ein kleines Beispiel mit einem Button, der einen Agent startet, und dessen Ergebnis auswertet :

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:button value="Label" id="button1">
       <xp:eventHandler event="onclick" submit="true"
       refreshMode="complete">
         <xp:this.action><![CDATA[#{javascript:
            var document = database.createDocument();
            var agent = database.getAgent("InMemoryAgent");
            agent.runWithDocumentContext( document );
            print( document.getItemValueString("InMemReturn") );
            }]]>
         </xp:this.action>
      </xp:eventHandler>
   </xp:button>
</xp:view>

[In Rot: Der Aufruf des Agenten. Der Code nach dem Aufruf wird erst nach Laufen des Agenten ausgeführt ]

Der LotusScript-Agent für dieses Beispiel schreibt den aktuellen Zeitstempel in das übergebene Dokument:

Option Public
Option Declare

Sub Initialize
 Dim session As New NotesSession
 Dim doc As NotesDocument

 Set doc = session.Documentcontext
 doc.Appenditemvalue "InMemReturn", CStr(Now)

End Sub

Der print-Befehl in der obigen XPage wird erst nach der Verarbeitung des Agenten ausgeführt; auf der Serverkonsole wird der aktuelle Datumsstring ausgegeben.

Natürlich kann es sich auch um einen Java-Agenten handeln, doch für beide gilt, daß der Haken „Run as Web user“ aktiviert sein muß:

Ist der Haken nicht aktiviert, erhält man ansonsten eine im ersten Moment nichtssagende Fehlermeldung serviert:

Exception occured calling
NotesAgent.runWithDocumentContext (lotus.domino.local.Document) null

Die Meldung verwirrt im ersten Moment, und es ist das erste Mal, daß sich im Stack Trace eine wirklich aussagekräftige Information wiederfindet (man muß etwas suchen, hier stark verkürzt):

(…)
com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:304)
com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:261)
com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:291)
NotesException: Unable to pass doc context – Inner agent must run as webuser
lotus.domino.local.Agent.runWithDocumentContext(Unknown Source)
com.ibm.xsp.script.DominoHelper$5.run(DominoHelper.java:154)
(…)

[In Fett: Die erste aussagekräftige Stack Trace-Meldung. Es geht doch, liebe IBM!]

Veröffentlicht unter Agenten, Allgemein, Lotus Script, XPages | Verschlagwortet mit , , , , | Schreib einen Kommentar

Datacontext not found

Der Datacontext ist eine praktische Möglichkeit, in einer XPage globale Objekte zu definieren. Nur muß man bei der Werte-Berechnung darauf achten, daß ein Datacontext mit dem Wert „null“ nicht möglich ist:

Definiert man ein Datacontext-Objekt mit „null“…

<xp:this.dataContexts>
   <xp:dataContext var="mailAdress">
      <xp:this.value><![CDATA[#{javascript:
 return null;
         }]]>
      </xp:this.value>
   </xp:dataContext>
</xp:this.dataContexts>

[Fett: Name des Datacontext-Objektes // In Rot: Wertezuweisung]

… führt dies dazu, daß das Objekt nicht existent ist und daher nicht referenziert werden kann:

[Das Datacontext-Objekt „mailAdress“ wird nicht gefunden, da keine Referenz darauf vorhanden ist]

Veröffentlicht unter Allgemein, Java Script, ServerSide JavaScript, XPages | Verschlagwortet mit , , | Schreib einen Kommentar