XPages Renderers and Thread Safety
Thu May 27 09:56:13 EDT 2021
I got to thinking yesterday about renderers in XPages - the part of the stack that takes the abstract back-end representation and actually turns it into HTML. They've been an interest of mine for a good while and they have interesting characteristics worth revisiting from time to time.
One of those characteristics is their general statelessness: the way they work, there's generally only one instance of the renderer object, and then it's applied to many pages. This is reflected in the Javadoc for the base javax.faces.render.Renderer
class (Java EE 5 here because that's what XPages is based on):
Individual `Renderer` instances will be instantiated as requested during the rendering process, and will remain in existence for the remainder of the lifetime of a web application. Because each instance may be invoked from more than one request processing thread simultaneously, they MUST be programmed in a thread-safe manner.
As a quick aside, that all-caps "MUST" is an application of the delightful RFC 2119, which defines common meanings for those types of words in specs. It's worth reading.
Implementation
Anyway, how does this shake out in practice? Well, we can use the Bootstrap theme as provided by IBM back when it was open source as a baseline example of how it's done. If you look at the, for example, ResponsiveAppLayoutRenderer
class, you can see a ton of code, but no instance variables. Looking at any number of classes, there are static constants, but no instance variables. In general, that bundle as present on GitHub there is a great example of the craft.
In general, thread safety is tricky, and the easiest way to make a class thread-safe is to not have any instance variables. These renderer examples take that route, and for good reason: since renderers are instantiated once per app, they're potentially shared across all pages in the app, multiple components within a page, and multiple instances of the same page.
Demonstration
Thread safety in general and in Java in particular is a huge topic, and it's something that harries basically all programmers working in a potentially-threaded environment, even when the code you're writing doesn't seem troublesome. It's one thing if you're explicitly writing multithreaded code, divvying up tasks among executors or something, but what would be the trouble here? Well, fortunately, writing a demonstration is fairly quick. To do so, I made a new NSF with a few design elements. First, a theme:
1 2 3 4 5 6 7 8 9 | <theme> <control> <name>ViewRoot</name> <property> <name>rendererType</name> <value>renderer.TestViewRoot</value> </property> </control> </theme> |
You can name the theme whatever you want, since the name of a theme is intended to be of human interest only. It will only matter that it's then selected in the Xsp Properties file.
Then, a customized faces-config.xml:
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version="1.0" encoding="UTF-8"?> <faces-config> <render-kit> <renderer> <component-family>javax.faces.ViewRoot</component-family> <renderer-type>renderer.TestViewRoot</renderer-type> <renderer-class>renderer.TestViewRoot</renderer-class> </renderer> </render-kit> <!--AUTOGEN-START-BUILDER: Automatically generated by HCL Domino Designer. Do not modify.--> <!--AUTOGEN-END-BUILDER: End of automatically generated section--> </faces-config> |
This has prepared us to use a custom renderer for the view root, which is the main page itself. Finally, as the last step before the renderer class, I made five XPages, named "home1.xsp" through "home5.xsp". The content is irrelevant, so I made them the simplest possible:
1 2 | <?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"/> |
Now, to the renderer class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package renderer; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.render.Renderer; import com.ibm.xsp.component.UIViewRootEx; public class TestViewRoot extends Renderer { String viewId; @Override public void encodeBegin(FacesContext context, UIComponent component) { UIViewRootEx viewRoot = (UIViewRootEx)context.getViewRoot(); viewId = viewRoot.getViewId(); } @Override public void encodeEnd(FacesContext context, UIComponent component) { UIViewRootEx viewRoot = (UIViewRootEx)context.getViewRoot(); String endingViewId = viewRoot.getViewId(); if(!this.viewId.equals(endingViewId)) { System.out.println("finished rendering view ID "+ endingViewId + " - started with " + this.viewId); } } } |
This class gets the view ID (e.g. "/home1") in encodeBegin
and again in encodeEnd
. When the two are different - essentially, our bug condition - it emits a message to the console. This "viewId" is a stand-in for any sort of expected shared state, to demonstrate that the renderer can make no assumptions that encodeBegin
and encodeEnd
are called in sequence for the same page or page instance. The latter could be demonstrated by checking viewRoot.getUniqueViewId()
instead, which identifies the specific page instance and is thus distinct even across different users on the same page.
Then, I wrote a script that, in a multithreaded fashion, requests home1.xsp through home5.xsp randomly for a little while, and it was only a couple seconds before the messages started appearing:
1 2 3 4 5 | [0B4C:000A-14A0] 05/27/2021 09:25:21 AM HTTP JVM: finished rendering view ID /home4 - started with /home2 [0B4C:0023-1388] 05/27/2021 09:25:21 AM HTTP JVM: finished rendering view ID /home2 - started with /home1 [0B4C:001E-0C88] 05/27/2021 09:25:21 AM HTTP JVM: finished rendering view ID /home2 - started with /home1 [0B4C:001B-1784] 05/27/2021 09:25:21 AM HTTP JVM: finished rendering view ID /home3 - started with /home1 [0B4C:001C-1AE8] 05/27/2021 09:25:22 AM HTTP JVM: finished rendering view ID /home2 - started with /home5 |
Knock-On Demo
Okay, so that's bad, but there's a subtle other problem I created here and that's to do with code reuse via subclassing. In general, renderers are subclassed out the wazoo. Just look at the type hierarchy for FacesRendererEx
:
It goes on for a while like that.
While renderers being subclass-friendly isn't a "MUST"-type rule like thread safety, it's both an associated benefit of that and a general cultural idiom. But imagine if I were to subclass my example above and override just the encodeBegin
portion (to represent, say, changing just the output of the page header but leaving the footer the same):
1 2 3 4 5 6 7 8 9 10 11 | package renderer; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; public class TestViewRootEx extends TestViewRoot { @Override public void encodeBegin(FacesContext context, UIComponent component) { // Do something else here } } |
If I use that as the renderer, I get a dramatically-new look for my page:
The trouble here is that I overrode encodeBegin
but didn't call super.encodeBegin
- which I naturally wouldn't, since I explicitly don't want whatever the parent class is outputting. However, since encodeEnd
assumes that its version of encodeBegin
runs and sets this.viewId
, I hit a NullPointerException
when it tries to access it. Curses.
Conclusion
Anyway, this is all a long-winded way of pointing out that designing for thread safety is tricky business, and it can crop up when you wouldn't otherwise expect it. It's good here that the JSF developers included that "MUST" business in the Javadoc, but, unfortunately, Java-the-language doesn't have a way of actually enforcing it, making room for this sort of thing to creep in.