OCPSoft.com - Simple SolutionsCommunity Documentation
Version: 3.1.0
While compatible with any Servlet 2.5+ container, some features are only available in JavaServer Faces.
Sections describing these features are marked with a '*'. This means that the feature either requires JSF, or requires an environment that has configured JSF in order to function; otherwise, those marked features will not be available.
PrettyFaces is an OpenSource extension for Servlet, Java EE, and JSF, which enables creation of bookmarkable, REST-ful, "pretty" URLs. PrettyFaces solves several problems elegantly, such as: custom URL-rewriting, page-load actions, seamless integration with JSF navigation and links, dynamic view-id assignment, managed parameter parsing, and configuration-free compatibility with other JSF frameworks.
Any business knows how important Search Engine Optimization can be for sales. PrettyFaces allows SEO-friendly URLs, and improved customer experience. Give your site a uniform, well understood feeling, from the address bar to the buy button.
What is sometimes neglected, even when building web-sites that aren't for external customers, is the consistency of the URL displayed to users in the browser address bar. Keeping the URL tidy can make a big difference in usability, providing that smooth web-browsing experience.
Example 1.1. A URL rewritten using PrettyFaces
Consider the following URL.
http://ocpsoft.com/index.jsf?post=docs?category=prettyfaces
When presenting information to users (frequently your clients,) it is generally bad practice to show more information than necessary; by hiding parameter names, we are able to clean up the URL. The following URL-mapping accomplishes our goal:
<url-mapping> <pattern value="/#{post}/#{category}" /> <view-id>/index.jsf</view-id> </url-mapping>
Our final result looks like this:
http://ocpsoft.com/prettyfaces/docs/
This is just a simple example of the many features PrettyFaces provides to standardize your URLs
Notice that outbound links encoded using
HttpServletResponse.encodeRedirectURL(url)
will also be automatically rewritten to new URLs,
unless disabled by using the outbound="false"
attribute on a given URL-mapping configuration.
It should not be difficult to install PrettyFaces into any new or existing Servlet-based application. Follow the next few steps and you should have a working configuration in only a few minutes.
This step is pretty straight-forward, right? Copy necessary JAR files into your /WEB-INF/lib directory, or include a Maven dependency in your pom.xml (recommended):
Non-Maven users may download JAR files manually from one of the following repositories: (Central, OcpSoft). Be sure to select the correct package for your version of JSF.
Non-Maven Users must also include the following required JAR dependencies (downloaded separately) in addition to the PrettyFaces JAR file:
List of Maven Dependencies:
<!-- for JSF 2.x --> <dependency> <groupId>com.ocpsoft</groupId> <artifactId>prettyfaces-jsf2</artifactId> <version>${latest.prettyfaces.version}</version> </dependency> <!-- for JSF 1.2.x --> <dependency> <groupId>com.ocpsoft</groupId> <artifactId>prettyfaces-jsf12</artifactId> <version>${latest.prettyfaces.version}</version> </dependency> <!-- for JSF 1.1.x (UNSUPPORTED) --> <dependency> <groupId>com.ocpsoft</groupId> <artifactId>prettyfaces-jsf11</artifactId> <version>${latest.prettyfaces.version}</version> </dependency>
If you are using a Servlet 3.0 compliant container, you may skip this step because PrettyFaces will automatically register the required Servlet Filter; otherwise, make sure PrettyFilter is the first filter in your web.xml file. (The dispatcher elements are required to ensure PrettyFaces intercepts both internal and external requests.)
Example 2.1. /WEB-INF/web.xml (Ignore if using Servlet 3.0)
<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>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping>
This is where you'll tell PrettyFaces what to do, which URLs to rewrite. Each URL-mapping contained in the configuration must specify a pattern and a viewId. The pattern specifies which inbound URL to match, and the viewId specifies the location that URL should resolve -be redirected- to.
Example 2.2. /WEB-INF/pretty-config.xml (You will need to customize this)
<pretty-config xmlns="http://ocpsoft.com/prettyfaces/3.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ocpsoft.com/prettyfaces/3.1.0 http://ocpsoft.com/xml/ns/prettyfaces/ocpsoft-pretty-faces-3.1.0.xsd"> <!-- Begin RewriteRules --> <rewrite trailingSlash="append" toCase="lowercase" /> <rewrite match="^/old-url/(\w+)/$" substitute="/new_url/$1/" redirect="301" /> <!-- Begin UrlMappings --> <url-mapping id="home"> <pattern value="/" /> <view-id>/faces/index.jsf</view-id> </url-mapping> <url-mapping id="store"> <pattern value="/store/" /> <view-id>/faces/shop/store.jsf</view-id> </url-mapping> <url-mapping id="viewCategory"> <pattern value="/store/#{ cat : bean.category }/" /> <view-id>/faces/shop/store.jsf</view-id> </url-mapping> <url-mapping id="viewItem"> <pattern value="/store/#{ cat : bean.category }/#{ iid : bean.itemId }/" /> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping> </pretty-config>
In JAR files, pretty-config.xml may also be placed in /META-INF/pretty-config.xml - additionally, comma-separated configuration file locations may be specified in web.xml, using the servlet context-param configuration API:
<context-param> <param-name>com.ocpsoft.pretty.CONFIG_FILES</param-name> <param-value>/WEB-INF/custom-mappings.xml,/META-INF/another-config.xml</param-value> </context-param>
Congratulations! That's all you should have to do in order to use PrettyFaces. The rest of this reference guide covers detailed configuration options, framework integration with JavaServer Faces, and special use-cases.
PrettyFaces offers in-depth configuration options for completely customizable use. We'll start with the most commonly used features, then move on to the advanced goodies.
The URL mapping element is the core configuration element for
most applications. Let's consider, for instance, that we are building a web-store and we want
our users to access the store at the URL /store/
- we only need the most basic
features of the URL-mapping to achieve this.
<url-mapping id="store"> <pattern value="/store/" /> <view-id>/faces/shop/store.jsf</view-id> </url-mapping>
As you can see, we've specified a URL-mapping with pattern="/store/"
(the
URL the user will see in the browser) and the view-id: /faces/shop/store.jsf
(the actual location of the page on the server.)
With these two simple lines of configuration, the user sees: /store/
, but
the server displays the page at: /faces/shop/store.jsf
. Ignore the
id="store"
attribute for now, we'll cover that under
navigation.
Suppose we want to create a URL mapping that allows users to access items in a specific category
of a web-store, such as: /store/category/
. In other words, we need to parse
the URL and gain access to the values we care about.
This is commonly known as creating a REST-ful URL, but here we'll use the term "path-parameter" to represent each individual data element created in our URL path.
Path-parameters are defined by using #{...} expressions in the pattern element. By default,
these expressions match any character except for '/' in a URL. So the following pattern
will match /foo/
, but it will not match /foo/bar/
.
<pattern value="/#{ name }/" />
More specifically, path-parameter expressions such as #{ name }
are replaced with the
regular expression [^/]+
before being matched against the URL.
Custom regular expressions can also be supplied
if more control over parameter matching is required.
<url-mapping id="viewCategory"> <pattern value="/store/#{ cat }/" /> <view-id>/faces/shop/store.jsf</view-id> </url-mapping>
Here we've used PrettyFaces path-parameter syntax to capture values stored in part of inbound URLs that match our pattern. Take, for instance, the following URL:
/store/shoes/
Part of this URL will be matched by the path-parameter expression: '#{ cat }', making its value, 'shoes', available in the application logic, the rest of the URL will be matched against the pattern, but ignored.
<pattern value="/store/#{ cat }/" />
The application can gain access to these path-parameters in a few ways, via request parameter naming, or EL bean injection; both techniques will be covered below.
In order to gain access to a value matched by our path-parameter #{...}
,
we can use the named path-parameter syntax: #{ cat }
,
which means that the value in the URL will be available in the HTTP Servlet request parameter map:
HttpServletRequest.getParameter(String name); stored in the key 'cat'.
In our application code, we would access the URL just like we access request query-parameters,
e.g.: url?cat=value
, where 'cat' is the key, and 'value' is the value.
String category = request.getParameter("cat");
You could also use JSF 2.0 meta-data view-parameters to propagate the named request parameter value to EL.
If you are adding PrettyFaces to an existing web-application, it is very likely that you will want to use named path-parameters in order to tie in to existing application features.
When starting a new application, it is good practice to always include a name for each path-parameter, even if you are using EL bean injection as well.
Another method of accessing path-parameter values is via EL value injection, where PrettyFaces can inject the value of the URL parameter directly into a managed-bean. This requires a syntax similar to specifying a named parameter, but PrettyFaces looks for '.' characters, an easy way to distinguish a name from an EL value-injection expression:
<pattern value="/store/#{ bean.location }/" />
In this case, we must have a managed bean defined in our application; these beans can be registered either in JSF, Spring, Google Guice, or as shown here using CDI/Weld. Based on the configuration in this particular example, the bean must be named "bean", and must have an accessible field named "category".
Any value matched by the path-parameter will be injected directly into the bean.
@Named("bean") @RequestScoped public class CategoryBean { private String category; /* Getters & Setters */ }
Notice, you can specify both a name and an EL value-injection for the same path-parameter.
<pattern value="/store/#{ cat : bean.location }/" />
In PrettyFaces, each url-pattern compiles into a regular expression that is used to match incoming
URLs. By default, any path-parameter expressions
(#{...}
) found in the URL pattern will be compiled into the regular expression:
[^/]+
, meaning that path-parameters do not match over the '/'
character.
The time will come when you need to make a URL match more selectively, for instance, when you have two URLs that share the same parameter order. Or perhaps you need to take a very complex URL and parse it more selectively (matching across multiple '/' characters, for instance.) It is in scenarios like these when you should consider using a custom regular expression within your url-mapping pattern.
Let's consider, for instance, that you are building a blog application, where the URLs:
/blog/2010/12/
and /blog/lincoln/23/
have the same number of
parameters, but mean different things. One is going to display a list of articles from December 2010,
and the other is going to display Lincoln's 23rd post. There has to be a way for the system to tell
the two apart, and the answer is custum regex patterns.
<url-mapping id="archives"> <pattern value="/#{ /\d{4}/ year }/#{ /\d{2}/ month }/" /> <view-id>/faces/blog/archives.jsf</view-id> </url-mapping>
This pattern specifies custom regular expressions for each path-parameter, the former must match exactly four digits (numbers 0-9), while the latter must match exactly two digits. In order to understand what this means, it might make sense to think of what the pattern would look like once it is compiled into a regular expression:
/(\d{4})/(\d{2})/
Below we see how to map the second of our two example URLs.
<url-mapping id="viewAuthorPost"> <pattern value="/#{ /[a-z]+/ blogger }/#{ /\d+/ postId }/" /> <view-id>/faces/blog/viewPost.jsf</view-id> </url-mapping>
Notice that this compiled pattern looks somewhat different from the first example URL:
/([a-z]+)/(\d+)/
This is how you can completely customize and parse complex URLs (even capture values that include
the '/' character.) Any path-parameter expression can accept a /custom-regex/
, but
the custom regex must be placed before any name or EL injections.
Using custom regular expressions can lead to very difficult trouble-shooting, and is only recommended in cases where basic path-parameter expressions are not sufficient.
Most people are already familiar with URL query-parameters. They come in key=value
pairs, and begin where the '?' character is found in a URL. For example:
http://example.com/path?query=data
Here, 'query' is the name of the parameter, and 'data' is the value of the parameter; order of parameters does not matter, and if duplicates parameter names are encountered, an array of multiple values will be stored for that parameter.
While query-parameters are automatically stored in the HttpServletRequest
parameter map,
it may sometimes be convenient to also inject those values directly into JSF managed beans.
<query-param name="language"> #{bean.language} </query-param>
In this case, we must have a managed bean defined in our application; these beans can be registered either in JSF, Spring, Google Guice, or as shown here using CDI/Weld. Based on the configuration in this particular example, the bean must be named "bean", and must have an accessible field named "language".
<url-mapping id="store"> <pattern value="/store/" /> <view-id>/faces/shop/store.jsf</view-id> <query-param name="language"> #{bean.language} </query-param> </url-mapping>
Any value matched by the query-parameter will be injected directly into the bean.
@Named("bean") @RequestScoped public class LanguageBean { private String language; /* Getters + Setters */ }
By default, query-parameters are URL-decoded. If this is not desired, URL-decoding may be
disabled using the optional decode="false"
attribute:
<query-param name="language" decode="false"> #{bean.language} </query-param>
Dynamic view-IDs (referred to as DynaView) allow us to determine (at the time the page is requested) the page our users should see for a given URL-mapping pattern.
<url-mapping id="home"> <pattern value="/home/" /> <view-id> #{bean.getViewPath} </view-id> </url-mapping>
Notice that we've used an EL method-expression where we would normally have specified a view-ID. PrettyFaces will invoke this method-expression, and use the result of the method as a final URL to be displayed by the server. In a sense, we're asking the system to figure out which page to display, and telling it that it can get the answer by calling our method:
@Named("bean") @RequestScoped public class HomeBean { @Inject CurrentUser user; public String getViewPath() { if ( user.isLoggedIn() ) { return "/faces/home/home.jsf"; } return "/faces/login.jsf"; } }
Here, our method #{bean.getViewPath}
is going to check the current user's logged-in
status, display the home page if he/she is logged in, or display the login page if they are not.
Automatic out-bound URL-rewriting will not function on pages with dynamic view-ID's.
This feature is currently only available for URL mappings that target a JSF view-ID.
Most of the time, when creating bookmarkable URLs, it is not enough to simply display a page to the user; we also need to load data to be shown on that page. This would typically be difficult in JSF, but PrettyFaces comes to the rescue once again.
Consider, for a moment, that we have a web-store, and would like to map a URL to display one specific item in that store:
Example 3.1. Displaying a single item in a web-store.
Here we have defined a page-action to be executed on a bean, #{bean.loadItem}
,
when a URL matching our pattern is requested. For example: /store/item/32
.
<url-mapping id="viewItem"> <pattern value="/store/item/#{ iid : bean.itemId }/" /> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping>
And the corresponding managed-bean, here shown using CDI/Weld annotations:
@Named("bean") @RequestScoped public class CategoryBean { private String itemId; private String category; private Item item; @Inject StoreItems items; public String loadItem() { if ( itemId != null ) { this.item = items.findById(itemId); return null; } // Add a message here, "The item {..} could not be found." return "pretty:store"; } /* Getters & Setters */ }
Once we have defined our action method, it is very likely that situations will arise where we do not want to continue displaying the current page, say, when data could not be loaded, or if the user does not have sufficient permissions to perform the action; instead, we want to redirect the user to another page in our site.
This can be done by returning a JSF navigation-string, just like we would do from a normal JSF action-method. PrettyFaces integrated navigation can also be used to perform dynamic redirection.
Example 3.2. Invoking JSF navigation from a page-action method
Here we have defined a page-action to be executed on a bean, #{bean.loadItem}
,
when a URL matching our pattern is requested. For example: /store/item/32
.
<url-mapping id="viewItem"> <pattern value="/store/item/#{ iid : bean.itemId }/" /> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping>
The corresponding managed-bean is shown here using CDI/Weld annotations. If the item
cannot be found, we will invoke JSF navigation for the outcome: "failure". This outcome
must be defined in faces-config.xml
, or JSF will not know how to process
it.
@Named("bean") @RequestScoped public class CategoryBean { public String loadItem() { if ( itemId != null ) { this.item = items.findById(itemId); return null; } return "failure"; } }
If a PrettyFaces URL-mapping ID is returned in this manner (e.g: "pretty:mappingId"), PrettyFaces integrated navigation will be invoked instead of JSF navigation.
By default, actions are executed after the Faces RESTORE_VIEW phase, but actions may also be
triggered before
other JSF phases, using the optional phaseId="..."
attribute.
<action phaseId="RENDER_RESPONSE">#{bean.loadItem}</action>
If the JSF Phase does not occur, page-actions associated with that phase will not execute; in this case -for example- if no validation is required, this action will never be called:
<action phaseId="PROCESS_VALIDATIONS">#{bean.loadItem}</action>
Sometimes we only need to trigger a page-action the first time a page is requested, and not when
the user submits forms on that page. In order to accomplish this, we use the optional
onPostback="false"
attribute.
<action onPostback="false">#{bean.loadItem}</action>
This will ensure that the action is never executed if the user is submitting a JSF form via POST (otherwise referred to as a postback.) The action will only be executed if the page is accessed via a HTTP GET request.
The validation of path and query parameters is very important as they are directly modifiable by the user. Therefore PrettyFaces offers the possibility to attach validation code to each of your parameters.
Validation of query parameters can be achieved by attaching standard JSF validators to the parameter.
<query-param name="language" validatorIds="languageValidator"> #{bean.language} </query-param>
This example shows how to attach the JSF validator with the ID languageValidator
to the query parameter. This validator will be executed directly after the URL has been parsed.
If the validation fails, PrettyFaces will send a 404 HTTP response. You can change this behavior
by using the onError
attribute and specifying an alternative view to show if validation fails.
<query-param name="language" validatorIds="languageValidator" onError="pretty:error"> #{bean.language} </query-param>
If you don't want to write a custom JSF validator, you can also reference a managed
bean method performing the validation. This method must have the exact same
signature as the validate
method of the Validator
interface
<query-param name="language" validator="#{languageBean.validateLanguage}"> #{bean.language} </query-param>
@Named("languageBean") @RequestScoped public class LanguageBean { public void validateLanguage(FacesContext context, UIComponent component, Object value) { for( String lang : Locale.getISOLanguages() ) { if( lang.equals( value.toString() ) ) { return; } } throw new ValidatorException( new FacesMessage("invalid.language") ) } }
Validation of path parameters is very similar to the validation of query parameters.
You just have to add a validate
element to the pattern
element
of your mapping. It accepts the same validation attributes as the
query-param
element.
The only important difference to the declaration of query parameter validation
is that you have to specify the index
of the path parameter you want
to validate. The index
is the absolute position of the path parameter
in the pattern. The first parameter is addressed with 0
.
<url-mapping id="viewCategory"> <pattern value="/store/#{ cat }/"> <validate index="0" validatorIds="categoryValidator" onError="pretty:error" /> </pattern> <view-id>/faces/shop/store.jsf</view-id> </url-mapping>
Recently PrettyFaces added support to configure URL mappings via annotations. This feature is primarily intended for people who don't want to maintain a separate XML configuration file for the mappings and instead prefer to declare them directly on the affected classes.
PrettyFaces supports configuration via annotations out of the box. Nevertheless it is strongly recommended to manually specify the java packages that contain your annotated classes. In this case the annotation scanner isn't required to scan the complete classpath, which might be a performance problem when you have many dependencies.
You can specify the packages to scan for annotations by adding a
comma-separated list of packages to your web.xml
:
<context-param> <param-name>com.ocpsoft.pretty.BASE_PACKAGES</param-name> <param-value>com.example.myapp,com.ocpsoft</param-value> </context-param>
PrettyFaces will scan these packages recursively. So typically you will only have to add the top-level package of your web application here.
If you don't want to use PrettyFaces annotations at all, you can completely
disable the annotation scanning by setting the package configuration parameter
to none
.
<context-param> <param-name>com.ocpsoft.pretty.BASE_PACKAGES</param-name> <param-value>none</param-value> </context-param>
In the default configuration PrettyFaces will only scan for annotations in the
/WEB-INF/classes
directory of your web application. If you want
the JAR files in /WEB-INF/lib
to be scanned as well, add the
following entry to your web.xml
:
<context-param> <param-name>com.ocpsoft.pretty.SCAN_LIB_DIRECTORY</param-name> <param-value>true</param-value> </context-param>
To create a simple URL mapping, you must annotate one of your beans with a
@URLMapping
annotation. You will typically want to place this
annotation on a class that is primarily responsible for the page.
@Named("bean") @RequestScoped @URLMapping(id = "store", pattern = "/store/", viewId = "/faces/shop/store.jsf") public class StoreBean { /* your code */ }
You can see that the annotation attributes are very similar to the attributes of
the url-mapping
element in the PrettyFaces XML configuration file.
Refer to Mapping a simple URL for details
on the configuration of URL mappings.
If you want to use path parameters in the URL pattern, you can add these the
same way as you would in pretty-config.xml
.
@Named("bean") @RequestScoped @URLMapping(id = "categoryBean", pattern = "/store/#{ bean.category }/", viewId = "/faces/shop/store.jsf") public class CategoryBean { private String category; /* Getters & Setters */ }
PrettyFaces offers a very intuitive way to specify page actions with
annotations. All you have to do is add a @URLAction
annotation to the method you want to be executed.
@Named("bean") @RequestScoped @URLMapping(id = "viewItem", pattern = "/store/item/#{ bean.itemId }/", viewId = "/faces/shop/item.jsf") public class CategoryBean { private String itemId; private Item item; @Inject StoreItems items; @URLAction public String loadItem() { if ( itemId != null ) { this.item = items.findById(itemId); return null; } // Add a message here, "The item {..} could not be found." return "pretty:store"; } /* Getters & Setters */ }
The annotation supports all attributes that are available in the XML configuration file.
@URLAction(phaseId=PhaseId.RENDER_RESPONSE, onPostback=false) public String loadItem() { // do something }
Sometimes you might want to call methods on other beans than the bean
annotated with the @URLMapping. In this case just refer to the foreign mapping
using the mappingId
attribute.
@Named("bean") @RequestScoped @URLMapping(id = "viewItem", pattern = "/store/item/#{ bean.itemId }/", viewId = "/faces/shop/item.jsf") public class CategoryBean { /* some code */ } @Named("otherBean") @RequestScoped public class OtherBean { @URLAction(mappingId = "viewItem") public void myAction() { /* your code */ } }
If you configure PrettyFaces using annotations, you can declare query
parameters by adding a @URLQueryParameter
annotation to
the target field. PrettyFaces will then inject the value of the
query parameter into that field.
@Named("bean") @RequestScoped public class LanguageBean { @URLQueryParameter("language") private String language; /* Getters + Setters */ }
Validation is of major importance when processing any kind of user input. This also applies to path and query parameters as they are directly modifiable by the user.
The declaration of validation rules is very simple when using PrettyFaces
annotations. To validate a query parameter with a standard JSF validator,
you'll just have to add a @URLValidator
annotation to the
field.
@Named("bean") @RequestScoped public class LanguageBean { @URLQueryParameter("language") @URLValidator(validatorIds="com.example.LanguageValidator") private String language; /* Getters + Setters */ }
This example shows how to attach the com.example.LanguageValidator
validator to the query parameter language
. You can also specify
a mapping to redirect to if the validation fails or attach multiple
validators to the same query parameter.
@Named("bean") @RequestScoped public class LanguageBean { @URLQueryParameter("language") @URLValidator(onError="pretty:error", validatorIds= { "com.example.LanguageValidator", "com.example.OtherValidator" }) private String language; /* Getters + Setters */ }
To validate path parameters, you have to add the @URLValidator
to the @URLMapping
and specify the index of the
path parameter you are referring to.
@Named("bean") @RequestScoped @URLMapping(id = "viewItem", pattern = "/store/item/#{ bean.itemId }/", viewId = "/faces/shop/item.jsf", validation=@URLValidator(index=0, validatorIds="com.example.ItemIdValidator")) public class CategoryBean { /* some code */ }
This will tell PrettyFaces to validate the first path parameter
#{bean.itemId}
with the validator com.example.ItemIdValidator
.
PrettyFaces is required to access your managed beans in multiple ways. If you declare a page action to be executed for a specific URL, the framework will create a method expression and execute it. If you want to inject a query parameter into your bean, a value expression is created to write the value into the field.
All these actions require PrettyFaces to know the name of your beans in the EL context. In most cases this can be done by an auto-detection mechanism that supports the most common environments for defining managed beans. Currently PrettyFaces supports:
faces-config.xml
and @ManagedBean
)If you are using a non-standard way of defining managed beans within your application, the auto-detection will not work. In this case you'll have two options.
The first option is to use a @URLBeanName
annotation on the
class to explicitly tell PrettyFaces the name of the bean. The framework will
then use this name to build EL expressions for this bean.
@URLBeanName("bean") public class LanguageBean { @URLQueryParameter("language") private String language; /* Getters + Setters */ }
In this example PrettyFaces will create the EL expression
#{bean.language}
to access the language field.
The other option to tell PrettyFaces about your beans names is
to implement a custom BeanNameResolver
. PrettyFaces already
ships with resolver implementations for the most common environments.
If your environment is not supported, you can easily create your own resolver.
The following example shows a resolver that will resolve the bean name
by searching for a @Named
annotation.
public class MyBeanNameResolver implements BeanNameResolver { public boolean init(ServletContext servletContext, ClassLoader classLoader) { // tell PrettyFaces that initialization was successful return true; } public String getBeanName(Class<?> clazz) { // try to find @Named annotation Named annotation = clazz.getAnnotation(Named.class); // return name attribute if annotation has been found if(annotation != null) { return annotation.value(); } // we don't know the name return null; } }
To let PrettyFaces know about your resolver, you'll have to register your
implementation by creating a file named
META-INF/services/com.ocpsoft.pretty.faces.el.BeanNameResolver
in your classpath and add the fully-qualified class name of your implementation
class there.
META-INF/services/com.ocpsoft.pretty.faces.el.BeanNameResolver
com.example.prettyfaces.MyBeanNameResolver
PrettyFaces follows a set order of events when processing each request. If a request is not mapped, or does not match a rewrite-rule, then the request will not be processed by PrettyFaces, and will continue normally.
URL-matching, path-parameter parsing, query-parameter handling, and value injection into managed beans.
DynaView calculation (if a view Id is dynamic, the EL method will be called.)
PrettyFaces relinquishes control of the current request via RequestDispatcher.forward(“/context/faces/viewId.jsf”).
If the view-ID is a JSF view, page-action methods are called after RESTORE_VIEW phase, unless the optional phaseId attribute is specified.
The server continues to process the request as normal.
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.
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.
Check for the existence of a web application configuration resource named /WEB-INF/pretty-config.xml, and load it if the resource exists.
Navigation is a critical part of any web-application, and PrettyFaces provides simple integrated
navigation - both with JSF, and in non-JSF environments (such as Servlets) that only have access
to the HttpRequest
object. PrettyFaces navigation is non-intrusive, and may be used
optionally at your discretion.
Typically when navigating in JSF, one must define navigation cases (in JSF 1.x) or return the path to a view (in JSF 2.x). PrettyFaces, however, lets us simply reference the URL-mapping ID in these scenarios. Since the URL-mapping pattern already contains the locations of all required values (path-parameter EL expressions), these values are extracted from managed beans and used in URL generation.
Simply return a URL-mapping ID from any JSF action-method just as you would when using
an <h:commandLink action="..." />
. This outcome string should be
prefixed by "pretty:" followed e.g.: "pretty:store", where "store" is our URL-mapping ID.
<url-mapping id="viewItem"> <pattern value="/store/item/#{ iid : bean.itemId }/" /> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping>
Now look at our action-method; notice that if the item is found, we return null, signaling
that no navigation should occur, but if the item cannot be loaded, we return
"pretty:store"
which is a PrettyFaces navigation outcome; PrettyFaces will
intercept this outcome string, and redirect the user to the URL-mapping identified by
"store" in pretty-config.xml
.
public String loadItem() { if(itemId != null) { this.item = items.findById(itemId); return null; } // Add a message here, "The item {..} could not be found." return "pretty:store"; }
Returning "pretty:", without a URL-mapping ID will cause the current page to be refreshed.
If any action method (JSF action-method or PrettyFaces page-actions) returns a mapping-ID that specifies path-parameters in the pattern, PrettyFaces will automatically extract values from bean locations specified in the pattern. The client browser will be redirected to the URL built using these values injected back into the pattern. For example:
<pattern value="/store/#{ cat : bean.category }/" />
Let's assume for a moment that #{ bean.category }
contains the value, "shoes". if we return
"pretty:viewCategory"
from our action method, where "viewCategory" is the
id
of a URL-mapping in pretty-config.xml, PrettyFaces will extract the current
value from the bean #{ bean.category }
and use that value to generate a new URL:
/store/shoes/
This means that we can control the generated URLs directly from Java code. Simply set the value of the bean field (the field used in the URL pattern) to control the outcome:
public String loadItem() { ... // Add a message here, "The item {..} could not be found." this.setCategory("shoes"); return "pretty:viewCategory"; }
It is generally good practice to dedicate a separate bean to store page-parameters, this way, the same bean can be used throughout the application when setting values for PrettyFaces navigation.
It is also possible to navigate without needing a bean action method at all; this is done by referencing the URL-mapping ID in the command component directly. Similar to generating HTML links and URLs, for example:
Example 5.1. Navigating directly from <h:commandLink>
or <h:commandButton>
<url-mapping id="viewItem"> <pattern value="/store/#{ cat : bean.category }/#{ iid : bean.itemId }/" /> <query-param name="language"> #{ bean.language } </query-param> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping>
Note that if the specified URL-mapping ID requires any parameters, the current values found in the required managed bean locations will be used when navigating.
<h:commandLink action="pretty:home"> Go home. </h:commandLink>
Navigating directly from a command component is most commonly used for refreshing a page:
<h:commandLink action="pretty:"> Refresh this page. </h:commandLink>
Often there you might find yourself somewhere in a custom Servlet, or in a situation where
you do not have access to the Faces NavigationHandler
. For these situations,
PrettyFaces provides the PrettyURLBuilder
, which can be used to generate the
String representation of any URL-mapping; one need only have access to an
HttpServletRequest
object in order to get the current configuration, and the
current HttpServletResponse
in order to issue a redirect.
Example 5.2. Redirecting from a custom Servlet
public class CustomRedirector { public void redirect(HttpServletRequest request, HttpServletResponse response, String mappingId, Map<String, String[]>... params) { PrettyContext context = PrettyContext.getCurrentInstance(request); PrettyURLBuilder builder = new PrettyURLBuilder(); URLMapping mapping = context.getConfig().getMappingById(mappingId); String targetURL = builder.build(mapping, params); targetURL = response.encodeRedirectURL(targetURL); response.sendRedirect(targetURL); } }
PrettyFaces inbound URL rewriting provides seamless URL rewriting to all Servlets within the Context. This is the capability of intercepting and changing the location of the client's browser URL, modifying or replacing that URL entirely, and displaying a resource.
There are several commonly used rewriting features that PrettyFaces provides for you:
manipulating trailing slashes, upper and lower-casing, custom regex search and replace,
and redirect types such as '301 - Permanent'
and '302 - Temporary'
.
<rewrite trailingSlash="append" toCase="lowercase" redirect="301"/> <rewrite match="/foo" substitute="/bar" redirect="301"/>
The two rules above will cause ALL inbound and outbound URLs be appended with a trailing slash
(if necessary,) but because of the "match" attribute, only URLs containing '/foo'
will be replaced with '/bar/
. Inbound URL-rewriting changes the browser URL
unless the redirect attribute is set to “chain”:
<rewrite toCase="lowercase" redirect="chain" />
Each <rewrite />
rule may specify a match="..."
attribute. This attribute defines which URLs will and will not be transformed by a given
rewrite rule.
<rewrite match="/foo" trailingSlash="append" toCase="lowercase" />
Once a URL has been matched, regex groups may be used to perform value-substitution on the URL; this effectively takes parts of the original URL, and makes them availible in its replacement.
<rewrite match="/foo/(\w+)/" substitute="/bar/$1/" />
This is equivalent to calling the String method url.replaceAll("/foo/(\w+)/", "/bar/$1/");
Note, also, that the same type of substitution is available when issuing an external redirect
from a rewrite-rule:
<rewrite match="/foo/(\w+)/" url="http://example.com/$1/" />
Click here for more detailed information on regular expressions in Java.
The table below outlines each of the individual rewrite-rule options:
Option | Allowed values | Usage |
---|---|---|
inbound | true/false | (Default: true) Enable or disable inbound URL rewriting for this rule. Setting this value to false means that this rule will be ignored on incoming requests. |
match | a regex | (Optional) Describes, via a regular expression pattern, when this 'rewrite' rule should trigger on an inbound or outbound URL. If empty, this rule will match all URLs. |
outbound | true/false | (Default: true) Enable or disable outbound URL rewriting for this rule. If enabled, any matching links encoded using HttpServletResponse.encodeURL() will be rewritten according to the rules specified. |
processor | qualified class name |
(Optional.) Specify a custom processor class to perform more complex, custom URL-rewriting.
This class must implement the interface: 'com.ocpsoft.pretty.faces.rewrite.Processor'
public class CustomClassProcessor implements Processor { public static final String RESULT = "I PROCESSED!"; public String process(final RewriteRule rewrite, final String url) { return RESULT; } } |
redirect | 301, 301, chain | (Default: 301) Specifies which type of redirect should be issued when this rule triggers. If 'chain' is specified, a Servlet forward will be issued to the new URL instead of a redirect. |
substitute | lifecycle | (Optional.) The regular expression substitution value of the "match" attribute. This effectively enables a "search and replace" functionality. Regular expression back-references to the match="..." attribute are supported in the URL, so using '$' and '/' characters may change the value of the result. See Rewriting URLs that match a specific pattern, for more details. |
toCase | uppercase, lowercase, ignore | (Default: ignore) Change the entire URL (excluding context-path and query- parameters) to 'UPPERCASE' or 'lowercase'. |
trailingSlash | append, remove, ignore | (Default: ignore) Control whether trailing slashes on a URL should be appended if missing, or removed if present. |
url | a well-formed URL | (Optional.) Specify an well-formed URL to replace the current URL. This will overwrite the context-path and query-parameters. This attribute should usually be combined with redirect="301" (default), which is recommended to prevent adverse SEO effects, loss of page- rank.) Note: You must provide a fully qualified URL, including scheme (such as 'http://", 'ftp://', 'mailto:'). Regular expression back-references to the match="..." attribute are supported in the URL, so using '$' and '/' characters may change the value of the result. See Rewriting URLs that match a specific pattern, for more details. |
Rewrite rules must be defined in pretty-config.xml
.
Outbound URL-rewriting in provides natural integration with most existing URL
components (including all of those from the JavaServer Faces framework.) When PrettyFaces is
installed, any URL passed into HttpServletRequest.encodeRedirectURL(String url)
will
be processed by PrettyFaces outbound URL-rewriting.
Given the following URL-mapping, we can render a pretty URL simply by invoking
HttpServletRequest.encodeRedirectURL(String url)
:
<url-mapping id="viewCategory"> <pattern value="/store/#{ cat : bean.category }/" /> <view-id>/faces/shop/store.jsf</view-id> </url-mapping>
For example:
HttpServletResponse response = getHttpServletResponse(); String rewrittenURL = response.encodeRedirectURL("/faces/shop/store.jsf?cat=shoes&lang=en_US")';
Or if using JSF:
String rewrittenURL = FacesContext.getCurrentInstance().getExternalContext() .encodeResourceURL("/faces/shop/store.jsf?cat=shoes&lang=en_US");
Will produce the following output URL:
/store/shoes/?lang=en_US
Notice that even though we did not define a managed query-parameter, the resulting URL still contains the 'lang' parameter. This is because PrettyFaces only rewrites the named path-parameters defined in the URL-pattern; all other query-parameters are ignored.
Given the following URL-mapping, some of our JSF URLs will automatically be rewritten:
<url-mapping id="viewCategory"> <pattern value="/store/#{ cat : bean.category }/" /> <view-id>/faces/shop/store.jsf</view-id> </url-mapping>
For example:
<h:link outcome="/faces/shop/store.jsf" value="View category: Shoes> <f:param name="cat" value="shoes" /> <f:param name="lang" value="en_US" /> </h:link>
And:
<h:link outcome="pretty:viewCategory" value="View category: Shoes> <f:param name="cat" value="shoes" /> <f:param name="lang" value="en_US" /> </h:link>
Will both produce the same output URL:
/store/shoes/?lang=en_US
Notice that even though we did not define a managed query-parameter, the resulting URL still contains the 'lang' parameter. This is because PrettyFaces only rewrites the named path-parameters defined in the URL-pattern; all other query-parameters are ignored.
PrettyFaces provides several methods of generating HTML links via a set of components, and when operating in a JSF 2.0 environment, standard JSF 'h:link' components may be used instead. 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 path-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.
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.
Example 8.1. Using the link component
<url-mapping id="viewItem"> <pattern value="/store/#{ cat : bean.category }/#{ iid : bean.itemId }/" /> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping>
<%@ taglib prefix="pretty" uri="http://ocpsoft.com/prettyfaces" %> <pretty:link mappingId="viewItem"> <f:param value="#{item.category}" /> <f:param value="#{item.id}" /> View Item. (This is Link Text) </pretty:link>
Output, assuming that #{item.category} == shoes
, and #{item.id} == 24
/store/shoes/24
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)
Example 8.2. Using the URL buffer component
<url-mapping id="viewItem"> <pattern value="/store/#{ cat : bean.category }/#{ iid : bean.itemId }/" /> <query-param name="language"> #{ bean.language } </query-param> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping>
<%@ taglib prefix="pretty" uri="http://ocpsoft.com/prettyfaces" %> <pretty:urlbuffer var="itemListURL" mappingId="viewItem"> <f:param value="shoes" /> <f:param value="24" /> <f:param name="language" value="en_US" /> </pretty:urlbuffer> <h:outputText value="Generated URL Is: #{requestScope.itemListURL}" />
Output:
/store/shoes/24?language=en_US
Mappings using DynaView functionality will not function with JSF link components.
Because PrettyFaces provides out-bound URL-rewriting,
one can actually use standard JSF components such as <h:outputLink>
in JSF 1.x, or <h:link>
in JSF 2.x.
Example 8.3. Using the <h:outputLink>
in JSF 1.x
<url-mapping id="viewItem"> <pattern value="/store/#{ cat : bean.category }/#{ iid : bean.itemId }/" /> <query-param name="language"> #{ bean.language } </query-param> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %> <h:outputLink value="/faces/shop/item.jsf"> <f:param name="cat" value="shoes" /> <f:param name="iid" value="24" /> <f:param name="language" value="en_US" /> </h:outputLink>
Output:
/store/shoes/24?language=en_US
In JSF 2.x, you can achieve an even greater level of abstraction by using the mapping-ID
in combination with <h:link>
Example 8.4. Using the <h:link>
in JSF 2.x
<url-mapping id="viewItem"> <pattern value="/store/#{ cat : bean.category }/#{ iid : bean.itemId }/" /> <query-param name="language"> #{ bean.language } </query-param> <view-id>/faces/shop/item.jsf</view-id> <action>#{bean.loadItem}</action> </url-mapping>
Both of the following components will generate the same output:
<h:link outcome="/faces/shop/item.jsf"> <f:param name="cat" value="shoes" /> <f:param name="iid" value="24" /> <f:param name="language" value="en_US" /> </h:link> <h:link outcome="pretty:viewItem"> <f:param name="cat" value="shoes" /> <f:param name="iid" value="24" /> <f:param name="language" value="en_US" /> </h:link>
Output:
/store/shoes/24?language=en_US
Contribute ideas to PrettyFaces by submitting a feature request or bug report. Join the team; check out the source code - submit a patch! Ask a question on the forums or the mailing list.
Q. Can I use PrettyFaces to handle UrlRewriting for other (non-JSF) resources on my server?
A. Yes. PrettyFaces still requires a configured JSF instance to function, but it can be used to map a URL to any resource in the Servlet Container – without invoking FacesServlet. Values will be injected into JSF beans as usual, but PrettyFaces Action methods will not trigger (since no JSF lifecycle executes for non-Faces requests.)
Example 10.1. Mapping a non-JSF resource
<pretty-config> <url-mapping id="login"> <pattern> /login </pattern> <view-id> /legacy/user/login.jsp </view-id> <!-- Non JSF View Id --> </url-mapping> <url-mapping id="register"> <pattern> /register </pattern> <view-id>/faces/user/register.jsf</view-id> <!-- JSF View Id --> </url-mapping> </pretty-config>
Q. Why do my Tomahawk / MyFaces components, or other 3rd party add-ons, break when I use PrettyFaces?
A. Since PrettyFaces intercepts mapped HttpRequests then forwards those requests to JSF, it is necessary to enable any additional filters between PrettyFaces and JSF to listen to Servlet Forwards. This is done in the web.xml deployment descriptor by adding the following dispatcher elements to any needed Filters:
Example 10.2. Enabling PrettyFaces for Tomahawk
<filter-mapping> <filter-name>Tomahawk Filter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping>
Q. Why, when using MyFaces, am I getting a NullPointerException when I try to use normal faces-navigation?
A. Some MyFaces versions do not completely comply with the JSF specification, thus the ViewRoot is null when the request is processed. There is a patch/workaround, which can be added to fix this issue. You must add this ViewHandler to your faces-config.xml.
Q. Can I configure PrettyFaces via Annotations?
A. Yes – please refer to Annotation based configuration for details.
Q. How do I enable logging, so that I can tell what the heck is really going on?
A. Create or update your log4j.properties file with the following values:
Example 10.3. 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 log4j.rootLogger=warn, stdout ### Log for OcpSoft log4j.logger.com.ocpsoft=debug
Q. Can I map and process URLs that span a dynamic number of ‘/’ characters?
A. Yes, please read about custom path-parameter patterns.
Q. How do I save FacesMessage objects after performing a redirect or pretty:redirect?
A. You need to configure the optional MultiPageMessagesSupport PhaseListener (or something like it.) JBoss Seam provides a Messaging API that goes above and beyond JSF, providing this feature automatically.
See this article for a full explanation of how this works.
Q. Does PrettyFaces work on IBM’s WebSphere?
A. Yes, but websphere requires a custom setting in order to behave like a sane server.