wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Managed Beans, XPages and Testability


I like my Java to be well managed, properly prepared and of top quality.
Unless you are living under an XRock, you know that managed beans are (one of) the talk of the town in the XPages community.
While managed beans are simple beans that have been given a name and a scope, they need some thought when you want to test your applications.
Refering from one bean to another or to a session and the current database is made easy using com.ibm.xsp.extlib.util.ExtLibUtil. However I like to test my code outside the XPages environment, where such a call (or any call to a JSF resolver) will fail. So I came up with a helper class to make the beans testable. Let's start with a typical managed bean setup:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
<managed-bean>
<managed-bean-name>huey </managed-bean-name>
<managed-bean-class>com.ibm.disney.toons.ducks.Huey </managed-bean-class>
<managed-bean-scope>application </managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>dewey </managed-bean-name>
<managed-bean-class>com.ibm.disney.toons.ducks.Dewey </managed-bean-class>
<managed-bean-scope>session </managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>louie </managed-bean-name>
<managed-bean-class>com.ibm.disney.toons.ducks.Louie </managed-bean-class>
<managed-bean-scope>view </managed-bean-scope>
</managed-bean>
<!--AUTOGEN-START-BUILDER: Automatically generated by IBM Domino Designer. Do not modify.-->
<!--AUTOGEN-END-BUILDER: End of automatically generated section-->
</faces-config>
To access those beans from plain Java as well as the session and current database I use a helper class. It doesn't matter if you use thee helper from the command line or in an XPages, the results will be the same. What is important: you can't store the oobjects when you use them in the XPage since they are either not thread save (session, database) or would loose their scope association (the rest). If you want to make the setBeans (see below) method more robust, you could call javax.faces.context.FacesContext.getCurrentInstance(). If anything else but null is coming back, you are in the XPages thread and should not set the objects to anything but null (or add that to your getter?)
The enum.INSTANCE is a Singleton and asures that I always get the same object back. This allows me to write a test class (or actually use JUnit tests) like this:
package com.ibm.disney.toons.ducks ;

import lotus.domino.Database ;
import lotus.domino.NotesException ;
import lotus.domino.NotesFactory ;
import lotus.domino.NotesThread ;
import lotus.domino.Session ;

public class LocalTest {
public static void main ( String [ ] args ) throws NotesException {
System. out. println ( "Starting local test" ) ;
NotesThread. sinitThread ( ) ;
Session s = NotesFactory. createSession ( ) ;
Database db = s. getDatabase ( "", "Customers\\swg\\ctpsi-ap.nsf" ) ;
String [ ] roles = [ "[Scout]", "[Naseweis]", "[Neffe]" ] ;
Huey huey = (Huey ) DuckFactory. makeDuck ( "Huey" ) ;
Dewey dewey = (Dewey ) DuckFactory. makeDuck ( "Dewey" ) ;
Louie louie = (Louie ) DuckFactory. makeDuck ( "Louie" ) ;
DuckFetcher. INSTANCE. setBeans (s, db, huey, dewey, louie, roles ) ;
// Ducks do what ducks have to do
DuckAdeventure d = new DuckAdventure ( ) ;
d. getStarted ( ) ;
d. chaseBadguys ( ) ;
d. findSolution ( ) ;
d. shutDown ( ) ;
// Game over
DuckFetcher. INSTANCE. setBeans ( null, null, null, null, null ) ;
db. recycle ( ) ;
NotesThread. stermThread ( ) ;
System. out. println ( "Done!" ) ;
}
}
The test class is easily run from Eclipse or the debug perspecitve in Domino Designer and you can step through with the debugger. Other than the local http preview the code runs with the current userid's permissions, so you don't need to mess with the ACL. The whole class is about a hundred lines and pretty easy to understand:
package com.ibm.disney.toons.ducks ;

import javax.faces.context.FacesContext ;
import lotus.domino.Database ;
import lotus.domino.NotesException ;
import lotus.domino.Session ;
import com.ibm.xsp.extlib.beans.UserBean ;
import com.ibm.xsp.extlib.util.ExtLibUtil ;

public enum DuckFetcher {
INSTANCE ;
private Session s ;
private Database db ;
private Huey huey ;
private Dewey dewey ;
private Louie louie ;
private String [ ] userRoles ;

private DuckFetcher ( ) {
this. s = null ;
this. db = null ;
this. huey = null ;
this. dewey = null ;
this. louie = null ;
this. userRoles = null ;
}

// The standard Notes objects
public void setBeans (Session s, Database db, Huey h, Dewey d, Louie l, String [ ] roles ) {
his. s = s ;
this. db = db ;
this. huey = h ;
this. dewey = d ;
this. louie = l ;
this. userRoles = roles ;
}

public Database getCurrentDatabase ( ) {
if (db != null ) {
return db ;
}
return ExtLibUtil. getCurrentDatabase ( ) ;
}

public Session getCurrentSession ( ) {
if (s != null ) {
return s ;
}
return ExtLibUtil. getCurrentSession ( ) ;
}

public String [ ] getUserRoles ( ) {
if (userRoles == null ) {
Object object = UserBean. get ( ). getField ( "accessRoles" ) ;
if (object != null ) {
return ( String [ ] ) object ;
}
}
return userRoles ;
}

// Now the ducklings
public Huey getHuey ( ) {
if ( this. huey == null ) {
FacesContext fc = FacesContext. getCurrentInstance ( ) ;
Huey result = (Huey ) ExtLibUtil. resolveVariable (fc, "huey" ) ;
return result ;
}

return this. huey ;
}

public Huey getDewey ( ) {
if ( this. dewey == null ) {
FacesContext fc = FacesContext. getCurrentInstance ( ) ;
Dewey result = (Dewey ) ExtLibUtil. resolveVariable (fc, "dewey" ) ;
return result ;
}

return this. dewey ;
}

public Huey getLouie ( ) {
if ( this. louie == null ) {
FacesContext fc = FacesContext. getCurrentInstance ( ) ;
Louie result = (Louie ) ExtLibUtil. resolveVariable (fc, "louie" ) ;
return result ;
}

return this. louie ;
}
}
As usual: YMMV

Disclaimer: No ducks have been harmed creating this article


Posted by on 20 June 2013 | Comments (1) | categories: XPages

Comments

  1. posted by Jesse Gallagher on Thursday 20 June 2013 AD:
    The importance of using an AeroPress cannot be overstated.