Mindoo Blog - Cutting edge technologies - About Java, Lotus Notes and iPhone

  • XPages series #12: XAgents and performance bottlenecks

    Karsten Lehmann  17 July 2011 10:18:55
    XAgent is a term that describes the equivalent of a classic Notes web agent in XPages technology: an XPage is called via URL and produces any kind of data (e.g. HTML, dynamic images, data in JSON format or ODF documents) by sending Strings or even raw bytes directly to the browser.
     
    Chris Toohey and Stephan Wissel have already blogged about this topic a few years ago and discussed some use cases:

    IBM Lotus Notes Domino REST Web Services via XPage XAgents
    Web Agents XPages style

    How to write an XAgent
    To write an XAgent, you basically need to do two things: first you add the attribute rendered="false" to the xp:view tag of an XPage to prevent the XPages engine from rendering any output. Second, you need to write the code that produces the data and add it to the beforeRenderResponse event of an XPage.

    Here are two simple example XAgents. The first one writes XML content to the writer object of the servlet response (which is used to return character data):

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

            <xp:this.beforeRenderResponse>><![CDATA[#{javascript:try {
            var uName = session.createName(session.getEffectiveUserName());
            var exCon = facesContext.getExternalContext();
            var response = exCon.getResponse();
            var writer = response.getWriter();
            response.setContentType("text/xml");
            response.setHeader("Cache-Control", "no-cache");
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            writer.write("<test>Hello "+uName.getAbbreviated()+"</test>");
            facesContext.responseComplete();
    }
    catch (e) {
            _dump(e);
    }}]]></xp:this.beforeRenderResponse></xp:view>


    The second example demonstrates how you can load a file resource from the database design and write it to the output stream object of the servlet response:

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

            <xp:this.beforeRenderResponse><![CDATA[#{javascript:try {
            var exCon = facesContext.getExternalContext();
            var response = exCon.getResponse();
            var outStream = response.getOutputStream();
           
            //load a file resource from db design and write it to the output stream
            var cl=com.ibm.domino.xsp.module.nsf.NotesContext.getCurrent().getModule().getModuleClassLoader();
            var buf=new byte[16384];
            var len;
            var filePath="/test/document.docx";
            var inStream=cl.getResourceAsStream(filePath);
            if (inStream==null) {
                    var errMsg="File "+filePath+" not found";
                    _dump(errMsg);
                    response.sendError(404, errMsg);
            }
            else {
                    //set mime type according to file type
                    response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
                    response.setHeader("Cache-Control", "no-cache");
                    //send http header with filename (needed so that the browser does not use the XPage's name as filename)
                    response.setHeader("Content-Disposition", 'attachment; filename="document.docx"');
                   
                    //read bytes from inStream into the buffer
                    while ((len=inStream.read(buf))>-1) {
                            //and write the data to the output stream
                            outStream.write(buf, 0, len);
                    }
                    inStream.close();
                    outStream.close();
            }
            facesContext.responseComplete();
    }
    catch (e) {
            _dump(e);
    }}]]></xp:this.beforeRenderResponse></xp:view>


    The loaded file resource must be part of the NSF project's build path to make the XPage code find it (that's where the module classloader is looking for files).
    Please refer to part two of this XPages series to get more information about how to add a folder to the build path.


    Image:XPages series #12: XAgents and performance bottlenecks

    If you just want to offer a file for download, developing an XAgent to stream the data would probably be the wrong solution.

    In that case, you should add your files to the database design in the Java Perspective of Domino Designer instead and reference them directly in a URL. This is something that is used in the OpenNTF project XPages Mobile Controls to add a newer Dojo version (e.g. 1.6 with advanced mobile device support) to a Domino server than the default one (e.g. version 1.4.3 for Lotus Domino 8.5.2).

    But the second code example from above could of course be modified to do something more useful, for example to replace placeholders in the file, create a zip file from multiple files on the fly using Java's ZipOutputStream or render a captcha image for spam protection.

    Sounds great?
    Well, before you start thinking about building large applications based on this approach, you should read on to get to know about it's limitations.

    The limitations
    What I'm going to show you is not only a limitation of XAgents, but of XPages technology in general. For XPages producing the user interface of an application, this limitation may not be critical, but it can become a show stopper for using them the XAgent way.

    I've created a small test application with three XPages: Start.xsp, Frame1.xsp and Frame2.xsp (download link). The first one (Start.xsp) contains two iframes and a button. By clicking on the button, the other two XPages are loaded as iframe contents:

    var now=new Date();
    var nowTime=now.getTime();
    var iframe1=document.getElementById("iframe1");
    var iframe2=document.getElementById("iframe2");
    iframe1.src=document.location.pathname+"/Frame1.xsp?startTime="+nowTime;
    iframe2.src=document.location.pathname+"/Frame2.xsp?startTime="+nowTime;


    Frame1.xsp contains beforeRenderResponse event code that delays the rendering for 5000 milliseconds (java.lang.Thread.sleep(5000)), while Frame2.xsp waits for 2500 milliseconds.

    You would expect that both XPages are opened and rendered concurrently. Because of the delay, Frame2.xsp should be visible first, then Frame1.xsp.

    Unfortunately, the output looks like this instead (click on the image to open the test application):

    Image:XPages series #12: XAgents and performance bottlenecks

    Frame1.xsp is displayed first, followed by Frame2.xsp. Frame2.xsp takes about 7600 milliseconds to be rendered, which is the sum of both delays, because the XPages engine does not render multiple XPages in an application at the same time for a single user.

    It was a real surprise when we noticed this effect in a customer project. Our UI was based on the Ext JS toolkit which is heavily using REST services to read the UI component data. Our plan was to use XAgents for those REST services, but we quickly noticed that all UI elements only received data one at a time, which was a no-go for the application UI performance. Nothing was loading in parallel.

    There are technical reasons for such a restriction, as Philippe Riand, XPages Chief Architect at IBM, explained to me in April 2011.
    As you may know, the XPages runtime is based on JSF technology, which uses a tree of components on the server side that represents the structure of a web page and its current state. The tree is created/restored when a HTTP request comes in and saved/discarded after it is completed.
    Unfortunately, this tree does not support being accessed by two concurrent threads, which would break many things like data sources and component states.

    To prevent this to happen, XPages synchronizes on the user session object (HttpSession), so that only one thread can access the tree at a time. But by synchronizing on the user session (and not just on the component tree instance), the XPages dev crew also prevented any other XPage in an application from running (the Domino server seems to assign session objects on a per database basis).

    The consequence is that you should think twice about your XPages application architecture, if you have many concurrent HTTP requests or if some of them take a lot of time to be processed. An XAgent may be the easiest solution to deploy, but may not produce the best user experience in all cases.

    Workarounds and solution
    There is some hope that the situation will improve in Domino 8.5.3.

    As Philippe said, the next XPages engine will be smarter in synchronization and synchronize less than previously.
    In addition, there will be a new database property called xsp.session.transient. This flag means that unique session objects will be created per request to the server and discarded right after the request ended. This is a first attempt to provide session less mode.
    If you use this option, then you can create one database with all the services and no synchronization will happen, as each request will have its own session object.

    I have requested a more granular option so that you can activate session less mode for single XPages in an application, but I'm not sure if this will make 8.5.3.

    For Domino 8.5.2, I can see three things that you can do if you can't live with this performance issue:

    1. Move XPages that run for a long time to separate databases
    This does not help a lot, but if you have an XPages application with an XPage that needs some time to process, you could move it to different database. In that case, the rest of the application would still be responsive, although your long running XPage for example produces a 100 MB log output in XML format that you want to download.

    2.  Write your own servlet in Java
    By writing your own servlet as an Eclipse plugin running in Domino 8.5.2's OSGi framework, your code can run without any performance bottleneck caused by synchronization. See this article in the OpenNTF blog for implementation details.

    3. There is an app for that :-)
    As you may know, we have developed XPages2Eclipse, an extensive toolkit for XPages development, which is currently in beta-testing (June 2011). The articles Building servlets in JavaScript and Building servlets in Java demonstrate how you can write your own servlet implementation in JavaScript or Java language.
    This solution reads all your code from the database design, so that you don't have to deploy every servlet individually. Just install the toolkit on a Domino server once, then you can create any number of servlets by writing code in Domino Designer and replicate them to the server.


    Comments

    1Stephan H. Wissel  18.07.2011 06:46:55  XPages series #12: XAgents and performance bottlenecks

    Writing XML content using String operations is punishable by {insert-something-unpleasant-here}. Use SAX to write it out. Takes care of encoding and escaping:

    { Link }

    (of course for demo purposed this transgression is forgiven) :-)

    2Karsten Lehmann  18.07.2011 07:15:31  XPages series #12: XAgents and performance bottlenecks

    You are so right! That code snippet was taken (and slightly modified) from your blog posting about XAgents:

    { Link }

    :-))

    And to write JSON code, you should use the Jackson project:

    { Link }

    3Philippe Riand  19.07.2011 15:09:58  XPages series #12: XAgents and performance bottlenecks

    Hey Karsten,

    This is fixed in the latest 8.5.3 (post CD5), as the synchronization no longer happens on the session itself, but on the page instance. In short, it can now execute 2 different page instances at the same time, while it locks for postback calls to the same page instance and execute them sequentially.

    4Karsten Lehmann  19.07.2011 15:23:02  XPages series #12: XAgents and performance bottlenecks

    Hi Philippe,

    great to hear! Good to see that 8.5.3 is not completely closed for modifications with CD5.

    5Russell Maher  19.07.2011 16:45:46  XPages series #12: XAgents and performance bottlenecks

    Phillipe,

    Excellent! Thanks.

    6Fredrik Stöckel  17.08.2011 11:48:56  XPages series #12: XAgents and performance bottlenecks

    @3 Philippe : Thank you! -- I just ran into a problem related to this (rest solution) so I'm very glad to hear that it has been resolved

    performance is everything :)

    @Karsten - thank you for a very good article and explanation (as always) about the problem. This article saved me a lot of investigation time.

    7Paul Hannan  18.08.2011 14:37:37  XPages series #12: XAgents and performance bottlenecks

    There's a small issue with the reload button on the app where the url isn't being built right.

    Change the script to...

    <xp:this.script><![CDATA[var now=new Date();

    var nowTime=now.getTime();

    var iframe1=document.getElementById("iframe1");

    var iframe2=document.getElementById("iframe2");

    var prefix = document.location.pathname.substring(0,document.location.pathname.lastIndexOf('/'));

    iframe1.src=prefix+"/Frame1.xsp?startTime="+nowTime;

    iframe2.src=prefix+"/Frame2.xsp?startTime="+nowTime;

    ]]></xp:this.script>

    8Daniel Lindskog  02.12.2011 15:54:18  XPages series #12: XAgents and performance bottlenecks

    Thank you for this article!

    We have seen this behavior sometimes and never was able to explain it.

    This makes it all more understandable and we will implement it immediately for some of our customers that are lucky to be on 8.5.3.

    /Wave @ fredrik Stöckel

    9Vikas Tiwari  29.03.2012 11:38:08  XPages series #12: XAgents and performance bottlenecks

    I have tested the sample code of "XPages Syncronization test" in 853 and it's been working as expected! So no performance worries for XAgents now!

    10  22.05.2012 09:58:29  XPages series #12: XAgents and performance bottlenecks

    There is another bottleneck I faced. Any suggestion / solution will be great.

    For a particular reason, the xagent I wrote should have run under server's right (on behalf of server).

    However, I found that is no such option available as in lotusscript agent.

    Anyway to do this??

    11Karsten Lehmann  22.05.2012 10:38:22  XPages series #12: XAgents and performance bottlenecks

    Maybe by using sessionAsSigner and signing the XPage with the server id (haven't tried it yet).

    If that does not help, here is a more challenging solution:

    You could create an Eclipse plugin. If you call NotesFactory.createSession() from the plugin code (e.g. from the Activator class), you get a server ID session. Don't think you get a server session when you are already running in an XPages thread, because the XPages web user session has already been created.

    You would need to develop some tunneling between your XPages application and the plugin code.

    For example in the Activator, start a NotesThread that is running with server privileges and waiting for tasks that get scheduled in a BlockingQueue

    ( { Link } ).

    To call plugin code from XPages code, you could create a custom XPages control ( { Link } ).

    Methods in the control can be called from SSJS via:

    getComponent("controlId").myMethod(1,2,3);

    But you should think about security implications when you create such a solution.

    Only execute specific tasks with the server id, not any java.lang.Runnable that is passed from XPages code to the plugin.

    12Brian Tweed  20.11.2012 12:47:14  XPages series #12: XAgents and performance bottlenecks

    Hi Karsten

    I'm using your second method to load up a word template. How would I add data from a notes document to the template

    Thank

    13Karsten Lehmann  20.11.2012 15:29:12  XPages series #12: XAgents and performance bottlenecks

    Brian,

    you could use Apache POI for several Office file types

    { Link }

    or jXLS for Excel sheets

    { Link }

    I'm not sure if they can be used in XPages apps without changing the java.policy (because of SecurityManager restrictions).

    We only used jXLS so far in a servlet to generate XLS files.

    14Wilson  28.12.2012 21:22:40  XPages series #12: XAgents and performance bottlenecks

    Xpages are definitely the best and the worst thing for Domino.

    The best thing because we can now do things that was previously impossible to do with Domino.

    The worst because, the more specialized the component become, the less reusable and understable they are for "the common developers".

    A few years ago, any developer could jump in Domino technology and discover its cool feature.

    I know NOBODY that would try Xpages development without being first a JEDI in Domino Web Development !

    Not talking about the mess in all the IBM documentations, with a lot of "TO DO" chapters.

    Finally, despite all the good work of the IBM developpers, Xpages is still a nice alpha version of a funny sandbox - that has to be "patched" with the last version of the Extension Library -, but, for sure, it's not an environment to produce a huge production application with confidence.

    Hope IBM will wake up one day and focus, one day, on what really makes a great user experience : great out of-the-box widgets, powerful grids (ANY apps require it), dynamic views (wow, this one is a ten years old wish... is there somebody up there ???)