Posts Tagged: ‘8.5.3’

XPages application events: Create your own ApplicationListener

7. Juli 2012 Posted by Sven Hasselbach

If you want to get a handle to application events and want to know if a XPages application is created or destroyed (which means the application was destroyed because of a time out), you can implement this by creating your own own application listener.

For this you have to do the following steps:

  1. Create a class which implements the ApplicationListener interface
  2. Activate the class in XPages runtime

To realise the first part, you have to create a class that implements the interface com.ibm.xsp.application.events.ApplicationListener. This interface has two methods: applicationCreated and applicationDestroyed (the method names should be self-describing enough).

Here is an example:

package ch.hasselba.jsf.debug;

import com.ibm.xsp.application.ApplicationEx;
import com.ibm.xsp.application.events.ApplicationListener;

public class MyApplicationListener implements ApplicationListener {

    public void applicationCreated(ApplicationEx arg0) {
        System.out.println("applicationCreated()");
        System.out.println("ID: " + arg0.getApplicationId());
    }

    public void applicationDestroyed(ApplicationEx arg0) {
        System.out.println("applicationDestroyed()");
        System.out.println("ID: " + arg0.getApplicationId());  
    }

}

Now to step two, the activation of the class. To do this, you have to add a special configuration file to your application:

  1. Switch to “Java” perspective
  2. Create a new folder “META-INF” in the “Code/Java” section
  3. Create a sub folder “services
  4. Create a file named “com.ibm.xsp.core.events.ApplicationListener
  5. In this file you add the full name of your MyApplicationListener class (including the package name):

The file structure should now look like this in Java perspective:

If you switch back to Domino perspective, the structure in the “Code/Java” looks like this:

You can now verify that all works correctly by opening a XPage and have a look on your server console:

[The application timeout was set to one minute.]

Troubleshooting Mail: Blank return receipts sent to external senders (8.5.3)

5. Juli 2012 Posted by Noteshexe

Problem: When a return receipt is sent to an external user and disclaimers are enabled on the client or server, the return receipt does not include the return receipt body – only the disclaimer is included. This causes confusion with the sender and is thought to be SPAM. Steps: Enable Return Receipts Enable message disclaimers [...]

DocumentDataSource with Signer/SignerWithFullAccess-Rights

31. Mai 2012 Posted by Sven Hasselbach

Yesterday I read the very interessting question from Daniele Grillo at stackoverflow.com: Is a datasource available for XPages who can access the underlying document with different access levels?

I have never seen one before, so I decided to do some tests and tried to find a workaround / hack for this problem. But after a while and a some lines of coding, I was unable to get a solution, so I decided to create a new datasource: A datasource which can access the documents in the backend with different access levels: sessionAsSigner and sessionAsSignerWithFullAccess.

The datasource is still in beta version, there are some limitations because I have implemented the basic functionallity only. It must be added programmatically, I have no ambitions to create a design element of it, but perhaps I will create a XSnippet.

To use this datasource, you can add it to a XPage and control it via URL parameters.

  • Open document as current user
XPage.xsp?documentId=<DocUNID>
  • Open document as Signer
XPage.xsp?documentId=<DocUNID>&access=sessionAsSigner
  • Open document as Signer with Full Access
XPage.xsp?documentId=<DocUNID>&access=sessionAsSignerWithFullAccess

To use this datasource in your XPage, you have to add some SSJS code:

<xp:this.beforePageLoad>
   <![CDATA[#{javascript:
      importPackage(ch.hasselba.xpages.jsf.core);
      var ds = new
         ch.hasselba.xpages.jsf.core.AccessDocDataSource();
      ds.setVar( "document1" );
      ds.setConcurrencyMode( "force" );
      view.addData(ds);
   }]]>
</xp:this.beforePageLoad>

[You can change the name of the datasource (marked red) to fit your requirements.]

The datasource can be used like a normal document datasource:

<xp:inputText id="inputText1" value="#{document1.Test}" />

To save the document you have to call the save method directly (The default actions are currently not working. Maybe because the actions check the object type. I am trying to figure this out):

<xp:button value="Save" id="button1">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="complete">
      <xp:this.action>
         <![CDATA[#{javascript:document1.save()}]]>
      </xp:this.action>
   </xp:eventHandler>
</xp:button>

Here is the Java code:

package ch.hasselba.xpages.jsf.core;

import com.ibm.xsp.FacesExceptionEx;
import com.ibm.xsp.model.AbstractDocumentDataSource;
import com.ibm.xsp.model.DocDataSource;
import com.ibm.xsp.model.DocumentDataContainer;
import com.ibm.xsp.model.domino.wrapped.DominoDocument;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import lotus.domino.Document;
import lotus.domino.NotesException;
import lotus.domino.Session;
import java.util.Map;

public class AccessDocDataSource extends AbstractDocumentDataSource
   implements DocDataSource {

    private final static String DEFAULT_CONCURRENCYMODE = "force";
    private final static String CONCURRENCYMODE = "concurrencyMode";
    private final static String ACTION = "action";
    private final static String ACCESS = "access";
    private final static String DOCUMENTID = "documentId";
    private final static String CONST_SESSION = "session";
    private final static String CONST_SESSIONASSIGNER = "sessionAsSigner";
    private final static String CONST_SESSIONASSIGNERFULLACCESS = "sessionAsSignerWithFullAccess";
    private final static String MSG_DOCSAVE_FAILED = "doSaveDocument failed!";

    private String _docId;
    private String _action;
    private String _concurrencyMode;
    private String _access;

    private DominoDocument _doc;

    private static boolean isStrEmpty(final String str) {
        if (str == null)
            return true;
        if (str.length() == 0)
            return true;

        return false;
    }

    private static Object getVariableValue(String varName) {

        FacesContext context = FacesContext.getCurrentInstance();
        return context.getApplication().getVariableResolver().resolveVariable(
                context, varName);
    }

    @Override
    public void readRequestParams(FacesContext paramFacesContext,
            Map<String, Object> pMap) {

        String tmpStr = (String) pMap.get(prefixRequestParam(DOCUMENTID));
        if (!isStrEmpty(tmpStr))
            this._docId = tmpStr;

        tmpStr = (String) pMap.get(prefixRequestParam(ACTION));
        if (!isStrEmpty(tmpStr))
            this._action = tmpStr;

        tmpStr = (String) pMap.get(prefixRequestParam(ACCESS));
        if (!isStrEmpty(tmpStr))
            this._access = tmpStr;
    }

    @Override
    public Object getDataObject() {

        return this._doc;
    }

    @Override
    public boolean isReadonly() {

        DominoDocument doc = getDocument();
        if (doc == null)
            return true;
        return isReadOnly( doc );

    }
    @Override
    public boolean isReadOnly(Object paramObject) {

        return !((DominoDocument) paramObject).isEditable();
    }
    @SuppressWarnings("deprecation")
    public DominoDocument getDocument() {

        Document d = null;
        DominoDocument doc = null;
        if (this._doc != null)
            return this._doc;

        try {
            Session s = (Session) getVariableValue(getAccess());

            d = s.getCurrentDatabase().getDocumentByUNID(getDocumentId());
            doc = com.ibm.xsp.model.domino.wrapped.DominoDocument.wrap(d
                    .getParentDatabase().getFilePath(), d, null,
                    getConcurrencyMode(), false, null);
            this._doc = doc;

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

        return doc;
    }

    public String getConcurrencyMode() {

        if (this._concurrencyMode != null) {
            return this._concurrencyMode;
        }

        ValueBinding vb = getValueBinding(CONCURRENCYMODE);
        if (vb != null) {
            return (String) vb.getValue(FacesContext.getCurrentInstance());
        }
        return DEFAULT_CONCURRENCYMODE;
    }

    public void setConcurrencyMode(String pString) {

        this._concurrencyMode = pString;
    }

    public String getDocumentId() {

        if (this._docId != null) {
            return this._docId;
        }

        ValueBinding vb = getValueBinding(DOCUMENTID);
        if (vb != null) {
            return (String) vb.getValue(FacesContext.getCurrentInstance());
        }

        return null;
    }

    public void setDocumentId(String pString) {

        this._docId = pString;
    }

    public String getAccess(){
        if (this._access != null) {
            return this._access;
        }

        ValueBinding vb = getValueBinding(ACCESS);
        if (vb != null) {
            return (String) vb.getValue(FacesContext.getCurrentInstance());
        }

        return null;
    }
    public void setAccess( String pString ){
        if( isStrEmpty( pString ) ){
            this._access = CONST_SESSION;
            return;
        }

        if( pString.equals( CONST_SESSIONASSIGNER ) ){
            this._access = CONST_SESSIONASSIGNER;
            return;
        }

        if( pString.equals( CONST_SESSIONASSIGNERFULLACCESS  ) ){
            this._access = CONST_SESSIONASSIGNERFULLACCESS ;
            return;
        }

        this._access = CONST_SESSION;

    }
    @Override
    public boolean doSaveDocument(FacesContext paramFacesContext, Object paramObject)
            throws FacesExceptionEx {

        try {
            return ((DominoDocument) paramObject).save();
        } catch (NotesException ne) {
            ne.printStackTrace();
        }
        throw new FacesExceptionEx( MSG_DOCSAVE_FAILED , null);
    }

    @Override
    public Object saveState(FacesContext paramFacesContext) {

        Object[] objArr = new Object[5];
        objArr[0] = super.saveState(paramFacesContext);
        objArr[1] = this._docId;
        objArr[2] = this._concurrencyMode;
        objArr[3] = this._action;
        objArr[4] = this._access;

        return objArr;
    }

    @Override
    public void restoreState(FacesContext paramFacesContext, Object paramObject) {

        Object[] objArr = (Object[]) paramObject;
        super.restoreState(paramFacesContext, objArr[0]);
        this._docId = ((String) objArr[1]);
        this._concurrencyMode = ((String) objArr[2]);
        this._action = ((String) objArr[3]);
        this._access = ((String) objArr[4]);
    }

    @Override
    public DocumentDataContainer doNewDocument(FacesContext paramFacesContext)
            throws FacesExceptionEx {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public DocumentDataContainer doOpenDocument(FacesContext paramFacesContext)
            throws FacesExceptionEx {
        // TODO Auto-generated method stub
        return null;
    }

    public boolean isNewDocument(FacesContext paramFacesContext) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void doComputeDocument(FacesContext paramFacesContext, Object paramObject)
            throws FacesExceptionEx {
        // TODO Auto-generated method stub

    }

    @Override
    public void doDeleteDocument(FacesContext paramFacesContext, Object paramObject)
            throws FacesExceptionEx {
        // TODO Auto-generated method stub

    }

    @Override
    protected String composeUniqueId() {
        // TODO Auto-generated method stub
        return null;
    }

    public boolean isDocument(Object paramObject) {
        // TODO Auto-generated method stub
        return false;
    }

}

Welche Sametime Version (Client oder Server) ist installiert?

24. Mai 2012 Posted by Noteshexe

 Frage:  Wie kann ich überprüfen, welche Sametime Server Version installiert ist? Antwort:   http://<servername>/Sametime/buildinfo.txt (<servername>  ist der voll qualifizierten Domain-Namen (FQDN = Fully Qualified Domain Name) des Sametime Server) Frage:  Wie kann ich überprüfen, welche Sametime Cient Version (standard)  installiert ist? Antwort:   Link ——————————————————————————————————————– Question:  How can you verify which version of the Sametime [...]

Controlling the HTTP Expires Header

23. Mai 2012 Posted by Sven Hasselbach

After reading a question on stack overflow about setting an own HTTP expires header and the problem that an additional header is generated automatically,  I made some tests how the domino server can be forced to stop this behaviour programmatically.

During my tests I was able to stop it by using  facesContext.responseComplete() but this works only for “headless” XPages (set rendered to false). If you are calling the method in a normal XPage (set rendered to true), the generated output will be discarded and looks like the “headless” version.

Here are two examples including screenshots from firebug console (containing the HTTP response):

  • Adding the header in beforeRenderResponse / beforePageLoad
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:this.beforeRenderResponse>
   <![CDATA[#{javascript:
      var ec = facesContext.getExternalContext();
      var response = ec.getResponse();
      var writer = response.getWriter();

      // set headers
      var now = new Date();
      response.setDateHeader("Expires",
         now.getTime() + (60*60*1000));
      response.setHeader("Cache-Control", "public");

      // Output it
      writer.write( now.getTime().toString() );
      }]]>
   </xp:this.beforeRenderResponse>
   <xp:label value="Test!" id="label1"></xp:label>
 </xp:view>

As you can see here, the output from the writer was added to the XPage first (the output will be added before the <HTML>-Tag and causes invalid HTML, but this can be ignored in this demo):

The response contains two HTTP expires header. The -1 is added after the programmatically generated one.

  • Adding the header in afterRenderResponse / afterPageLoad
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:this.afterRenderResponse>
   <![CDATA[#{javascript:
      var ec = facesContext.getExternalContext();
      var response = ec.getResponse();
      var writer = response.getWriter();

      // set headers
      var now = new Date();
      response.setDateHeader("Expires",
         now.getTime() + (60*60*1000));
      response.setHeader("Cache-Control", "public");

      // Output it
      writer.write( now.getTime().toString() );
      }]]>
   </xp:this.afterRenderResponse>
   <xp:label value="Test!" id="label1"></xp:label>
</xp:view>

In this scenario, the output is added after the HTML code.

The HTTP expires header was added first.

  • How to remove the header

After some research, I was able to remove the header programmatically:

First, you have to create a new java class which implements com.ibm.xsp.context.RequestParameters.ResponseCacheHeader.

package ch.hasselba.jsf.core;

import javax.servlet.http.HttpServletResponse;
import com.ibm.xsp.context.RequestParameters.ResponseCacheHeader;

public class SHResponseCacheHeader implements ResponseCacheHeader {

   public SHResponseCacheHeader(){}

   public boolean initResponseCacheHeader(HttpServletResponse arg0){
      return true;
   }
}

It is required that the initResponseCacheHeader() method returns true, otherwise this won’t work!

Then, you have to add the responseCacheHeader object to the requestParameters of facesContext:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
   <xp:this.beforePageLoad>
   <![CDATA[#{javascript:
      importPackage( ch.hasselba.jsf.core );

      var ec = facesContext.getExternalContext();
      var response = ec.getResponse();
      var writer = response.getWriter();

      // set headers
      var now = new Date();
      response.setDateHeader("Expires",
         now.getTime() + (60*60*1000));
      response.setHeader("Cache-Control", "public");

      // set response cache header object
      var reqParam = facesContext.getRequestParameters();
      var resCH = new SHResponseCacheHeader();
      reqParam.setResponseCacheHeader( resCH );

      // Output it
      writer.write( now.getTime().toString() );
      }]]>
   </xp:this.beforePageLoad>
   <xp:label value="Test!" id="label1"></xp:label>
 </xp:view>

The output will be generated as expected, but the useless HTTP expires header won’t be added anymore:

Quick-n-Dirty: HTML5 UIComponents without effort

17. Mai 2012 Posted by Sven Hasselbach

Obviously it was Chris Toohey who first has discovered the way of manipulation UIComponents with the tagName attribute: http://www.dominoguru.com/pages/xpage_xptext_tagName_options.html

 

The xp:text – element can easily manipulated to add HTML5 functionality to a XPages. By overriding the property tagName, the component can be accessed like every other UI component in the component tree.

Normally the designer only allows a small list of choices for the tagName property:

But this can be easily overwritten by manually editing the tagName attribute in the source and allows to change the generated HTML element to whatever you want.

Here is a small example for a HTML5 progessbar which can be accessed like every other UIComponent:

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

   <xp:text escape="false" id="pBar" disableTheme="true">
      <xp:this.value><![CDATA[#{javascript:""}]]></xp:this.value>
      <xp:this.tagName>
         <![CDATA[#{javascript:"progress"}]]>
      </xp:this.tagName>
      <xp:this.attrs>
         <xp:attr name="value">
            <xp:this.value><![CDATA[#{javascript:
               if( !sessionScope.containsKey("pBar") )
                  sessionScope.put("pBar", 0 );

                var value = sessionScope.get("pBar");

                if( value > 100 ) value = 0;

                sessionScope.put("pBar", (value+10) );

                value
               }]]></xp:this.value>
         </xp:attr>
         <xp:attr value="100" name="max"></xp:attr>
      </xp:this.attrs>
   </xp:text>

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

</xp:view>

The generated HTML Tag looks like this and can for example be refreshed with a partial refresh etc.

<progress id="view:_id1:pBar" value="40" max="100"></progress>

XSnippets: Cancel a partial refresh via SSJS

3. Mai 2012 Posted by Sven Hasselbach

With the assistance of Philippe Riand I was able to shorten the original idea of canceling a partial refresh to a single SSJS function.  By setting the HTTP header “X-XspRefreshId” to “@none” it is possible to get the same result as in the posting before, but there is no “Dojo hack” required.

function cancelPartialRefresh(){
   var response = facesContext.getExternalContext()
      .getResponse();
   response.setHeader("X-XspRefreshId", "@none");
   response.reset();
   response.commitResponse();
   facesContext.responseComplete();
}

To use this function you just have to call it.  Here is the same example like in the previous posting:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:button value="Label" id="button1">
      <xp:eventHandler event="onclick" submit="true"
         refreshMode="partial" refreshId="label1">
      </xp:eventHandler>
   </xp:button>
   <xp:br/><xp:br/>
   <xp:label id="label1">
      <xp:this.value>
      <![CDATA[#{javascript:
      function cancelPartialRefresh(){
         var response = facesContext.getExternalContext()
            .getResponse();
         response.setHeader("X-XspRefreshId", "@none");
         response.reset();
         response.commitResponse();
         facesContext.responseComplete();
      }

      var ajax = new com.ibm.xsp.ajax.AjaxUtil();
      if( ajax.isAjaxPartialRefresh(facesContext) == true){
         cancelPartialRefresh();
         return; // stop execution of SSJS code
      }
      java.lang.System.currentTimeMillis();}]]>
      </xp:this.value>
   </xp:label>
</xp:view>

I have added the code to the XSnippet Contest. Here is the link to the XSnippet: http://openntf.org/XSnippets.nsf/snippet.xsp?id=cancel-partial-refresh

If you want to read more information about the HTTP header  you can read an earlier posting (Sorry, on german only).

Cancel a partial refresh via SSJS

1. Mai 2012 Posted by Sven Hasselbach

After reading Tim Tripcony’s blog post , I thought about a way how to cancel a partial refresh via server side javascript. To bring this to life, there are just three things to do:

  1. Abort the processing of the request on the server
  2. Give feedback to the client that request is canceled
  3. Stop Dojo to process the XHR request

To stop the processing of a request on the server and to send an empty response to the client, this SSJS code can be used:

var response = facesContext.getExternalContext().getResponse();
response.reset();
response.commitResponse();

The client will receive an empty HTTP response body:

A HTTP Header has to be added to the response to inform Dojo that the request was canceled. In this example it is “X-Partial-Refresh-Cancel“:

response.setHeader("X-Partial-Refresh-Cancel", "1");

The header is now added to the HTTP response and sent back to the client:

The XHR response must be hijacked before it is processed by Dojo. To do this, a new XHR function has to be injected between the existing one. Here is a basic code snippet:

var xhrLoad = null; // placeholder for the original function

if( !dojo._xhr )
   dojo._xhr = dojo.xhr;

dojo.xhr = function(){
   var args = arguments[1];
   xhrLoad = args["load"]; // "save" the original load function
                           // and overwrite with a new one
   args["load"] = function( a, ioArgs ){
      // execute custom code
      // call the original load function:
      xhrLoad( a, ioArgs );
   }

   // do XHR request
   dojo._xhr( arguments[0], arguments[1], arguments[2] );
}

The load function of a Dojo XHR request has a parameter ioArgs. This parameter gives a handle to an object that allows to access the HTTP response headers, so the response can be identified as canceled:

var canceled = ioArgs.xhr &&
   ioArgs.xhr.getResponseHeader("X-Partial-Refresh-Cancel");

if( canceled ){
   XSP.allowSubmit();
   return;
}

If the HTTP header is set, the processing of the request can be stopped. After stopping the request, the XSP object is not allowed to fire the next event. To allow this again, the function XSP.allowSubmit() must be called.

Here is a demonstration XPage with a button and a label. The partial refresh will never be processed, instead a “Canceled” message will occur.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
   <xp:button value="Label" id="button1">
      <xp:eventHandler event="onclick" submit="true"
         refreshMode="partial" refreshId="label1">
      </xp:eventHandler>
   </xp:button>
   <xp:br/><xp:br/>
   <xp:label id="label1">
      <xp:this.value>
      <![CDATA[#{javascript:
      var response = facesContext.getExternalContext().getResponse();
      var ajax = new com.ibm.xsp.ajax.AjaxUtil();
      if( ajax.isAjaxPartialRefresh(facesContext) == true){
         response.setHeader("X-Partial-Refresh-Cancel", "1");
         response.reset();
         response.commitResponse();
         return;
      }
      java.lang.System.currentTimeMillis();}]]>
      </xp:this.value>
   </xp:label>
   <xp:br/>
   <xp:br/>
   <xp:scriptBlock id="scriptBlockXHRHandler">
   <xp:this.value><![CDATA[
      var xhrLoad = null;
      dojo.addOnLoad( function(){
         if( !dojo._xhr )
            dojo._xhr = dojo.xhr;

        dojo.xhr = function(){
           try{
              var args = arguments[1];
              xhrLoad = args["load"];
              args["load"] = function( a, ioArgs ){
                 var canceled = ioArgs.xhr &&
                    ioArgs.xhr.getResponseHeader("X-Partial-Refresh-Cancel");
                 if( canceled ){
                    alert("Canceled!");
                    XSP.allowSubmit();
                    return;
                 }
                 xhrLoad( a, ioArgs );
               }
            }catch(e){}
            dojo._xhr( arguments[0], arguments[1], arguments[2] );
         }
      });]]>
      </xp:this.value>
   </xp:scriptBlock>
</xp:view>

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.

ObjectDataSource: Kleines “How To”

1. April 2012 Posted by Sven Hasselbach

Mit der Extension Library bzw. dem Upgrade Pack 1 ist für XPages eine neue Datasource-Komponente hinzugekommen, die ObjectDataSource. Diese Datasource kann wie die Standard-Datasources für View und Dokument ebenfalls an den verschiedensten Elementen einer XPage angehangen werden, d.h. sowohl an die UIViewRoot der XPage, als auch in Custom Controls, Repeat Controls oder sonstigen Elementen, die eine DataSource verwenden.

Grundlegend gilt für ObjectDataSources, das sie beliebige Java-Objekte beinhalten können, solange die Objekte selbst serialisierbar sind, also das Interface java.io.Serializable implementieren. Hier ein einfaches Beispiel eines Java-Objektes:

package ch.hasselba.extlib.demo;

import java.io.Serializable;

public class ObjectDataDemo implements Serializable {

    long timeStamp;

    public ObjectDataDemo(){
        this.update();
    }

    public void update(){
        this.timeStamp = java.lang.System.currentTimeMillis();
    }

    public long getTimeStamp() {
        return timeStamp;
    }

}

Die Java-Klasse ist einfach aufgebaut und liefert den Zeitstempel zurück, der beim letzten Aufruf der update()-Methode gesetzt wurde. Um die Klasse in der XPage verwenden zu können, muss sie nur in einem Source-Folder bereitgestellt sein, der dem Build-Path hinzugefügt wurde bzw. die Klasse im neuen “JavaCode”-Ordner  abgelegt sein – eine Modifikation der faces-config.xml ist nicht nötig.

Mittels der createObject-Eigenschaft der ObjectDataSource wird die XPage dazu veranlasst, eine Instanz der Klasse anzulegen, womit das instanzierte Objekte der ObjectDataSource gehört; um die Stabilität einer Applikation nicht zu gefährden und unerwartete Ergebnisse zu provozieren, sollten die Objekte daher nicht anderweitig gespeichert werden (z.B. in Managed Beans o.ä.).

<xp:this.data>
   <xe:objectData var="objectData1" scope="view"
      createObject="#{javascript:
         new ch.hasselba.extlib.demo.ObjectDataDemo();}">
   </xe:objectData>
</xp:this.data>

Eine ObjectDataSource hat neben dem zu verwendenden scope auch noch die Eigenschaft var, die (wie bei den anderen Datasources auch) den programmatischen Namen darstellt, mit dem die DataSource referenziert werden kann. In diesem Fall ist das Objekt und alle Eigenschaften/Methoden des Objektes über objectData1 zu erreichen:

<xp:label id="label1"
   value="#{javascript:objectData1.getTimeStamp()}" />

Da z.B. ein Partial Refresh die Datasources nicht jedesmal neuberechnet, muss ein eventuell benötigter Update-Mechanismus selbst implementiert werden. In diesem Beispiel dient dafür die Methode update(), die im Backend aufgerufen werden kann:

<xp:button value="Label" id="button1">
   <xp:eventHandler event="onclick" submit="true"
      refreshMode="partial" refreshId="label1">
      <xp:this.action>
         <![CDATA[#{javascript:objectData1.update()}]]>
      </xp:this.action>
   </xp:eventHandler>
</xp:button>

[Button ruft die Update-Funktion auf, label1 wird refresht und neuer Zeitstempel angezeigt]

Die ObjectDataSources besitzen eine weitere Eigenschaft namens saveObject. Diese Eigenschaft ist optional und nur erforderlich, wenn man beabsichtigt, auf ein Speichern der DataSources reagieren zu wollen (um z.B. Daten ins Backend zu speichern, o.ä.).

Achtung:

Einige Methodennamen dürfen in der Java-Klasse nicht verwendet werden, da dies sonst zu Problemen mit der bestehenden Architektur kommen kann. Namen wie save() oder load() sind zu vermeiden!

Bug: facesContext.getRenderResponse()

11. März 2012 Posted by Sven Hasselbach

Eigentlich sollte die Methode  getRenderResponse() true zurück liefern, wenn der Code in der Render Response-Phase ausgeführt wird, doch leider ist die Funktion nicht korrekt implementiert. So liefert die Funktion bei einem normalen Seitenaufruf falsche Ergebnisse, bei einem Partial Refresh hingegen funktioniert sie jedoch wie sie soll.

Zur Erläuterung verwende ich folgende XPage:

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

   <xp:label value="Label" id="label1">
      <xp:this.rendered>
         <![CDATA[#{javascript:
            if( facesContext.getRenderResponse() ){
               print( "Render Response!" );
            }else{
               print( "NOT Render Response!" );
            }
            return true}]]>
      </xp:this.rendered>
   </xp:label>

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

Öffnet man die XPage im Browser, wird auf der Serverkonsole folgendes ausgegeben:

Bei einem Partial Refresh hingegen wird die Phase korrekt erkannt:

Neue Funktion in Lotus Notes 8.5.3 – In Schnelladressierung verbergen (Hide in Typeahead)

7. März 2012 Posted by Noteshexe

Neue Funktion: Wenn Sie eine E-Mail adressieren, wird während der Adresseingabe eine Liste mit Namen und Adressen angezeigt. Dabei handelt es sich um Ihre “letzten Kontakte “, die automatisch basierend auf den Personen ausgewählt werden, mit denen Sie kürzlich per Chat, E-Mail oder in einer Besprechung Kontakt hatten. Um eine “unerwünschte” Adresse (zum Beispiel fehlerhafte, [...]

Neue Funktion in Lotus Notes 8.5.3 – Zusätzliche Optionen für letzte Kontakte

1. März 2012 Posted by Noteshexe

Neue Funktion: Es stehen neue Optionen zur Verfügung, um auszuwählen, wer zu Ihren letzten Kontakten hinzugefügt werden soll. Einstellung Variante 1: Datei >Vorgaben> Kontakte File >Preferences>Contacts Einstellung Variante 2: notes.ini Setting Keinen Namen hinzufügen (Do not add any names) notes.ini: DisableDPABprocessing=1 Alle Namen (All names) default Nur Absendernamen aus E-mails, die ich empfange (Only Sender’s [...]