March 18th, 2009 by Lincoln Baxter III

PrettyFaces v1.1.x, v1.2.x, v2.0.x Docs (legacy)

Configuration

Setting up PrettyFaces is simple.

0. Get PrettyFaces.

This step is pretty straight-forward, right? Extract necessary JAR files into your /WEB-INF/lib directory, or include a maven dependency in your pom.xml

<dependency>
	<!-- For JSF1.1 -->
	<groupId>com.ocpsoft</groupId>
	<artifactId>ocpsoft-pretty-faces</artifactId>
	<version>1.1.0</version>
</dependency>
<dependency>
	<!-- For JSF1.2 -->
	<groupId>com.ocpsoft</groupId>
	<artifactId>ocpsoft-pretty-faces</artifactId>
	<version>1.2.6_RC1</version>
</dependency>
<dependency>
	<!-- For JSF2.0 -->
	<groupId>com.ocpsoft</groupId>
	<artifactId>ocpsoft-pretty-faces</artifactId>
	<version>2.0.2_GA</version>
</dependency>

1. Add the <filter> and <filter-mapping> to /WEB-INF/web.xml

PrettyFilter does most of the work. Without it, not much would happen.

<filter>
	<filter-name>Pretty Filter</filter-name>
	<filter-class>com.ocpsoft.pretty.PrettyFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>Pretty Filter</filter-name>
	<url-pattern>/*</url-pattern>
	<dispatcher>REQUEST</dispatcher>
	<dispatcher>FORWARD</dispatcher>
	<dispatcher>ERROR</dispatcher>
</filter-mapping>

2. Create the /WEB-INF/pretty-config.xml file

This is where you will map URLs to Faces Views. Read on for details.

<pretty-config
	xmlns="http://ocpsoft.com/prettyfaces-xsd"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://ocpsoft.com/prettyfaces-xsd 
	http://ocpsoft.com/xml/ns/prettyfaces/pretty-1.0.xsd">
	<url-mapping id="login">
		<pattern> /login </pattern>
		<view-id> /faces/login.jsf </view-id>
	</url-mapping>
	<url-mapping id="home">
		<pattern> /home </pattern>
		<query-param name="displayWelcomeMessage">#{homeBean.displayWelcomeMessage}</query-param>
		<view-id> #{homeBean.getViewPath} </view-id>
	</url-mapping>
	<url-mapping id="viewStory">
		<pattern>  /story/#{myBean.currentStoryId}/  </pattern>
		<query-param name="commentText" decode="false">#{myBean.commentText}</query-param>
		<view-id> /faces/story/viewStory.jsf </view-id>
		<action onPostback="false">#{myBean.loadStory}</action>
	</url-mapping>
	<url-mapping id="viewComment">
		<pattern> /story/#{myBean.currentStoryId}/#{myBean.commentId} </pattern>
		<view-id>/faces/story/comment.jsf</view-id>
		<action>#{myBean.loadComment}</action>
	</url-mapping>
</pretty-config>

Url Mappings

Each <url-mapping id=””> must specify a unique id. And contains the following attributes and elements, in order:

  1. <pattern>/blog/article/#{someBean.paramName}</pattern>

    Specify the pattern for which this URL will be matched. This element is required, and has a multiplicity of 1 (only one)

    Any EL expressions #{someBean.paramName} found within the pattern will be processed as value injections. The URL will be parsed and the value found at the location of the EL expression will be injected into the location specified in that EL expression. Note: EL expressions will not match over the ‘/’ character.

    The pattern itself is compiled parsed as a regular expression, meaning that the actual URL matching can be as simple or as complex as desired.

    If further custom URL parsing is required, it is recommended to do this manually via an Action Method: See custom parsing.

  2. <query-param name=”key”>#{someBean.queryParamValue}</query-param>

    Defines a managed query parameter of the form http://site.com/url?key=somevalue, where if the parameter exists, the value will be injected into the specified managed bean. This also handles JSF commandLink and AJAX <f:param> values. This element is optional, and has a multiplicity of 0…N (zero or more)

    Attributes:

    1. name — String, required. This is the request value key
    2. decode — boolean, optional (default true), if set to false, this query-param will not be URLDecoded (see java.net.URLDecoder)
  3. <view-id>#{someBean.methodName}<view-id>

    Specify the JSF ViewId displayed by this mapping, by either calling an el Method (must return an object for which the toString() method will return the view Id) or by returning a literal String value. This element is required, and has a multiplicity of 1 (only one)

    The ViewId may be any resource located within the current Servlet Context: E.g. PrettyFaces can also forward to a non-Faces servlet.

  4. <action>#{someBean.methodName}</action>

    Specify an action method to be called after URL parameters have been parsed and assigned into beans. This element has a multiplicity of 0…N (zero or more)

    Attributes:

    1. phaseId — String, optional (default RESTORE_VIEW) if set to a valid JSF PhaseId, the action will occur immediately before the specified Phase. (see javax.faces.event.PhaseId)

      Valid values for this attribute are: RESTORE_VIEW, APPLY_REQUEST_VALUES, PROCESS_VALIDATIONS, UPDATE_MODEL_VALUES, INVOKE_APPLICATION, RENDER_RESPONSE, ANY_PHASE.

      Note however, that if the phase does not occur, neither will your action method.

    2. onPostback — boolean, optional (default true), if set to false, this action method will not occur on form postback. (see ResponseStateManager.isPostback())

Order of processing:

  1. URL pattern parsing, query-parameter handling, and value injection into JSF managed beans.
  2. View-Id calculation (if a view Id is dynamic, the el method will be called.)
  3. JSF gains control of the request via RequestDispatcher.forward(“/context/faces/viewId.jsf”).
  4. Action methods are called before RESTORE_VIEW phase, unless the optional phaseId attribute is specified.

Config Loading:

At application startup time, before any requests are processed, Pretty Faces processes zero or more configuration resources, located according to the following algorithm:

  1. Search for classpath resources named META-INF/pretty-config.xml in the ServletContext resource paths for this web application, and load each as a configuration resource.
  2. Check for the existence of a context initialization parameter named com.ocpsoft.pretty.CONFIG_FILES. If it exists, treat it as a comma-delimited list of context relative resource paths (starting with a /), and load each of the specfied resources.
  3. Check for the existence of a web application configuration resource named /WEB-INF/pretty-config.xml, and load it if the resource exists.

3. Advanced Features

3.1 Using Dynamic View ID Capabilities

Dynamic view IDs allow a mapped URL to display content from any JSF view. This is prevents doing redirects which would otherwise destroy information stored in the URL, and also provides some extra functionality for application design. This pretty-config mapping uses a dynamic view-id:
<pretty-config
	xmlns="http://ocpsoft.com/prettyfaces-xsd"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://ocpsoft.com/prettyfaces-xsd 
	http://ocpsoft.com/xml/ns/prettyfaces/pretty-1.0.xsd">
	<url-mapping id="home">
		<pattern> /home </pattern>
		<query-param name="displayWelcomeMessage">#{homeBean.displayWelcomeMessage}</query-param>
		<view-id> #{homeBean.getViewPath} </view-id>
	</url-mapping>
</pretty-config>
The corresponding backing-bean method must return a JSF ViewID, which will proceed with the faces lifecycle (leaving the URL /home), or a pretty:mappingId. In the case when a pretty:mappingId is returned, PrettyFaces will “switch mappings” and instead treat the request as if it had been sent to the new mappingId, without issuing a browser redirect.
public class ManagedBean
{
    public String getViewPath()
    {
        // This method returns the path of the JSF view to display
        // when the URL /home is accessed.
        if(user.isLoggedIn())
        {
        	// Note: the extension '.jsf' is the mapped faces extension
        	return "/faces/home.jsf";
        }
 
        // The home page can instead display a different view; return
        // the pretty:mappingId of the view you wish to display.
        // Note that this will not cause a redirect, and will not 
        // change the client browser URL.
        // If you wish to issue a redirect, you should use a page
        // load action instead of a dynamic view Id function.
        return "pretty:login";
    }
}

3.2 Using the Managed Query Parameter facility

Managed query parameters allow automatic assignment of values into JSF managed bean fields, instead of parsing and URL Decoding the value manually out of the request object. Examining this sample mapping, we can see that the developer has specified two managed query-parameters. The ‘sortBy’ and ‘itemId’ parameters.
<pretty-config>
	<url-mapping id="itemList">
		<pattern> /items/list </pattern>
		<query-param name="sortBy">#{itemBean.sortByField}</query-param>
		<query-param name="itemId">#{itemBean.currentItemId}</query-param>
		<view-id> /faces/items/list.jsf </view-id>
		<action>#{itemBean.loadItems}</action>
	</url-mapping>
</pretty-config>
The managed bean that accompanies this mapping:
public class ItemBean
{
	private List<Item> items;
	private Integer currentItemId;
	private String sortByField;
 
	public String deleteItem()
	{
		// currentItemId will be automatically populated by
		// PrettyFaces if the parameter was passed in the request
		// (see example JSF page below)
		ItemManager.deleteById(currentItemId);
 
		// Redisplay the current page via redirect.
		return "pretty:"
	}
 
	public void loadItems()
	{
		// The sortByField member will be null if the sortBy
		// query-parameter is not found in the request
		this.items = ItemManager.getSortedItems(sortByField);
	}
 
	//... getters and setters...
}
Example JSF page: Notice the <f:param> tag.
This will generate a link that provides the ‘itemId’ parameter to the request for PrettyFaces to parse.
<c:forEach var="item" items="${itemBean.items}">
	<h:commandLink>
		Delete this item.
		<f:param name="itemId" value="${item.id}" />
	</h:commandLink>
</c:forEach>

3.3 Wiring navigation into JSF action methods

public class PageBean
{
    public String goHome()
    {
        // this will tell pretty to redirect the client to the home-page
        // no parameters are mapped, so this is pretty simple
        return "pretty:home";
    }
 
    public String goHomeAndWelcome()
    {
        // this will tell pretty to redirect the client to the home-page
        // since there is a managed query-parameter defined in the mapping,
        // PrettyFaces will generate the URL, and append the mapped param
        // eg: /home?displayWelcome=true 
        homeBean.displayWelcomeMessage(true);
        return "pretty:home";
    }
 
    public String goViewStory()
    {
        // this will tell pretty to redirect the client to the viewStory page
        // PrettyFaces will generate the URL by extracting any values from
        // the mapping beans and using them to inject back into the pattern
        // therefore, navigation can be controlled by placing a value into
        // the mapped field before PrettyFaces extracts it and generates the URL
	// so... /story/#{myBean.currentStoryId}/ ...becomes... /story/12/ 
 
        viewStoryBean.setCurrentStoryId(12);
        return "pretty:viewStory";
    }
 
    public String doRefreshByRedirect()
    {
        // using the "pretty:" prefix without a mapping-id will cause a
        // redirect to the current page
        return "pretty:";
    }
 
    public String doNormalJSFRender()
    {
        // returning an value without the "pretty:" prefix will fall back to
        // the default JSF navigation handlers
        return "someNavigationCase";
    }
}

3.4 Parsing complex / dynamic-length URLs

Consider the following example configuration and bean: The URL contains many dynamic layers of mysite.com/value/value2/value3/…/valueN, which cannot be specified using a static URL pattern.

Since patterns are regexes, we can use a catch-all pattern, and process the values ourselves.

	<url-mapping id="dynamicUrl">
		<pattern> /blog/categories/.* </pattern>
		<view-id> #{urlParsingBean.parseComplexUrl} </view-id>
	</url-mapping>

Example of a complex URL parser. Remember to URLDecode before using any values from the URL:

public class UrlParsingBean
{
    public String parseComplexUrl() throws UnsupportedEncodingException
    {
        String uri = PrettyContext.getCurrentInstance().getOriginalUri();
        List<String> categoryChain = new ArrayList<String>();
 
        while(uri.length() > 0)
        {
            int index = uri.indexOf('/');
            String value = uri.substring(0, index);
            categoryChain.add(URLDecoder.decode(value, "UTF-8"));
            uri = uri.substring(index);
        }
 
        //now load the data...
 
        return "/blag/viewArticle.jsf";
    }
}

3.5 Accessing PrettyContext through EL

Since a new PrettyContext is generated on each request, and stored into the requestMap, it is possible to access the context object through EL in a JSP, or Facelet.

 
#{prettyContext.currentInstance} <!-- returns the current PrettyContext -->
#{prettyContext.currentInstance.currentMapping.id} <!-- returns the current UrlMapping Id -->
<!-- And so on... -->

4. Rendering HTML Links and URLs

4.1 The pretty:link component

PrettyFaces provides a JSF component to output an HTML link to the page. The link tag requires a mapping-id (specified in the pretty-config.xml,) identifying which link to render.

If the provided mappingId requires any url-pattern-parameters or managed-query-parameters, they can be passed in via the <f:param> tag.

Url pattern parameters can be passed individually, as a java.util.List or as an Array. In the latter two cases, toString() will be called on each of the objects in the list/array. If an empty or null list/array is passed, it will be ignored.

Url pattern parameters do NOT have a name attribute, and are parsed in the order they are passed into the tag. Managed-query-parameters DO have a name attribute, and order is irrelevant.

For Example: The viewComment pattern requires:

<!-- From pretty-config.xml, viewComment mapping-id, above:
 /story/#{myBean.currentStoryId}/#{myBean.commentId}-->
 
<%@ taglib prefix="pretty" uri="http://ocpsoft.com/prettyfaces" %>
 
<pretty:link mappingId="comment">
	<f:param value="#{myBean.currentStoryId}" />
	<f:param value="#{myBean.commentId}" />
	Go to Comment. (This is Link Text)
</pretty:link>

4.2 The pretty:urlbuffer component

PrettyFaces provides a JSF component to generate a URL for use as a page scoped variable through El. This tag requires a mapping-id (specified in the pretty-config.xml)

If the provided mappingId requires any url-pattern-parameters or managed-query-parameters, they can be passed in via the <f:param> tag.

Url pattern parameters can be passed individually, as a java.util.List or as an Array. In the latter two cases, toString() will be called on each of the objects in the list/array. If an empty or null list/array is passed, it will be ignored.

Url pattern parameters do NOT have a name attribute, and are parsed in the order they are passed into the tag. Managed-query-parameters DO have a name attribute, and order is irrelevant.

For Example: The viewItem pattern requires:

<!-- From the managed-query-parameter section above, itemList mapping-id.-->
<pretty:urlbuffer var="itemListUrl" mappingId="itemList">
	<f:param name="itemId" value="22" />
	<f:param name="sortBy" value="price" />
</pretty:urlbuffer>
<h:outputText value="Generated Url Is: #{requestScope.itemListUrl}" />
<!-- /items/list?itemId=22&sortBy=price -->
<br />
This design is intended to reduce complexity and prevent manual manipulation of URLs.

5. Configuring Logging (log4j)

example log4j.properties

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
 
### set log levels - for more verbose logging change 'info' to 'debug' ###
 
log4j.rootLogger=warn, stdout
 
#this will set PrettyFaces logging level to 'info'
log4j.logger.com.ocpsoft.pretty=info

Finished. Run your application!

You should now have a fully functional PrettyFaces configuration.
Subscribe to prettyfaces-users


Posted in PrettyFaces

8 Comments

  1. Tim Holloway says:

    Is there any way to invoke a method BEFORE the query parameters are injected? I’d like to reset the backing bean to a baseline state.

  2. Lincoln says:

    If you make the bean request-scoped, then the bean will clear itself on every request. Otherwise, I recommend using a separate bean to store your query parameters, freeing you to clear your other bean at any time without destroying the parameter information.

  3. Jeremy says:

    Perfect integration with seam !

    I let you know when the wensite goes online

    Thanks

  4. Marcio says:

    Lincoln,

    it is a best practice to put PrettyFaces on the last position of the filter chain? When a url request is mapped by pretty faces it executes a forward and ignores the rest of the chain filter.

  5. Gad says:

    Hi Folks,
    How can I redirect to an *external* url with pretty-faces? I’d like to redirect to the “action” bean and from there to another – external – website (e.g. http://www.cnn.com).

    Any help would be appreciated.

    Thanks!

  6. Lincoln says:

    Gad – call facesContext.getExternalContext.redirect(…url…) from your action method code

  7. […] Logging (log4j)This version of the documentation is for PrettyFaces 2.0.3, but you can view older versions of the PrettyFaces docs (1.1.0, 1.2.x, and 2.0.2_GA).0. Get PrettyFaces.This step is pretty […]

Comments are closed.