Steven Verborgh

Old Entries for those of you on a journey

Porting the ViewScoped JSF annotation to CDI

Jan 06, 2010
UPDATE: This implementation is something I created as a temporary solution untill JBoss releases the seam face module. You should try to get that one working instead. This article might help you gasp the inner workings a bit, but keep in mind this code is to simple to be reliable.

As you might know, JavaServer Faces 2 comes with some optional annotations. Optional, because halfway throught the specification process something called Dependency Injection for Java (JSR330) and something else called Context and Dependency Injection or CDI (JSR299) came to life. That means we have some very usefull annotations like @ViewScoped that are specified in the JSF 2 specification that have no alternatives in CDI.

CDI specifies @RequestScoped, @SessionScoped, @ApplicationScoped and @ConversationScoped because these scopes are somewhat technology independent. A scope like @ViewScoped is bound to a view technologie that has a concept of views. In the case of JSF this is a scope that remains into existence as long as we are using the page and doing postbacks to that page.

This is a very usefull scope. We can put objects in this scope by putting them into the the map that is associated to the UIViewRoot of a view. In this article we will look at how we can create a custom @ViewScoped annotation, a custom CDI Context and a Extension that will behave "like" the optional JSF @ViewScoped annotation.

As usual I will be using the latest glassfish v3 build and Netbeans 6.8, for the simple reason that this setup is extremely easy and contains all that we need for this tutorial. The easiest way to test this out is create the needed code all in the same JSF web application. If you want, you can move the project to a Java class library project (with the correct libraries) and reuse the created extension in all your projects.

So as explained what we want is the ability to add a @ViewScoped annotation to a javabean with a @Named. This annotation should have the same effect as e.g. the @SessionScoped annotation, but should of course place the instance in the "viewMap"-Map of the current UIViewRoot. The bean with the annotation should look like this:

package be.verborgh.ui.listeners;

import be.verborgh.faces.ViewScoped;
import javax.faces.event.ActionEvent;
import javax.inject.Named;

@Named("toggleState")
@ViewScoped
public class ToggleActionListener {
    private boolean toggled;

    public boolean isToggled() {
        return toggled;
    }
    
    public void toggle(ActionEvent evt) {
        toggled = !toggled;
    }
}

Notice, we are using out own @ViewScoped annotation, not the optional one supplied by JavaServer Faces. It's a simple bean, that should keep its state, even if another form is submitted, meaning multiple "postback" requests to the same page.

What we are calling scopes, are actually Context instances. So what is called a session scope, is a context in which we can put objects. The Context interface is part of the spi provide by the CDI specification. It is this interface we need to implement.
This interface requires us to implement four methods:

getScope()
This is what links the scope annotation to this context. When we register this scope later on, it is this method that will make sure that beans annotated with the @ViewScoped annotation. The implementation will return the class of this annotation
isActive()
The second easy method is a method which indicates if the context exists. I will return true from this method due to lack of better understanding, and because I will never put non view related beans into it. Still if you use this code in production, you might want to get into the details of the method.
get(Contextual)
This method will find an existing instance of the class attached to the Contextual object or return null. This Contextual-class is the base type of all object that can be saved in a Context. It provides the means to instantiate and delete a object. To simplify the code, we will only save Contextual objects that are Bean's. Bean's being the JavaBean classes that are marked with @Named of related annotations (@Default). Using the Bean subclass of Contextual allows us to get the EL-name, which we can use to save into the Map associated with the UIViewRoot.
get(Contextual, CreationalContext)
This method works like the get method above, but creates the object when it doesn't exist. The extra CreationalContext is a temporary internal context that is used to create the object. This CreationalContext makes sure that all needed dependencies of the new instance we are creating get satisfied. It is needed as an argument for the create method of the Contextual interface.

Now you understand the Interface, lets look at our implementation:

package be.verborgh.faces;

import java.lang.annotation.Annotation;
import java.util.Map;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

public class ViewContext implements Context {

    public Class getScope() {
        return ViewScoped.class;
    }

    public  T get(Contextual contextual, CreationalContext creationalContext) {
        Bean bean = (Bean) contextual;
        Map viewMap = getViewMap();
        if(viewMap.containsKey(bean.getName())) {
            return (T) viewMap.get(bean.getName());
        } else {
            T t = bean.create(creationalContext);
            viewMap.put(bean.getName(), t);
            return t;
        }
    }

    private Map getViewMap() {
        FacesContext fctx = FacesContext.getCurrentInstance();
        UIViewRoot viewRoot = fctx.getViewRoot();
        return viewRoot.getViewMap(true);
    }

    public  T get(Contextual contextual) {
        Bean bean = (Bean) contextual;
        Map viewMap = getViewMap();
        if(viewMap.containsKey(bean.getName())) {
            return (T) viewMap.get(bean.getName());
        } else {
            return null;
        }
    }

    public boolean isActive() {
        return true;
    }
}

The code speaks for itself. The only method that might be unexplained is the getViewMap() method. All this does is retrieve the FacesContext, from there on the UIViewRoot of the current saved view the user is accessing and from this root it gets the Map that contains the "variables" associated with the view. (I'm not saying attributes :) ... look it up in the API what getAttributes() does).

What needs to be done is have a look at the annotation we created. According to the specification there are "real" scopes annotated with @NormalScope and pseudo scopes annotated with @Scope. This is best explained with some examples: session, application, conversation and request scopes are real scopes; dependant and singleton are pseudo-scopes. I think that the view scope is real enough to justify the use of the @NormalScope annotation. So our annotation looks like:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package be.verborgh.faces;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.enterprise.context.NormalScope;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

@Target(value={METHOD,TYPE,FIELD})
@Retention(value=RUNTIME)
@NormalScope
@Inherited
public @interface ViewScoped {

}

From this code you can deduct that scopes can be placed on classes, methods and fields and needs to be available at runtime. The @Inherited annotation means that subclasses are also placed in the view scope.

So now you have the scope and the annotation, but it doesn't do anything! What a bummer. But again the documentation brings us relief: A custom context object may be registered with the container using AfterBeanDiscovery.addContext(Context).
It might not ring a bell, but the CDI specification has a Extension marker (It's empty) interface that get loaded by means of the standard Java Service Loader ... which you know? Right?
The AfterBeanDiscovery hint makes it clear we will need to use an observator method for the AfterBeanDiscovery event. I'm not going to write a post about this, but i advise you to have a look at some blogs that talk about it.
So lets create a class that implements the empty interface and has a method that observers the above event in order to call the addContext method:

package be.verborgh.faces;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;

public class ViewContextExtension implements Extension{

    public void afterBeanDiscovery(@Observes AfterBeanDiscovery event, BeanManager manager) {
        event.addContext(new ViewContext());
    }
}

If you really want to know what this does look at: AfterBeanDiscovery and @Observes.

All that we now need to do is provide a file with the name "javax.enterprise.inject.spi.Extension" in the "META-INF/services" folder of you application in order to allow the java ServiceLoader that weld uses to load the extension.

I hope this article helped those that wondered whether the CDI spec could replace the JSF scope annotation... and those that are waiting for seam to implement these needed annotations.
It should be clear you can also make a FlashScoped annotation if you need it.