It’s been about a year since I last blogged about XOTS. Just to recap, XOTS is a method of running background multi-threaded tasks coded within an NSF. It’s basically DOTS tasklets within XPages. The tasklet extends Runnable if you just want to call it and let it run in the background, or Callable if you want to wait for it to complete and return a value. There are, by default, ten XOTS threads so up to ten tasklets can be running simultaneously. That doesn’t mean you can’t kick off more. The others are just queued up behind it and will start once a thread is available. But there was a limitation last year, that any XPages elements that you wanted to use inside the tasklet needed passing into the constructor. Was. Because there have been some recent developments.
But before explaining all about that, there’s an aspect I realised I didn’t cover in the original blog posts, which was error handling. Obviously without access to the XPages runtime, it’s not possible to use
XspOpenLogUtil.logError(), as in XPages. Instead there has been a method,
XotsUtil.handleException(final Throwable t, final Database currDb) method that should be used to log errors. However, when I hit an error in a Xots task earlier in the week, I realised that in 2.0.0 there was a bug in
BaseOpenLogItem.writeToLog() because it was checking for the current database, but that threw a NullPointerException. That’s now been fixed. So
XotsUtil is still the way to handle errors from XOTS. But if you’re using a XOTS task from XPages, don’t use
XotsUtil.handleException(final Throwable t, final Database currDb).
That’s because I finally worked out how to pass everything related to the XPages runtime into a XOTS tasklet. The code is in my development branch on Stash and is working nicely in tests. This brings me back to the topic of the annotations on XOTS. Again, to recap, a XOTS tasklet needs the annotation
@Tasklet with various parameters. The one covered before was
@Tasklet(session = Tasklet.Session.CLONE), to clone the current user’s session. I’ve moved away from using SessionAsSigner, so I wouldn’t use
Tasklet.Session.SIGNER. And since the native server session is always included anyway, there’s no real need to specify
session = Tasklet.Session.NATIVE if you want to process the tasklet as the server. So
Tasklet.Session.CLONE is probably the only one I would typically use.
However, there are other annotations. The one of relevance here is
Tasklet.Context, with the options
Tasklet.Context.XSPSCOPED. If set to
XSPBARE, when the tasklet is run, an object
XotsXspContext is created into which is passed the
XSPContext objects. This will be of relevance for scheduled tasklets, where you want access to the XPages runtime but there’s no point interacting with the scope maps. If set to
XSPSCOPED then the
XotsXspContext object also includes the four scope maps – applicationScope, sessionScope, viewScope and requestScope. As a point of best practice, I would recommend using requestScope and viewScope only for read activities. After all, if you’re using a Callable then you don’t need to write to anywhere, just return an object. And if you’re using a Runnable then there’s rarely any point using anything below sessionScope because you can’t guarantee the user will still be on the same page.
This brings me to the other key requirement for leveraging the XotsXspContext. Because we’re storing this
XotsXspContext in the tasklet, whereas tasklets previously used
implements Callable or
implements Runnable, the tasklets now need to use
extends AbstractXotsXspCallable or
extends AbstractXotsXspRunnable. If the tasklets don’t extend these classes, you will not be able to use
getContext() to retrieve the
XotsXspContext from which to get all the XPages runtime objects.
This finally brings me back to error handling and OpenLog.
XotsContext, just as
AbstractXotsCallable. That’s because
XotsContext holds the current database path, OpenLog path and tasklet class. These are used by OpenLog to know where to log to (picking from xsp.properties or notes.ini), which database to log as running in, and which tasklet class name to log as running from. So if you’re not wanting an XPages context but are running from within a database and want to use OpenLog error handling, you should extend
AbstractXotsCallable. Then, instead of
XotsUtil.handleException(final Throwable t, final Database currDb), you should use
XotsUtil.handleException(final Throwable t, final XotsContext xotsContext). There are also two other helper methods –
getOpenLogItem(final XotsContext xotsContext) and
getOpenLogItem(final Database currDb), to be used if you want to log events or use any of other OpenLog methods. The methods that take a Database as the parameter are still there for Runnables or Callables triggered from non-NSF contexts, for example in a plugin.
All of this functionality will be included in the next release of ODA, which we’re aiming to have out in around a month. I also hope to have scheduled XOTS working for then as well, so stay tuned for that. But before getting to that, I’ll also do a blog post about functionality available for configuring and troubleshooting XOTS, which will come next. As ever, if you have suggestions for features or are interested in testing, get in touch (issues can be logged on OpenNTF’s JIRA project for ODA).