Red Hat
May 4, 2013
by Daniel Sachse
Hi Java web-developers. I hope you are already familiar with PrettyFaces . If not, I will give you a very short introduction taken from the project documentation:

PrettyFaces is:

“The open-source /url/#{rewriting} solution for Servlet, JSF, and Java EE, including features such as: page-load actions, seamless integration with faces navigation, dynamic view-id assignment, managed parameter parsing, and configuration-free compatibility with other web frameworks.”

Rewrite , on the other hand, is a URL-rewriting framework built for extendability, for use with any web-framework or pure Servlet itself, and is used for the core of PrettyFaces “4.0″ – bringing the best of both worlds… so that sounds very cool thus far, but what do we want to achieve with it? Why would we use either of these frameworks?

Just compare these two URLs:

Very ugly one :

http://www.example.com/blog.html?author=w0mbat&post_id=23&year=2012
A very pretty one :
http://www.example.com/blog/w0mbat/2012/23

This is something that both PrettyFaces and Rewrite can accomplish for us, but what if, for example, we wanted to intercept all URLs and require a login? This is where PrettyFaces can no longer help us, but rewrite is ready to come to our aid!

Why migrate?

PrettyFaces itself is really great but the configuration is not runtime dynamic. There is a feature called “DynaView”, which can be used to determine the page to display for a given URL-mapping at runtime, but it is fairly limited and is difficult to use when things get hairy. To achieve some level of dynamism, one can implement what is called a “RewriteProcessor,” but it’s basically all manual coding; there are no dynamic rules that one would need to e.g. display a login page for every requested URL if the user is not logged in.

This is only one of many cool features that Rewrite offers in comparison to PrettyFaces.

In this post we are going to migrate a PrettyFaces project which is NOT annotation based. We just use the pretty-config.xml to map everything.

Part 1 : Stock-taking

I created a small JEE 6 sample webapp with JBoss Forge which you can fork or clone from Github if you want. This application will show us several things:

  • a small pom.xml with only a few dependencies
  • an XHTML template to be DRY
  • four pages: index, about, profile, login
  • a WEB-INF/pretty-config.xml

We will start with our pretty-config.xml shown below:

WEB-INF/pretty-config.xml
<pretty-config xmlns="http://ocpsoft.com/prettyfaces/3.3.2" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xsi:schemaLocation="http://ocpsoft.com/prettyfaces/3.3.2 http://ocpsoft.com/xml/ns/prettyfaces/ocpsoft-pretty-faces-3.3.2.xsd">
 <url-mapping id="home"> 
  <pattern value="/" /> 
  <view-id value="/index.xhtml" />
 </url-mapping>
 
 <url-mapping id="about"> 
  <pattern value="/About" />
  <view-id value="/about.xhtml" />
 </url-mapping>
 
 <url-mapping id="profile"> 
  <pattern value="/Profile" />
  <view-id value="/profile.xhtml" />
 </url-mapping>
</pretty-config>

This file just maps / to index.xhtml, /About to about.xhtml and /Profile to profile.xhtml. The pages just have links to each other, to provide some sort of interaction. Nothing too special ;)

Below you can see some content of the index.xhtml to get an idea of how the navigation with PrettyFaces works:

/src/main/webapp/index.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    template="/WEB-INF/templates/default.xhtml">
    
 <ui:define name="links">
  <h:link outcome="pretty:about" value="About"/>
  <h:outputText value=" " />
  <h:link outcome="pretty:profile" value="Profile"/>
 </ui:define>       
</ui:composition>

All the other files(web.xml, faces-config.xml,..) are pretty straight forward and you can check them out via Github.

Part 2 : Migration

The easiest way to migrate to Rewrite, is simply to use the Rewrite PrettyFaces compatibility module. Remove any PrettyFaces dependencies that you may have in your project, then include the following dependencies. First we will have to change the pom to exclude PrettyFaces and include Rewrite:

/pom.xml
<dependency>
 <groupId>org.ocpsoft.rewrite</groupId>
 <artifactId>rewrite-servlet</artifactId>
 <version>2.0.0.Final</version>
</dependency>
<dependency>
   <groupId>org.ocpsoft.rewrite</groupId>
   <artifactId>rewrite-config-prettyfaces</artifactId>
   <version>2.0.0.Final</version>
</dependency>

That’s it! You’re done. You’d now using PrettyFaces with Rewrite core, but since you probably want to know a little bit more about how to use the power of Rewrite , let’s continue and learn how to replace our PrettyFaces configuration with Rewrite completely. We’ll also learn a few tricks that Rewrite can help us with.

Migrating the configuration

First, delete your pretty-config.xml , but keep it somewhere you can look at while you move the functionality over to Rewrite.

Now, we need to create several ConfigurationProvider classes. These classes have to extend org.ocpsoft.rewrite.servlet.config.HttpConfigurationProvider (for servlet environments.) A ConfigurationProvider has a method which returns a priority and a method which returns the Configuration. Have a look at the AccessRewriteConfiguration below:

at.w0mb.prettyMigration.rewrite.AccessRewriteConfiguration
package at.w0mb.prettyMigration.rewrite;

import javax.servlet.ServletContext;

import org.ocpsoft.rewrite.bind.El;
import org.ocpsoft.rewrite.config.*;
import org.ocpsoft.rewrite.servlet.config.*;

public class AccessRewriteConfiguration extends HttpConfigurationProvider {
 @Override
 public Configuration getConfiguration(final ServletContext context) {
  return ConfigurationBuilder.begin()
    .addRule(Join.path("/").to("/index.xhtml"))
    .addRule(Join.path("/about").to("/about.xhtml"))
    .addRule(Join.path("/profile").to("/profile.xhtml"))
    .addRule(Join.path("/login").to("/login.xhtml"))

    // Authentication
    .defineRule()
    .when(Direction.isInbound().and(Path.matches("/logout")))
    .perform(Invoke.binding(El.retrievalMethod("identity.logout"))
      .and(Redirect.temporary(context
       .getContextPath() + "/")));
 }

 @Override
 public int priority() {
  return 10;
 }
}

With this Configuration, we mainly implemented what we saw earlier in the pretty-config.xml ; but we have also defined a ‘virtual’ URL that doesn´t map to an *.xhtml file, but rather, invokes a bean method that performs the logout and redirects to / .

Question : What happens if we try to invoke the application now?!? No idea? Ok.

Answer : We will get error messages because JSF cannot find any mappings for our ourcomes e.g. pretty:about, pretty:profile, …

So what do we need to do now? We have to do the mapping ourselves in the faces-config.xml:

WEB-INF/faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
   version="2.0">
   
   <navigation-rule>
  <from-view-id>*</from-view-id>
  <navigation-case>
   <from-outcome>pretty:home</from-outcome>
   <to-view-id>/index.xhtml</to-view-id>
  </navigation-case>
 </navigation-rule>
 
 <navigation-rule>
  <from-view-id>*</from-view-id>
  <navigation-case>
   <from-outcome>pretty:about</from-outcome>
   <to-view-id>/about.xhtml</to-view-id>
  </navigation-case>
 </navigation-rule>
 
 <navigation-rule>
  <from-view-id>*</from-view-id>
  <navigation-case>
   <from-outcome>pretty:profile</from-outcome>
   <to-view-id>/profile.xhtml</to-view-id>
  </navigation-case>
 </navigation-rule>
</faces-config>

The application would nearly work now as we have written it. But how about the login? We will define another ConfigurationProvider as shown below.

First, though, we will need to add another dependency to our POM. This will enable us to @Inject our Identity object directly into the configuration. The rewrite-integration-cdi extension module, enriches our rewrite configuration objects with CDI support, and gives us access to the Unified Expression Language (not shown.)

Exhibit 5
<dependency>
 <groupId>org.ocpsoft.rewrite</groupId>
 <artifactId>rewrite-integration-cdi</artifactId>
 <version>2.0.0.Final</version>
</dependency>
at.w0mb.prettyMigration.rewrite.LoginInterceptor
package at.w0mb.prettyMigration.rewrite;

import javax.inject.Inject;
import javax.servlet.ServletContext;

import at.w0mb.prettyMigration.Identity;

import org.ocpsoft.rewrite.config.*;
import org.ocpsoft.rewrite.servlet.config.*;

public class LoginInterceptor extends HttpConfigurationProvider {

 @Inject
 private Identity identity;

 @Override
 public Configuration getConfiguration(ServletContext arg0) {
  ConfigurationBuilder config = ConfigurationBuilder.begin();

  if (!identity.isLoggedIn()) {
   return config
     .defineRule()
     .when(DispatchType.isRequest().and(Direction.isInbound())
     .and(Resources.excluded()))
     .perform(Forward.to("/login"))
     .addRule(Join.path("/login").to("/login.xhtml"));
  }

  return config;
 }

 @Override
 public int priority() {
  return 0;
 }
}

As a final step, we have to tell Rewrite which ConfigurationProvider classes to use at runtime:

META-INF/services/org.ocpsoft.rewrite.config.ConfigurationProvider
at.w0mb.prettyMigration.rewrite.LoginInterceptor
at.w0mb.prettyMigration.rewrite.AccessRewriteConfiguration

Part 3 : Conclusion

We are now able to call our application under http://localhost:8080/prettyMigration and we will see our login page. This page gets displayed no matter which URL we will call. After clicking on ‘login’ we will be ‘logged in ‘ and redirected to the index page. Once we open http://localhost:8080/prettyMigration/logout, we are ‘logged out’ and we will again see the login page.

You can grab this project from Github . The project was build and tested on JBoss 7.1 , in my opinion the fastest and most advanced application server on the market.

I hope you liked my first post on this blog and if you have any comments/questions/improvements, please just post a comment here or contact me!