NotesSessions, XPages and Threads
Enter Java multi-threading. I call it " Teenage-mode": your code does multiple things at the same time and doesn't seem to get distracted.
When you read the classics you will find threading hard and rather threatening (pun intended), but thanks to progress in Java6 and above it isn't too hard.
There is the Executor framework that handles all your
Runnables. There are 2 challenges to overcome: one specific to Notes and one general to all concurrency programming. The former: The Notes Session class is not threadsave and can't be shared between threads, the later: you have to get your queueing right:
(Image courtesy of Lee Chee Chew)
To get a new
Session object, we have the session cloner, thanks to the extension library. All your threads implement the Runnable interface that is later called by the executor framework. There are 2 approaches for background task: one where you expect the thread to return some value at some time and the other where a thread runs its course and terminates silently when done. For a current project I opted for the later. The threads contain a callback class where they report back what they have to say. To make it all work we need:
- A management class that executes the threads for us. Preferably long living (like session or application beans)
- A cloned Notes Session
- One or more classes implementing the runnable interface
import java.util.Collection ;
import java.util.Map ;
import lotus.domino.Database ;
import lotus.domino.NotesException ;
import lotus.domino.Session ;
import com.ibm.domino.xsp.module.nsf.NSFComponentModule ;
import com.ibm.domino.xsp.module.nsf.NotesContext ;
import com.ibm.domino.xsp.module.nsf.SessionCloner ;
public abstract class AbstractNotesBackgroundTask implements Runnable {
protected final Session notesSession ;
protected final Collection < String > callBackMessages ;
private SessionCloner sessionCloner ;
private NSFComponentModule module ;
public AbstractNotesBackgroundTask ( final Session optionalSession, final Collection < String > messageHandler ) {
this. callBackMessages = messageHandler ;
// optionalSession MUST be NULL when this should run in a thread, contain a session when
// the class is running in the same thread as it was constructed
this. notesSession = optionalSession ;
this. setDominoContextCloner ( ) ;
}
public void run ( ) {
this. callBackMessages. add ( "Background task run starting: " + this. getClass ( ). toString ( ) ) ;
try {
Session session ;
if ( this. notesSession == null ) {
NotesContext context = new NotesContext ( this. module ) ;
NotesContext. initThread (context ) ;
session = this. sessionCloner. getSession ( ) ;
} else {
// We run in an established session
session = this. notesSession ;
}
/* Do the work here */
this. runNotes ( ) ;
} catch ( Throwable e ) {
e. printStacktrace ( ) ;
} finally {
if ( this. notesSession == null ) {
NotesContext. termThread ( ) ;
try {
this. sessionCloner. recycle ( ) ;
} catch (NotesException e1 ) {
e1. printStacktrace ( ) ;
}
}
}
this. callBackMessages. add ( "Background task run completed: " + this. getClass ( ). toString ( ) ) ;
}
private void setDominoContextCloner ( ) {
// Domino stuff to be able to get a cloned session
if ( this. notesSession == null ) {
try {
this. module = NotesContext. getCurrent ( ). getModule ( ) ;
this. sessionCloner = SessionCloner. getSessionCloner ( ) ;
} catch ( Exception e ) {
e. printStacktrace ( ) ;
}
}
}
protected abstract void runNotes ( ) ;
}
runNotes() to get started. However you probably want to hand over more parameters, so you need to modify the constructor accordingly. Please note: you can't bring Notes objects across thread boundaries. Next the class that controls all the threads. It should live in the session or application.The class is actually quite simple:
import java.io.Serializable ;
import java.util.concurrent.ExecutorService ;
import java.util.concurrent.Executors ;
public class NotesBackgroundManager implements Serializable {
private static final long serialVersionUID = 1L ;
// # of threads running concurrently
private static final int THREADPOOLSIZE = 10 ;
private final ExecutorService service = Executors. newFixedThreadPool (THREADPOOLSIZE ) ;
public CSIApplication ( ) {
// For use in managed beans
}
public void submitService ( final Runnable taskDef ) {
if (taskDef == null ) {
System. err. println ( "submitService: NULL callable submitted to submitService" ) ;
return ;
}
// Execute runs without return
this. service. execute (taskDef ) ;
}
@ Override
protected void finalize ( ) throws Throwable {
if ( ( this. service != null ) && ! this. service. isTerminated ( ) ) {
this. service. shutdown ( ) ;
}
super. finalize ( ) ;
}
}
The final missing piece is to define
NotesBackgroundManager as managed bean e.g. background and you can launch a background task in SSJS: background.submitService(new com.notessensei.demo.SampleNotesBackgroundTask());
As usual YMMV.
Posted by Stephan H Wissel on 23 July 2013 8 comments categories: XPages