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"; } ----