DocumentDataSource with Signer/SignerWithFullAccess-Rights

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;
    }

}
Veröffentlicht unter Java, JSF, Security, ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , , | 7 Kommentare

Controlling the HTTP Expires Header

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:

Veröffentlicht unter Java, JSF, Server, ServerSide JavaScript, Web, XPages | Verschlagwortet mit , , , , , , , , | Schreib einen Kommentar

Quick-n-Dirty: HTML5 UIComponents without effort

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"
      tagName="progress" value="">
      <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>
Veröffentlicht unter Allgemein, HTML, Java Script, JSF, Web, XPages, XSP | Verschlagwortet mit , , , , , , , , , , | 2 Kommentare

XSnippets: Cancel a partial refresh via SSJS

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).

Veröffentlicht unter Allgemein, Dojo Toolkit, HTML, Java Script, Performance, Server, ServerSide JavaScript, Web, XPages, XSnippet, XSP | Verschlagwortet mit , , , , , , , , , , , , , , | Ein Kommentar

Cancel a partial refresh via SSJS

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>
Veröffentlicht unter Allgemein, Dojo Toolkit, Java Script, Performance, Server, ServerSide JavaScript, Web, XPages, XSP | Verschlagwortet mit , , , , , , , , , , , , , | 4 Kommentare

Lotus Domino Entwickler ab 07/2012 verfügbar

Als erfahrener freiberuflicher Lotus Domino Entwickler suche ich ab Juli 2012 neue Herausforderungen in Form von spannenden Projekten, bevorzugt im XPages-Umfeld.

Ich bin deutschlandweit verfügbar, bin aber anderen Länder gegenüber nicht abgeneigt. Auf Wunsch stelle ich natürlich gern mein Profil zur Verfügung.

Bei Interesse bitte einfach eine eMail an contact<at>hasselba.ch oder mich auf XING aufsuchen:

Sven Hasselbach

Veröffentlicht unter Allgemein | Schreib einen Kommentar

XSnippets: Fire querySave / postSave – Events

The second XSnippet I have added to the XSnippet Contest is a help to fire the querySave– and postSave-events from SSJS: Save Datasource & Fire querySave/postSave events

If you only do a simple document1.save() , the events of a datasource won’t be executed. To fix this, you have to use the save() method of com.ibm.xsp.model.domino.DominoDocumentData instead:

var dsName = "document1.DATASOURCE"; // change this to the name of
                                     // the datasource you want
                                     // to save
var app = facesContext.getApplication();
var ds = app.getVariableResolver()
           .resolveVariable(facesContext, dsName);
ds.save( facesContext, true);

The variable dsName contains the name of the datasource to save, followed by „.DATASOURCE„. To use it f.e. with the current document, you have to change the variable to „currentDocument.DATASOURCE„.

The difference here is the type of object that is used: If you save a document datasource, the save method of an object of type NotesXspDocument is called. The com.ibm.xsp.model.domino.DominoDocumentData object has another method for saving. The first object type is like a backend NotesDocument, the second object is like the NotesUIDocument class.

Veröffentlicht unter Java Script, ServerSide JavaScript, Web, XPages, XSnippet | Verschlagwortet mit , , , , , | Schreib einen Kommentar

XSnippets: XPages Localization Setter

I have submitted some XSnippets for the XSnippets-Contest. This is the first one, the XPages Localization Setter: The Snippet allows to change the language settings of a XPage „On-The-Fly“, including the used browser language, the dojo settings, the ressource files etc.

package ch.hasselba.xpages.jsf.core;

import javax.faces.context.FacesContext;
import javax.faces.application.Application;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.component.UIViewRoot;
import java.util.Locale;
import java.util.Map;

public class LocalizationSetter implements PhaseListener {

    private static final long serialVersionUID = -1L;
    private static final String scopeVarName = "Language";
    private static final String scopeName = "sessionScope";

    public void afterPhase(PhaseEvent event) {}

    public void beforePhase(PhaseEvent event) {
        FacesContext facesContext = event.getFacesContext();
        UIViewRoot view = facesContext.getViewRoot();
        view.setLocale( getLanguage(facesContext) ) ;
    }

    public PhaseId getPhaseId() {
        return PhaseId.RENDER_RESPONSE;
    }

    private Locale getLanguage( FacesContext facesContext ){       
        try{
            Application app = facesContext.getApplication();
            Object obj = app.getVariableResolver()
                  .resolveVariable(facesContext, scopeName );
            Object lang = ((Map) obj).get( scopeVarName );
            if( lang != null ){
                return new Locale((String) lang);
            }
        }catch(Exception e){}

        return Locale.getDefault();
    }
}

This phase listener updates the language settings of the UIViewRoot-Element during the rendering of the response. The language the XPage will be set to the stored value in a session scope variable named „Language„. If you want to change this you just have to change the variable scopeVarName.

To activate the phase listener in your XPages-Application you have to alter the faces-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
    <lifecycle>
        <phase-listener>
           ch.hasselba.xpages.jsf.core.LocalizationSetter
        </phase-listener>
    </lifecycle>
</faces-config>

Here is a simple example to demonstrate the XSnippet:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:comboBox id="comboBox1" value="#{sessionScope.Language}">
   <xp:selectItem itemLabel="Chinese" itemValue="zh" />
   <xp:selectItem itemLabel="German" itemValue="de" />
   <xp:selectItem itemLabel="Turkish" itemValue="tr" />
   <xp:eventHandler event="onchange" submit="true"
      refreshMode="complete" />
</xp:comboBox>
</xp:view>

If you are choosing „turkish“ from the combobox, the html source looks like this after the page was reloaded:

If you add a simple date picker to your XPage and change the language to chinese, the dojo dialog will be in chinese from now on:

To add more languages you have to use the two letter ISO 639 code. To get a list of all available locales you can use this small repeat control:

<xp:repeat id="repeat1" rows="999" var="locale">
   <xp:this.value>
      <![CDATA[#{javascript:
         java.text.SimpleDateFormat.getAvailableLocales();
      }]]>
   </xp:this.value>
   <xp:label id="label1">
      <xp:this.value>
         <![CDATA[#{javascript:
            locale.getDisplayName() + " -> " + locale.toString()
         }]]>
      </xp:this.value>
   </xp:label>
   <xp:br/>
</xp:repeat>

Note:

After changing the language a full refresh is required!

Veröffentlicht unter Allgemein, Java, JSF, Server, Web, XPages, XSnippet | Verschlagwortet mit , , , , , , , | 3 Kommentare

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

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.

Veröffentlicht unter Allgemein, Expression Language, Extensibility API, Java, JSF, Lotus Script, XPages, XSP | Verschlagwortet mit , , , , , , , , , | 6 Kommentare

LotusScript in XPages (2): LotusScript-Wrapper

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.

Veröffentlicht unter Agenten, Allgemein, Extensibility API, Java, JSF, Lotus Script, XPages, XSP | Verschlagwortet mit , , , , , , , , , , | 2 Kommentare