CDI Conversations part 2
This article will look at using the Conversation scope defined in JSR 299 (Java Contexts and Dependency Injection), and released as part of Java EE 6. For now, we’ll stick to non-data driven examples as we explore the ins and outs of the Conversation scope. We’ll finish up by creating a workspace manager so we can list all the active conversations and switch between them.
A Conversation is like a numbered bucket in the session that exists until either the end of the users session, the conversation times out, or the server side code deliberately ends the conversation. The benefits of a conversational scope are many, letting us easily create pages that can be opened in multiple tabs without having to juggle the state on the client without filling the session with data that will last as long as the user session should we forget to remove it.
We’ll start by creating a project from the knappsackjee6-servlet-basic
archetype. This gives us all the features of Java EE 6 without too much code to get in the way. We’ll start by creating a backing bean that is request scoped and looking at how that is used and what effect request scope has.
import java.io.Serializable; import javax.enterprise.context.RequestScoped; import javax.inject.Named; @Named("bean") @RequestScoped public class BackingBean implements Serializable { private Long value = new Long(0); public Long getValue() { return value; } public void setValue(Long value) { this.value = value; } public String getMessage() { return "The value is : "+value; } }
Simple enough, now lets add a page called listEdit.xhtml
that lets us enter the value, post the value back and see what the message is.
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" template="/WEB-INF/templates/template.xhtml"> <ui:define name="content"> <h:form id="form"> Value : <h:inputText value="#{bean.value}" /> <h:commandButton value="Submit Value" action="submit" /> <br /> <br /> Message : #{bean.message} </h:form> </ui:define> </ui:composition>
Edit home.xhtml
to include a link to this page :
<a href="listEdit.jsf">Start New List</a>
Open up the home page and click the link to go to the listEdit
page. If we enter a value in the input box and click the submit button, predictably, our message says that the value is whatever we entered.
What is happening here is that when the form is posted back, the value in the input box is sent back to the bean.value
attribute. When the page is rendered back to the user and the message is rendered, it is rendered using this value that we passed back to the server. We pass the state of the attribute from the server to the client, and then back to the server, and we can do that all day long using just the request scope without any problems. This is a stateless page and doesn’t require any state to be held on the server.
Let’s add something a bit more stateful to the page such as a list of values that we have added previously. In our backing bean, add the following field and getter method and a method to add the value.
private List<Long> values = new ArrayList<Long>(); .. .. .. public List<Long> getValues() { return values; } public void addToList() { values.add(value); }
Now we will add the list of values to our page to be displayed.
<ui:define name="content"> <h:form id="form"> Value : <h:inputText value="#{bean.value}" /> <h:commandButton value="Submit Value" action="#{bean.addToList}" /> <br /> <br /> Message : #{bean.message} <h2>Added Values</h2> <ui:repeat var="v_value" value="#{bean.values}"> #{v_value}<br /> </ui:repeat> </h:form> </ui:define>
If we refresh our page, enter a number and click submit, you will see that the number appears at the bottom of the page where it should. When we click the submit button on the form the value is assigned to the value
attribute. When the addToList
method is called, the value
attribute is added to the list. When the page is rendered, the latest value is used to generate the message and for the list of items we return the list containing one item, the new value.
So far so good. Let’s enter a new number and add it to the list. When we click the button the second time, the only number in the list of numbers is the one we just entered. The first value is gone! The problem is that the second time we posted the value the bean was re-constructed from scratch and had no idea whatsoever about the first value we added to the list. There was nothing on the form to tell the bean about the first number even though we displayed it on the page. The backing bean keeps forgetting our list of numbers from one request to the next, or in other words, our application is missing some state.
As per part 1, there are a number of ways we can get around this problem. We could save the list to the database each time we post, which is over kill for this simple task. We could push the state down to the client, say using a comma delimited list of existing numbers pushed into a hidden field. When the form is posted, the comma delimited list is sent back to the server where the numbers are unpacked and the list is rebuilt on the server side and the state is restored.
Both of these two methods are doable but what happens when we have more information on the server that we need to keep hold of? Do we have to keep adding add each piece of information to the client state manually, including passing it around from one page to the next for multi-page processes? What if it is a list of entity objects we want to hold on to? Do we just save the Ids on the client and keep re-loading them from the database each time?
These solutions are impractical from the perspective of building a maintainable app quickly. However, note that these solutions are completely possible with JSF and CDI. It just offers better alternatives, but the opportunity to get under the hood and use more lower level solutions in the interests of optimizing the application are always there. This is an important factor since if you have a widely used conversational page that is creating a bottle neck, you can refactor it down to use a more stateless approach.
Since this article is all about conversations, obviously, we are going to solve our problems using the conversation scope. We will change our bean to be @ConversationScoped
and @Inject
a Conversation
instance we’ll be using. We’ll then start the conversation for the page when our bean is constructed. This is an example and typically you don’t start conversations in bean construction but it serves our purpose here.
Tip : Conversations are typically started at specific points on certain initialization methods, starting it on bean construction is bad because you never know when another task might use an instance of that bean
@Named("bean") @ConversationScoped public class BackingBean implements Serializable { ... ... ... @Inject private Conversation conversation; ... ... @PostConstruct public void postConstruct() { conversation.begin(); } public Conversation getConversation() { return conversation; } ... ... }
Reload our page and you can see how you can enter and submit as many numbers as you want to the page and the list state is managed.
Conversations are just a natural extension of the existing scopes in current web applications. They also become very natural to use. Let’s add another page that lets us review our list of numbers before ‘confirming’ them. Add the following new button at the bottom of our page :
<h:commandButton action="review" value="Review Numbers"/>
Because the action
attribute is set to review
we will create a new page called review.xhtml
that will display the numbers. Our first concern obviously is ‘where does it get the list of numbers from?’, well the answer is, from the same place it got the numbers from last time, using the expression #{bean.values}
.
review.xhtml
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" template="/WEB-INF/templates/template.xhtml"> <ui:define name="content"> <h:form id="form"> <h1>Please Confirm The Values : </h1> <h2>Added Values</h2> <ui:repeat var="v_value" value="#{bean.values}"> #{v_value}<br /> </ui:repeat> <h:commandButton action="#{bean.confirm}" value="Confirm"/> </h:form> </ui:define> </ui:composition>
The way to think about this is to imagine what happens when the page is rendered. JSF will look for the value beans.values
which the CDI implementation can provide. Since this page is being rendered in an active conversation, CDI will look in the active conversation ‘bucket’ for a bean called bean
. Since we are coming from the page that adds the numbers, this bean will already exist in the conversation and so the same instance is returned. Should we render this page without an active conversation, by typing in the URL manually, the page will render, but this time, the instance of beans.xml
will not already exist and so CDI will create a new instance of it that doesn’t contain any items.
Tip : There are ways to prevent pages rendering without an active conversation which can also help with duplicate form submissions
Getting back to the page, we just want to add our confirm method to our backing bean. This method ends the conversation and redirects to the home page which is going back to the number entry page.
public void confirm() { conversation.end(); try { FacesContext.getCurrentInstance().getExternalContext().redirect("home.jsf?faces-redirect=true"); } catch (IOException e) { e.printStackTrace(); } }
Add the following at the top of either the review or list edit page to see the conversation Id you are currently in :
Conversation : #{bean.conversation.id}
If you run the application, you can add numbers and when you click review, you get a list of all the numbers you just entered on a separate page.
If we were managing state ourselves, we would have to pass something to the review page to let is know what the list of numbers was, either a database row key, or the comma delimited values. Again, the more state you have to pass around the less verbose and maintainable the application becomes. CDI is automatically passing our key (the conversation id) around for us. Under the review button the list edit page, add the following GET link :
<h:link outcome="review" value="Review"/>
If you look at the link URL it is http://localhost:8080/conversationdemo/review.jsf?cid=xxx
where xxx is some number. CDI automatically creates a URL that propagates the conversation for us. If we didn’t want to propagate the conversation, we can just add a blank cid
parameter and CDI will leave it alone. This can be useful when you want to launch a page without a conversation from a page that is in a conversation. Add the following link :
<h:link value="Start New List" target="_blank"> <f:param name="cid" /> </h:link>
This lets you start a fresh new list in a separate window (since we didn’t specify an outcome it defaults to the current page). If you create a new list and add some numbers you can see that you can manage separate lists independently without doing anything to handle multiple browser windows.
When you have multiple windows open, set the url of one window to the url of other (i.e. http://localhost:8080/conversationdemo/listEdit.jsf?cid=xxx
where xxx is the other conversation ID) when you load this page, you see the list from the other conversation so you can just switch conversations using GET requests by specifying a different conversation id.
Workspace Management
One cool feature of Seam that had a lot of potential was workspace management which lets you see a list of active conversations and select one. We are going to implement a version of that here by providing the user with a set of links that takes us to the active conversation. Create a new bean that is session scoped that will keep a track of our conversation ids.
import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.enterprise.context.SessionScoped; import javax.inject.Named; @Named @SessionScoped public class WorkspaceBean implements Serializable { private List<String> conversations = new ArrayList<String>(); public List<String> getConversations() { return conversations; } }
This is just a bean that keeps a list of the conversation Ids currently active. When we start a conversation, we need to add that id to the list and when we end a conversation, we need to remove it from the list.
We will inject this session scoped bean into our backing bean and change the postConstruct
and confirm
methods in the BackingBean
class to add and remove the conversation from the list.
@Inject private WorkspaceBean workspace; ... ... @PostConstruct public void postConstruct() { conversation.begin(); workspace.getConversations().add(conversation.getId()); } public void confirm() { workspace.getConversations().remove(conversation.getId()); conversation.end(); try { FacesContext ctx = FacesContext.getCurrentInstance(); ctx.getExternalContext().redirect("home.jsf?faces-redirect=true"); } catch (IOException e) { e.printStackTrace(); } }
Now we need some view code to display the list of conversations and provide links to them. Simply add this to the home.xhtml
page, and even the listEdit.xhtml
or review.xhtml
pages if you want :
<h1>Workspaces</h1> <ui:repeat var="v_conv" value="#{workspaceBean.conversations}"> <h:link outcome="listEdit" value="Goto Conversation #{v_conv}"> <f:param name="cid" value="#{v_conv}" /> </h:link> <br /> </ui:repeat>
This just loops through the conversations and creates a link to the listEdit
page and passes the conversation Id as a parameter. Because we pass a value for cid
CDI will not try to add the current conversation as the cid
value.
That’s all you need for a workspace demonstration. You can create new lists and if you jump back to the home page, you will see the available conversations and be able to click the link and jump back into the conversation. When you review the list and confirm it and the conversation ends, when you end up back on the front page, that conversation is longer listed.
This is a fairly powerful mechanism that is built upon the simplicity of CDI Conversations. There is very little work that is required to use conversations, just inject the Conversation
instance and call the begin()
and end()
methods when you need to start and end the conversation. Conversations also have a minimal impact on our code, mainly just an annotation since the getters and settings and other methods don’t change. We can also easily revert back to a stateless request scoped solution should we have to which would cause additional code to be required to read/write the list to the client.
Conversations should be used with care, especially in terms of making sure you have strict demarcation boundaries, and careful consideration should be given to determining what you include in a conversation.
You can download the maven project source code for this demo (Conversation Demo ), just unzip it and enter mvn jetty:run
and navigate to http://localhost:8080/conversationdemo/home.jsf.
2 thoughts on “CDI Conversations part 2”
Comments are closed.
Is it possible to achieve sth similar to seam 2:
Contexts.getConversationContext().getNames()?
It would be better than converstations list in my opinion.
You can give the conversation any name you want since we are implementing the conversation selector ourselves, just pass a descriptive name across and store it with the conversation.