OCPSoft.com - Simple SolutionsCommunity Documentation
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 PrettyFaces
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 value="/faces/shop/store.jsf" /> </url-mapping>
With these two simple lines of configuration, the user sees: pattern="/store/"
in the browser URL and in the output HTML, but the server is actually rendering the resource:
/faces/shop/store.jsf
(the actual location of the page on the server.)
Ignore the id="store"
attribute for now, we'll cover that under
navigation. Which is useful when redirecting a user between
different pages in your application.
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 want to take a piece
of the URL string itself - for example: /store/[category]/
and use that value:
[category]
in our application's logic.
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 value="/faces/shop/store.jsf" /> </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.category }/" />
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 */ }
Please note that PrettyFaces will automatically use the JSF converter registered for the type of
the referenced bean property to convert the path parameter. This means that
PrettyFaces supports all JSF standard converters and converters that have been manually registered
to be used for a specific type using the converter-for-class
element in the
faces-config.xml
(or the forClass
attribute of the
@FacesConverter
annotation).
Notice, you can specify both a name and an EL value-injection for the same path-parameter.
<pattern value="/store/#{ cat : bean.category }/" />
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.
When using any escape sequences in a custom regular expression, be sure to "double-escape" the
backslash character '\', otherwise the pattern will not be properly compiled, just as you would in
Java itself.
For example, in order to match digits: "\d+
", you would actually need to add an
additional backslash, like so: "\\d+
"
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 value="/faces/blog/archives.jsf" /> </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 value="/faces/blog/viewPost.jsf"/> </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.
Sometimes you might want PrettyFaces to inject path parameters only on GET requests. This could be the case if you inject path parameters into a view-scoped bean and want to change these values at a later time.
You can use the onPostback
attribute of url-mapping
to specifiy
if values should be injected on postbacks. Please note that the default value of
the attribute is true
.
<url-mapping id="viewCategory" onPostback="false"> <pattern value="/store/#{ cat }/" /> <view-id value="/faces/shop/store.jsf"/> </url-mapping>
Please note that the bean properties bound to path parameters must never be
null
as they are required to build the URL for form postbacks.
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 value="/faces/shop/store.jsf" /> <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 */ }
Please note that PrettyFaces will automatically use the JSF converter registered for the type of
the referenced bean property to convert the query parameter. This means that
PrettyFaces supports all JSF standard converters and converters that have been manually registered
to be used for a specific type using the converter-for-class
element in the
faces-config.xml
(or the forClass
attribute of the
@FacesConverter
annotation).
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>
In some situations you might want that PrettyFaces doesn't inject the value of a query parameter on JSF postbacks. A typical usecase for this would be a query parameter that is used to initially populate a bean property holding the value bound to an input component. In this case you will want the query parameter value to be injected only on the initial GET request but not on postbacks, because the postback will contain the submitted value of the input component.
You can use the onPostback
attribute to tell PrettyFaces whether you want the query
parameter to be injected on postbacks. Please note that the default value of the attribute
is true
.
<query-param name="query" onPostback="false">#{searchBean.query}</query-param>
Frequently, you may find that several - or all - of your mappings share a common base-URL.
It is on these occasions when you should consider using a parent URL-mapping by using the
parentId
attribute.
<url-mapping parentId="store" id="category"> ... </url-mapping>
Notice that the parentId
attribute must refer to a mappingId
of another URL-mapping. See examples below:
<url-mapping id="store"> <pattern value="/store" /> <~-- Result: /store --> <view-id value="/faces/shop/store.jsf" /> </url-mapping> <url-mapping parentId="store" id="category"> <pattern value="/#{category}" /> <~-- Result: /store/#{category} --> <view-id value="/faces/shop/category.jsf" /> </url-mapping> <url-mapping parentId="category" id="item"> <pattern value="/#{item}" /> <~-- Result: /store/#{category}/#{item} --> <view-id value="/faces/shop/item.jsf" /> </url-mapping> <url-mapping parentId="category" id="sales"> <pattern value="/sales" /> <~-- Result: /store/#{category}/sales --> <view-id value="/faces/shop/sales.jsf" /> </url-mapping>
Child mappings will inherit all properties from the parent mapping. This includes the pattern,
validators and query parameters. URL actions won't be inherited per default. If you want an action
to be inherited by child mappings, set it's inheritable
attribute to true
.
<url-mapping id="store"> <pattern value="/store" /> <view-id value="/faces/shop/store.jsf" /> <action inheritable="true">#{storeBean.someAction}</action> </url-mapping> <url-mapping parentId="store" id="category"> <pattern value="/#{category}" /> <view-id value="/faces/shop/category.jsf" /> </url-mapping>
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 value="#{bean.getViewPath}" /> </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 - allowing for pages to appear completely stateless to the end-user. This would typically be difficult in JSF, but PrettyFaces has another option to satisfy this requirement that breaks the coupling typically associated with other solutions such as using @SessionScoped data beans to save data across pages, or passing values across views using: <f:setPropertyActionListener/>.
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 value="/faces/shop/item.jsf" /> <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 value="/faces/shop/item.jsf" /> <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 value="/faces/shop/store.jsf" /> </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 */ }
Sometimes you may want to declare multiple URL mappings on a single class. Unfortunately
Java does not allow to add the same annotation to a class more than once. PrettyFaces
offers a simple container annotation called @URLMapping
that can be used
in this case.
@Named("bean") @RequestScoped @URLMappings(mappings={ @URLMapping(id = "categoryBean", pattern = "/store/#{ bean.category }/", viewId = "/faces/shop/store.jsf"), @URLMapping(id = "categoryBean2", pattern = "/shop/#{ 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 */ }
@URLMappings
annotation, the action will be used for each of the mappings.
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 */ }
@URLMappings
annotation, the query parameter will be used for each of
the mappings.
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.