Java
Java EE
Documentation
| By Roger Kitain and Jennifer Ball, August 2005 |
| |
The lifecycle demo doesn't require the use of XUL; it is there just to demonstrate how easy it is for a JavaServer Faces application to utilize more than one render kit. In fact, when using releases of JavaServer Faces technology prior to 1.2, you had to implement a custom ViewHandler class (see Creating a RenderKit Class for more information on this class) to handle multiple render kits, in addition to doing everything outlined in this document.
The lifecycle demo application uses three render kits: the standard HTML render kit, a custom SVG render kit, and a custom XUL render kit. They each render their respective markups. The following diagram shows the flow of the demo between the pages of different markup.
Figure 1: Page Flow of the Demo
The markup for the first page is produced with the standard HTML render kit, which means it contains HTML markup. From there, you navigate to the SVG page, which displays a diagram of the JavaServer Faces life cycle phases. Pressing any of the buttons on the lower left corner of the diagram produces an animation that shows how a request flows through each of the phases. The SVG markup for the SVG page is produced from an SVG render kit.
Clicking on any of the life cycle phase boxes causes an HTTP post to the JavaServer Faces controller, which in turn causes the next view to be returned. The response that is returned is the markup and a view identifier for an XUL page. This page provides more detailed information about the selected phase.
This demo also shows how to deal with the fact that SVG and XUL do not support the notion of an HTTP post mechanism as does HTML. Instead, the developer must use JavaScript to register an event handler so that when a button is pressed, an event is generated. The JavaScript onclick event handler collects the form input data, builds a post data string, and sends it to the server.
From SVG there are a couple of different ways to post data to a server from JavaScript. This demo relies on browsers that have built-in SVG support, such as in Deer Park Alpha 2, which makes available the XMLHttpRequest object as the posting mechanism. The demo uses this object to post the request to the JavaServer Faces controller. It also uses this object to handle the response from the JavaServer Faces controller back to the client.
The problem with using the XMLHttpRequest object is that the response it generates will not include the view identifier of the view to send with the response. This demo includes an implementation of a ResponsePhaseListener instance that will access the view identifier from the FacesContext instance and add it to the response. This way, the JavaScript callback handling the response knows what view to display next. The following figure illustrates how this process works. See Handling Submits Generated from a non-HTML Page for more details on implementing the ResponsePhaseListener class.
Figure 2: Handling Submits Generated from a non-HTML Page
We recommend that you download the demo, take a look at the code, and run it. You will need to register at java.net to download the example, but registration is free of charge. To download the example, go to
https://javaserverfaces.dev.java.net/ , go to the section that lists the nightly bundles, and follow the instructions for downloading and unpacking the samples bundle. The lifecycle demo is packaged as the
jsf-renderkits.war. This demo runs on Sun's Java System Application Server PE 9.0, code-named glassfish. You can download glassfish from
https://glassfish.dev.java.net/. Please read the README file for information on software requirements and how to deploy and run the demo. If you would like to see the source code of the demo, you can unpack the WAR file using the command
jar -xvf jsf-renderkits.war
The rest of this document describes how to create and use a render kit using the SVG render kit included in the lifecycle demo.
On the other hand, JavaServer Faces technology does not provide any components that represent the shapes that SVG can render. Therefore, the lifecycle demo needs custom components to represent the shapes that it uses.
If you decide that you do need a custom component, then you must decide whether it should extend a standard component or directly extend UIComponentBase, the base class for all the standard components. This base class, along with the standard component classes, are located in the javax.faces.component package and their names begin with UI.
If your custom component serves the same purpose as a standard component, you should extend that standard component rather than directly extend UIComponentBase. For example, the UIOutput component is intended for displaying something. Therefore, the Shape component class used by the lifecycle demo extends UIOutput rather than UIComponentBase.
The dynamic lifecycle example defines three component classes:
Shape ,
Line, and
Rectangle. The
Line and
Rectangle classes both extend
Shape, which makes sense because lines and rectangles are shapes.
The sole purpose of the
Line and
Rectangle components is to represent the appropriate shapes on the page; they don't hold any data that needs to be validated, respond to events, or do anything that a component class usually needs to define for the component. On the other hand, there is a lot of work to be done to render the components, which is what the corresponding renderer classes do.
The job of the Line and Rectangle component classes is to identify the component type and the component family for the purpose of registering the components with the application and delegating the rendering of the components to the appropriate renderers. To delegate rendering, a component class must override the getFamily method of UIComponent to return the identifier of a component family. A component family is used to refer to a component or set of components that can be rendered by a set of renderers. Here is the getFamily method from Line.java:
public String getFamily() {
return (COMPONENT_FAMILY);
}
LineCOMPONENT_FAMILY"Line"LineRegistering the Render Kit, Renderers, and Components in the Configuration FileThe Line class also sets a static variable called COMPONENT_TYPE to "Line". This value must match that returned by the getComponentType method of the tag handler that implements the component tag representing the Line component on the page. The implementation of the tag handler is described in Creating Custom Tags and Tag Handlers.
The Rectangle class also implements getFamily and sets the COMPONENT_TYPE variable to match that defined in the RectangleTag tag handler class.
The Shape class does not define a component type or component family; it is merely the base class for Rectangle and Line.
After creating the component classes, you can create renderers for them.
By delegating the rendering to a separate renderer, the component makes itself more versatile because multiple renderers would be able to render it to different clients. This is why the custom components of the lifecycle demo all delegate their rendering to separate renderers. LineRenderer renders the Line component, and RectangleRenderer renders the Rectangle component.
The lifecycle demo also provides renderers for the standard components that it uses so that it can render them to the SVG client. ButtonRenderer renders the UICommand component as a button. FormRenderer renders the UIForm component as an HTML form. TextRenderer renders the UIOutput component as a label.
This section uses LineRenderer and ButtonRenderer to explain the basic requirements for writing a renderer class for custom and standard components. At the very least, a renderer class must perform the encoding of a response. This is the process of generating the markup for the target client.
A renderer class might also need to do some decoding, which involves taking the component's local value from the request and converting it to a type acceptable to the component class--essentially the reverse of encoding. A renderer is required to perform decoding only if it needs to retrieve a component's local value or if it needs to queue an event onto the component.
Let's first talk about how to perform encoding.
The lifecycle demo components don't have any child components. Therefore, rendering these components is a bit simpler. The renderer class can perform the rendering with its
encodeBegin method,
encodeEnd method, or both methods. In fact,
LineRenderer uses both methods:
encodeBegin to render most of the
start tag and
encodeEnd to render the
end tag. Here are the
encodeBegin and
encodeEnd methods of
LineRenderer:
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException {
if (context == null || component == null) {
// PENDING - i18n
throw new NullPointerException("'context' and/or 'component' is null");
}
if (log.isTraceEnabled()) {
log.trace("Begin encoding component " + component.getId());
}
// suppress rendering if "rendered" property on the component is
// false.
if (!component.isRendered()) {
if (log.isTraceEnabled()) {
log.trace("End encoding component " + component.getId() +
" since rendered attribute is set to false ");
}
return;
}
ResponseWriter writer = context.getResponseWriter();
writer.startElement("line", component);
writeIdAttributeIfNecessary(context, writer, component);
String x1 = (String)component.getAttributes().get("x1");
if (x1 != null) {
writer.writeAttribute("x1", x1, "x1");
}
String y1 = (String)component.getAttributes().get("y1");
if (y1 != null) {
writer.writeAttribute("y1", y1, "y1");
}
String x2 = (String)component.getAttributes().get("x2");
if (x2 != null) {
writer.writeAttribute("x2", x2, "x2");
}
String y2 = (String)component.getAttributes().get("y2");
if (y2 != null) {
writer.writeAttribute("y2", y2, "y2");
}
String style = (String)component.getAttributes().get("style");
if (style != null) {
writer.writeAttribute("style", style, "style");
}
writer.writeText("\n ", null);
}
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
if (context == null || component == null) {
// PENDING - i18n
throw new NullPointerException("'context' and/or 'component' is null");
}
ResponseWriter writer = context.getResponseWriter();
writer.endElement("line");
writer.writeText("\n", null);
}
Notice that
encodeBegin renders the beginning
line tag. The
encodeEnd method renders the ending
line tag.
The encoding methods accept a UIComponent argument and a FacesContext argument. The FacesContext instance contains all the information associated with the current request. The UIComponent argument is the component that needs to be rendered.
The methods render the markup to the ResponseWriter instance, which writes out the markup to the current response. This basically involves passing the HTML tag names and attribute names to the ResponseWriter instance as strings, retrieving the values of the component attributes, and passing these values to the ResponseWriter instance.
The startElement method takes a String (the name of the tag) and the component to which the tag corresponds (in this case, line). Passing this information to the ResponseWriter instance helps design-time tools know which portions of the generated markup are related to which components.
After calling startElement, the encodeBegin method calls writeIdAttributeIfNecessary (defined in BaseRenderer), which tries to get the ID of the component in order to write it out. After rendering the ID, the method calls writeAttribute to render all the tag's attributes. The writeAttribute method takes the name of the attribute, its value, and the name of the corresponding property or attribute of the containing component. The last parameter can be null, and it won't be rendered.
As explained in
Creating the Renderer Classes, your renderer might also need to supply a
decode method. The next section describes the
decode method included in
ButtonRenderer.
A renderer must implement the decode method only if it must retrieve the local value or if it needs to queue events. The decode method of ButtonRenderer does both of these things. It first retrieves the client ID of the button component that it rendered previously and checks if it matches the client ID for the button the user has just clicked. If the IDs match, the decode method calls the component's queueEvent method to queue on ActionEvent onto the component. Here is the decode method from ButtonRenderer:
public void decode(FacesContext context, UIComponent component) {
if (context == null || component == null) {
throw new NullPointerException("'context' and/or 'component is null");
}
if (log.isTraceEnabled()) {
log.trace("Begin decoding component " + component.getId());
}
String clientId = component.getClientId(context);
Map requestParameterMap = context.getExternalContext()
.getRequestParameterMap();
String value = (String) requestParameterMap.get(clientId);
if (value == null) {
if (requestParameterMap.get(clientId + ".x") == null &&
requestParameterMap.get(clientId + ".y") == null) {
return;
}
}
ActionEvent actionEvent = new ActionEvent(component);
component.queueEvent(actionEvent);
if (log.isDebugEnabled()) {
log.debug("This command resulted in form submission " +
" ActionEvent queued " + actionEvent);
}
if (log.isTraceEnabled()) {
log.trace("End decoding component " + component.getId());
}
return;
}
Now that you have your set of renderers, it's time to add them to your render kit. Before you do that, you need to create a RenderKit class.
As you've guessed by now, a render kit defines a set of renderers that have the ability to render a set of components to one particular kind of client. For example, this section shows you how to create a render kit for an SVG client.
Much more than just defining a set of renderers, a RenderKit instance also takes part in rendering the response, re-building the component tree structure, and saving and restoring component state. It does this in partnership with the default ViewHandler implementation, which allows applications to control what happens in the restore view and render response phases of the life cycle.
In a nutshell, the life cycle implementation uses ViewHandler to get information and necessary objects from the render kit so that it can determine whether ViewHandler should create or restore a view, save or restore state, or render the response, and can instruct ViewHandler to do one of these things. The primary objects that participate with ViewHandler in building the view, rendering it, and restoring state are summarized in the following figure.
Figure 3: Objects Used in Rendering
One of the objects that ViewHandler obtains from the render kit is the ResponseStateManager object, which knows the rendering technology used by the render kit and can perform rendering-specific state-management duties. It might not be immediately obvious what the relationship is between rendering and state-management. The two are actually related because state is saved on the client by default. In order to save the state on the client, it must be rendered using the rendering technology specified by the render kit.
Another object the ViewHandler instance obtains from the render kit is the ResponseWriter object. It implements methods for writing out markup to a specific client.
Let's take a look at how these objects are used during the life cycle of a JavaServer Faces page.
During the restore view phase of the life cycle, ViewHandler retrieves the ResponseStateManager object in order to test if the request is a postback or an initial request. The ResponseStateManager object is needed in this case because it is the only one that knows what rendering technology is being used and is therefore the only one that can look at a request, which is rendering-technology specifiec.
If the request is an initial request, the life cycle implementation jumps to the render response phase. During the render response phase, the ViewHandler instance uses the ResponseStateManager object to save the state of the tree for the benefit of subsequent requests. Additionally, the renderView method of ViewHandler is called. This method calls encodeAll on the UIViewRoot instance, which represents the root of the component tree.
The encodeAll method traverses the tree of components, calling each component's encoding methods. These methods use the component family and renderer type specified in the application's configuration file to find the renderer associated with the component. Once the renderer is found, it calls the methods of the ResponseWriter object to render the view.
If a request is a postback, the restoreView method of ViewHandler is called. This method uses the ResponseStateManager object to re-build the component tree and restore state. After the tree is built and state is restored, the ViewHandler instance is not needed until the render response phase occurs again.
Meanwhile, the render kit and renderers are needed to decode the request values during the apply request values phase. To do this, the FacesContext instance calls the processDecodes method on the UIView instance. This method traverses the component tree, calling processDecodes on all the components in the tree. The components' processDecodes methods call the associated decoding methods of the renderers. The renderers then call the methods of the ResponseWriter object that write out the markup to the client.
Subsequently, the rest of the life cycle phases are executed, including render response, during which the same process occurs as did during the render response phase that was executed for the initial request.
When using JavaServer Faces technology, version 1.2, you do not have to create a custom ViewHandler to handle multiple render kits in an application, as you did when using prior versions. You need only specify the render kit to be used for each view using the view tag on the corresponding page, as explained in Using the Render Kit in the Page.
Now that you understand the role of the render kit and its ResponseStateManager and ResponseWriter objects in the life cycle, you're ready to implement a RenderKit class, which must perform the following tasks:
private static String SVG_CONTENT_TYPE = "image/svg+xml";
private static String APPLICATION_XML_CONTENT_TYPE = "application/xml";
private static String TEXT_XML_CONTENT_TYPE = "text/xml";
private static String CHAR_ENCODING = "ISO-8859-1";
private static String CONTENT_TYPE_IS_SVG = "ContentTypeIsSVG";
The JavaServer Faces implementation invokes the addRenderer method of RenderKit at application startup time as it processes the application configuration file, in which the render kit and set of renderers are configured. You'll see in Configuring the Render Kit, Components, and Renderers how to configure the render kit, renderers, and components included in your application. The addRenderer method populates a map with the renderer, as shown here:
public void addRenderer(String family, String rendererType,
Renderer renderer) {
if (family == null || rendererType == null || renderer == null) {
// PENDING - i18n
String message = "Argument Error: One or more parameters are null.";
message = message + " family " + family + " rendererType " +
rendererType + " renderer " + renderer;
throw new NullPointerException(message);
}
HashMap renderers = null;
synchronized (rendererFamilies) {
if (null == (renderers = (HashMap) rendererFamilies.get(family))) {
rendererFamilies.put(family, renderers = new HashMap());
}
renderers.put(rendererType, renderer);
}
}
The
getRenderer method returns a renderer that can render the components of the specified component family and has the specified renderer type. The component family is defined in the component class. The renderer type is defined by the tag handler implementing the tag that renders the component.
public Renderer getRenderer(String family, String rendererType) {
if (rendererType == null || family == null) {
// PENDING - i18n
String message =
"Argument Error: One or more parameters are null.";
message = message + " family " + family + " rendererType " +
rendererType;
throw new NullPointerException(message);
}
HashMap renderers = null;
Renderer renderer = null;
if (null != (renderers = (HashMap) rendererFamilies.get(family))) {
renderer = (Renderer) renderers.get(rendererType);
}
return renderer;
}
The following method returns a ResponseStateManager instance:
public synchronized ResponseStateManager getResponseStateManager() {
if (responseStateManager == null) {
responseStateManager = new SVGResponseStateManager();
}
return responseStateManager;
}
The createResponseWriter method takes a Writer object, a String that contains a list of content types, and the character encoding, all of which are used to render the response. The method first loads the supported content types defined by the RenderKit class into an array. The method then checks if the String of content types passed to the method is null. If it is, the method looks for a content type in the context representing the response and in the request header. If the method finds a content type, it looks for a matching content type in the array of supported types. If no match is found, the content type is set to SVG. The method then returns a new SVGRenderKit instance with the specified writer, and an acceptable content type and character encoding.
The default ResponseWriter that comes with JavaServer Faces technology extends the Writer class from the Java 2 Standard Edition to add special methods for producing elements and attributes for markup languages, such as XML and HTML. It also defines the content type for the render kit that creates it.
Because SVG is derived from XML, SVGResponseWriter pretty much re-uses the code from the default response writer class. The only real difference is that SVGResponseWriter defines a content type of SVG, as shown by this line of code from SVGResponseWriter:
private String contentType = "image/svg+xml";
Creating a
ResponseStateManager class involves providing implementations for the following methods:
Much of the
SVGResponseStateManager class is copied directly from the
ResponseStateManagerImpl class that is part of the JavaServer Faces reference implementation. The
writeState method is the only one that has code that is specific to rendering SVG.
More specifically, the writeState method does the following:
public class SerializedView extends Object implements Serializable {
private Object structure = null;
private Object state = null;
public SerializedView(Object newStructure, Object newState) {
structure = newStructure;
state = newState;
}
public Object getStructure() {
return structure;
}
public Object getState() {
return state;
}
}
The following piece of the lifecycle demo's configuration file show's how to register the SVG render kit, the
Line component, the
LineRenderer renderer, and the
ButtonRenderer renderer, which renders a
UICommand component to an SVG client. The rest of the configuration information is ommitted. Please refer to the lifecycle demo's
faces-config.xml file to find out how the other components and renderers are configured.
...
<component>
<component-type>Line</component-type>
<component-class>
renderkits.components.svg.Line
</component-class>
</component>
...
<render-kit>
<render-kit-id>SVG</render-kit-id>
<render-kit-class>
renderkits.renderkit.svg.SVGRenderKit
</render-kit-class>
...
<renderer>
<component-family>
javax.faces.Command
</component-family>
<renderer-type>
renderkit.svg.Button
</renderer-type>
<renderer-class>
renderkits.renderkit.svg.ButtonRenderer
</renderer-class>
</renderer>
<renderer>
<component-family>Line</component-family>
<renderer-type>
renderkit.svg.Line
</renderer-type>
<renderer-class>
renderkits.renderkit.svg.LineRenderer
</renderer-class>
</renderer>
</render-kit>
componentrender-kitcomponent-familyrenderer-typerenderercomponent-familygetFamilyrenderer-typegetRendererTypeTo implement a tag handler, you need to create a class that extends UIComponentELTag and add the following to it:
// PROPERTY: onclick
private javax.el.ValueExpression onclick;
public void setOnclick(javax.el.ValueExpression onclick) {
this.onclick = onclick;
}
To pass the values of the tag attributes to Line component, the tag handler implements the setProperties method. The following lines set the value on the onClick property:
if (onclick != null) {
if (!onclick.isLiteralText()) {
line.setValueExpression("onclick", onclick);
} else {
line.getAttributes().
put("onclick", onclick.getExpressionString());
}
}
public String getRendererType() {
return "renderkit.svg.Line";
}
public String getComponentType() {
return "Line";
}
Finally, it's recommended that all tag handlers implement a release method, which releases resources allocated during the execution of the tag handler. The release method of LineTag is as follows:
public void release() {
super.release();
// rendered attributes
this.onclick = null;
this.onfocusin = null;
this.onfocusout = null;
this.onmousedown = null;
this.onmousemove = null;
this.onmouseout = null;
this.onmouseover = null;
this.onmouseup = null;
this.style = null;
this.x1 = null;
this.y1 = null;
this.x2 = null;
this.y2 = null;
The custom tags used to render SVG components for the lifecycle demo are defined in the svg.tld file. The following code snippet is part of the line tag's definition. The definition must have the following sub-elements:
All attribute elements except the one defining the id attribute must also include a deferred-value element that specifies what type of value that the attribute accepts.
<tag>
<name>line</name>
<tag-class>
renderkits.taglib.svg.LineTag
</tag-class>
<body-content>JSP</body-content>
<attribute>
...
<name>id</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>rendered</name>
<required>false</required>
<deferred-value>
<type>boolean</type>
</deferred-value>
</attribute>
<attribute>
...
<name>x1</name>
<required>false</required>
<deferred-value>
<type>java.lang.String</type>
</deferred-value>
</attribute>
...
</tag>
<%@ page contentType="image/svg+xml"%>
<%@ taglib uri="http://java.sun.com/jsf/svg" prefix="g" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<style type="text/css">
rect:hover {fill-opacity:0.3;}
</style>
<f:view renderKitId="SVG" > <g:form id="form">
<g:outputText x="100" y="50" textAnchor="middle"
value="JSF Request Processing Lifecycle"
style="stroke:black;
stroke-width:0.5;
fill:none; font-size:32pt;" />
<!-- Restore View Graphic -->
<g:line id="toRestore" x1="25" y1="125"
x2="100" y2="125"
style="stroke:black; fill:none;" />
<g:commandButton id="restore" width="120"
height="50" x="100" y="100" type="submit"
action="xul-restore"
style="stroke:black; fill:#8470ff;" >
<g:outputText x="130" y="120"
textAnchor="middle" value="Restore" />
<g:outputText x="135" y="140"
textAnchor="middle" value="View" />
</g:commandButton>
...
</g:form>
</f:view>
</svg>
For this reason, the lifecycle demo requires a ResponsePhaseListener implementation to get the view ID after the invoke application phase and before the render response phase. When the ResponsePhaseListener object is notified that the invoke application phase has occurred, it retrieves the view ID from the FacesContext instance and adds it to the URL to be used to render the next page. It then adds this URL to the response header so that the next view can be rendered in the subsequent render response phase.
The following code sample shows the lifecycle demo's ResponsePhaseListener.afterPhase method, which the JavaServer Faces life cycle implementation calls after the invoke application phase. This method has the task of appending the URL of the new view to the response.
public void afterPhase(PhaseEvent event) {
// Disregard requests that are not XMLHttpRequest(s)
Map requestHeaderMap =
event.getFacesContext().getExternalContext().
getRequestHeaderMap();
if (requestHeaderMap.get(XML_HTTP) == null) {
return;
}
// If we're dealing with an XMLHttpRequest...
// Get the URI and stuff it in the response header.
FacesContext context = event.getFacesContext();
String viewId = context.getViewRoot().getViewId();
String actionURL =
context.getApplication().getViewHandler().
getActionURL(context, viewId);
HttpServletResponse response =
(HttpServletResponse)context.getExternalContext().
getResponse();
response.setHeader("Cache-Control", "no-cache");
response.setHeader(VIEW_URI, actionURL);
}
AJAX entries