Recently, we attended Adam Bien’s presentation concerning Java EE 6 technology. Inspired by Adam Bien we decided to describe one of the CDI features: conversational context lifecycle. With an example we will show you the advantages.
A short introduction to show the context
Conversation context allows maintaining the state of objects between HTTP requests; however it has a smaller scope than session context. That’s because the lifecycle of conversation consists of a specified number of requests, while a session has unlimited requests, which makes it a lot more heavy.
For a web wizard conversation context is all you need
Conversation context is the ideal solution for creating web wizards in association with JSF; this is actually what is going to be shown in this post. First let’s start organizing the project.
An example paints more than 1000 words
Our example is created with Eclipse and Maven. The structure of the project, as shown in the above image, is rather common for this tool and it doesn’t require more explanation, so let’s move on to the content of the pom.xml file.
[xml] <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.wizard</groupId> <artifactId>com.wizard</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-api</artifactId> <version>2.1.1-b02</version> <scope>provided</scope> </dependency> </dependencies> <repositories> <repository> <id>JavaNet</id> <url>http://download.java.net/maven/2/</url> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <inherited>true</inherited> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project> [/xml]
As you can see for this project we need only two dependencies which are CDI and JSF APIs (implementations will be provided by the container which is Glassfish server 3.1). In order to activate CDI in our application a beans.xml configuration file is required. It needs to be placed under /WEB-INF directory (see project tree). For the purpose of our application this file can be empty, so in fact, only a single line of code is required:
[xml] <beans 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/beans_1_0.xsd"/> [/xml]
The next step is to create a bean which will be the engine of our web wizard application. This is very easy, take a look at the code below:
[java] @Named(“wizard”) @ConversationScoped public class WebWizard implements Serializable { @Inject private Conversation conversation; public void beginConversation() { if (conversation.isTransient()) { conversation.begin(); } } public void endConversation() { if (!conversation.isTransient()) { conversation.end(); } } } [/java]
A few things need to be done while creating the conversational bean:
- Bean has to be serializable
- Bean class must be annotated with @ConversationScoped annotation which tells the container that the state of this particular bean will be maintained while conversation is running
- Bean class must have a reference to Conversation object (injected via @Inject annotation) which takes the responsibility of managing the conversation context (beginning and ending conversation with begin and end methods respectively)
- Additionally @Named annotation makes the bean accessible from Expression Language (in JSF page).
By default the Conversation object is in transient state. Invocation of the begin method marks it as long-running (when a real conversation starts). Ending conversation (by invoking end method) marks Conversation object as transient. If we try to invoke begin method when Conversation object is in long-running state an IllegalStateException will be thrown. Similarly, we can’t invoke end method while Conversation object is in transient state, otherwise we will get the same exception. That’s why I’m using isTransient method in order to check the state of Conversation object.
Some logic is still needed
As you might guess our example code is not sufficient to make web wizard working. We need to add some logic which will control the workflow of application as well as some views (JSF pages) and model. The conversation bean itself is almost done, however we decided to go one step further and write a generic wizard which will be reusable. This requires more code, which looks as follows:
[java] @ConversationScoped public abstract class AbstractWizard implements Serializable { @Inject private Conversation conversation; @Inject private ViewManager viewManager; protected abstract List<View> prepareViews(); protected abstract String complete(); protected abstract String clean(); public String start() { beginConversation(); viewManager.setViews(prepareViews()); return next(); } public String finish() { endConversation(); return complete(); } public String cancel() { endConversation(); return clean(); } public String next() { View view = viewManager.getNextView(); view.setAvailable(true); return view.getOutcome(); } public String previous() { return viewManager.getPreviousView().getOutcome(); } public String getViewAt(int index) { return viewManager.getViewAt(index).getOutcome(); } public List<View> getViews() { return viewManager.getViews(); } public boolean isFirst() { return viewManager.isFirst(); } public boolean isLast() { return viewManager.isLast(); } private void beginConversation() { if (conversation.isTransient()) { conversation.begin(); return; } throw new IllegalStateException(); } private void endConversation() { if (!conversation.isTransient()) { conversation.end(); return; } throw new IllegalStateException(); } } [/java]
The core functionality of this class is closed in two private methods: beginConversation and endConversation which are parts of template methods such as start, finish and cancel. The main assumption of this approach is to keep basic code in one place and delegate specific responsibilities (via prepareViews, complete and clean abstract methods) to concrete wizard classes. In our example specific responsibilities mean:
- Preparing a list of views which are part of a concrete web wizard (prepareViews method)
- Storing model object(s) which state will be maintained during the conversation
- Completing conversation by invoking some business logic (which processes the model) and navigating to specific view (that’s why the complete method returns a String which is the name of the view’s outcome)
- Canceling conversation (clean method) which is similar to completion as described before.
There are also next, previous and getViewAt methods which control the flow of conversation (don’t pay attention to ViewManager and View classes, we will describe them later).
The concrete wizard class
Let’s have a look at the concrete wizard class:
[java] @Named("wizard") public class UserWizard extends AbstractWizard { @Inject private UserService userService; private User user = new User(); public User getUser() { return user; } public void setUser(User user) { this.user = user; } protected List<View> prepareViews() { List<View> views = new ArrayList<View>(); views.add(new View("First step", Outcome.FIRST_STEP)); views.add(new View("Second step", Outcome.SECOND_STEP)); views.add(new View("Third step", Outcome.THIRD_STEP)); views.add(new View("Summary", Outcome.SUMMARY)); return views; } protected String complete() { userService.processUserData(user); return Outcome.WIZARD_FINISH; } protected String clean() { return Outcome.WIZARD_CANCEL; } } [/java]
The responsibility of UserWizard class conforms to the four points described earlier. The main advantage of this approach is the flexibility of adding or removing views without affecting the basic code, what makes it reusable (we can have many different concrete wizards which will extend the abstract one).
ViewManager class
Now let’s have a look at the ViewManager class, which is just a simple iterator for a list of views:
[java] public class ViewManager implements Serializable { private static final int START_INDEX = -1; private static final int FIRST_INDEX = 0; private List<View> views; private int currentIndex, lastIndex; public void setViews(List<View> views) { this.views = views; currentIndex = START_INDEX; lastIndex = views.size() - 1; } public List<View> getViews() { return views; } public View getViewAt(int index) { currentIndex = index; return views.get(index); } public View getNextView() { return views.get(++currentIndex); } public View getPreviousView() { return views.get(--currentIndex); } public boolean isFirst() { return currentIndex == FIRST_INDEX; } public boolean isLast() { return currentIndex == lastIndex; } } [/java]
This class contains some useful methods allowing to manage a list of view objects, which represents JSF pages in our example. It also controls the display of particular components on JSF pages. ViewManager uses a View class which represents a particular JSF page (we could use also JSP or even pure HTML).
[java] public class View implements Serializable { private String name, outcome; private boolean available = false; public View(String name, String outcome) { this.name = name; this.outcome = outcome; } // getters and setter omitted for better readability } [/java]
View object holds information about the view’s name, outcome (page URL) and flag which determines whether or not the name of a particular view should be available for a user. For better understanding you will find the code of model objects (notice that they are serializable) below:
[java] public class User implements Serializable { private String firstName, middleName, lastName, phone, mobile, email; private Date birthDate; private Address address; public User() { address = new Address(); } // getters and setter omitted for better readability } public class Address implements Serializable { private String street, number, postalCode, city, country; // getters and setter omitted for better readability } [/java]
Time to move on to JSF pages
Ok, let’s move on to JSF pages now. In order to activate JSF in our application we need to configure the web.xml file. We have to register a Facelet servlet which will handle all JSF requests within the application.
[xml] <web-app 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-app_3_0.xsd" version="3.0"> <welcome-file-list> <welcome-file>index.jsf</welcome-file> </welcome-file-list> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.jsf</url-pattern> </servlet-mapping> </web-app> [/xml]
Firstly, we show the main template which will be used by all pages in application.
[xml] <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sum.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title> <h:outputText value="#{title}"/> </title> </head> <body> <f:view> <ui:insert name="body"/> </f:view> </body> </html> [/xml]
Below you will find the code of the JSF pages which use template: index.xhtml
[xml] <ui:composition template="/template/mainTemplate.xhtml"> <ui:param name="title" value="Main page"/> <ui:define name="body"> <h:form> <h:commandLink value="Go to Wizard" action="#{wizard.start}"/> </h:form> </ui:define> </ui:composition> [/xml]
wizardFinish.xhtml
[xml] <ui:composition template="/template/mainTemplate.xhtml"> <ui:param name="title" value="Wizard finish"/> <ui:define name="body"> <h:outputText value="Wizard finished!!!"/> </ui:define> </ui:composition> [/xml]
wizardCancel.xhtml
[xml] <ui:composition template="/template/mainTemplate.xhtml"> <ui:param name="title" value="Wizard cancel"/> <ui:define name="body"> <h:outputText value="Wizard cancelled!!!"/> <h:form> <h:commandLink value="Go to Wizard again" action="#{wizard.start}"/> </h:form> </ui:define> </ui:composition> [/xml]
wizardTemplate.xhtml
[xml] <ui:composition template="/template/mainTemplate.xhtml"> <ui:define name="body"> <h:form> <ui:repeat value="#{wizard.views}" var="currentView" varStatus="status"> <c:set var="index" value="#{status.index}"/> <c:set var="name" value="#{currentView.name}"/> <c:set var="available" value="#{currentView.available}"/> <h:commandLink value="#{index + 1} #{name}" action="#{wizard.getViewAt(index)}" rendered="#{available}"/> <h:outputText value="#{index + 1} #{name}" rendered="#{not available}"/> <h:outputText value=" "/> </ui:repeat> <h:panelGrid columns="2"> <ui:insert name="stepBody"/> </h:panelGrid> <h:commandButton value="Previous" action="#{wizard.previous}" disabled="#{wizard.first}"/> <h:commandButton value="Next" action="#{wizard.next}" disabled="#{wizard.last}"/> <h:commandButton value="Finish" action="#{wizard.finish}" disabled="#{not wizard.last}"/> <h:commandButton value="Cancel" action="#{wizard.cancel}"/> </h:form> </ui:define> </ui:composition> [/xml]
The last one is an example of nesting templates using Facelets. Generally wizardTemplate.xhtml contains elements (control buttons and a list of available steps) which will be used by all step pages included in the particular web wizard. Below we present the code of the step pages: firstStep.xhtml
[xml] <ui:composition template="/template/wizardTemplate.xhtml"> <ui:param name="title" value="First step"/> <ui:define name="stepBody"> <h:outputLabel value="First name:"/> <h:inputText value="#{wizard.user.firstName}"/> <h:outputLabel value="Middle name:"/> <h:inputText value="#{wizard.user.middleName}"/> <h:outputLabel value="Last name:"/> <h:inputText value="#{wizard.user.lastName}"/> <h:outputLabel value="Birth date:"/> <h:inputText value="#{wizard.user.birthDate}"> <f:convertDateTime pattern="dd-MM-yyyy"/> </h:inputText> </ui:define> </ui:composition> [/xml]
secondStep.xhtml
[xml] <ui:composition template="/template/wizardTemplate.xhtml"> <ui:param name="title" value="Second step"/> <ui:define name="stepBody"> <h:outputLabel value="Street:"/> <h:inputText value="#{wizard.user.address.street}"/> <h:outputLabel value="Number:"/> <h:inputText value="#{wizard.user.address.number}"/> <h:outputLabel value="Postal code:"/> <h:inputText value="#{wizard.user.address.postalCode}"/> <h:outputLabel value="City:"/> <h:inputText value="#{wizard.user.address.city}"/> <h:outputLabel value="Country:"/> <h:inputText value="#{wizard.user.address.country}"/> </ui:define> </ui:composition> [/xml]
thirdStep.xhtml
[xml] <ui:composition template="/template/wizardTemplate.xhtml"> <ui:param name="title" value="Third step"/> <ui:define name="stepBody"> <h:outputLabel value="Phone:"/> <h:inputText value="#{wizard.user.phone}"/> <h:outputLabel value="Mobile:"/> <h:inputText value="#{wizard.user.mobile}"/> <h:outputLabel value="Email:"/> <h:inputText value="#{wizard.user.email}"/> </ui:define> </ui:composition> [/xml]
summary.xhtml
[xml] <ui:composition template="/template/wizardTemplate.xhtml"> <ui:param name="title" value="Summary"/> <ui:define name="stepBody"> <h:outputLabel value="First name:"/> <h:outputText value="#{wizard.user.firstName}"/> <h:outputLabel value="Middle name:"/> <h:outputText value="#{wizard.user.middleName}"/> <h:outputLabel value="Last name:"/> <h:outputText value="#{wizard.user.lastName}"/> <h:outputLabel value="Birth date:"/> <h:outputText value="#{wizard.user.birthDate}"> <f:convertDateTime pattern="dd-MM-yyyy"/> </h:outputText> <h:outputLabel value="Street:"/> <h:outputText value="#{wizard.user.address.street}"/> <h:outputLabel value="Number:"/> <h:outputText value="#{wizard.user.address.number}"/> <h:outputLabel value="Postal code:"/> <h:outputText value="#{wizard.user.address.postalCode}"/> <h:outputLabel value="City:"/> <h:outputText value="#{wizard.user.address.city}"/> <h:outputLabel value="Country:"/> <h:outputText value="#{wizard.user.address.country}"/> <h:outputLabel value="Phone:"/> <h:outputText value="#{wizard.user.phone}"/> <h:outputLabel value="Mobile:"/> <h:outputText value="#{wizard.user.mobile}"/> <h:outputLabel value="Email:"/> <h:outputText value="#{wizard.user.email}"/> </ui:define> </ui:composition> [/xml]
Time to look forward to the end result
We are done now. After building the project and launching it by using an application server you should get the following result: We can freely move between particular steps and the state of conversation will be maintained. We can also cancel the conversation any moment. Clicking the finish button will end the conversation as well. Be aware we completely omitted validation as well as internationalization, because this is out of the scope for this article. As you can see using conversation context lifecycle is quite easy to implement, designing a generic wizard which could be reusable takes a lot more time. Below you can find the source code files described as well as a war file, which can be deployed on an application server. Thanks for reading this post. I’m looking forward to read about your experiences or to answer your questions. Please share them below. com.wizard-1.0.0-SNAPSHOT wizard
First off, great article showing on how to use the CDI conversation scope.
I have read through a couple of examples on the CDI conversation scope and each of them starts the conversation with a POST to a start() method in the backing bean, do you know if it’s possible to start a conversation through a GET request(this would make the wizard bookmarkable)?
Starting the conversation in the PostConstruct method of a CDI injected beans causes the conversation to reset after every step in the wizard.
You can share the long-runing conversation context between multiple GET requests by passing the “cid” parameter in the url. You can do this by doing something like this:
Of course you need to proveide accessor for conversation in the nyBean object, so that you can actually execute #{myBean.conversation}
Cheers,
Andrzej
Thanks for this article, I tried to implement it in my own project. If I dont use the content of your web.xml I get a strange URL like http://localhost:8080/MyProject/faces/faces/view/secondStep.xhtml?cid=1&cid=1. Did you had the same problems? I use JBoss 7.0.2. If I change every ‘jsf’ to ‘xhtml’ in your web.xml it is a little bit better the “faces/faces” in the URL disappear but “cid=1&cid=1” still remains.
Will this example work in Tomcat6…….. i am getting the following error
javax.el.PropertyNotFoundException: /index.xhtml @11,69 action=”#{wizard.start}”: Target Unreachable, identifier ‘wizard’ resolved to null ..pls reply me its urgent……i am facing this problem from last week
Very good article.
But I am wondering. is there any annotation for starting and ending conversation?
And injection of Conversation is mandatory? I mean is there no way to be a bean conversation scoped without injecting Conversation object?
UserService class? Outcome enum? Is the entire project available for download? I’m new to this. It’s much easier to step through stuff with debug… at least for me.
Download links are available in the article.
https://blog.goyello.com/wp-content/uploads/2011/06/com.wizard-1.0.0-SNAPSHOT.zip
https://blog.goyello.com/wp-content/uploads/2011/06/wizard.zip
Thanks, nice post