- Updating The XPages JEE Support Project To Jakarta EE 9, A Travelogue
- JSP and MVC Support in the XPages JEE Project
- Migrating a Large XPages App to Jakarta EE 9
- XPages Jakarta EE Support 2.2.0
- DQL, QueryResultsProcessor, and JNoSQL
- Implementing a Basic JNoSQL Driver for Domino
- Video Series On The XPages Jakarta EE Project
- JSF in the XPages Jakarta EE Support Project
- So Why Jakarta?
- Adding Concurrency to the XPages Jakarta EE Support Project
- Adding Transactions to the XPages Jakarta EE Support Project
- XPages Jakarta EE 2.9.0 and Next Steps
- The Loose Roadmap for XPages Jakarta EE Support
For a little while, I've had a task open for me to investigate the Jakarta Concurrency and MP Context Propagation specs, and this weekend I decided to dive into that. While I've shelved the MicroProfile part for now, I was successful in implementing Concurrency, at least for the most part.
The Jakarta Concurrency spec deals with extending Java's default multithreading services -
ScheduledExecutorServices - in a couple ways that make them more capable in Jakarta EE applications. The spec provides
Managed variants of these executors, though they extend the base Java interfaces and can be treated the same way by user code.
While the extra methods here and there for task monitoring are nice, and I may work with them eventually, the big-ticket item for my needs is propagating context from the initializer to the thread. By "context" here I mean things like knowledge of the running NSF, its CDI environment, the user making the HTTP request, and so forth. As it shakes out, this is no small task, but the spec makes it workable.
In its basic form, an
ExecutorService lets you submit a task and then either let it run on its own time or use
get() to synchronously wait for its execution - sort of like
async/await but less built-in. For example:
ScheduledExecutorService extends this a bit to allow for future-scheduled and repeating tasks:
Those examples aren't exactly useful, but hopefully you can get some further ideas. With an
ExecutorService, you can spin up multiple concurrent tasks and then wait for them all - in a client project, I do this to speed up large view reading by divvying it up into chunks, for example. Alternatively, you could accept an interactive request from a user and then kick off a thread to do hefty work while returning a response before it's done.
There's an example of this sort of thing in XPages up on OpenNTF from about a decade ago. It uses Eclipse Jobs as its concurrency tool of choice, but the idea is largely the same.
The Basic Implementation
For the core implementation code, I grabbed the GlassFish implementation, which was the Reference Implementation back when JEE had Reference Implementations. With that as the baseline, I was responsible for just a few tasks:
- Write a
ContextSetupProviderimplementation that does the work of ferrying context from an initiating thread to the executing one
- Add a hook to create and shut down the executors for each NSF
- Add a hook to push those executors into JNDI per request
The devil was in the details, but the core lifecycle wasn't too bad.
One intriguing and slightly vexing thing about this API is that the official way to access these executors is to use JNDI, the "Java Naming and Directory Interface", which is something of an old and weird spec. It's also one of the specs that remains in standard Java while being in the
javax.* namespace, and those always feel weird nowadays.
Anyway, JNDI is used for a bunch of things (like ruining Thanksgiving), but one of them is to provide named objects from a container to an application - kind of like managed beans.
One common use for this is to allow an app container (such as Open Liberty) to manage a JDBC connection to a relational database, allowing the app to just reference it by name and not have to manage the driver and connection specifics. I use that for this blog, in fact.
XPages apps don't really do this, but Domino does include a
com.ibm.pvc.jndi.provider.java OSGi bundle that handles JNDI basics. I'm sure there are some proper ways to go about registering services with this, but I couldn't be bothered: in practice, I just call
context.rebind(...) and call it a day.
Ferrying the Context
The core workhorse of this is the
ContextSetupProvider implementation. It's the part that's responsible for being notified when context is going to be shunted around and then doing the work of grabbing what's needed in a portable way and setting it up for threaded code. For my needs, I set up an extension interface that can be used to register different participants, so that the Concurrency bundle doesn't have to retain knowledge about everything.
So far, there are a few of these.
The first of these is the
NSFNotesContextParticipant, which takes on the job of identifying the current Notes/XPages environment and preparing an equivalent one in the worker thread. The "Threads and Jobs" project above does something like this using the
ThreadSessionExecutor class provided with the runtime, but that didn't really suit my needs.
What this class does is grab the current
com.ibm.domino.xsp.module.nsf.NotesContext, pulls the
HttpServletRequest from it, and then uses that information to set up the new thread before tearing it down when the task is done.
This process initializes the thread like a
NotesThread and also sets appropriate
Database objects in the context, so code running in an NSF can use those in their threaded tasks without having to think about the threading:
This class does some checking to make sure it's in an NSF-specific request. I may end up also writing an equivalent one for OSGi Servlet/Web Container requests as well.
Outside of Notes-runtime specifics, the most important context to retain is the CDI context. Fortunately, that one's not too difficult:
This checks to make sure CDI is enabled for the current app and, if so, uses the standard
CDI.current() method to find the container. This is the same method used everywhere and ends up falling to the
NSFCDIProvider class to actually locate it. That bounces back to a locator service that returns the thread-set one, and thus the executor is able to find it. Then, threaded code is able to use
CDI.current().select(...) to find beans from the application:
To flesh this out, I have some other immediate work to do. For one, I'll want to see if I can ferry over the current JAX-RS application context - that will be needed for using the MicroProfile Rest Client, for example.
Beyond that, I'm considering implementing the MicroProfile Context Propagation spec, which provides some alternate capabilities to go along with this functionality. It may be a bit more work than it's worth for NSF use, but I like to check as many boxes as I can. Those concepts look to have made it into Concurrency 3.0, but, as that version is targeted for Jakarta EE 10, it's almost certain that final implementation builds will require Java 11.
Finally, though, and along similar lines, I'm pondering backporting the
@Asynchronous annotation from Concurrency 3.0, which is a CDI extension that allows you to implicitly make a method asynchronous:
With that, it's similar to submitting the task specifically, but CDI will do all the work of actually making it async when called. We'll see - I've avoided backporting much, but that one is tempting.