The XPages engine has its own executor service to run jobs concurrently in another thread: the XPagesExecutor service. Under the hood the service uses a ThreadPoolExecutor for executing tasks, so it allows to use Runnables or Callables/Futures for asynchronous computation. For fun, you can even run a server instance, the started threads can run as long as you want (e.g. they are not bound to the XPages lifecycle).
Here is a small XPage with a button to start a Runnable via the Executor:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:button
value="run the job"
id="buttonDoIt">
<xp:eventHandler
event="onclick"
submit="true"
refreshMode="complete">
<xp:this.action>
<![CDATA[#{javascript:
importPackage(ch.hasselba.xpages);
new ch.hasselba.xpages.DemoExecutor().doIt();}]]>
</xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:view>
To use the Service, you have to aquire it first, then you can execute a Runnable like in this code snippet:
package ch.hasselba.xpages;
import java.util.concurrent.ExecutorService;
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;
import com.ibm.xsp.application.XPagesExecutor;
public class DemoExecutor {
private final NSFComponentModule module;
private final SessionCloner sessionCloner = SessionCloner
.getSessionCloner();
public DemoExecutor() {
// get the current NSFComponentModule
this.module = NotesContext.getCurrent().getModule();
}
public void doIt() {
// aquire the Executor
ExecutorService exService = XPagesExecutor.acquire();
// run the Runnable
exService.execute(new DemoRunnable(this.module));
}
class DemoRunnable implements Runnable {
private final NSFComponentModule module;
public DemoRunnable(NSFComponentModule module) {
this.module = module;
}
public void run() {
try {
// init the NotesThread
NotesContext context = new NotesContext(this.module);
NotesContext.initThread(context);
// now do the job...
System.out.println("Starting the job...");
Session session = null;
try {
// grab the session
session = sessionCloner.getSession();
System.out.println("Running as "
+ session.getEffectiveUserName());
Thread.sleep(10000);
} catch (NotesException e) {
e.printStackTrace();
} finally {
DominoUtils.recycle( session );
}
System.out.println("Done!");
} catch (InterruptedException ie) {
// handle interruption here
} finally {
// kill the Notes thread
NotesContext.termThread();
}
}
}
}
Callables must be submitted instead:
public long doItWithCallable() {
ExecutorService exService = XPagesExecutor.acquire();
Future<Long> result = exService.submit(new MyCallable(this.module));
long returnValue = (-1);
try {
returnValue = result.get();
} catch (InterruptedException ie) {
ie.printStackTrace();
} catch (ExecutionException ee) {
ee.printStackTrace();
}
return returnValue;
}
class DemoCallable implements Callable {
private final NSFComponentModule module;
public DemoCallable(NSFComponentModule module) {
this.module = module;
}
public Long call() throws Exception {
try {
// init the NotesThread
NotesContext context = new NotesContext(this.module);
NotesContext.initThread(context);
// now do the job...
System.out.println("Starting the job...");
Session session = null;
try {
// grab the session
session = sessionCloner.getSession();
System.out.println("Running as "
+ session.getEffectiveUserName());
Thread.sleep(10000);
} catch (NotesException e) {
e.printStackTrace();
} finally {
DominoUtils.recycle( session );
}
System.out.println("Done!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// kill the Notes thread
NotesContext.termThread();
}
return System.currentTimeMillis();
}
}
Hope someone finds it usefull.
Very useful! Will definitely try in my app!
Thanks for sharing 🙂