Our next task is to create a new book entry or to edit an existing one. The book editor shall consist of a tab panel offering one page for the books meta data like title, author, publisher, and one page for each language, where you can edit a review. It must be possible to switch between these pages without loosing data. Not very hard to imagine, that we need a backing bean living longer than just one request.

(Ok, there are some other tricks, to pass data to the next request without prolonging the life cycle. But this is another story, I’m going to tell you later on.)

During one session, the user may edit more than one book. Unlike the categories (all of them were edited together), we have to offer a function to edit one book after the other. Thus, a session scope wont fit here. We need a scope longer than just one request but shorter than a session. According to the table in the short chapter before, using a built in CDI scope, @ConversationScoped would be the choice. We will talk about the JSF specific CDI scope extensions or custom scopes later on.

The book editor will be invoked with a parameter representing the book id. To create a new book, this id will be let away. The bookId is extracted from the request map, which we can access via the external context. For sake of simplicity, if the bookId is unknown or not an integer, we’ll always create a new book. In a mission critical application, we would choose a different strategy, but for Books this is sufficient. Once the book is loaded or created, we will turn the conversation into a long running one.

[...]
@Named
@ConversationScoped
public class BookEditor implements Serializable {
   [...]
   @Inject Conversation _conversation;

   @PostConstruct
   private void init() {
       [...]
       loadOrCreateBook();
       if (_conversation.isTransient()) {
           _conversation.begin();
       }

   private void loadOrCreateBook() {
       FacesContext fc = FacesContext.getCurrentInstance();
       Map<String, String> paramMap = fc.getExternalContext()
                       .getRequestParameterMap();
       try {
           int bookId = Integer.parseInt(paramMap.get("bookId"));
           _book = _bookService.find(bookId);
           if (_book == null) {
               _book = new Book();
           }
       } catch (NumberFormatException e) {
           _book = new Book();
       }
   }

[...]
}

Co-working with CDI, JSF injects a conversation into the book editor. Whether this is an existing or new conversation depends an the navigation type we would choose. The principle is demonstrated in the code excerpt above. And you will find similar solutions in many examples. Later on, we’re going to move the conversation handling into a different class.

It is important to check first, whether the conversation is transient (not long running). Only in this state, it is allowed to start the long run.

Starting the long run is quite easy. But ending it might become a bit complicated. If the editor had a Save and close button, then this would be a wonderful candidate to store the data and to quit the long running mode of the conversation. But usually, the user may save and stay in the dialog. Or he may hit any navigation button to move to a different location, to create a new book, to edit an other one. Having no single point of leaving the editor requires some more effort.

Now, let’s examine, when JSF will re-use an existing conversation or inject a new one. A book bean will be created every time it is used in a new request and either the conversation is transient or in case of a long running conversation, a new conversation is used. In such a case, the init() method is invoked. Thus, we can track new conversations by tracking calls to this method.

[...]
@Named
@ConversationScoped
public class BookEditor implements Serializable {
   [...]
   private static final Logger _logger = Logger.getLogger("BookEditor");

   @PostConstruct
   private void init() {
       _logger.log(Level.INFO, "init in BookEditor");
       [...]
   }

[...]
}

Once the bean is such prepared, it will log every creation (more precise: it will log every time it is created by CDI. If created by a new, init would not be called). Now, we can invoke our book editor in different ways to observe the behavior.

For testing purpose, we’ll add two links to the book editor to the navigation section of adminTemplate.xhtml.

   <h:form>
       <h:commandLink styleClass="button"
                                     value="#{msg.btnNewBook} commandLink"
                                     action="/admin/bookEditor.xhtml"/>
   </h:form>
   <h:link styleClass="button"
                   value="#{msg.btnNewBook} link"
                   outcome="/admin/bookEditor.xhtml"/>

Try and observe

Start *Books* and navigate to the book editor via the “commandLink” button.
When the book editor is opened, click onto the “commandLink” button again.
You may observe the log output live in NetBeans output, GlassFish window.

Re-start the application and repeat your observations by using the “link” button.

commandLink and link provide different navigation models.
Whilst commandLink implements a more traditional JSF navigation using a postback, link had been introduced with JSF 2.0 and realizes a navigation via get.
Next, we’ll discuss the differences and its impact to conversation handling.

 

The preceding part of the tutorial is from my book “Web Development with Java and JSF“.

Want to read more? Get your copy here!