- XPages: The UI Toolkit and the App Framework
- The RuntimeEnvironment Idiom
- NSF ODP Tooling 3.1.0: Dynamically Including Web Resources
One of the specific problems that we encountered with my aforementioned client app first when expanding it to include REST services and then later to be portable outside an NSF entirely is dealing with varying mechanisms for interacting with the surrounding environment.
The Problem to Solve
The immediate way this distinction comes up when adding JAX-RS services or other OSGi servlets is trying to get a handle on the current Domino user session or context database. In an XPages app (including in code called in a plugin-based library), you can just do:
However, this will return
null if called while processing an OSGi servlet. Instead, servlet code should call:
Same idea - they both return a session based on the current authenticated user from the HTTP stack - but they have different backing implementations. So my first pass was to coordinate these inside an
AppUtil class in a method like this:
This worked pretty well, until I added Tycho-based compile-time unit tests, which is an OSGi environment where neither of those paths would return a session. So I had to add a fallback that would just eventually spawn a new
NotesFactory.createSession() if it couldn't find another one.
It's one thing for a
getSession() method to balloon in logic, but Notes runtime access isn't the only problem like this. Take the case of validating model objects as part of the "save" process. In an XPages environment, validation errors should be reported as
FacesMessages on the view root or, ideally, attached directly to the form control that represents the invalid field. In a REST service, though, the
ConstraintViolationException should bubble right up to the top and be returned as an appropriately-formatted JSON object with a corresponding HTTP status code. Originally, we handled this similarly: we moved the
FacesMessage stuff out of the model objects and into the
AppUtil class and handled it with an
Eventually, though, there was enough customizable behavior that these branching methods in one class got out of hand, and that's even before getting into cases where a class (like
FacesContext) may not even be available at runtime at all. So I implemented a
RuntimeEnvironment class as a service. It started out like this:
AppUtil.findExtensions method is a simplified wrapper around the IBM Commons
ExtensionManager call to find services in a type-safe way.
This allows me to define a series of
RuntimeEnvironment implementations that may or may not be included in a given packaging of the app, while the
getWeight() methods allow me to distinguish between multiple valid environments to find the most specific. To get an idea of what I mean, here is the current suite of environment implementations:
These run a wide gamut.
OSGiServletEnvironment are the big ones that kicked it off, but
TychoEnvironment is there to handle compile-time tests, while
NotesEnvironment lets the same code work in some utilities we launch from within the Notes client - and
SWTRuntimeEnvironment allows those same tools to run outside of OSGi.
Once I broke ground on these classes, the number of situations where they're useful began to become obvious. Take, for example, resolving a variable. The on-Domino XPages implementation looks like what you'd expect:
JakartaRuntimeEnvironment cases, though, I can use CDI instead:
It gets down to little things, too, like how the
POST destination for form-based login can be
/names.nsf?Login on Domino but
/j_security_check on other webapp servers.
Seeing It Elsewhere
This sort of idiom is by no means anything I came up with. You can see it pretty frequently - in fact, I highlighted the way the IBM Commons stack does a very similar thing when running XPages outside Domino:
This serves essentially the same purpose, being filled with mechanisms for getting output streams, finding resource locations, and retrieving named objects.
Should you implement something like this for most apps? Probably not, no - for most even-moderately-complex XPages applications, having some
if tests in a central util to distinguish between XPages and OSGi servlets should be enough. I think it's a useful instructional example, though, and it sure was critical in getting this massive thing working outside Domino. As we make our apps more portable, this is the sort of technique we should keep in mind.