I've been working on an exception handling mechanism for a JSF-based application using IceFaces, and I was thinking... "why oh why can't we dynamically navigate to error pages using a navigation rule?"
Navigation rules in the faces-config.xml look like this:
<navigation-rule>That is fine but very limiting for my navigation purposes. What if I want to navigate to a different page according to the actual error code? In other words, I want to be able to do this:
<from-view-id>/somePage.xhtml</from-view-id>
<navigation-case>
<from-outcome>error</from-outcome>
<to-view-id>/errorPage.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>Well, it seems that I can't do that with the Sun JSF RI or IceFaces, so I decided to make it happen. I figured that if I wanted to add expression evaluation in a navigation target URL I needed to write a view handler. With IceFaces, the application view handler is com.icesoft.faces.facelets.D2DFaceletViewHandler, which is reponsible for setting up the direct-to-DOM rendering etc, so I needed to extend that class and find what methods I needed to override in order to get me to where I wanted to be. After a bit of experimentation I found there are two scenarios:
<from-view-id>/somePage.xhtml</from-view-id>
<navigation-case>
<from-outcome>error</from-outcome>
<to-view-id>/#{errorBean.errorCode}.xhtml</to-view-id>
</navigation-case> </navigation-rule>
Scenario #1: Navigation Rule With Redirection
This is where the navigation rule has a <redirect/> tag. The method in D2DFaceletViewHandler that handles this is
public String getActionURL(FacesContext context, String viewId)Scenario #2: Navigation Rule Without Redirection
This is where the navigation rule does not have a <redirect/> tag. The method in D2DFaceletViewHandler that handles this is
public void renderView(FacesContext context, UIViewRoot viewToRender)The way I decided to process the expression is very simple, almost elementary:
- Parse the URL/ViewId looking for a sub-string that begins with '#{' or '${' and ends with '}'
- capture the sub-string and create a value binding to evaluate the expression
- Replace the expression in the URL/ViewId with the actual value
- Process the newly evaluated URL/ViewId
Before I get any comments on step 1... no, I don't like regex because my brain just doesn't get it, and it takes me considerably longer to figure out a regex pattern to capture such a simple substring than to actually write a few lines of code to do the parsing.
So here's my (edited) code.
/**To use my spanking new view handler I just have to change the application section in the faces-config.xml file:
* Constructor
*/
public MyViewHandler(ViewHandler delegate) {
super(delegate);
}
/**
* Processes a view id that may contain an expression, by evaluating the
* expression and replacing the expression tag in the original view id with
* the expression result.
*
* @param context The faces context.
* @param viewId The view id to process.
* @return The processed view id.
*/
private String processViewId(FacesContext context, String viewId) {
String processedViewId = viewId;
int startExpression = processedViewId.indexOf("{") - 1;
if (startExpression > 0) {
char expChar = processedViewId.charAt(startExpression);
// expressions start with # or $
if ((expChar == '#') || (expChar == '$')) {
int endExpression = processedViewId.indexOf("}", startExpression);
if (endExpression > startExpression) {
// viewId contains an expression
String expression = processedViewId.substring(startExpression, endExpression + 1);
try {
ValueBinding vb = context.getApplication().createValueBinding(expression);
if (vb != null) {
String evaluatedExpression = vb.getValue(context).toString();
// replace the expression tag in the view id
// with the expression's actual value
processedViewId = processedViewId.replace(expression, evaluatedExpression);
}
}
catch (ReferenceSyntaxException ex) {
// do nothing: processedViewId = viewId;
}
}
}
}
return processedViewId;
}
/**
* Used to process a URL that may contain an expression. If a navigation
* rule in the faces configuration file has a <redirect> tag, this
* method will be used to process the URL specified in the
* <to-view-id> tag
*
* @see javax.faces.application.ViewHandler#getActionURL(FacesContext, String)
*/
@Override
public String getActionURL(FacesContext context, String viewId) {
String processedViewId = super.getActionURL(context, viewId);
processedViewId = this.processViewId(context, processedViewId);
return processedViewId;
}
/**
* If a navigation rule in the faces configuration file does not have a
* <redirect> tag, this method will be used to process the URL
* specified in the <to-view-id> tag
*
* @see com.icesoft.faces.application.D2DViewHandler#renderView(FacesContext,
* UIViewRoot)
*/
@Override
public void renderView(FacesContext context, UIViewRoot viewToRender)
throws IOException, NullPointerException {
String viewId = this.processViewId(context, viewToRender.getViewId());
viewToRender.setViewId(viewId);
super.renderView(context, viewToRender);
}
<faces-config>M.
<application>
...
...
<view-handler>
<!--
com.icesoft.faces.facelets.D2DFaceletViewHandler
-->
myViewHandler
</view-handler>
</application>
</faces-config>
No comments:
Post a Comment