XPages Renderers and Thread Safety

May 27, 2021, 9:56 AM

Tags: xpages

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:

FacesRendererEx Type Hierarchy

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:

NPE on subclass

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.

Commenter Photo

Georgeget - Oct 9, 2021, 12:29 AM

Проиграли деньги в казино? Мы поможем их вернуть! Обращайтесь.

https://tinyurl.com/36nfysut

Commenter Photo

marianlj3 - Oct 9, 2021, 7:49 PM

Enjoy daily galleries http://tatt00designs.badgirlstattoo.xblognetwork.com/?jolie

porn tube videos madagascar porn troopers video reveiw porn older women of porn jap puke porn

Commenter Photo

RaymondSuefs - Oct 20, 2021, 12:43 AM

Гадаю дистанционно 30 лет. Оставьте ТОЛЬКО !!! сообщение в Вотцап на телефон 1 (248) 730-4177. Я С Вами свяжусь сама. На неизвестные звонки не отвечаю

New Comment