Posts Tagged: ‘ServerSide JavaScript’

XPages: Running Google’s Chrome V8 Javascript Engine (2)

10. April 2015 Posted by Sven Hasselbach

A while ago I tried to run Google’s V8 Javascript engine on top of XPages, and today I found the reason why my server crashed after the first click: I have tried to load the engine only once (statically), and that killed Domino completly.

Today I moved the code back into the processAction method, and now it works without problems.

package ch.hasselba.xpages.jav8;

import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JAV8Test implements javax.faces.event.ActionListener {

    public void processAction(ActionEvent actionEvent)
            throws AbortProcessingException {

        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName("jav8");

        try {
            System.out.println(engine.getClass().getCanonicalName());
            engine.eval("var i=1+1;");
            System.out.println("i = " + engine.get("i"));
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
    }
}

REST & Security: Same-Origin Policy / CORS

2. Februar 2015 Posted by Sven Hasselbach

The “Same-orginin policy is an important concept for protecting web applications. In short, only resources from the same domain are allowed, everything else is permitted. To allow access other domains in your application, you have to enable CORS, a tutorial how to enable this on a Domino server was written by Mark Barton a while ago.

It works fine for protecting an applications against DOM manipulations and/or injection of malicous script code, but this client side security restriction only blocks the response from the server. The client still sends a request, and this can be problematic for the security of a RESTful application.

To clearify this, here is a short example:

I have created a small HTML page containing an Ajax request to load some code of a XPages-based REST service on another server. This file is hosted on my hasselba.ch server, and wants to access some data on my local Domino server:

<html>
   <body>
   <h1>SOP Demo</h1>
   <script>
      var xhr =(window.XMLHttpRequest)?new XMLHttpRequest():
          new ActiveXObject("Microsoft.XMLHTTP");

      xhr.open("GET","http://localhost/REST.nsf/SOPDemo.xsp/foo/",true);
      xhr.withCredentials = true;
      xhr.send();
   </script>

   </body>
</html>

The “withCredential” options ensures that an eventually existing Domino session is used when performing the request.

The REST service on my Domino server prints the actual username to the console:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view
    xmlns:xp="http://www.ibm.com/xsp/core"
    xmlns:xe="http://www.ibm.com/xsp/coreex"
    rendered="false">
    
    <xe:restService
        id="restService"
        pathInfo="foo">
        <xe:this.service>
            <xe:customRestService
                requestContentType="application/json"
                requestVar="data">             
                <xp:this.doGet>
                   <![CDATA[#{javascript:
                      print("Hello '" + session.getEffectiveUserName() + "'");
                      "{}"
                   }]]>
                </xp:this.doGet>
             </xe:customRestService>
         </xe:this.service>
     </xe:restService>
</xp:view>

When opening this page, the response of the request is blocked, and that’s what the Same-origin policy was made for: If the response contains malicious Javascript code, this script won’t be executed.

01

The client is protected, but what about the request send to the server?

02

The request was made with my credentials, and that is why the “Same origin-policy” does not protect RESTful applications: If a victim visits my page, I am able perform malicious requests against a RESTful webservice in his context.

XPages: Running Google’s Chrome V8 Javascript Engine

9. November 2014 Posted by Sven Hasselbach

After answering a question on Stackoverflow.com about the Prototype problematic in the XPages SSJS engine, I thought of running another Javascript engine on top of Domino.

While you can use the JavaScripting API JSR223, I choosed the jav8 project for a test how this can be realized. So I downloaded the Windows binaries to get the required DLL and imported it into a new database. I also imported the source files of the lu.fler.script package to recompile all required classes.

Then, I registered the factory service by creating a javax.script.ScriptEngineFactory file in the /META-INF/services folder and added the line lu.flier.script.V8ScriptEngineFactory.

The package explorer looked like this:

To prevent collisions, I commented out some names in the V8ScriptEngineFactory class:

For a simple test, I decided to invoke the engine manually when clicking on a button on a XPage. To do this, I created a simple ActionListener in Java which loads the JavaScript Engine and evals a simple ” var i = 1+1″. The Javascript variable is then accessed and printed out to the server console.

package ch.hasselba.xpages.jav8;

import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JAV8Test implements javax.faces.event.ActionListener {

    static ScriptEngineManager factory = new ScriptEngineManager();
    static ScriptEngine engine = factory.getEngineByName("jav8");

    public void processAction(ActionEvent actionEvent)
            throws AbortProcessingException {
        try {
            System.out.println( engine.getClass().getCanonicalName() );
            engine.eval("var i=1+1;");
            System.out.println( "i = " + engine.get("i") );
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
    }
}

The XPage to test the ActionListener is rather simple too:

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

    <xp:button
        value="Click Me!"
        id="buttonClickMe">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete">
            <xp:this.actionListeners>
                <xp:actionListener type="ch.hasselba.xpages.jav8.JAV8Test" />
            </xp:this.actionListeners>
        </xp:eventHandler>
    </xp:button>
    
</xp:view>

When the button is clicked, the V8 engine works as it should:

But now comes a hard problems: It works only once! After doing this, my test server crashes completly. During playing with it, I was able to run it as it should, but I have no idea anymore how I did it. I think it is the DLL and static classes, but I am currently to busy to investigate the problem further. The @Override notations added to the methods (which must removed before the code can be compiled) do not override exitisting ones (I checked the bundled javax.script JAR of the binary package), this does not seem to be the problem. Maybe someone else has an idea?

XPages & Angular.js: Accessing Rich Text (1)

5. Juni 2014 Posted by Sven Hasselbach

XPages & Angular.js: Fileuploads

2. Juni 2014 Posted by Sven Hasselbach

When using Angular.js you sooner or later want to upload a file to your Domino server. But to do this, you not only need some nice looking frontend, you also need some code in the backend. For the frontend you can use one of the already exsiting modules which are available for Angular.js, for example the angular-file-upload. For a quick start, I have choosen to modify the provided Simple example.

After stripping down the example files (just saved the whole website with Firefox), you can import all the files into a new database. Mark Roden has written a very good tutorial about the details for this step.

The WebContent folder should look like this:

Now you have to modify the index.html and make all pathes relative:

When you open the index.html in the browser you will see some errors in the console, because of some missing fonts, but we will ignore this issue in this quick example.

Instead, we are adding a link for a download of an uploaded file:

&#160;<a download="{{ item.file.name }}" ng-href="{{ item.file.dlPath }}">download</a></td>

The download link will be filled with the URL of the uploaded attachment in the target database after a successfull upload.

Now it’s time to tell the frontend where to store the file in the backend. For this, you have to modify the controller.js and define the target of the upload process. In this example, the target is a XPage named “upload.xsp“. After uploading a file to the server, the XPage returns the URL of the attachment as a JSON string. To update the link in the frontend, we bind a function to the event “success” which adds the URL to the current file item:

angular.module('app', ['angularFileUpload'])

.controller('TestController', function ($scope, $fileUploader) {
    'use strict';

    // create a uploader with options
    var uploader = $scope.uploader = $fileUploader.create({
        scope: $scope,   
        url: 'upload.xsp'
    });

    uploader.bind('success', function (event, xhr, item, response) {
        // add the response url to the file item 
        item.file.dlPath = response.url;
    });

});

 [This is the complete controler.js which replaces the file from the module's example.]

The XPage handles the uploaded files them directly without a file upload control. Every uploaded file will be attached to a single document, and embedded to the Richtext item “Body“. If the upload was successfull, the XPages returns the JSON data containing the URL to the newly created attachment.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
    <xp:this.data>
        <xp:dominoDocument var="documentFile" action="editDocument" concurrencyMode="force" />
    </xp:this.data>

    <xp:this.afterRenderResponse>
        <![CDATA[#{javascript:

            /**
             * saves a uploaded attachment to a datasource
             *
             * @param ds
             *        the datasource
             * @param rtItemName
             *        the richtext item to save the file
             * @return
             *        the name of the uploaded file
             * @author Sven Hasselbach
             */
            function saveAttachment( ds:NotesXspDocument, rtItemName:String ):String {

                // get the file data
                var con = facesContext.getExternalContext();
                var request:com.sun.faces.context.MyHttpServletRequestWrapper = con.getRequest();
                var map:java.util.Map = request.getParameterMap();
                var fileData:com.ibm.xsp.http.UploadedFile = map.get( "file" );

                if( fileData == null )
                      return;

                // get the file
                var tempFile:java.io.File = fileData.getServerFile();
                var correctedFile = new java.io.File( tempFile.getParentFile().getAbsolutePath() +
                java.io.File.separator + fileData.getClientFileName() );
                var success = tempFile.renameTo(correctedFile);

                // create or use an existing RT item
                var rtFiles:NotesRichTextItem = null;
                if(!(ds.getDocument().hasItem( rtItemName ))){
                    rtFiles = ds.getDocument().createRichTextItem( rtItemName )
                }else{
                    rtFiles = ds.getDocument().getFirstItem( rtItemName );
                }

                // embed the file
                rtFiles.embedObject(lotus.domino.local.EmbeddedObject.EMBED_ATTACHMENT, "",
                      correctedFile.getAbsolutePath(), null);

                  // rename the file back for server processing
                  correctedFile.renameTo(tempFile);

                  // save the datasource
                ds.save();

                // return the filenam
                return fileData.getClientFileName();

            }

            // save the doc
            var file = saveAttachment( documentFile, "Body" );

            // create the response
            var res = facesContext.getExternalContext().getResponse();
            res.setContentType( "application/json" );
            res.setCharacterEncoding( "UTF-8" );
            var writer = res.getWriter();

            if( file != null ){
                // send a JSON url string back to the client
                var url = documentFile.getDocument().getHttpURL();
                var fileUrl = url.replace("?OpenDocument","/$File/"+file+"?OpenElement");
                writer.write( '{"url": "' + fileUrl + '"}' );    
            }else{
                // otherwise send empty JSON data
                writer.write( '{}' );
            }
            writer.flush();
            facesContext.responseComplete();
        }]]>
    </xp:this.afterRenderResponse>
</xp:view>

After uploading a file, you can see that the file was uploaded successfully in the firebug console:

When clicking the “download” link, the file is available to download:

And the attachment was uploaded to the database:

That’s it!

XPages: SSJS & How you can have fun at the office

4. März 2014 Posted by Sven Hasselbach

This article is a demonstration of what harmful things you can do when using the SSJS & prototyping wrong( decribed here by the great Mark Roden). Don’t do that! Especially not on a productive server!
 

Wanna have a lot of fun in the office with the other developers? Just overwrite some global SSJS functionality! They will never find out what happend to their applications!

 

This is the application we destroy

Our demonstration application uses a small Java class which has only a single method:

package ch.hasselba.xpages;

public class Demo {

    public String foo() {
       return "bar";
    }
 }

This class is used for a label on our XPage and is loaded with the importPackage functionality of SSJS:

<?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:
                importPackage( ch.hasselba.xpages );
                return new ch.hasselba.xpages.Demo().foo();}]]>
        </xp:this.value>
    </xp:label>

</xp:view>

When opening the XPage, nothing special happens, the label is filled with the value “bar”:

Now let’s destroy it!

Add a button on the XPage, with containing a single line of SSJS code…

<xp:button
        value="Click Me"
        id="button1">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete">
            <xp:this.action>
                <![CDATA[#{javascript:prototype.importPackage = null;}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>

… and reopen the XPage:

Question: What will happen if you click the button?

Answer:

Exactly: The method is destroyed on the whole server! Every XPage which imports a Java package with SSJS will not work anymore. Only a complete server restart helps now. The error message you will receive won’t help you, because it looks like the Java classes could not be found. Now “help” the other developers with tips like “Maybe a build problem? Have you tried to clean the application?”.

P.S. You can execute the code on other ways, f.e, with an eval statement, or a method binding of your choice.

Tested on ND 8.5.3 & ND 9.0

Quick-n-Dirty: Ajax Fileuploads

9. September 2013 Posted by Sven Hasselbach

Here is an example how to upload a file with Ajax to a XPage. It is a simple Javascript, and adds the required fields to the xhr request. This example works in FireFox and Chrome and should work on Domino >=8.5.2.

<?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="RTItem"
            action="editDocument"
            concurrencyMode="force">
        </xp:dominoDocument>
    </xp:this.data>

    <xp:fileUpload onchange="handleFiles(this.files)"
        id="fileUpload1"
        value="#{document1.Body}">
    </xp:fileUpload>

    <xp:eventHandler event="onsaveDoc" submit="false" refreshMode="norefresh" immediate="false" save="true" id="saveDoc" />

    <xp:button
        value="ajaxSave"
        id="buttonAjax">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script><![CDATA[sendFiles();]]></xp:this.script>
        </xp:eventHandler>
    </xp:button>

    <xp:div id="fileData" />

      <xp:scriptBlock id="scriptBlockAjax">
          <xp:this.value>
          <![CDATA[

          function handleFiles(files) {
            var i = 0;
            var dataDiv = dojo.byId('#{id:fileData}');
             var fileList = files;

            for(i = 0; i < fileList.length; i++)
            {
                    var img = document.createElement("img");
                    img.file = fileList[i];
                    img.name = 'no_'+ i;
                    img.classList.add("fileData");

                    var reader = new FileReader();
                    reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
                    reader.readAsDataURL(fileList[i]);

                    dataDiv.appendChild(img);    
            }
        }

        function FileUpload(img, file) {
             var xhr = new XMLHttpRequest();
            this.xhr = xhr;

            var frm = new FormData;
            frm.append( "#{id:fileUpload1}", file);
            frm.append( "$$viewid", dojo.byId('view:_id1__VUID').value);
            frm.append( "$$xspsubmitid", "#{id:saveDoc}");
            frm.append( "view:_id1", "view:_id1");

            xhr.open("POST", "#{javascript:facesContext.getExternalContext().getRequest().getRequestURI()}", true);
            xhr.send(frm);
        }

        function sendFiles(){
          var files = document.querySelectorAll(".fileData");

          for(var i = 0; i < files.length; i++)
          {
              new FileUpload(files[i], files[i].file);
          }

        }
        ]]>
        </xp:this.value>
    </xp:scriptBlock>

</xp:view>

The fileupload control executes the javascript method handleFiles when a new file is selected. This method “converts” the file to a DOM image and attaches it to the DIV fileData.

When clicking the button ajaxSend, the sendFiles method is fired and sends all selected files with xhr Requests to the server. The value of the $$submitid identifies a server side event which saves the document with the received attachment as new document.

Keep in mind: There is NO full refresh.

If you selecting some file in the browser…

.. and click on “ajaxSave“, the files are posted to the server:

The server side event saves the uploaded files to new documents:

XPages: Create your own Required Validators

20. Juni 2013 Posted by Sven Hasselbach

If you try to implement your own JSF Validator (by implementing javax.faces.validator.Validator), you will notice that you are unable to check for an empty value. The reason for this is rather simple: The method validate() is only called, if there is something to do. If the value is empty, there is nothing to do, that’s why nothing happens.

But wait! What about the available required validator for XPages?

<xp:inputText id="inputText1">
   <xp:this.validators>
      <xp:validateRequired></xp:validateRequired>
   </xp:this.validators>
</xp:inputText>

And yes, this is a validator, but the IBM implemented a workaround for their UIComponents: If the UIComponents required property is set to true, the UIComponent knows that she needs to be filled in. Or a validator has to be attached to the UIComponent which implements the com.ibm.xsp.validator.FacesRequiredValidator interface.

Here is an example:

package ch.hasselba.xpages.core;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import com.ibm.xsp.validator.FacesRequiredValidator;

public class RequiredValidator implements FacesRequiredValidator {

    public String getRequiredMessage() {
        return "Error! Required field is empty!";
    }

    public void validate(FacesContext facesContext, UIComponent uiComponent,
            Object obj) throws ValidatorException {

    }

}

When processing the UIComponent data, the attached validators are checked if they are an instance of FacesRequiredValidator, and this is the reason why you cannot attach this validator to a component in the DDE directly: Because if you define the validator in the faces-config.xml and add it to the UIComponent with the validator id…

<xp:inputText id="inputText1">
   <xp:this.validators>
      <xp:validator validatorId="myValidator" />
   </xp:this.validators>
</xp:inputText>

… the XPages engine attaches a validator which is not implementing the FacesRequiredValidator interface. Instead, an instance of com.ibm.xsp.validator.ValidatorImpl is attached.

You have to do this by your own, for example in the beforeRenderResponse event:


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

   <xp:this.beforeRenderResponse>
      <![CDATA[#{javascript:getComponent('inputText1').addValidator(
         new ch.hasselba.xpages.core.RequiredValidator()
      );}]]>
   </xp:this.beforeRenderResponse>

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

   <xp:br />

   <xp:messages id="messages1"></xp:messages>

   <xp:br />

   <xp:inputText id="inputText1" />

</xp:view>

[This example is just for a demonstration. It contains a bug, the validator will attached over and over again, so you have to check if the validator is already attached or not]

If you now open the XPage, your validator will work as expected:

 

XPages: Dojo 1.8.1 & jQuery Mobile 1.3.1

25. April 2013 Posted by Sven Hasselbach

As David Leedy got into trouble with Dojo 1.8.1 and jQuery Mobile 1.3.1  and after reading the follow up from Ulrich Krause with the analysis of the problem, I thought that this problem is caused from the AMD loader of Dojo. That’s why I changed the loading order (by forcing the jQuery libraries to get loaded before Dojo), and this seems to work.

I have tested it in IE 8, FF 20.0 and Chrome 26.0.

Here is my test XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view
    xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.properties>
        <xp:parameter
            name="xsp.resources.aggregate"
            value="true" />
    </xp:this.properties>
    <xp:this.resources>
        <xp:styleSheet
            href="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.css" />
        <xp:headTag
            tagName="script">
            <xp:this.attributes>
                <xp:parameter
                    name="type"
                    value="text/javascript" />
                <xp:parameter
                    name="src"
                    value="http://code.jquery.com/jquery-1.9.1.min.js" />
            </xp:this.attributes>
        </xp:headTag>
        <xp:headTag
            tagName="script">
            <xp:this.attributes>
                <xp:parameter
                    name="type"
                    value="text/javascript" />
                <xp:parameter
                    name="src"
                    value="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.js" />
            </xp:this.attributes>
        </xp:headTag>
    </xp:this.resources>

    <div
        data-role="page"
        class="jqm-demos"
        data-quicklinks="true">

        <div
            data-role="content"
            class="jqm-content"
            id="contentRefresh">
            <h2
                id="accordion-markup">
                <script>document.write(dojo.version)</script>
            </h2>

            <div>
                <div
                    data-role="collapsible-set"
                    data-theme="c"
                    data-content-theme="d">

                    <div
                        data-role="collapsible">
                        <h3>Section</h3>
                        <p>
                            <xp:label
                                value="#{javascript:java.lang.System.currentTimeMillis()}"
                                id="labelRefresh" />
                        </p>
                    </div>
                </div>
            </div>
            <a
                href="#"
                onclick="XSP.partialRefreshGet('#{id:labelRefresh}')"
                data-role="button">Refresh Label</a>
        </div>
    </div>

</xp:view>

And this is the result:

The XSP object is loaded correctly and Partial Refreshs are executed correctly:

It is required that the resource aggregation is enabled. More details can be found here.

Quick-n-Dirty: How to add HTML 5 events

26. März 2013 Posted by Sven Hasselbach

One way for adding unsupported events to an XPage or a component is the trick from Keith. But this is limited to CSJS only. If you need to execute a server side event, you just need change the name of the event to a new HTML 5 event name which does not exist in the DDE.

Here is an example for the new onSearch event:

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

    <xp:inputText id="inputTextSearch" type="search">
        <xp:eventHandler event="onsearch" submit="true"
            refreshMode="partial" refreshId="labelSearchFor">
            <xp:this.action>
               <![CDATA[#{javascript:print("onSearch");}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:inputText>

    <xp:br />
    <xp:br />

    <xp:label id="labelSearchFor">
        <xp:this.value>
           <![CDATA[#{javascript:getComponent("inputTextSearch").value}]]>
        </xp:this.value>
    </xp:label>

</xp:view>

After entering a value and hitting enter…

… you can see on the server console that the event occurred:

This technique allows to add new events to the whole XPage, for example the onContextMenu event:

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

        <xp:eventHandler event="oncontextmenu" submit="true"
            refreshMode="norefresh">
            <xp:this.action>
                <![CDATA[#{javascript:print("onContextMenu");}]]>
            </xp:this.action>
        </xp:eventHandler>

</xp:view>

Every time if the user opens the context menu in the browser, the event is triggered and executed on the server:

The name of the event must be lowercased. A list of HTML 5 events can be found here: http://www.tutorialspoint.com/html5/html5_events.htm. Not all browsers support every HTML 5 event in the list.

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

A performance bottleneck?

17. Februar 2013 Posted by Sven Hasselbach

Paul Withers wrote an very interesting article about the difference between a passthrough UIComponents and the corresponding XPages elements. This means the use of <br> instead a <xp:br>, or a <div> instead of a <xp:div>. A while ago I have tested if this would affect the performance, and as far as I know it makes no difference.

Here is the code I used to test the performance:

<?xml version="1.0" encoding="UTF-8"?>
 <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <h1>Name of the Element</h1>
    <xp:repeat id="repeatFull" rows="9999999">
       <xp:this.value><![CDATA[#{javascript:
          var arr:java.util.Vector = new java.util.Vector();
          for( var i=0; i<10;i++){
             arr.add( i );
          }
          var start = java.lang.System.currentTimeMillis();

          arr}]]>
       </xp:this.value>

       <xp:repeat id="repeat1" rows="9999999">
          <xp:this.value><![CDATA[#{javascript:
             var arr:java.util.Vector = new java.util.Vector();
             for( var i=0; i<100000;i++){
                arr.add( i );
             }
             var start = java.lang.System.currentTimeMillis();
             arr}]]>
          </xp:this.value>

          <!-- HERE COMES THE REPEATING ELEMENT //-->

       </xp:repeat>
       <xp:label id="labelTimer">
          <xp:this.value>
             <![CDATA[#{javascript:
                var end = java.lang.System.currentTimeMillis();
                "Total " + (end - start) + " ms."
             }]]>
          </xp:this.value>
       </xp:label>
    </xp:repeat>
    <xp:br />
    <xp:button value="Refresh" id="buttonRefresh">
       <xp:eventHandler event="onclick" submit="true"
          refreshMode="partial" refreshId="repeatFull">
       </xp:eventHandler>
   </xp:button>

</xp:view>
  • Test with a <xp:div>

  • Open the page

  • Partial Refresh

  • Test with a <div>

  • Opening the page

  • Partial Refresh

[Similar results with the other tags]

As you can see there is nothing to worry about. Yes, a UIPassthroughComponent is faster then the corresponding XPages UIComponent. But keep in mind that these test are for 100.000 elements at once. If you have an XPage with 100.000 components on it, you will have other problems (f.e. memory usage etc.).

Quick-n-Dirty: Disable all validators at once

10. Februar 2013 Posted by Sven Hasselbach

In a larger project there are a lot of forms to fill in, each form has many fields with validators and converters. During the development of the workflow it was really helpful to disable all validators at once by using the setDisableValidators() method of the facesContext.

To control the behaviour I have added an URL parameter which is checked in the afterRestoreView event. If you open the XPage in the format

http://hostname/db.nsf/xPage.xsp?noValidators=true

all validators will be disabled. Here is a small example XPage:

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

    <xp:this.afterRestoreView>
    <![CDATA[#{javascript:
        if( param.containsKey("noValidators") ){
            facesContext.setDisableValidators(true);
        }else{
            facesContext.setDisableValidators(false);
        }
    }]]>
    </xp:this.afterRestoreView>
    <xp:inputText id="inputText1" required="true" />
    <xp:br />
    <xp:button value="Send" id="buttonSend">
        <xp:eventHandler event="onclick" submit="true" 
           refreshMode="complete" immediate="false" save="true" />
    </xp:button>
    <xp:br />
    <xp:messages id="messages1" />

</xp:view>

This makes developers life easier!

XPages: compositeData is undefined

10. Februar 2013 Posted by Sven Hasselbach

An interesting question was asked on StackOverflow.com: The compositeData of custom control is undefined in beforeRenderResponse event. I have never noticed this before, but if you are accessing the compositeData object in the before-, afterRenderResponse or the afterRestoreView event, the object is undefined.

Here is a simple demonstration CC which just prints the type of the compositeData object to the console:

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

    <xp:this.beforePageLoad>
        <![CDATA[#{javascript:print( "beforePageLoad: " + typeof( compositeData )) }]]>
    </xp:this.beforePageLoad>

    <xp:this.afterPageLoad>
        <![CDATA[#{javascript:print( "afterPageLoad: " + typeof( compositeData )) }]]>
    </xp:this.afterPageLoad>

    <xp:this.afterRestoreView>
        <![CDATA[#{javascript:print( "afterRestoreView: "  + typeof( compositeData )) }]]>
    </xp:this.afterRestoreView>

    <xp:this.beforeRenderResponse>
        <![CDATA[#{javascript: print( "beforeRenderResponse: "  + typeof( compositeData )) }]]>
    </xp:this.beforeRenderResponse>

    <xp:this.afterRenderResponse>
        <![CDATA[#{javascript: print( "afterRenderResponse: "  + typeof( compositeData )) }]]>
    </xp:this.afterRenderResponse>

</xp:view>

And here is a screenshot of the console, including a partial refresh of the page:

Only in the PageLoad events the compositeData object is available. But why?

The answer is rather simple: The events are executed in a different context. The PageLoad events are running “inside” of the UIInlcudeComposite (the Custom Control), the other events are running in the UIViewRoot:

That’s why the compositeData is undefined in these events. To use the object in the events, you have to do the following:

First you have to add an ID to your custom control:

<xc:ccWithId test="foo" id="ccWithId" />

This allows you to access the CC as a regular component with getComponent(). Now you can access the com.ibm.xsp.binding.PropertyMap of the component in the custom control’s event which holds the variable you want:

<xp:this.beforeRenderResponse>
   <![CDATA[#{javascript:
      var cmp:com.ibm.xsp.component.UIIncludeComposite = getComponent("ccWithId");
      print("Value of 'test' -> " + cmp.getPropertyMap().getString("test") )
   }]]>
</xp:this.beforeRenderResponse>

Quick-n-Dirty: Import SSJS libraries with DXL

10. Januar 2013 Posted by Sven Hasselbach

In the last time I have developed different techniques for manipulating the design elements of XPages applications. While I am still working on a way for manipulating the localization files, I was playing a little bit with DXL imports.

Here comes a Java class to import a SSJS library with DXL to a database. It allows to create a new library directly from the browser:

By clicking the Import button the new Library will be added to your database (perhaps you have to refresh the DDE):

To use the new imported code you have to sign the library first (or the complete database), otherwise you will receive a security error (This can be done in the XPage too, just alter the code of the button).

Here is the code of the example XPage:

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

    <xp:inputTextarea id="inputTextareaSSJSCode"
       value="#{sessionScope.SSJSCode}">
    </xp:inputTextarea>

    <xp:br />

    <xp:inputText id="inputTextSSJSLib" value="#{sessionScope.SSJSLib}" />

    <xp:br />

    <xp:button value="Import" id="buttonImport">
       <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
       <xp:this.action>
          <![CDATA[#{javascript:
             importPackage( ch.hasselba.xpages.util );

             var dxl = SSJSLibImporter.convertToDXL(
             sessionScope.SSJSCode, sessionScope.SSJSLib,
                database.getReplicaID()
             );

             var dxlImp = sessionAsSignerWithFullAccess.createDxlImporter();
             dxlImp.setDesignImportOption( 6 );
             dxlImp.importDxl( dxl,
                sessionAsSignerWithFullAccess.getCurrentDatabase() );
          }]]>
       </xp:this.action>
     </xp:eventHandler>
   </xp:button>

</xp:view>

And here is the Java class:

package ch.hasselba.xpages.util;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.ibm.misc.BASE64Encoder;

/**
 * Helper Class for DXL Import
 * of a SSJS library
 *
 * @author Sven Hasselbach
 * @version 0.2
 * @category DXL
 * @category Util
 */
public class SSJSLibImporter {

    /**
     * converts a integer to a byte array
     * in little endian order
     *
     * @param i integer to convert
     * @return byte array
     */
    static byte[] toBytes(final int i)
    {
            ByteBuffer b = ByteBuffer.allocate(4);
            b.order( ByteOrder.LITTLE_ENDIAN );
            b.putInt(i);
                return b.array();
    }

    /**
     * encapsulates raw SSJS lib data to RT records
     *  
     * @param data array to encapsulate
     * @return byte array with RT records
     */
    public static byte[] generateSSJSBlock( byte[] data ){

            final int RECORDLENGTH_OVERALL = 50;
            final int RECORDLENGTH_BLOB = 18;

            int size = data.length;  // size of the data
            int roundedSize = (size % 2 == 0)?size:size+1; // rounded size

            byte[] record = new byte[ roundedSize + RECORDLENGTH_OVERALL ];
            byte[] hlp;

            // Record Type (Event)
            record[0] = -7;
            record[1] = -1;

            // Record Length
            record[2] = 32;
            record[3] = 0;

            // event type
            record[8] = 22;
            record[9] = 0;

            // action type
            record[10] = 4;
            record[11] = 0;

            // data size
            hlp = toBytes( size );
            record[12] = hlp[0];
            record[13] = hlp[1];

            // Record Type (Blob)
            record[32] = -36;
            record[33] = -1;

            // Rounded size + recordlength
            hlp = toBytes( roundedSize + RECORDLENGTH_BLOB );
            record[34] = hlp[0];
            record[35] = hlp[1];

            record[36] = -7;
            record[37] = -1;

            // Rounded size
            hlp = toBytes( roundedSize );
            record[38] = hlp[0];
            record[39] = hlp[1];

            record[40] = 32;
            record[41] = 78;

            // Add data to BLOB          
            for( int  i=0; i<data.length; i++ ){
                    record[i+RECORDLENGTH_OVERALL] = data[i];
            }
            return record;
    }

    /**
     * generates the DXL to import a new SSJS library
     *
     * @param data array of bytes 
     * @param libName Name of the SSJS library
     * @param dbReplicaId
     * @return DXL to import
     * 
     */
    public static String generateSSJSDXL( byte[] data, final String libName, final String dbReplicaId ){
            StringBuffer dxl = new StringBuffer();
            BASE64Encoder benc = new BASE64Encoder();
            dxl.append( "<?xml version='1.0'?><!DOCTYPE scriptlibrary SYSTEM 'xmlschemas/domino_8_5_3.dtd'>" );
            dxl.append( "<scriptlibrary name='" + libName + "' xmlns='http://www.lotus.com/dxl' version='8.5' " );
            dxl.append( "maintenanceversion='3.0' replicaid='" + dbReplicaId + "' hide='v3 v4strict' " );
            dxl.append( "designerversion='8.5.3'>" );
            dxl.append( "<item name='$Flags'><text>.5834Q</text></item>" );
            dxl.append( "<item name='$ServerJavaScriptLibrary' sign='true'>" );
            dxl.append( "<rawitemdata type='1'>" );
            dxl.append( benc.encode( data ) );
            dxl.append( "</rawitemdata></item></scriptlibrary>" );

            return dxl.toString();

    }

    /**
     * converts a String to a DXL for a SSJS library to import
     *
     * @param String to convert
     * @param Library name
     * @param ReplicaId
     * @return String with DXL
     */
    public static String convertToDXL( String raw, String libName, String dbReplicaId ){
            byte[] data = generateSSJSBlock( raw.getBytes() );
            return generateSSJSDXL( data, libName, dbReplicaId );          
    }

}

This class only imports a new SSJS library. If you want to update an existing library, you need to add a <note-info> tag to the DXL. And don’t forget to update the ACL of your database, otherwise the DXL import will fail!

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

XPages: UI for editing localization files (1)

17. Dezember 2012 Posted by Sven Hasselbach

For one of my customers I am developing an easy way for editing the localization/translation of existing XPages applications: Instead of importing and exporting the property files with the Domino Designer (which is not an option for non-developers) it would be more suitable, if the users would have the possibility to edit the files directly in the browser. I am currently  working on a XPage solution for this, and this is my current beta version.

This is a proof-of-concept with some known bugs, f.e. HTML special chars are not encoded correctly. But this can be fixied easily. First, it is required that the users can choose a property file to edit. This can be realized with a small Java class I have created (based on a stackoverflow.com answer):

package ch.hasselba.xpages.localization;

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

public class DesignElements {

    private final String EMPTY_STRING = "";
    private final String[] FLAG_PROPERTIES = { "gC~4K2P", "gC~4K2" };
    private final String FIELD_$FLAGS = "$Flags";
    private final String FIELD_TITLE = "$TITLE";

    /**
     * returns Vector containing all property files of a database
     * 
     * No error handling included!
     * 
     * @category Domino
     * @author Sven Hasselbach
     * @category Tools
     * @version 0.2
     */
    public Vector<String> getPropertFileList() {

        FacesContext fc = FacesContext.getCurrentInstance();

        Vector<String> data = new Vector<String>();
        try {

            // get DB
            Database db = (Database) fc.getApplication().getVariableResolver()
                    .resolveVariable(fc, "database");

            // get all design docs
            NoteCollection nc = db.createNoteCollection(false);
            nc.selectAllDesignElements(true);
            nc.buildCollection();

            // process all notes
            String noteId = "";
            noteId = nc.getFirstNoteID();

            Document doc = null;
            // 
            while (!(EMPTY_STRING.equals(noteId))) {

                // get design doc
                doc = db.getDocumentByID(noteId);

                // check if its a property file
                for (int i = 0; i < FLAG_PROPERTIES.length; i++) {
                    if (FLAG_PROPERTIES[i].equals(doc
                            .getItemValueString(FIELD_$FLAGS))) {
                        // add to Vector
                        data.add(doc.getItemValueString(FIELD_TITLE));
                    }
                }

                // next one
                noteId = nc.getNextNoteID(noteId);

                // recycle doc
                recycleObject(doc);
            }

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

        return data;

    }

    /**
     * recycles a domino document instance
     * 
     * @param lotus
     *            .domino.Base obj to recycle
     * @category Domino
     * @author Sven Hasselbach
     * @category Tools
     * @version 1.1
     */
    public static void recycleObject(lotus.domino.Base obj) {
        if (obj != null) {
            try {
                obj.recycle();
            } catch (Exception e) {
            }
        }
    }
}

The difference to the answer on stackoverflow.com is the additional property flag I have added to this class (I know it would be better to check the flags instead of comparing the strings, but it seems to work this way).

Next step is to load the selected property file. This can be done with a managed bean and a helper class for the entries itself. Here is the helper class:

package ch.hasselba.xpages.localization;

import java.util.UUID;

/**
 * Locale Entry of a locale file
 * 
 * @author Sven Hasselbach
 * @category Localization
 * @version 1.0
 * 
 */

public class LocaleEntry {

    private String id;
    private String name;
    private String value;

    /**
     * initializes the object and
     * sets an unique identifier
     */
    public LocaleEntry(){
        this.id = UUID.randomUUID().toString();
        this.name = "";
        this.value = "";
    }

    /**
     * returns unique identifier of the object
     * @return String unique id
     */
    public String getId() {
        return id;
    }

    /**
     * sets the unique identifier of the entry
     * @param id String unique id
     */
    public void setId(final String id) {
        this.id = id;
    }

    /**
     * returns the name of the entry
     * @return String
     */
    public String getName() {
        return name;
    }

    /**
     * sets the name of the entry
     * @param String
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * returns the value of the entry
     * @return String
     */
    public String getValue() {
        return value;
    }

    /**
     * sets the value of the entry
     * @param value
     */
    public void setValue(String value) {
        this.value = value;
    }

}

And here comes the “Loader” class:

package ch.hasselba.xpages.localization;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import com.ibm.xsp.context.FacesContextEx;

/**
 * "Loader" class to access the entries
 * of a .property file
 * 
 * @author Sven Hasselbach
 * @version 1.0
 */
public class Locale {

    private Vector<LocaleEntry> locales;

    /**
     * @return vector containing the LocalEntry objects
     */
    public Vector<LocaleEntry> getLocales() {
        return locales;
    }

    /**
     * sets vector containing the LocalEntry objects
     * @param Vector
     */
    public void setLocales(Vector<LocaleEntry> locales) {
        this.locales = locales;
    }

    /**
     * wrapper for the static method call
     * 
     * @param fileName name of the property file to load
     */
    public void loadFile(final String fileName) {
        Map<String, String> m = getPropertiesFromFile(fileName);
        this.locales = parseMap(m);
    }

    /**
     * loads a property file and parses it to a key/value map
     * 
     * @param fileName name of the property file to load
     * @return Map containing the key/values
     * 
     * The loading routine is shamelessly copied from Ulrich Krause:
     * http://openntf.org/XSnippets.nsf/snippet.xsp?id=access-.properties-files
     */
    public static Map<String, String> getPropertiesFromFile(String fileName) {
        Properties prop = new Properties();

        try {
            prop.load(FacesContextEx.getCurrentInstance().getExternalContext()
                    .getResourceAsStream(fileName));
            Map<String, String> map = new HashMap<String, String>((Map) prop);

            return map;

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

    }

    /**
     * parses a property map and create a vector
     * with LocalEntry objects
     * 
     * @param map to parse
     * @return Vector containing the LocalEntry objects
     */
    public static Vector<LocaleEntry> parseMap(final Map<String, String> map) {

        // init new vector
        Vector<LocaleEntry> localEntries = new Vector<LocaleEntry>();
        String key;
        String value;

        // get key set for iteration
        Iterator<?> it = map.keySet().iterator();

        while (it.hasNext()) {

            // extract key & value
            key = (String) it.next();
            value = (String) map.get(key);

            // create new entry and add to vector
            LocaleEntry lEntry = new LocaleEntry();
            lEntry.setName(key);
            lEntry.setValue(value);
            localEntries.add(lEntry);

        }

        // return vector
        return localEntries;

    }

    /**
     * dumps current object data to console
     * Just for debugging
     * 
     * @category Debug
     */
    public void dump() {
        for (int i = 0; i < this.locales.size(); i++) {
            LocaleEntry e = this.locales.get(i);
            System.out.println(e.getId() + " - " + e.getName() + " - "
                    + e.getValue());
        }

    }

}

Now the new managed bean can be added to the faces-config.xml:

<managed-bean>
  <managed-bean-name>locale</managed-bean-name>
  <managed-bean-class>ch.hasselba.xpages.localization.Locale</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

The resulting XPage can look like this:

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

    Property File:&#160;
    <xp:comboBox id="comboBoxFile" value="#{sessionScope.PropertyFile}">
        <xp:selectItems>
            <xp:this.value>
            <![CDATA[#{javascript:
               importPackage(ch.hasselba.xpages.localization);
               ch.hasselba.xpages.DesignElements().getPropertFileList();
            }]]>
            </xp:this.value>
        </xp:selectItems>
    </xp:comboBox>
    &#160;
    &#160;
    <xp:button value="Load" id="buttonLoadFile">
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete">
            <xp:this.action>
               <![CDATA[#{javascript:
                  importPackage(ch.hasselba.xpages.localization);
                  var bean = facesContext.getApplication().getVariableResolver()
                     .resolveVariable(facesContext, "locale");
                  bean.loadFile(sessionScope.get("PropertyFile") )}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>
    <hr />
    <xp:label id="labelSelecteFile">
        <xp:this.value>
           <![CDATA[#{javascript:sessionScope.get("PropertyFile")}]]>
        </xp:this.value>
    </xp:label>
    <xp:br />
    <xp:br />
    <xp:repeat id="repeat1" rows="9999" var="lMap" value="#{locale.locales}">
        <xp:label value="#{lMap.name}" id="labelMapName" />
        <xp:inputText id="inputTextValue" value="#{lMap.value}" />
        <xp:br />
    </xp:repeat>

</xp:view>

And this is how it looks in the browser:

The next article describes the procedure to save the properties back the database.