Posts Tagged: ‘EL’

XPages: The Problem with DataContext Variables

11. Februar 2013 Posted by Sven Hasselbach

There is a large problem with data context variables if they are bound dynamically.
They will be recomputed again and again, even when in Partial Execution mode and if they are not in use. Here is a small demo XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

   <xp:this.dataContexts>
      <xp:dataContext var="dataContext">
         <xp:this.value>
            <![CDATA[#{javascript:
               // Sleep for 100ms
               java.lang.Thread.currentThread().sleep( 100 );
               print('-=> Recalc: ' + java.lang.System.currentTimeMillis());
               return true;
            }]]>
          </xp:this.value>
       </xp:dataContext>
    </xp:this.dataContexts>

   <xp:label id="labelRefreshMe">
      <xp:this.value>
         <![CDATA[#{javascript:
            java.lang.System.currentTimeMillis()
         }]]>
      </xp:this.value>
   </xp:label>

   <xp:button value="Refresh" id="buttonRefresh" >
      <xp:eventHandler event="onclick" submit="false"
         refreshMode="partial"
         refreshId="labelRefreshMe"
         execId="labelRefreshMe"
         execMode="partial" />
   </xp:button>

</xp:view>

If you are open the XPage, a dataContext variable will be recalculated for three times:

Clicking on the button will recalculate them up to eleven times:

Keep in mind: Partial Refresh & Partial Execution is enabled. That makes them only useable if their values are computed on page load.

LotusScript in XPages (3): Quick-n-Dirty-Aktivierung

10. April 2012 Posted by Sven Hasselbach

LotusScript in XPages

Dies ist der dritte Teil des Artikels “LotusScript in XPages”. Der erste Teil befindet sich hier, der zweite Teil hier.

 

Die Quick-n-Dirty-Aktivierung

Damit die BindingFactory verwendet werden kann, müsste eigentlich ein Plugin erstellt werden, doch es gibt auch eine “Abkürzung”, denn die Factory kann auch über einen angepassten ViewHandler in XPages verwendet werden. Dies ist beim Testen / Entwickeln eine sehr praktische Angelegenheit, da sich dadurch der Aufwand deutlich verringern lässt (Vielen Dank an dieser Stelle an Jesse Gallagher für seine Idee).

Der ViewHandler ist einfach aufgebaut und sieht wie folgt aus:

package ch.hasselba.xpages.jsf.el;

import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import com.ibm.xsp.application.ApplicationEx;
import com.ibm.xsp.factory.FactoryLookup;

public class LSViewHandler extends
   com.ibm.xsp.application.ViewHandlerExImpl {
    public LSViewHandler(ViewHandler paramHandler) {
        super(paramHandler);
    }

    @SuppressWarnings({ "deprecation" })
    @Override
    public UIViewRoot createView(FacesContext context,
       String paramString) {
        ApplicationEx app = (ApplicationEx) context.getApplication();
        FactoryLookup facts = app.getFactoryLookup();

        LSBindingFactory lsfac = new LSBindingFactory();
        if(facts.getFactory(lsfac.getPrefix()) == null) {
            facts.setFactory(lsfac.getPrefix(), lsfac);
        }

        return super.createView(context, paramString);
    }

}

Der aktuellen Application-Instanz wird hierbei die BindingFactory im createView() hinzugefügt, wenn diese noch nicht vorhanden ist, danach wird die ursprüngliche createView()-Methode der erweiterten Klasse com.ibm.xsp.application.ViewHandlerExImpl aufgerufen.

Dann muss nur noch die faces-config.xml modifiziert und der neue ViewHandler eingetragen werden:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <application>
    <view-handler>ch.hasselba.xpages.jsf.el.LSViewHandler</view-handler>
  </application>
</faces-config>

Nun kann in der XPage mit dem neuen “lotusscript”-Tag experimentiert werden. Da der Designer den neuen Tag nicht kennt, wird immer eine Warnung ausgegeben werden bzw. die Syntax mit einem Ausufezeichen markiert:

Dies stellt aber (zumindest unter 8.5.3) kein Problem dar, ist aber erst durch eine “saubere” Plugin-Lösung behebbar.

In den nächsten Teilen wird die Plugin-Variante vorgestellt, und ein paar Erweiterungsmöglichkeiten für den LotusScript-Wrapper gezeigt.

LotusScript in XPages (2): LotusScript-Wrapper

8. April 2012 Posted by Sven Hasselbach

LotusScript in XPages

Dies ist der zweite Teil des Artikels “LotusScript in XPages”. Der erste Teil befindet sich hier.

 

Der LotusScript-Wrapper

Um dynamischen LotusScript-Code auszuführen, bietet sich die Execute()-Funktion an: Mit der Funktion lässt sich fast der gesamte Umfang der LotusScript-eigenen Backendfunktionalität nutzen, also auch Scriptlibraries einbinden uvm.

Leider steht diese Methode jedoch nicht  in Java direkt zur Verfügung (im Gegensatz zur Session.evaluate()-Methode für @Formelsprache), so dass nur der Umweg bleibt, die Funktion durch Aufruf eines LotusScript-Agenten zu verwenden, und das Ergebnis an die XPage zurück zu liefern. Dabei wird der auszuführende LotusScript-Code und das Ergebnis der Operation über ein temporäres NotesDokument via DocumentContext hin- und hergereicht.

Hier die Klasse “LSExceutor”, die die nötigen Hilfsfunktionen bereitstellt:

package ch.hasselba.xpages.jsf.el;

import javax.faces.context.FacesContext;
import java.util.Vector;
import lotus.domino.Agent;
import lotus.domino.Database;
import lotus.domino.Document;

public class LSExecutor {
    private final static String agentName  = "(LSExecutor)";
    private final static String fieldLSResult = "LSResult";
    private final static String fieldLSCode = "LSCode";

    public static Vector execLotusScriptCode( final String lscode ){
        try{
            Database curDB =  (Database) getVariableValue("database");
            Document doc = curDB.createDocument();
            String hlp = lscode.replace( "\n", "\t" );
            doc.appendItemValue( fieldLSCode, hlp );
            Agent agent = curDB.getAgent(  agentName );
            agent.runWithDocumentContext( doc );
            return doc.getItemValue( fieldLSResult );

        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }

     public static Object getVariableValue(String varName) {
         FacesContext context = FacesContext.getCurrentInstance();
         return context.getApplication().getVariableResolver().
          resolveVariable(context, varName);
     }
}

Die statische Methode execLotusScriptCode() wird in den Bindings verwendet, die in Teil 1 beschrieben wurden. Durch den Umstand, dass die runWithDocumentContext()-Methode auf das Ende der Agentenausführung wartet, ist eine sequentielle Verarbeitung gewährleistet.

Der Agent ist ebenfalls recht einfach aufgebaut:

%REM
    Agent LSExecutor
    Created Apr 6, 2012 by Sven Hasselbach/Sven Hasselbach
    Description: LSExecutor agent is a mapper for executing
                 LotusScript code from XPages
%END REM
Option Public
Option Declare

Const fieldLSCode = "LSCode"
Const fieldLSResult = "LSResult"

Dim returnValue
Sub Initialize
    Dim session As New NotesSession
    Dim doc As NotesDocument
    Dim lsCode, tmp , ret

    Set doc = session.Documentcontext
    lsCode = doc.Getitemvalue( fieldLSCode )
    tmp = Evaluate( | @ReplaceSubstring( "| & _
        lsCode(0) & |"; @Char(9) ; @Char(10) ) | )

    ret = Execute( tmp(0) )
    doc.Replaceitemvalue fieldLSResult , returnValue

End Sub

Um auf das Ergebnis der Berechnung zurückgreifen zu können, muss eine globale Variable verwendet werden, da die Execute()-Funktion selbst keine Rückgabemöglichkeit bietet (siehe Domino Designer Hilfe). In diesem Fall ist es “returnValue”, dessen Wert in das via DocumentContext übergebene Dokument geschrieben wird. Entsprechend muss der LotusScript-Code angepasst sein, siehe dazu auch die Beispiele am Anfang des 1. Teils des Artikels. Hier zeigt sich eine Schwachstelle: Es können keine Objekte zwischen Java und LotusScript übergeben werden; ein Zugriff auf z.B. den FacesContext ist in LotusScript nicht möglich. Soll eine DocumentCollection zurück geliefert werden, muss dieses als Array von DocumentUniversalIds geschehen usw.

Der Agent muss im Namen des Benutzers laufen, daher muss der “Run as Web user”-Haken gesetzt sein:

Im dritten Teil wird eine Quick-n-Dirty-Variante gezeigt, die BindingFactory bekannt zu machen.

 

Anmerkung:

An dieser Stelle sei nocheinmal ausdrücklich darauf hingewiesen, das der hier gezeigte Code sich maximal im “Alpha”-Status befindet.

Performance-Killer in der XPage

20. Februar 2012 Posted by Sven Hasselbach

Dank einer Frage von Ulrich Krause im XPages Developer Forum wurde ein Thema “wiederbelebt”, das mir vor einiger Zeit in einem Projekt aufgefallen ist und sich als wahre Bremse bei XPages-Applikationen herausstellt:

Sämtliche Datenquellen (DataContext-Variablen, Repeat Controls, usw.) werden bei jedem Partial Refresh neu berechnet, auch wenn sie nicht das Ziel (bzw. ein Kind-Element des Ziels) des Refreshs sind.

Ich nutze als Beispiel hierfür die von Ulrich Krause gepostete Beispiel-XPage, um das Problem zu verdeutlichen. Den fett markierten Code ist von mir zur Verdeutlichung auf der Serverkonsole eingebaut worden, dass es sich nicht um einen fehlerhaftes print-Statement handelt, sondern der Code wirklich neu berechnet wird.

<?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="partial" refreshId="col1" />
 </xp:button>
 <xp:table>
 <xp:tr>
 <xp:td id="col1">
 <xp:text escape="true" id="computedField2"
   value="#{javascript:@Now()}">
 <xp:this.converter>
 <xp:convertDateTime type="both" />
 </xp:this.converter>
 </xp:text>
 </xp:td>
 <xp:td id="col2">
 <xp:text escape="true" id="computedField1"
   value="#{javascript:@Now()}">
 <xp:this.converter>
 <xp:convertDateTime type="both" />
 </xp:this.converter>
 </xp:text>
 <xp:dataTable id="dataTable1" rows="30">
 <xp:this.value><![CDATA[#{javascript:
 print(java.lang.System.currentTimeMillis() + ' You shall not refresh');
}]]></xp:this.value>
 <xp:column id="column1" />
 </xp:dataTable>
 </xp:td>
 </xp:tr>
 </xp:table>
</xp:view>

Wird der Button geklickt, wird ein Refresh auf das Element mit der Id “col1″ ausgelöst, und eigentlich dürfte die Data Table nicht neu berechnet werden, doch genau das ist aber der Fall!

[Die Ausgabe auf der Serverkonsole habe ich mit einem Debug PhaseListener kombiniert, um die Phasen im Lifecycle hervorzuheben]

Button

<xp:button value="Label" id="button1">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="partial" refreshId="col1" />
</xp:button>

1. Öffnen der XPage

19.02.2012 18:02:17   HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:02:17   HTTP JVM: 1329670937472 You shall not refresh
19.02.2012 18:02:17   HTTP JVM: After phase: RENDER_RESPONSE 6

2. Partial Refresh

19.02.2012 18:05:37   HTTP JVM: Before phase: RESTORE_VIEW 1
19.02.2012 18:05:37   HTTP JVM: After phase: RESTORE_VIEW 1
19.02.2012 18:05:37   HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:05:37   HTTP JVM: 1329671137733 You shall not refresh
19.02.2012 18:05:37   HTTP JVM: After phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:05:37   HTTP JVM: Before phase: PROCESS_VALIDATIONS 3
19.02.2012 18:05:37   HTTP JVM: After phase: PROCESS_VALIDATIONS 3
19.02.2012 18:05:37   HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:05:37   HTTP JVM: After phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:05:37   HTTP JVM: Before phase: INVOKE_APPLICATION 5
19.02.2012 18:05:37   HTTP JVM: After phase: INVOKE_APPLICATION 5
19.02.2012 18:05:37   HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:05:37   HTTP JVM: 1329671137769 You shall not refresh
19.02.2012 18:05:37   HTTP JVM: After phase: RENDER_RESPONSE 6

Die Berechung der DataTable erfolgt zwei Mal währen des Partial Refreshs.

Ändert man die Ausführung des Buttons auf Partial Excecution, erhält man folgendes Ergebnis:

Button

<xp:button value="Label" id="button1" execMode="partial">
   <xp:eventHandler event="onclick" submit="true"
   refreshMode="partial" refreshId="col1" />
</xp:button>

1. Öffnen der XPage

19.02.2012 18:13:54   HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:13:54   HTTP JVM: 1329671634490 You shall not refresh
19.02.2012 18:13:54   HTTP JVM: After phase: RENDER_RESPONSE 6

2. Partial Refresh

19.02.2012 18:14:02   HTTP JVM: Before phase: RESTORE_VIEW 1
19.02.2012 18:14:02   HTTP JVM: After phase: RESTORE_VIEW 1
19.02.2012 18:14:02   HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:14:02   HTTP JVM: 1329671642321 You shall not refresh
19.02.2012 18:14:02   HTTP JVM: After phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:14:02   HTTP JVM: Before phase: PROCESS_VALIDATIONS 3
19.02.2012 18:14:02   HTTP JVM: After phase: PROCESS_VALIDATIONS 3
19.02.2012 18:14:02   HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:14:02   HTTP JVM: After phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:14:02   HTTP JVM: Before phase: INVOKE_APPLICATION 5
19.02.2012 18:14:02   HTTP JVM: After phase: INVOKE_APPLICATION 5
19.02.2012 18:14:02   HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:14:02   HTTP JVM: 1329671642360 You shall not refresh
19.02.2012 18:14:02   HTTP JVM: After phase: RENDER_RESPONSE 6

Wieder erfolgt die Berechung der DataTable zwei Mal währen des Partial Refreshs.

Eine Änderung des Execution-Modes des Events verringert die Berechnung zumindest auf eine Neuberechnung pro Partial Refresh:

Button

<xp:button value="Label" id="button1" execMode="partial">
   <xp:eventHandler event="onclick" submit="true"
   refreshMode="partial" refreshId="col1" execMode="partial"/>
</xp:button>

1. Öffnen der XPage

19.02.2012 18:23:17   HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:23:17   HTTP JVM: 1329672197161 You shall not refresh
19.02.2012 18:23:17   HTTP JVM: After phase: RENDER_RESPONSE 6

2. Partial Refresh

19.02.2012 18:23:18   HTTP JVM: Before phase: RESTORE_VIEW 1
19.02.2012 18:23:18   HTTP JVM: After phase: RESTORE_VIEW 1
19.02.2012 18:23:18   HTTP JVM: Before phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:23:18   HTTP JVM: After phase: APPLY_REQUEST_VALUES 2
19.02.2012 18:23:18   HTTP JVM: Before phase: PROCESS_VALIDATIONS 3
19.02.2012 18:23:18   HTTP JVM: After phase: PROCESS_VALIDATIONS 3
19.02.2012 18:23:18   HTTP JVM: Before phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:23:18   HTTP JVM: After phase: UPDATE_MODEL_VALUES 4
19.02.2012 18:23:18   HTTP JVM: Before phase: INVOKE_APPLICATION 5
19.02.2012 18:23:18   HTTP JVM: After phase: INVOKE_APPLICATION 5
19.02.2012 18:23:18   HTTP JVM: Before phase: RENDER_RESPONSE 6
19.02.2012 18:23:18   HTTP JVM: 1329672198875 You shall not refresh
19.02.2012 18:23:18   HTTP JVM: After phase: RENDER_RESPONSE 6

Selbst das Setzen der execId auf col1 ändert nichts – der Refresh führt immer zu einer Neuberechnung der DataTable.

Da diese Neuberechnung bei jedem Partial Refresh ausgelöst wird, werden Applikationen extrem ausgebremst. Daher sollte die Berechnung der Datenquelle wenn möglich auf “Computed On Page Load” gesetzt werden.

Wenn dies nicht möglich ist, habe ich hier ein kleines Snippet, das die Berechnung nur dann durchführt, wenn die Datenquelle auch wirklich refresht wird:

<xp:repeat id="repeat1" rows="30" var="rCol">
   <xp:this.value><![CDATA[#{javascript:
      function calculate(){
         var data:java.util.Vector = new java.util.Vector();
         data.add(java.lang.System.currentTimeMillis());
         return data;
      }

      if( viewScope.containsKey("data") == false){
         viewScope.data = calculate();
         return viewScope.data;
      }
      if( com.ibm.xsp.ajax.AjaxUtil.isAjaxPartialRefresh(facesContext) === true ){
         var pId = com.ibm.xsp.ajax.AjaxUtil.getAjaxComponentId( facesContext  );
         if( pId !== getClientId( 'repeat1' ) )
            return viewScope.data;
      }

      viewScope.data = calculate();
      viewScope.data}]]>
   </xp:this.value>
   <xp:label value="#{javascript:rCol }" id="label1"></xp:label>
</xp:repeat>

[Hier ein Beispiel mit einem Repeat Control]

Die Version ist recht simpel aufgebaut, eine verbesserte Version werde ich in den nächsten Tagen als XSnippet veröffentlichen.

Bug: Invalider Java-Code durch berechnete Tag-Attribute

17. Februar 2012 Posted by Sven Hasselbach

António A Ramos hat einen interessanten Bug entdeckt: Werden die Attribute eines HTML-Tags im Designer berechnet, wird die XPage nicht mehr verarbeitet und ein Internal Server Error tritt auf

So wird der folgende HTML-Tag ordnungsgemäß gerendert…

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <li data-theme="e"></li>
</xp:view>

… jedoch ist eine Berechung nicht zulässig:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <li data-theme="#{javascript:'e'}"></li>
</xp:view>

Der Grund ist dabei die Umsetzung in den Java-Code, denn dieser verwendet das Attribute ungeprüft als Variablenname – was natürlich fehl schlagen muss.

So sieht der generierte Java-Code aus:

Ein Workaround ist, den generierten Java-Code direkt im Designer zu ändern und fehlerhafte Variablendeklaration von Hand zu bereinigen – allerdings muss der Vorgang nach jedem Build wiederholt werden.