JSF’s declarative navigation management requires you to specify a navigation rule with navigation cases in faces-config.xml. If your application gets bigger, your faces-config.xml would get bigger as a result. Following the convention over configuration design paradigm, it is possible to avoid navigation rules in xml.
As usual we need to extend JSF and the idea is to plug-in a custom navigation handler. This navigation handler assumes the outcome is the name of the target view of the navigation.
public class LetsGetRidofXMLStuffNavigationHandler extends NavigationHandler{
public final static String REDIRECT_PREFIX = "redirect";
@Override
public void handleNavigation(FacesContext facesContext, String fromAction, String outcome) {
if(outcome == null)
return; //no navigation
ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
String targetViewId = getTargetViewId(facesContext, outcome);
if(isRedirect(outcome))
{
ExternalContext externalContext = facesContext.getExternalContext();
String redirectPath = viewHandler.getActionURL(facesContext, targetViewId);
try
{
externalContext.redirect(externalContext.encodeActionURL(redirectPath));
}
catch (IOException e)
{
throw new FacesException(e.getMessage(), e);
}
}
else
{
UIViewRoot viewRoot = viewHandler.createView(facesContext, targetViewId);
facesContext.setViewRoot(viewRoot);
facesContext.renderResponse();
}
}
private boolean isRedirect(String outcome) {
return outcome.startsWith(REDIRECT_PREFIX);
}
private String getTargetViewId(FacesContext facesContext, String outcome) {
String targetViewId;
String viewSuffix = getDefaultViewSuffix(facesContext);
if(isRedirect(outcome)) {
targetViewId = "/" + outcome.split(":")[1] + viewSuffix;
} else {
targetViewId = "/" + outcome + viewSuffix;
}
return targetViewId;
}
private String getDefaultViewSuffix(FacesContext facesContext) {
String suffix = facesContext.getExternalContext().getInitParameter("javax.faces.DEFAULT_SUFFIX");
return suffix!=null ? suffix : ".jsp";
}
}
After implementing this navigation handler, we need to plug it in so that it can take over navigation management. In faces-config;
<?xml version="1.0" encoding="utf-8"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<application>
<navigation-handler>com.mycompany.myproject.stuff.LetsGetRidofXMLStuffNavigationHandler</navigation-handler>
</application>
Now without defining an navigation rule in xml, in action binding methods just return the name of the page. For example, to navigate from a paged called login.jsf to mainpage.jsf, return “mainpage”.
public String login() {
//do some stuff
return "mainpage";
}
When the “mainpage” outcome reaches our navigation management, it finds the view id as /target.jsp or /target.xhtml(depending on what view technology you use) and then do a forward. For redirects add the redirect prefix so the string to be returned should be “redirect:mainpage”.
After all there is no need to define any navigation-rule in faces-config so we can get rid of this;
<navigation-rule>
<from-view-id>/login.jsp</from-view-id>
<navigation-case>
<from-outcome>mainpage</from-outcome>
<to-view-id>/mainpage.jsp</to-view-id>
</navigation-case>
</navigation-rule>