NotesSessions, XPages and Threads
When you build an XPages application, long running operations will time out. After all there is a timing limit how long a browser (or XPiNC) client (and the user) will wait for a response. You could resort to launching an agent via a console command, but besides the security consideration it is quite a hack and headache. Mixing XPages and agents doesn't have a happy ending (and should be confined to the upgrading phase of your classic application).
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
(Image courtesy of Lee Chee Chew)
To get a new
The final missing piece is to define
As usual YMMV.
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
packagecom.notessensei.demo;
importjava.util.Collection;
importjava.util.Map;
importlotus.domino.Database;
importlotus.domino.NotesException;
importlotus.domino.Session;
importcom.ibm.domino.xsp.module.nsf.NSFComponentModule;
importcom.ibm.domino.xsp.module.nsf.NotesContext;
importcom.ibm.domino.xsp.module.nsf.SessionCloner;
public abstract class AbstractNotesBackgroundTask implementsRunnable{
protectedfinal Session notesSession ;
protectedfinalCollection<String> callBackMessages ;
private SessionCloner sessionCloner ;
private NSFComponentModule module ;
public AbstractNotesBackgroundTask (final Session optionalSession, finalCollection<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();
}
publicvoid 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());
}
privatevoid 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();
}
}
}
protectedabstractvoid runNotes ();
}
You only need to subclass it and implement importjava.util.Collection;
importjava.util.Map;
importlotus.domino.Database;
importlotus.domino.NotesException;
importlotus.domino.Session;
importcom.ibm.domino.xsp.module.nsf.NSFComponentModule;
importcom.ibm.domino.xsp.module.nsf.NotesContext;
importcom.ibm.domino.xsp.module.nsf.SessionCloner;
public abstract class AbstractNotesBackgroundTask implementsRunnable{
protectedfinal Session notesSession ;
protectedfinalCollection<String> callBackMessages ;
private SessionCloner sessionCloner ;
private NSFComponentModule module ;
public AbstractNotesBackgroundTask (final Session optionalSession, finalCollection<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();
}
publicvoid 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());
}
privatevoid 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();
}
}
}
protectedabstractvoid 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:
packagecom.notessensei.demo;
importjava.io.Serializable;
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
publicclass NotesBackgroundManager implementsSerializable{
privatestaticfinallong serialVersionUID = 1L ;
// # of threads running concurrently
privatestaticfinalint THREADPOOLSIZE = 10;
privatefinalExecutorService service = Executors. newFixedThreadPool(THREADPOOLSIZE );
public CSIApplication (){
// For use in managed beans
}
publicvoid submitService (finalRunnable taskDef ){
if(taskDef == null){
System. err. println("submitService: NULL callable submitted to submitService");
return;
}
// Execute runs without return
this. service. execute(taskDef );
}
@ Override
protectedvoid finalize ()throwsThrowable{
if((this. service!= null)&&!this. service. isTerminated()){
this. service. shutdown();
}
super. finalize();
}
}
If you rather want the thread returning something at the end of the run (which you must poll from the ExecutorService, you would call ExecutorService.submit and get back a Future holding the future result, but that's another story for another time.
importjava.io.Serializable;
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
publicclass NotesBackgroundManager implementsSerializable{
privatestaticfinallong serialVersionUID = 1L ;
// # of threads running concurrently
privatestaticfinalint THREADPOOLSIZE = 10;
privatefinalExecutorService service = Executors. newFixedThreadPool(THREADPOOLSIZE );
public CSIApplication (){
// For use in managed beans
}
publicvoid submitService (finalRunnable taskDef ){
if(taskDef == null){
System. err. println("submitService: NULL callable submitted to submitService");
return;
}
// Execute runs without return
this. service. execute(taskDef );
}
@ Override
protectedvoid finalize ()throwsThrowable{
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 | Comments (7) | categories: XPages