Decorating the render result of 3rd party JSF components by injecting a custom ResponseWriter html

Decorating the render result of 3rd party JSF components by injecting a custom ResponseWriter

Today I crossed some boundaries with my personal understanding of Java Server Faces. I have gone where I had not gone before.

We were facing the following situation: our application is built using a 3rd party JSF library – in our case ADF Faces. These components provide us with 95% of what we need in terms of both behavior – event handling, validation, conversion, AJAX-support – and UI. The HTML they render may not always be to our liking, but by and large we manage to get the look and feel our users requested or at least will accept. That leaves some 5% of situations where only a lot of CSS, Skinning and even run-time JavaScript DOM manipulation is required to make the users happy.

Sometimes this gets frustrating: a 3rd party JSF components renders a bunch of HTML – it can be quite a bit of complex HTML with several Form input elements, some CSS styling and some JavaScript – and if only we could change one or two tiny little things. Add one CSS StyleClass to one of the HTML elements or change the name of an icon. All I want to do is take the component as it is, wrap it somehow and amend its behavior ever so subtly.

However, the ADF Faces components are not easy to extend from or to wrap. Most of them are black boxes – no source code – some are final and then there is the configutarion of the renderers that I do not really care to get involved with. So is there not a simpler solution to interfere with the HTML rendered by these components?

Intercept the rendered HTML

After a little soul-searching, I realized that there is a way. Without having to dig into the JSF components, I can intercept the HTML they render and if necessary tweak it just a little bit before passing it onwards. Eventually, all HTML goes into the Response Outputstream. But before it gets there, it passes through a number of layers.

JSF Components render their HTML to a ResponseWriter that gets passed into their encodeBegin, encodeEnd and encodeChildren methods as part of the FacesContext instance: on this context object, the component (or the renderer) can get hold of the ResponseWriter by calling context.getResponseWriter(). Rendering the HTML is now nothing more complex than writing HTML to the ResponseWriter, such as: writer.startElement(“div”, this); writer.writeAttribute(“class”, styleClass, null); etc.

The ResponseWriter is typically put on the FacesContext instance in the FacesContext Factory that is responsible for creating the FacesContext instances. By configuring a different FacesContext factory, we could have all of our application use a different ResponseWriter, one that we create ourselves as a wrapper around the one used by default.

However that approach is somewhat heavy handed if we only want to augment the HTML render-output for one or two components in a small number of pages – as is the case on our project. So what is a light-weight approach?

The FacesContext interface not only defined the getResponseWriter() method, it also has a setResponseWriter(). If we can set our own, temporary ResponseWriter that wraps the ‘real’ one, just for the purpose of intercepting the HTML rendered for one or two specific elements and slightly changing it, we can get the desired functionality.

Applying custom ResponseWriters

My approach in summary:

– I create a custom JSF component – ResponseWriterDecorator – that can act as a container for other JSF components; it does not render any HTML output itself. However, its encodeBegin method is used to set a custom ResponseWriter on the FacesContext; its encodeEnd restores the original ResponseWriter. All child elements of this custom JSF component render their HTML to the custom ResponseWriter, that may pass it through unchanged or may filter or manipulate it. Without making any change to the original JSF component, its output can be modified

– the custom JSF component has an attribute, decorator, that is used to specify the custom ResponseWriter that should be used in this particular case. Depending on the 3rd party component that is wrapped/augmented, different custom ResponseWriters with different behavior is required.

– the custom JSF component ResponseWriterDecorator expects an instance of interface IResponseWriterDecorator in the decorator attribute. This interface defines a single method: ResponseWriter getResponseWriter(ResponseWriter). This method is called by the ResponseWriterDecorator to get a decorated ResponseWriter that is then set on the FacesContext.

– the special custom ResponseWriter can be based on a base class BaseResponseWriterDecorator that simply calls the wrapped ResponseWriter passed in the getResponseWriter(ResponseWriter) method; it will override any methods it deems necessary to manipulate the HTML rendered by the children of the ResponseWriterDecorator.

Setting a StyleClass on the rendered Radio Buttons

The specific challenge I had to overcome today was this one: the Radio Buttons rendered from the ADF Faces selectOneRadio component do not have a CSS class associated with them. We can specify the styleClass attribute on the selectOneRadio compent, but that gets rendered as the HTML class attribute on the label element that is rendered, not on the input element.

So what I need to accomplish is to intercept the INPUT elements rendered by selectOneRadio and set the class attribute on that element.

In the jspx page, I will wrap the af:selectOneChoice element in my new custom ResponseWriterDecorator element:

<cxs:ResponseWriterDecorator decorator="#{radioResponseWriterDecorator}">
  <af:selectOneRadio id="EmpRadiotgest"
                     label="#{nls['EMP_RADIOTGEST']}"
                     layout="vertical">
   ...
  </af:selectOneRadio>
</cxs:ResponseWriterDecorator>

The decorator attribute is set to the EL expression #{radioResponseWriterDecorator}. This expression refers to a managed bean, set up in the faces-config.xml file:

  <managed-bean>
    <managed-bean-name>radioResponseWriterDecorator</managed-bean-name>
    <managed-bean-class>nl.cxs.view.util.RadioDecoratorProvider</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
  </managed-bean>

The class RadioDecoratorProvider implements the IResponseWriterDecorator interface:

package nl.cxs.view.util;

import javax.faces.context.ResponseWriter;
import nl.cxs.jsf.IResponseWriterDecorator;

public class RadioDecoratorProvider implements IResponseWriterDecorator {
    public RadioDecoratorProvider() {
    }

    public ResponseWriter getResponseWriter(ResponseWriter rw) {
        return new RadioDecorator(rw);
    }
}

The RadioDecorator returned by this provider is the ResponseWriter that will be used for rendering the selectOneRadio element. It is this ResponseWriter that will have the opportunity to intercept and interfere with the HTML rendered by the ADF component.

This class is pretty straightforward:

package nl.cxs.view.util;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.ResponseWriter;

public class RadioDecorator extends BaseResponseWriterDecorator {

    private ResponseWriter rw;

    public RadioDecorator(ResponseWriter responseWriter) {
        super(responseWriter);
        this.rw = responseWriter;
    }

    public void startElement(String string,
                             UIComponent uiComponent) throws IOException {
        super.startElement(string, uiComponent);
        if ("input".equalsIgnoreCase(string)) {
            writeAttribute("class","mySpecialRadioButtonStyleClass",null);
        }
    }
}

The class extends from BaseResponseWriterDecorator; this class implements the ResponseWriter interface, by call the wrapped ResponseWriter for every method in the interface, like this:

public class BaseResponseWriterDecorator extends ResponseWriter{
    private ResponseWriter rw;

    public BaseResponseWriterDecorator(ResponseWriter responseWriter) {
        this.rw = responseWriter;
    }


    public String getContentType() {
        return rw.getContentType();
    }

    public String getCharacterEncoding() {
        return rw.getCharacterEncoding();
    }
    .... etc.

A small fragment of the HTML rendered to the browser – the majority of which was created by the ADF Faces selectOneRadio component and a little bit that was added by the custom ResponseWriter:

<br/>
<input id="EmpRadiotgest:_5" class="mySpecialRadioButtonStyleClass" type="radio" value="5" name="EmpRadiotgest"/>
<label class="specialRadioStyle" for="EmpRadiotgest:_5">Manager</label>

Now I am not sure whether this is the very best way to approach this challenge. It does the job, it is a generic and versatile solution so it feels okay. But perhaps JSF allows for better ways of achieving the same ends. I would be interested in any feedback or suggestions you may have.

The Custom JSF Component ResponseWriterDecorator

The custom JSF component that wraps one or more 3rd party components to intercept the HTML they render, is created as follows:

  • create the JSP tag-class: ResponseWriterDecoratorTag
  • create the UIComponent class: ResponseWriterDecorator (that also relies on the IResponseWriterDecorator interface)
  • add the tag to the TLD
  • add the component to the faces-config.xml

The implementation of these objects:

1. create the JSP tag-class: ResponseWriterDecoratorTag

package nl.cxs.jsf.uicomponents.tags;

import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;


public class ResponseWriterDecoratorTag extends UIComponentTag{

    String decorator;

    public ResponseWriterDecoratorTag() {
    }

    public String getComponentType() {
        return "ResponseWriterDecorator";
    }

    public String getRendererType() {
        return null;
    }

    protected void setProperties(UIComponent component) {
        super.setProperties(component);
        processProperty(component, decorator, "decorator");
    }
    private void processProperty(UIComponent component, String property, String propertyName) {
      if (property!=null) {
        if (isValueReference(property)) {
            FacesContext context = FacesContext.getCurrentInstance();
            Application app = context.getApplication();
            ValueBinding vb = app.createValueBinding(property);
            component.setValueBinding(propertyName, vb);
        } else {
            component.getAttributes().put(propertyName, property);
        }
      }
    }

    public void setDecorator(String decorator) {
        this.decorator = decorator;
    }

    public String getDecorator() {
        return decorator;
    }
}

2. create the UIComponent class: ResponseWriterDecorator (that also relies on the IResponseWriterDecorator interface)

package nl.cxs.jsf.uicomponents;

import java.io.IOException;
import javax.faces.component.UIPanel;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import nl.cxs.jsf.IResponseWriterDecorator;

public class ResponseWriterDecorator extends UIPanel{
    public static final String decorator_key = "decorator";
    private ResponseWriter writer ;

    public ResponseWriterDecorator() {
        setRendererType(null);
        }

        public String getFamily() {
          return null;
        }

    public void encodeBegin(FacesContext context) throws IOException {
        writer = context.getResponseWriter();
        Object decorator = getAttributes().get(decorator_key);
        if (decorator instanceof IResponseWriterDecorator) {
            context.setResponseWriter(((IResponseWriterDecorator)decorator).getResponseWriter(writer));
        }

    }

    public void encodeEnd(FacesContext facesContext) throws IOException {
        super.encodeEnd(facesContext);
        facesContext.setResponseWriter(writer);
    }
}

The interface:

package nl.cxs.jsf;

import javax.faces.context.ResponseWriter;

public interface IResponseWriterDecorator {

    public ResponseWriter getResponseWriter(ResponseWriter rw);
}

3. add the tag to the TLD

  <tag>
    <description>Wraps the ResponseWriter used for rendering the child elements. The RW is wrapped in the response writer returned by the decorator set on the decorator attribute</description>
    <display-name>ResponseWriterDecorator</display-name>
    <name>ResponseWriterDecorator</name>
    <tag-class>nl.cxs.jsf.uicomponents.tags.ResponseWriterDecoratorTag</tag-class>
    <tei-class>com.sun.faces.taglib.FacesTagExtraInfo</tei-class>
    <body-content>JSP</body-content>
    <attribute>
      <name>id</name>
    </attribute>
    <attribute>
      <name>rendered</name>
    </attribute>
    <attribute>
      <name>binding</name>
    </attribute>
    <attribute>
      <name>decorator</name>
    </attribute>
  </tag>

4. add the component to the faces-config.xml

  <component>
    <component-type>ResponseWriterDecorator</component-type>
    <component-class>nl.cxs.jsf.uicomponents.ResponseWriterDecorator</component-class>
  </component>

Download the sources: CustomResponseWriter.zip.

 

3 Comments

  1. Jimmy V September 9, 2011
  2. Carles Biosca March 7, 2008
  3. Viktoras Agejevas February 7, 2008