link:../index.asciidoc[← Docs Index] link:./index.asciidoc[ ← Migration]
= PrettyFaces 3.x Migration Guide
Christian Kaltepoth
:description: This guide described the process of migrating existing PrettyFaces applications to Rewrite
:keywords: PrettyFaces, Rewrite, Migration
= PrettyFaces 3.x Migration Guide
This guide describes how to migrate existing applications built using PrettyFaces 3.x to Rewrite 2.0.
== Migration strategies
There are basically two ways of migrating such applications to Rewrite:
PrettyFaces compatibility module:: This Rewrite module allows you to keep your old PrettyFace 3.x
configuration and use it with Rewrite. The module supports both the XML configuration file
+pretty-config.xml+ and the PrettyFaces annotations like +@URLMapping+. You should use this way
of migration for your application if you don't want to modify your existing code.
JSF integration module:: If you don't want to use the old PrettyFaces 3.x configuration anymore and
want to use all the new features of Rewrite, you should choose the Rewrite JSF integration module.
This module provides a new way of configuration and therefore requires you to adapt your
existing configuration.
NOTE: Both ways of migrating will only work for JSF 2.x applications.
The following sections will describe both ways in detail.
== Strategy #1: PrettyFaces compatibility module
Migrating application using PrettyFaces 3.x is very easy. Just follow the steps described below.
=== Update your dependencies
First you have to remove the old `com.ocpsoft.prettyfaces` dependency from your project. Open
your +pom.xml+ and remove the following entry:
[source,xml]
----
com.ocpsoft
prettyfaces-jsf2
3.3.3
----
Now add the Rewrite servlet and the PrettyFaces compatibility module to your +pom.xml+:
[source,xml]
----
org.ocpsoft.rewrite
rewrite-servlet
${rewrite.version}
org.ocpsoft.rewrite
rewrite-integration-faces
${rewrite.version}
org.ocpsoft.rewrite
rewrite-config-prettyfaces
${rewrite.version}
----
Your code should still compile fine after updating the dependencies.
=== Filter registration
If you used PrettyFaces in a Servlet 3.0 environment, the required servlet filter is automatically
registered. In this case you typically don't have an entry for the +PrettyFilter+ in your +web.xml+
and you can therefore skip this section.
If you added the +PrettyFilter+ manually to your +web.xml+, you will have to replace the filter
declaration to use the Rewrite filter instead. Locate the following entry in your +web.xml+:
[source,xml]
----
Pretty Filter
com.ocpsoft.pretty.PrettyFilter
true
Pretty Filter
/*
FORWARD
REQUEST
INCLUDE
ASYNC
ERROR
----
Remove this entry completely and replace it with the corresponding entry for the +RewriteFilter+:
[source,xml]
----
OCPsoft Rewrite Filter
org.ocpsoft.rewrite.servlet.RewriteFilter
true
OCPsoft Rewrite Filter
/*
FORWARD
REQUEST
INCLUDE
ASYNC
ERROR
----
NOTE: Please note that if you are using a Servlet 3.x container and your +web.xml+ doesn't set
+metadata-complete="true"+, you don't have to register the Rewrite filter manually, because
this is done automatically. In this case just make sure to remove the old +PrettyFilter+ entry.
=== Test your application
After having performed the changes described in the two sections, your migration is complete.
Although we consider the compatibility module to be very solid and stable, you should test your
application carefully. If something doesn't work as before, let us know. :)
== Strategy #2: JSF integration module
This section describes how to migrate existing PrettyFaces configuration to the Rewrite
JSF integration module.
=== Adding the Rewrite dependencies
If you want to use the Rewrite JSF integration module, you have to add the following dependencies
to your project.
[source,xml]
----
org.ocpsoft.rewrite
rewrite-servlet
${rewrite.version}
org.ocpsoft.rewrite
rewrite-integration-faces
${rewrite.version}
org.ocpsoft.rewrite
rewrite-integration-cdi
${rewrite.version}
----
Adding the dependencies in a Servlet 3.0 environment will automatically register the required
Servlet listeners and filters.
=== PrettyFaces XML configuration
PrettyFaces used an XML file called +pretty-config.xml+ to configure URL mappings and rewrite rules.
Rewrite uses a fluent Java API for configuration instead. XML files are not supported any more.
----
public class MyConfigurationProvider extends HttpConfigurationProvider {
@Override
public Configuration getConfiguration(ServletContext context) {
return ConfigurationBuilder.begin()
/* add your rules here */
;
}
@Override
public int priority() {
return 10;
}
}
----
==== URL Mappings
The URL mappings of PrettyFaces have been replace with a Rewrite rule called +Join+.
The concepts are very similar. You can specify a _virtual path_ that is mapped to a
physical server resource.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
----
|
----
@Override
public Configuration getConfiguration(ServletContext context) {
return ConfigurationBuilder.begin()
.addRule(Join.path("/login").to("/login.jsf"))
;
}
|===
==== Path Parameters
Path parameters are dynamic parts of an URL which you typically use to embed details about an addressed
resource. With PrettyFaces you had to use EL-like expressions in the pattern part of the mapping to
add such parameters. Rewrite parameters are very similar to that. The only real difference is that
you have to use +\{param\}+ instead of +#\{param\}+ for the parameter.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
----
|
----
.addRule(
Join.path("/store/{category}/").to("/faces/shop/store.jsf")
)
|===
In the example show above the parameters will be automatically turned into query parameters with
the same name as the parameter. This means that you can access the parameter using the standard
Servlet API:
----
request.getParameter("category");
----
Instead of using the standard Servlet API to access the parameters, it is often easier
to use EL-injected path parameters. With PrettyFaces you would simply use an EL expression that
refers to a bean property for that. PrettyFaces will then automatically inject the path parameter
value into that bean property.
Rewrite uses a concept called _parameter bindings_ to achieve the same. With Rewrite you can
_bind_ parameters to bean properties by calling +.where("param").bindsTo(...)+. See the following
code for an example:
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
----
|
----
.addRule(
Join.path("/store/{category}/").to("/faces/shop/store.jsf")
).where("category").bindsTo(El.property("bean.category"))
|===
If your bean uses a JSF-specific scope like +@ViewScoped+), you have to wrap
the +El+ binding in a +PhaseBinding+. This will tell PrettyFaces to submit the
binding in the specified JSF phase which ensures, that the scope of the bean will
be active.
So instead of:
----
.bindsTo(El.property("bean.category"))
----
You have to write:
----
.bindsTo(PhaseBinding.to(El.property("bean.category"))).after(PhaseId.RESTORE_VIEW))
----
==== Page actions
PrettyFaces allowed the user to specify a _page action_ which is invoked when a request
for the mapping is received.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
#{bean.loadItem}
----
|
----
.addRule(
Join.path("/store/item/#{id}/").to("/faces/shop/item.jsf")
).perform(Invoke.binding(El.retrievalMethod("bean.loadItem")))
|===
With the Rewrite configuration shown above, the page action is invoked very early in request
processing, even before the JSF lifecycle starts. In some situations this may lead to problems.
Especially if you are using a scope like +@ViewScoped+, which requires an active JSF
lifecycle.
To work around this problem, you can defer the invocation of the page action by wrapping it in
a +PhaseOperation+.
So instead of:
----
.perform(
Invoke.binding(El.retrievalMethod("bean.loadItem"))
)
----
You have to write:
----
.perform(
PhaseOperation.enqueue(
Invoke.binding(El.retrievalMethod("bean.loadItem"))
).after(PhaseId.RESTORE_VIEW)
)
----
=== PrettyFaces Annotations
==== URL Mappings
As the URL mappings of PrettyFaces have been replaced with +Join+, the replacement
for the +@URLMapping+ annotation is called +@Join+.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
@URLMapping(pattern = "/login", viewId = "/login.jsf")
public class CustomerDetailsBean {
...
}
----
|
----
@Join(path = "/login", to="/login.jsf")
public class CustomerDetailsBean {
...
}
|===
==== Path parameters
With PrettyFaces, path parameters were specified using EL-like expressions in the pattern.
With Rewrite you simply specify the parameter in the path pattern using +\{name\}+. Rewrite
automatically transforms the value into a query parameter with the same name. you can also
directly inject the value into your bean by adding the +@Parameter+ annotation to a field
with the same name as the parameter.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
@URLMapping(
pattern = "/customer/#{ id : customerDetailsBean.id }",
viewId = "/customer-details.jsf")
public class CustomerDetailsBean {
private Long id;
}
----
|
----
@Join(path = "/customer/{id}", to="/customer-details.jsf")
public class CustomerDetailsBean {
@Parameter
private Long id;
}
|===
If you want to customize the regular expression that is used to match the parameter, just add
a +@Matches+ annotation:
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
@URLMapping(
pattern = "/customer/#{ /[0-9]+/ customerDetailsBean.id }",
viewId = "/customer-details.jsf")
public class CustomerDetailsBean {
private Long id;
}
----
|
----
@Join(path = "/customer/{id}", to="/customer-details.jsf")
public class CustomerDetailsBean {
@Parameter
@Matches("[0-9]+")
private Long id;
}
|===
==== Query parameters
Query parameters in Rewrite are handled the same way as path parameters. To inject the value
of a query parameter into your bean, add a +@Parameter+ to a field like this.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
@URLMapping(pattern = "/login", viewId = "/login.jsf")
public class CustomerDetailsBean {
@URLQueryParam("q")
private String query;
}
----
|
----
@Join(path = "/login", to="/login.jsf")
public class CustomerDetailsBean {
@Parameter("q")
private String query;
}
|===
TIP: You can omit the parameter name when using the +@Parameter+ annotation if the name of the query
parameter is the same as the name of the field.
==== Page actions
To invoke a specific method in your bean when the page is accessed, add a +@RequestAction+ annotation
to the method.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
@URLMapping(pattern = "/login", viewId = "/login.jsf")
public class CustomerDetailsBean {
@URLAction
public void action() {
...
}
}
----
|
----
@Join(path = "/login", to="/login.jsf")
public class CustomerDetailsBean {
@RequestAction
public void action() {
...
}
}
|===
The +ignorePostback+ attribute is now a separate annotation called +@IgnorePostback+.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
@URLAction(onPostback=false)
public void action() {
...
}
----
|
----
@RequestAction
@IgnorePostback
public void action() {
...
}
|===
TIP: The +@IgnorePostback+ annotation can also be used with +@Parameter+.
If the annotated bean has a scope that requires an active JSF lifecycle like for example
+@ViewScope+, you have to _defer_ the invocation so that it is executed within the JSF lifecycle.
To do so add a +@Deferred+ annotation to the method.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
@ManagedBean
@ViewScoped
@URLMapping(pattern = "/login", viewId = "/login.jsf")
public class CustomerDetailsBean {
@URLAction
public void action() {
...
}
}
----
|
----
@ManagedBean
@ViewScoped
@Join(path = "/login", to="/login.jsf")
public class CustomerDetailsBean {
@RequestAction
@Deferred
public void action() {
...
}
}
|===
=== Creating links
PrettyFaces shipped with a special JSF component that simplified creating links to mapped URLs.
However JSF 2.0 introduced ++, which works fine for creating such links. Rewrite doesn't
include any special JSF component. It is recommended to use the standard JSF component for rendering
links.
Using ++ for creating links to Rewrite URLs is very easy. Just use the URL you
configured as the +to+ part of the Join for the +outcome+. If the URL contains parameters,
set their value using ++.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
Show details
----
|
----
Show details
|===
=== Programmatic navigation
Programmatic navigation was very painful in PrettyFaces. To navigate to a page with path or
query parameters, you had to obtain a reference to the bean which the parameters were bound
to, set them to the desired values and return a PrettyFaces navigation string.
With Rewrite you can use the new +Navigate+ class, which provides a fluent way for navigation.
Just change your action method to return +Navigate+ instead of a string.
Then use +Navigate.to(..)+ to select the target view. You can either supply a class annotated
with +@Join+ or specify the +to+ part of a join for that. Then you can use +with()+ to set the
values of the query or path parameters.
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
public String actionMethod() {
// obtain the CustomerDetailsBean
customerDetailsBean.setId("123");
return "pretty:customerDetails";
}
----
|
----
public Navigate actionMethod() {
return Navigate.to(CustomerDetailsBean.class)
.with("id", "123");
}
|===
Referencing the page you want to navigate to using a class only works if you are using
the Rewrite +@Join+ annotation. If you are using the +ConfigurationProvider+ API for
configuration, you can reference the page using the JSF view-id like this:
[cols="1a,1a", options="header"]
|===
|PrettyFaces
|Rewrite
|
----
public String actionMethod() {
// obtain the CustomerDetailsBean
customerDetailsBean.setId("123");
return "pretty:customerDetails";
}
----
|
----
public Navigate actionMethod() {
return Navigate.to("/customer-details.xhtml")
.with("id", "123");
}
|===
If you used the JSF 2.0 implicit navigation for navigating with PrettyFaces,
you can do so in Rewrite too.
----
public String actionMethod() {
return "/customer-details.jsf?faces-redirect=true&id=123";
}
----