Posts Tagged: ‘Lotus Script’

Verse / Notes / Domino forever!

18. November 2016 Posted by Peter Schütt, IBM

index

IBM investiert seit Jahren nachhaltig in die IBM Notes Domino Plattform, auch wenn die Marktbegleiter das natürlich gern anders darstellen. Es waren in den letzten 2 Jahren viele große und kleine Dinge: Unter anderem der äußerst gelungene Verse Mail Client im Browser und als App, der Zugriff auch von Outlook auf Mail auf Domino, der Zugriff auf Domino Anwendungen in einer Browser Umgebung und deren Nutzung in der IBM Bluemix Cloud, dann auch zusammen mit anderen Cloud-Lösungen, wie Watson Analytics.

IBM Verse / Domino

Damit sicher gestellt ist, dass IBM Verse bei Kunden von Anfang an problemlos läuft, hat IBM den Selbsttest gemacht und Verse gleich nach Verfügbarkeit für über 430.000 Nutzer ausgerollt und sie dazu in die Cloud geschoben. Waren es anfänglich 7500 Nutzer pro Woche, so konnte diese Zahl am Ende durch Prozessoptimierungen auf über 25.000 gesteigert werden. Das Projekt wurde vor der geplanten Zeit fertig und hatte eine Fehlerrate von unter 0,1 Prozent. Das sollen andere Anbieter erst einmal nachmachen!

Nun kommt man also als IBM-Mitarbeiter in den Genuss – das meine ich jetzt wörtlich – von IBM Verse. Designt, sodass man maximal zwei Klicks benötigt um Aktionen auszuführen und (optional) tief mit Connections integriert, bietet es eine unvergleichlich produktive Arbeitsumgebung. Und was man als Benutzer merkt, ist was Cloud und agile Programmierung bedeuten. Fast jeden Montag gibt es neue Funktionen. Nicht hunderte, wie bei einem Release, die die Nutzer Tsunami-artig treffen und ein aufwendiges Change Management erfordern, sondern in kleinen Schritten, die zudem nett erklärt werden. Es gibt zwar noch einzelne Funktionen, die in Verse im Moment fehlen oder durch Workarounds gelöst sind, aber das werden jede Woche weniger.

Was beim Nutzer hängen bleibt, ist, dass jede Woche ein paar Erweiterungen und neue Funktionen seinen Charme hat, ähnlich wie bei Apps auf mobilen Geräten. Immer aktuell sein nennt man ja auch “evergreen”. Und das hat was!

Mehr “evergreen” – auch on Premises!

IBM hat herausgefunden, dass die IT Abteilungen, die ihre Lösungen im eigenen Rechenzentrum betreiben oder betreiben lassen (“on Premises”) neue Releases typisch erst nach 3 Jahren installieren und dann noch einmal bis zu 3 Jahre brauchen, um die Notes Clients flächendeckend auszurollen. Das ist heute, in Zeiten in denen die IBM Connections Cloud eine “Evergreen”-Umgebung bietet, also regelmäßige, inkrementelle Updates, und damit dem Nutzer /der Nutzerin immer das Neueste, einfach nicht mehr zeitgemäß. Unser Offering Management hat sich deshalb überlegt, wie man auch für Kunden, die Domino weiter im eigenen Rechenzentrum betreiben und betreiben wollen, ein ähnliches Feeling wie “evergreen” bieten kann.

Die erste Erkenntnis war: wir müssen weg von dicken Desktop Clients, deren Rollout Packages größer als 1GB geworden sind. Vielleicht nicht ganz weg, aber zumindest als die absolut zentrale Lösung. Änderungen, die heute auf dem Server installiert werden, sollten auch heute beim Nutzer ankommen. Darum haben wir die Prioritäten geändert: Mobile Apps zuerst, dann Browser-“Clients” und dann Desktop Clients. Und deshalb haben wir den Entwicklungsschwerpunkt auf IBM Verse als Client im Browser verschoben.

IBM Verse auch on Premises

Goethe und Schiller haben sich selbst Kurznachrichten immer in Versform zugesandt. Wir wollten den Schritt zu etwas Neuem, das so kein Wettbewerber bietet, auch mit einem neuen Namen belegen. Wir hätten es auch iNotes.next nennen können, denn das ist, was es im Prinzip ist: Ein ganz neuer Mail Client, entwickelt nach neusten Design Thinking Methoden, auf der Basis modernster Technologien, der übrigens auch “offline” kann – und das sogar verschlüsselt! Gibt es im Sinne von Goethe und Schiller einen besseren Namen als das poetische „Verse“? Auf der Serverseite bleibt es aber beim besten Mail Server am Markt, ähnlich wie bei iNotes: Domino. In der Cloud und noch diesem Jahr (2016) auch on Premises.

Nachdem wir zunächst komplexere Lösung verfolgten, die in der Cloud mithilfe von Apache Solr umgesetzte, facettierte Suche auch on Premises anbieten zu können – was einiges an Zeit gekostet hat – ist den Spezialisten im Labor doch noch eine geniale Alternative eingefallen, die es jetzt ganz einfach macht: Der Schritt zu der modernsten E-Mail Lösung am Markt ist insbesondere für Bestandskunden, die heute schon iNotes nutzen, extrem einfach. Denn Verse on Premises (VoP) ist denkbar einfach zu installieren: Auf Basis Domino 9.0.1 FP7 wird es das Einspielen eines HotFixes sein. Fertig. Fast zumindest: Wenn es für die Nutzer besonders gut werden soll, bindet man es noch an Connections 5.5 Profile und Files an, die lizenzseitig ja auch bereits im Domino Paket mit drin sind. Für IBM ein großes Investment in die Plattform, für die Kunden softwareseitig ohne neue Kosten.

Outlook auf Domino

Domino benötigt, so zeigen es Umsteigerprojekte, nur etwa 1/3 der Anzahl der Server, die ein Microsoft Exchange typisch benötigt. Domino läuft auch deutlich stabiler (Ausfallsicherheit) und ist, unter anderem wegen der besonders effizienten Speichernutzung (DAOS) kostengünstiger. Deshalb ist und bleibt Domino der Server der Wahl – auch in der Cloud.

Für Outlook-Freunde mag da der Wunsch aufkommen ihr Outlook ebenfalls mit Domino zu nutzen. Auch hier hat IBM investiert und ermöglicht seit kurzem auch Outlook 2010, 2013 und 2016 sogar gemischt mit Verse und/oder Notes zu betreiben. Die Lösung nennt sich “IBM Mail Support for Microsoft Outlook – kurz IMSMO und nutzt SyncML als wesentliche und von Seiten Microsoft aus stabile Schnittstelle. Es sei an dieser Stelle nicht verschwiegen, dass es einige, wenige Funktionen unter IMSMO nicht gibt, dafür kommen andere hinzu, die Outlook normalerweise nicht kann.

Die Wahl des Clients für E-Mail mit Domino liegt – so es der Administrator zulässt – beim Nutzer. Und das gilt für den Betrieb aus der Cloud und für on Premises und durchaus auch parallel: Man kann sogar mehrere verschiedene Clients gleichzeitig nutzen, wenn man denn möchte.

Domino Anwendungen in einer Browser-Umgebung

Nutzt man den modernen Verse Browser Zugriff auf E-Mail, dann stellt sich die Frage nach Domino Anwendungen. Hier hatten wir zunächst eine Lösung als Browser Plug-in vorgesehen. Aufgrund von nachvollziehbaren Security Anforderungen mussten die Browser-Anbieter ihre Plug-in Frameworks allerdings so weit ändern, dass das nun keine Option mehr ist. Dafür gibt es jetzt für Bestandskunden kostenfrei runterladbar das IBM Client Application Access (ICAA) Tool, das zwar einmal installiert werden muss, sich dann aber bezüglich Updates benimmt wie ein klassisches Browser Plug-in. Es ermöglicht mit einem schlanken Client auf fast alle Domino-Anwendungen zuzugreifen und ist damit der perfekte Bruder zu Verse für E-Mail. Die wichtigste Ausnahme sind Eclipse-Anwendungen, die mit Version 8.0 kamen und nur von einigen Kunden genutzt werden. Für sie benötigt man weiterhin zwingend den Notes Standard Client.

Die Notes Client Roadmap

Auch wenn Verse und ICAA heute und in Zukunft die Speerspitze der Entwicklung im Domino-Umfeld darstellen und wir strategische Neuerungen damit zuerst und vielleicht auch alleinig dort bringen werden, wird auch der Notes Desktop Client weiterentwickelt. Das bedeutet im Moment nicht, dass wir jetzt planen würden zum Beispiel die Verse Oberfläche auch im Desktop Client anzubieten. Das würde auch wenig Sinn machen, denn abgesehen von wenigen Lücken, die wir schließen werden, bildet die Kombination aus Verse mit ICAA schon heute eine vollwertige Lösung. Deren klarer Vorteil ist eben nicht mehr mit jedem Update neu ausgerollt werden zu müssen. Und Updates wird es geben: Wir planen nämlich die Neuerungen von Verse in der Cloud weitgehend auch für Verse on Premises jeweils nachzuziehen.

Es gibt eine ganze Reihe von anderen Dingen, wie die Unterstützung neuer Technologien und Schnittstellen, die wir auch im Notes Desktop Client wieder dynamischer Unterstützen wollen. Auf der IBM Connect Hauskonferenz war einstens laut über eine Version 9.0.2 nachgedacht worden und dazu auch eine Folie mit möglichen Neuerungen aufgelegt worden. Ein erheblicher Teil ist mittlerweile – von vielen leider fast unbemerkt – mit den Feature Packs verfügbar geworden. Und die noch fehlenden, sowie weitere, neue Dinge werden in den nächsten Feature Packs, die zukünftig häufiger im Jahr ausgeliefert werden werden, kommen. Auch hier schimmert das Stichwort “evergreen” durch. Hierzu planen wir als Daumenregel zu jedem zweiten Featurepack ein Update des Notes Templates.

Das Supportfenster oder wie lange wird Verse / Notes / Domino unterstützt?

Üblich ist seit jeher, dass mit neuen Releases das garantierte Supportfenster wieder auf 5+3 Jahre hochgesetzt wird: fünf Jahre normaler Support und dann noch drei weitere Jahre gegen Geld. Das läuft seit mindestens 20 Jahren so. Da wir jetzt aus genannten Gründen (evergreen, agile Entwicklung) zukünftig von neuen Releases absehen wollen – wie es auch andere Hersteller tun – und stattdessen auf mehr kleinere Feature Packs setzen, war eine Anpassung notwendig. Das haben wir parallel zum Feature Pack 7 im September gemacht und das garantierte Supportfenster von bisher 2018 auf wieder 5+3 Jahre bis 2021, bzw. 2024 erweitert. Ein ganz normaler Vorgang und keinesfalls eine Aussage, dass 2021 Schluss wäre. So etwas können nur Wettbewerber streuen – geplant ist es auf jeden Fall nicht. Ganz im Gegenteil und dazu nur ein Beispiel: Im nächsten Template sind bereits die Feiertage für 2027 eingepflegt. Das würde ansonsten wenig Sinn machen.

Domino Anwendungen

Domino Anwendungen sind schnell, einfach und damit günstig zu erstellen und insbesondere mit der xPages Technologie auf dem Stand moderner Techniken. Dass sie in vielen Unternehmen ungemanagt und damit bei Infrastrukturverantwortlichen nicht sehr beliebt sind, teilen sie mit Excel Makros. Nichtsdestotrotz setzen viele Unternehmen Domino Anwendungen nicht nur für Abteilungslösungen ein, sondern auch strategische Anwendungen und bauen damit immer noch wunderbare Lösungen. Dadurch, dass IBM fortlaufend in diese eben auch sehr sichere Umgebung investiert, gibt es bei den meisten Kunden mittlerweile eine breite Palette davon. IBM Notes Domino ist eines der beständigsten IT Tools am Markt. Investitionsschutz pur. Domino Anwendungen aus den 1980-ern laufen heute noch unverändert. Es gab bis heute kein Rip-and-Replace oder irgendwas anderes, das größere Folgekosten nach sich gezogen hätte. Ein nahezu genialer Return-on-Investment.

Die Beständigkeit der Domino Plattform hat auch einen Nachteil – nämlich die Beständigkeit. Die Anwendungen, einmal geschrieben, mussten nie wieder angefasst werden. Sahen sie zum Erstellungszeitpunkt modern und Top of the Art aus, so ist über die Jahre in der IT doch viel passiert. Heute sehen 20 Jahre alte Anwendungen nicht mehr besonders cool aus, selbst wenn sie in ihren Prozessen immer noch brav ihren Dienst tun. Darüber ist die Plattform bei manchem in Verruf gekommen, als nicht mehr zeitgemäß und ähnliches. Wäre man gezwungen worden fortlaufend in die Anwendungserneuerung zu investieren, also etwa von klassischem Lotus Script auf xPages zu modernisieren, dann wären heute viele Nutzer glücklicher. Die Domino Technologie erlaubt mit xPages schon länger die Unterstützung von Browsern und mobilen Geräten. Nachdem IBM in jüngerer Zeit auch hier weiter investiert hat, werden xPages Anwendungen nun auch in der IBM Bluemix Anwendungs-Cloud unterstützt und können hier ganz einfach mit anderen Cloud Services, zum Beispiel Watson Analytics, angereichert werden. Für mache Lösungen, etwa Personalanwendungen, gibt es heute aber zugegebenermaßen auch standardisierte Lösungen, wie etwa von IBM Kenexa, sodass die Bedeutung von Domino Anwendungen in Teilbereichen rückläufig ist. Dennoch bleibt Domino eine der wichtigsten Anwendungsumgebungen.

Und für das Problem der veralteten Optik gibt es gelungene Partnerlösungen, um Domino Anwendungen zu modernisieren – auch hier in Deutschland. Was uns als Hersteller aber gerade beschäftigt ist vielmehr die Frage, was passiert, wenn demnächst Kunden doch massiv auf Cloud setzen wollen und sich dann fragen, wie sie auch die alten und neuen Domino-Anwendungen in die Cloud bekommen. Das ist nicht ganz trivial, da die Anwendungen nicht direkt mandantenfähig sein können und damit nicht dem Ideal einer Public Cloud Lösung entsprechen. An einer Lösung hierzu arbeiten unser Offering Management und Labor gerade intensiv. Und spätestens zur IBM Connect 2017 (20-23.2.2017, Moscone West, San Francisco) wird es hierzu Ankündigungen geben. Und dann ahne ich, dass sie noch mindestens ein weiteres Ass im Ärmel haben werden …

Cognitive Collaboration

Auf der Connect 2017 werden wir auch viel mehr dazu hören (und sehen), wie Watson-Funktionen das Arbeiten mit E-Mail, aber auch allen anderen Bereichen unseres Angebots, vereinfachen wird. Einfach ausgedrückt bekommen wir alle virtuelle Assistenten, die Hinweise geben, was besonders wichtig sein könnte und dazu Aktionen vorbereiten – wobei der Nutzer bestimmt. Was wir dort für 2017 planen, ist wirklich extrem spannend und IBM Verse ist auf jeden Fall ganz vorn als Teil der Story – und damit auch Domino, der beste Mail Server den es je gab. Domino forever!

Der Beitrag Verse / Notes / Domino forever! erschien zuerst auf DNUG.

Problems with Handles: When the same document is not the same

24. April 2014 Posted by Sven Hasselbach

Disclaimer: This will work in Java, SSJS and Lotus Script.

When opening the same document from the same database in different instances, and then recycle one of them, the other documents will be recycled too, because the handle to the underlying C object are the same.

This SSJS example…

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

    <xp:label id="labelDemo">
        <xp:this.value>
            <![CDATA[#{javascript:
                var dbCur:NotesDatabase = session.getCurrentDatabase();
                var dbOther:NotesDatabase =  session.getCurrentDatabase();

                var docCur:NotesDocument = dbCur.getDocumentByUNID( "E5CA138B7F5A21E5C1257C190068DBA9" );
                var docOther:NotesDocument = dbOther.getDocumentByUNID( "E5CA138B7F5A21E5C1257C190068DBA9" );

                docCur.recycle();
                return docOther.getUniversalID();
            }]]>
        </xp:this.value>
    </xp:label>

</xp:view>

… fails, because docOther is recycled too.

But if you open the database dbOther after initializing the database object, the handles are not the same. Then, the recycling of the document won’t affect the other instance of the same object:

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

    <xp:label id="labelDemo">
        <xp:this.value>
            <![CDATA[#{javascript:
                var dbCur:NotesDatabase = session.getCurrentDatabase();
                var dbOther:NotesDatabase = session.getDatabase( "", "" );
                dbOther.openByReplicaID( dbCur.getServer(), dbCur.getReplicaID() );

                var docCur:NotesDocument = dbCur.getDocumentByUNID( "E5CA138B7F5A21E5C1257C190068DBA9" );
                var docOther:NotesDocument = dbOther.getDocumentByUNID( "E5CA138B7F5A21E5C1257C190068DBA9" );

                docCur.recycle();
                return docOther.getUniversalID();
            }]]>
        </xp:this.value>
    </xp:label>

</xp:view>

In Lotus Script, you can fall into the trap when doing something like this:

Dim session As New NotesSession
Dim dbCur As NotesDatabase
Dim dcCur As NotesDocumentCollection
Dim dbOther As NotesDatabase
Dim docOther As NotesDocument

' open current database & create a document collection
Set dbCur = session.Currentdatabase
Set dcCur = dbCur.CreatedocumentCollection()

' open the current database again  
Set dbOther = New NotesDatabase( "","" )
dbOther.Open dbCur.Server, dbCur.Filepath

' get a document...
Set docOther = dbOther.Alldocuments.Getfirstdocument()

' ... and add it to the collection
dcCur.Adddocument docOther

This will result in a “Document is from a different database” error:

Open PDF’s stored in a rich text item from lotus script.

10. Dezember 2013 Posted by Ralf Petter

I have an application which stores PDF's as embedded files in a rich text item. I want to write some lotus script code which detach the PDF to a temporary directory and then launch the os default reader for PDF's to view the file. Ok the first part to detach the file is very easy and strait forward.

 Set rtitem=dokument.Getfirstitem("Body")
Forall obj In rtitem.Embeddedobjects
fileName=Environ("TEMP")+"\"+obj.Source
Call obj.extractFile(fileName)
End ForAll

Hm now we have the attachment in the temp directory, but how can we launch this attachment? My first guess was to use shell(fileName). But Notes quits this with an "illegal function call" error. I have tried many ways to call the shell function, but could not get it to work. So i have searched for another solution and found out, that the easiest way to workaround this problem is to use the Windows Scripting host to execute the PDF file.

  Set objShell = CreateObject("WScript.Shell")
returnValue = objShell.Run(fileName, 3, false)

This works very well for all other file types your windows system recognize too. You can find a detailed description of the WScript.Shell function on MSDN

The Haunted Field

17. Juli 2013 Posted by Sven Hasselbach

I am currently working on a huge application which exists for many years now, and has a long history with different developers and just a few manuals and/or documentations. But as often it is a critical business application which is in use across different countries 24/7. The more danger, the more less honor: Every fault is just a further nail in the coffin of the developer…

Today I had to develop an extension for this application, and this has driven me crazy, because it was a pain to identify to problem: After creating a document from a XPage the workflow stopped. When filling out the form in in the NotesClient the problem did not occur. But using the same form in a XPage (with computeWithForm) did not work.

After investigating I found out that the problem was caused by a required field which was not created. It was just not there, nothing, nada.

In the form it was defined as a computed field…

… but even with a simple XPage …

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

    <xp:this.data>
        <xp:dominoDocument var="document1" formName="Doc"
            computeWithForm="onsave">
        </xp:dominoDocument>
    </xp:this.data>

    <xp:button value="Save" id="buttonSubmit">
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete" immediate="false" save="true" />
    </xp:button>
    <xp:br />
    <xp:messages id="errMessages" />

</xp:view>

… the field was not created:

[I have tried the computeWithForm parameter with every available option.]

Searching for a quick workaround I tried to use an oldschool LS agent, but even then the mystery was not solved. Instead, it grew:

%REM
    Agent ComputeWithForm
    Created Jul 17, 2013 by Sven Hasselbach/Hasselba/CH
%END REM
Option Public
Option Declare

Sub Initialize
    Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim doc As NotesDocument

    Set db = session.Currentdatabase
    Set doc = New NotesDocument( db )
    doc.Form = "Doc"
    doc.Computewithform True, true
    doc.Save true, false
End Sub

This was the resulting error message:

When opening the form in the NotesClient and saving the document, everything works as expected:

What’s going on here? It was really interesting to find the reason. In the last years I always thought that computed fields have always values defined in the form. But this is not a requirement for author fields:

Adding a simple @UserName to the field, and the issue was solved.

XPages: High Performance Applications

18. September 2012 Posted by Sven Hasselbach

During the last months I worked on a high performance XPages application used by a lot of end users.  To get a better data throughput, I decided to use a reverse proxy for load balancing, caching of ressources, SSL connections etc.

For static resources I am using some “special” domains: This means that the browser is allowed to do more HTTP requests at once to the same NSF. If you have for example some images in your database which are reachable from outside via http://www.example.com/mydb.nsf/image.gif, this can be changed to http://static.example.com/mydb.nsf/image.gif (Here you can find a list of best practices).

I solved the problem of multiple execution during the JSF lifecycle  by checking if the request has already actual data (details can be found here – German only), but there was still a problem: Everytime a XPage wants some data to display, a query is sent to the domino server. This is nice if your application requires data in real-time, but not for a normal application – it kills the user experience.

This is why I searched a way which easily allows to implement memory cached database queries for domino databases.  The main idea is that the database query is no longer send directly to the backend . Instead, the request is made against a JSON wrapper, and the request to this wrapper (an agent residing in the same NSF) is done via a proxy. This allows a full control of the requested data.

The browser sends the HTTP request to the XPage and receives the response. The XPage queries the MemCache for data; if the data is not in the cache, the proxy queries the data storage (the domino database) and caches the result. The XPage has to parse the JSON data only, and this boosts the performance in my test environment for about 250-300%.

By “stealing” the session cookie, the database query to the backend database will be done in the context of the user; the security for domino databases is not influenced.

In my solution, I am using Apache 2.2 as reverse proxy. The following modules are additionally enabled for this solution:

  • cache_module
  • deflate_module
  • expires_module
  • headers_module
  • mem_cache_module
  • proxy_module
  • proxy_http_module
  • rewrite_module
  • setenvif_module

The virtual host configuration looks like this:

<VirtualHost *:8080>
 ServerName localhost

 # Enable reverseproxy
 ProxyRequests Off
 ProxyPreserveHost On
 <Proxy *>
  AddDefaultCharset off
  Order allow,deny
  Allow from all
 </Proxy>

 # Proxy config for Domino server
 # 
 ProxyPass / http://localhost:80/
 ProxyPassReverse / http://localhost:80/

  # prevent max-age calculation from Last-Modified
  # prevent If-Modified-Since requests
  # reduces the number of requests that hit the server
 <LocationMatch "/.*$">
  Header unset Last-Modified
  Header unset ETag
  Header unset HTTP_CACHE_CONTROL
 </LocationMatch>

 # MemCache Config
 # 
 CacheEnable mem /
 CacheEnable mem http://

 # Cache-Size 80 MB
 MCacheSize 81920
 MCacheMaxObjectCount 8192

 # Min Obj. Size 1 Byte
 MCacheMinObjectSize 1

 # Max Obj. Size 1 MB
 MCacheMaxObjectSize 1000000

 # cache for 60 seconds by default
 CacheDefaultExpire 60

 # FORCE caching for all documents (without Cache-Control: no-cache)
 CacheIgnoreNoLastMod On

 # force caching for all requests
 # ignore client side Cache-Control header
 CacheIgnoreCacheControl On
 # don't add Set-Cookie header to cache
 CacheIgnoreHeaders Set-Cookie

 # Add expires headers for images, css & js files
 # reduces the number of requests that hit the server
 ExpiresActive On
 ExpiresByType domino/json A600

</VirtualHost>

As you can see, the proxy runs on port 8080, and I have added a special content type “domino/json“. This makes it easier to identify the relevant data.

This is the XPage the user accesses:

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

    <xp:pager layout="Previous Group Next" partialRefresh="true"
        id="pager1" for="repeat1">
    </xp:pager>

    <xp:inputText id="inputSearch" value="#{sessionScope.searchFor}" />

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

    <xp:repeat id="repeat1" rows="30" var="rowData">
        <xp:this.value><![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages.util );

            var sessionId = null;
            try{
                 sessionId = cookie.get("DomAuthSessId").getValue();
            }catch(e){}

            var url = "http://localhost:8080/Data.nsf/DoSearch?OpenAgent";
            url += "&sessionId=" + sessionId;

            if( sessionScope.get("searchFor") !== null ){
                if( sessionScope.get("searchFor") !== "" )
                    url += "&search="; 
                    url += java.net.URLEncoder.encode(sessionScope.get("searchFor"),"UTF-8");
            }

            var data = ch.hasselba.xpages.util.URLReader.read( url, sessionId );
            var parsed = null;
            try{
                 parsed = fromJson(data).data;
            }catch(e){}
            parsed
            }]]>
        </xp:this.value>
        <xp:text escape="true" id="computedField1" value="#{javascript:rowData.LastName}">
        </xp:text>
        &#160;
        <xp:text escape="true" id="computedField2" value="#{javascript:rowData.FirstName}">
        </xp:text>
        <xp:br />
    </xp:repeat>

</xp:view>

The Java class used is really simple, I know there are better ways to do a Http request, but this is a proof of concept.

package ch.hasselba.xpages.util;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

/**
 * URLReader
 * performs a HTTP request
 * 
 * @author Sven Hasselbach
 * @category URL
 * @category Proxy
 * @version 0.2
 */
public class URLReader {

    // Constants
    private final static String PROPERTY_COOKIE_NAME = "Cookie";
    private final static String PROPERTY_DOMAUTHSESSID_VALUE = "DomAuthSessId=";

    /**
     * reads data from a given URL
     * 
     * @param pURL URL to load data from
     * @param pSessionId session data for doing a request in the current user context
     * @return String containg the result of the http request
     * @author Sven Hasselbach
     * @category URL
     * @category Proxy
     * @version 0.2
     */
    public static String read( final String pURL, final String pSessionId ){
        String data = null;

        try{
            // init the URL connection
            URL url = new URL( pURL );
            URLConnection uc = url.openConnection();

            // "steal" the original user session cookie
            if( !("".equals(pSessionId)))
                    uc.setRequestProperty ( PROPERTY_COOKIE_NAME ,
                       PROPERTY_DOMAUTHSESSID_VALUE + pSessionId);

            // do the HTTP request
            BufferedReader in = new BufferedReader( 
               new InputStreamReader( uc.getInputStream() ));

            // process the data returned 
            StringBuffer strBuf = new StringBuffer();
            String tmpStr = "";
            while((tmpStr = in.readLine()) != null ){
                strBuf.append( tmpStr );
            }
            data = strBuf.toString();

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

        return data;
    }
}

And here comes the JSON handler, a simple Lotus Script agent:

Sub Initialize
    Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Dim isFirst As Boolean
    Dim contextDoc As NotesDocument
    Dim hlp
    Set contextDoc = session.Documentcontext
    Set db = session.Currentdatabase

    ' get the search string from the URL or use the default search
    hlp = Split( contextDoc.QUERY_STRING_DECODED(0), "search=" )
    If UBound( hlp ) = 0 Then
        Set dc = db.Ftsearch("[FirstNAME] CONTAINS AARON", 0)
    Else
        Set dc = db.Ftsearch(hlp(1), 0)
    End If

    ' create the JSON output    
    isFirst = true
    Set doc = dc.Getfirstdocument()

    ' special content type domino/json
    Print |Content-type: domino/json|
    Print
    Print |{"data":[|
    While Not doc Is Nothing
        If Not isFirst Then Print ","
        Print |{"LastName":"| & doc.LastName(0) & _
        |","FirstName":"| & doc.FirstName(0) & |"}|
        isFirst = False
        Set doc = dc.Getnextdocument( doc )
    Wend

    Print |]}|

End Sub

In the next days I will provide a sample database and add more details. A database with 300k test datasets will be added too.

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.

LotusScript in XPages (1): Basics

7. April 2012 Posted by Sven Hasselbach

LotusScript in XPages

Wäre es nicht schön, wenn man in XPages direkt mit Lotus Script arbeiten könnte? Wenn es einen Weg gäbe, mit der sich Lotus Script-Code direkt in der XPage einbetten liesse, und wie folgt zu verwenden wäre?

Prinzipiell ist das möglich, aber der hier dargestellte Weg ist eher als Workaround anzusehen und wird das Alpha-Stadium wohl eher nicht verlassen. Aber es lässt sich anhand dieser Anleitung zeigen, wie man XPages flexibel erweitern und weitere Interpreter-Sprachen der XPages-Engine hinzufügen kann.

Was mit der hier vorgestellten Lösung jedoch möglich ist, kann man in dieser kleinen Beispiel-XPage sehen:

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

   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{lotusscript:returnValue=-1 }]]>
      </xp:this.value>
   </xp:label>
   <xp:br/>
   <xp:br/>
   <xp:label id="label2">
      <xp:this.value><![CDATA[${lotusscript:
         Dim session as New NotesSession
         Dim db as NotesDatabase
         Set db = session.CurrentDatabase

         returnValue = db.AllDocuments.getFirstDocument.UniversalID
      }]]></xp:this.value>
   </xp:label>
</xp:view>

In der XPage kann der neue Tag “lotusscript:” verwendet und beliebiger LotusScript-Code direkt in die XPage eingebettet werden. Das Ergebnis im Browser sieht dann wie folgt aus:

Soviel vorab, nun zu erst einmal zu den Basics…

Grundlagen: Method-Bindings & Value-Bindings

Um einen neuen Tag für XPages bereitzustellen, müssen zu aller erst Bindings für Methoden- und für Werte-Zuweisungen erstellt werden. Die beiden Binding-Arten unterscheiden sich – grob formuliert* – wie folgt: Value-Bindings werden überall dort verwendet, wo ein Wert einer Komponente zugewiesen wird und (wie der Name erahnen lässt) eine value-Eigenschaft exitsiert. Dies ist bei praktisch allen UIKomponenten der Fall. Method-Bindings hingegen kommen bei der Zuweisung bei samtlichen Events ins Spiel: Der SSJS-Code eines BeforePageLoad-Events wird mit einem Method-Binding gesetzt, oder der Code bei Button-Events usw.

Die Value-Binding-Klasse, die für die hier geschilderten Zwecke benötigt wird, sieht wie folgt aus:

package ch.hasselba.xpages.jsf.el;

import com.ibm.xsp.binding.ValueBindingEx;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.PropertyNotFoundException;

public class LSValueBinding extends ValueBindingEx {
    private String data;

    public LSValueBinding(String content) {
        this.data = data;
    }

    @Override
    public Object getValue(FacesContext context)
        throws EvaluationException, PropertyNotFoundException {
            return LSExecutor.execLotusScriptCode( data );
    }

    @Override
    public void setValue(FacesContext context, Object obj)
       throws EvaluationException, PropertyNotFoundException {}

    @Override
    public boolean isReadOnly(FacesContext context)
        throws EvaluationException, PropertyNotFoundException {
            return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Class getType(FacesContext context)
        throws EvaluationException, PropertyNotFoundException {
            return Object.class;
    }
}

Das Value-Binding erweitert die Klasse com.ibm.xsp.binding.ValueBindingEx und überschreibt deren Eigenschaften, wo es nötig ist. Im Konstruktor der Klasse findet die Wertezuweisung statt; es wird hier der Teil, der in der XPage nach dem “lotusscript:” folgt, übergeben. Auch mehrzeiliger Code in der XPage wird als einfacher String durchgereich, getrennt durch Zeilenumbrüche.

Um an den Wert des Bindings zu gelangen, wird während der Verarbeitung durch das JSF Framework die Methode getValue() aufgerufen; dies ist die Stelle, an der die Daten des Value-Bindings verarbeitet werden, und wie man hier sehen kann, findet der Aufruf des LotusScript-Codes genau an dieser Stelle statt.

Das notwendige Method-Binding ist so ähnlich aufgebaut:

package ch.hasselba.xpages.jsf.el;

import com.ibm.xsp.binding.MethodBindingEx;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.MethodNotFoundException;

public class LSMethodBinding extends MethodBindingEx {
    private String data;

    public LSMethodBinding () {
        super();
    }

    public LSMethodBinding (String expr) {
        super();
        this.data = expr;
    }

    @Override
    public Object invoke(FacesContext context, Object[] obj)
        throws EvaluationException, MethodNotFoundException {
            return LSExecutor.execLotusScriptCode( content );
    }
    @SuppressWarnings("unchecked")
    @Override
    public Class getType(FacesContext context)
        throws MethodNotFoundException {
            return null;
    }
}

Hier wird wie Klasse com.ibm.xsp.binding.MethodBindingEx erweitert und wenn nötig überschrieben. Anders als bei Value-Binding erfolgt der “Abruf” der Daten eines Method-Bindings durch das JSF Framework nicht durch getValue(), sondern durch die Methode invoke(). Hierbei können theoretisch noch Parameter übergeben werden, die für die Verarbeitung relevant sein könnten. In diesem Fall kann dies aber getrost ignoriert werden.

Zu guter Letzt müssen die beiden Bindings in eine BindingFactory-Klasse zusammen geführt und mit dem “lotusscript“-Tag verbunden werden:

package ch.hasselba.xpages.jsf.el;

import com.ibm.xsp.util.ValueBindingUtil;
import com.ibm.xsp.binding.BindingFactory;
import javax.faces.application.Application;
import javax.faces.el.MethodBinding;
import javax.faces.el.ValueBinding;

public class LSBindingFactory implements BindingFactory {

    public String getPrefix() {
        return "lotusscript";
    }

    @SuppressWarnings("unchecked")
    public MethodBinding createMethodBinding(
         Application app, String expr, Class[] obj) {
        String script = ValueBindingUtil.parseSimpleExpression(expr);
        return new LSMethodBinding(script);
    }

    public ValueBinding createValueBinding(Application app, String expr) {
        String script = ValueBindingUtil.parseSimpleExpression( expr );
        return new LSValueBinding(script);
    }
}

Die Klasse erweitert com.ibm.xsp.binding.BindingFactory und ist die “Weiterleitung” innerhalb des JSF-Frameworks: Die Methode getPrefix() liefert den String zurück, für den diese BindingFactory zuständig ist; es ist praktisch jeder Bezeichner verwendbar, nur id und javascript sind bereits verwendet.

Während der Verarbeitung der Bindings such das JSF-Framework zur Laufzeit nach der passenden Factory. Dabei werden alle bekannten Factories nach dem passenden Prefix durchsucht, weshalb die BindingFactory dem Framework noch bekannt gemacht werden muss, um verwendet werden zu können.

Im zweiten Teil wird eine Quick-n-Dirty-Variante gezeigt, die BindingFactory bekannt zu machen und der LotusScript-Wrapper wird vorgestellt.

*: Anmerkung:

Aus Sicht der JSF-Spezifikation ist der Unterschied zwischen Value-Binding und Method-Binding etwas komplexer, als hier dargestellt.

Quick-n-Dirty: Leeres NotesDocumentCollection-Objekt instanzieren

28. November 2011 Posted by Sven Hasselbach

Domino bietet Out-of-the-Box leider keine Möglichkeit, ein leeres NotesDocumentCollection-Objekt zu instanzieren. Um trotzdem in den Genuss zu kommen, mit einer leeren NotesDocumentCollection arbeiten zu können, ist der schnellste Weg, einfach alle Dokumente der Datenbank zu verwenden und von sich selbst “abzuziehen”.

In SSJS sieht das wie folgt aus:

/*****
 *** getEmptyDocumentCollection()
 *** returns an empty NotesDocumentCollection-Object
 *****/

function getEmptyDocumentCollection(){
   var dc:NotesDocumentCollection = null;
   try{
      dc = database.getAllDocuments();
      dc.subtract( database.getAllDocuments() );
   }catch(e){}
   return dc;
}

/*** how to use ***/
var hlpDC:NotesDocumentCollection = getEmptyDocumentCollection();
var hlpDoc:NotesDocument =  database.getAllDocuments().getFirstDocument();

hlpDC.addDocument ( hlpDoc )
hlpDC.getCount();

Die gleiche Funktionalität sieht in LotusScript wie folgt aus:

%REM
    Function getEmptyDocumentCollection
    Description: Returns an empty document collection
    Created Nov 28, 2011 by Sven Hasselbach
%END REM
Function getEmptyDocumentCollection() As NotesDocumentCollection
    
    On Error GoTo errH
    
    Dim session As New NotesSession
    Dim db As NotesDatabase
    Dim dc As NotesDocumentCollection
    
    Set db = session.Currentdatabase
    Set dc = db.Alldocuments
    dc.Subtract db.Alldocuments
    Set getEmptyDocumentCollection = dc
    
the_end:
    Exit Function

errH:
    Print "getEmptyDocumentCollection() - Error #" & Err & _
    ": '" & Error & "' @ Line " & Erl
    Resume the_end

End Function

Und schon hat man eine leere NotesDocumentCollection, der man nach Belieben arbeiten kann!

OpenNTF XSnippets Beta gestartet

22. November 2011 Posted by Sven Hasselbach

XSnippets – The next generation code bin, ist als Beta gestartet.

Das neue OpenNTF Projekt dient als Sammlung für kleine Code-Schnippsel, die von der Community für die Community zur Verfügung gestellt werden. Hier sind die ersten Schnippsel zu finden.

Verändern der Schablonennamen via Lotus Script

22. August 2011 Posted by Jörg Fengler

Mehrere Datenbanken sollen das Design von einer Schablone erben? Kein Problem mit wenigen Handgriffen ist eine derartige Aufgabe gewöhnlich gemeistert. Wesentlich komplizierter wird es jedoch, wenn das Design einer Schablone verborgen werden soll. Mit Hilfe einer zweiten Datenbank, einem Replace …