Sunday, September 16, 2012

Configure timeout for CDI conversations

CDI conversation scope is a nice feature when developing JSF applications. Imagine you have large data tables which take a long time to be loaded. You normally don't want to place the loaded data in session scoped beans by reason of high memory consumption. And you can't place the loaded data in view scoped beans because you wouldn't like always to reload data again if user leave and enter the same view. It would be nice only to keep data if user enters the same page within a certain time interval and reload them again if the bean was not accessed within this time interval. This can be achieved by conversation scoped bean with timeout. We will deal with MyFaces CODI (CDI Extensions) and see how to set a custom timeout for beans annotated with @ConversationScoped. The default timeout is 30 min. what is too long for our example. We will configure it for 1 min. The first step is to extend CODI's ConversationConfig and overwrite the method getConversationTimeoutInMinutes(). Let's write a class AlternativeConversationConfig.
package controller.cdi;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;
import javax.enterprise.inject.Specializes;
import org.apache.myfaces.extensions.cdi.core.api.config.ConfigEntry;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.config.ConversationConfig;

@ApplicationScoped
@Alternative
@Specializes
public class AlternativeConversationConfig extends ConversationConfig {

    @ConfigEntry
    public int getConversationTimeoutInMinutes() {
        return 1;
    }
}
The important thing is the annotation @Specializes which allows to inject AlternativeConversationConfig instead of ConversationConfig at every existing places. The second step is a proper entry in beans.xml to use (activate) our class on all injection points for ConversationConfig.
<alternatives>
    <class>controller.cdi.AlternativeConversationConfig</class>
</alternatives>
Server's log output during startup contains these lines now
config implementation:
controller.cdi.AlternativeConversationConfig$Proxy$_$$_WeldClientProxy
config implementation: controller.cdi.AlternativeConversationConfig
   method: getConversationTimeoutInMinutes
   value: 1
To check if everything is ok, we can write a conversation scoped bean and use it in facelets.
import java.io.Serializable;
import javax.faces.event.ActionEvent;
import javax.inject.Named;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.ConversationScoped;

@Named
@ConversationScoped
public class CdiBeanConversationScoped implements Serializable {
 
    private int counter;
 
    public int getCounter() {
        return counter;
    }
 
    public void increment(ActionEvent e) {
        counter++;
    }
}
<h:outputText id="counter" value="Conversation scoped counter: #{cdiBeanConversationScoped.counter}"/>
   
<p:commandButton value="Increment counter" process="@this" update="counter"
                                         actionListener="#{cdiBeanConversationScoped.increment}"/>
The counter will expire after 1 min. if no access to the bean happens within this time interval. Simple push the button to increment the counter, wait longer than 1 min. and increment it again. You will see that counter was reseted.