Tuesday, December 20, 2011

JSF - Beans - Basics

JSF is the view.  But, the view doesn't do much if you have no model.  The model is defined by the beans.  But, there are different kinds of Beans.  Different Beans implement different parts of the logic.  A JSF Bean's purpose is to implement presentation logic.  There are two different types of beans used by JSF, they are JSF Managed Beans, and CDI Beans.  These two mechanisms behave differently, but are similar.  CDI is preferred as it is managed by the application server (which means you must have a JEE compliant server running) and can inject more then the ManagedBean.  The ManagedBean is controlled by JSF and can inject only ManagedBeans.  Mind you that is not to say that it can't inject persistence units, because both can.  Neither of these beans are EJB beans although you should now be injecting your EJB with CDI.  You can inject an EJB into an @Named or @ManagedBean but doing so into a ManagedBean has issues.  There are work arounds, but, as mentioned, CDI is prefered.

Once you have defined the type of bean you will implement, you need to define a scope.  The scope determines how long a bean is "alive".  Meaning, a request scoped bean will live only as long as the request and therefor hold no state outside of the request/response cycle.  That differs from a SessionScoped bean which is alive for the entire http session.

There are several different scopes for a ManagedBean:
@ApplicationScoped
@CustomScoped
@NoneScoped
@RequestScoped
@SessionScoped
@ViewScoped


CDI has its own scopes:
@RequestScoped
@ApplicationScoped
@SessionScoped
@ConversationScoped
@Dependent
@NormalScoped
And you can define your own scope with @Target({TYPE, METHOD, FIELD}) and @Retention(RUNTIME)

I won't go over each scope here as they are all defined in length by the specification.  I will go over the ones we end up using, but we do not use several of them.

In the JSF page, you access the bean by Java Server Faces Expression Language or EL.  #{bean.field}, #{bean.method}, #{bean.method(parameter)} are the basic syntaxes used to access a field, method, or method with a parameter.  Where bean is the name of the bean supplied with the declaration of the bean.  (i.e. @ManagedBean, @Named) and field is the name of the field.  A properly named getter / setter is called to access the field.  i.e. test.name is accessed via test.getName and set with test.setName(newName).  You can make a field read only by not providing a setter method.

When we tested our persistence environment, we used a bean.  Let's go over the code:

TestBean.java

,..
@ManagedBean(name = "testBean")
@RequestScoped

public class TestBean {

    @PersistenceContext(unitName = "JSFDemoPU")     EntityManager em;    
    @Resource     UserTransaction utx;    
    ...
    public int count() {         ...
        return results.size();     }
}


Here we have defined it as a managed bean with request scope.  Then we inject the persistence unit so we can access the database and get a count of records in some table.  We then declared a transaction so we could add and remove a part in the database to see that our JTA environment was working.  Then we declared the method that would perform all this and return an integer.  We access this from our JSF page with <h:outputText value="#{testBean.count()}"/>.  This is the only example of a ManagedBean in this tutorial.  I put it here so you could see it in operation, but the rest are the preferred CDI beans.  Notice, there is no state to be saved, and so a RequestScoped bean is appropriate.  Although, if you didn't want a database hit everytime the user hit this page, you could make it a SessionScoped bean, save the state of count - maybe in a @PostConstruct method, and then only hit the database once per session.

Here is an example of a CDI bean:
MainSampleViewBean.java


package com.sample.beans;
import com.sample.facades.OrderEntityFacadeInterface;
import com.sample.facades.PartEntityFacade;
import com.sample.entities.OrderEntity;
import java.io.Serializable;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
/**
 * Session scoped bean to hold state of the session.  
 * Open a second browser on the same machine and navigate to
 * the same URL, and you will see the same state.
 * 
 * @author Thomas Dias
 */
@Named("mainSampleViewBean")
@SessionScoped
public class MainSampleViewBean implements Serializable {
    @Inject // Notice: we inject the interface for an interface view bean
    private OrderEntityFacadeInterface orderEntityFacade;
    @Inject // This is an inverfaceless bean, so we inject it directly
    private PartEntityFacade partEntityFacade;
    @Inject // This is a stateful bean
    private LoginBean userLogin;
    @Inject
    private TreeBean treeBean;
    @Inject
    private OrderPartsBean orderPartsBean;
    private OrderEntity orderEntity;
    private String msg;
    /** Creates a new instance of MainSampleViewBean */
    public MainSampleViewBean() {
    }
    /**
     * performs what would have normally been done in the constructor, 
     * but since this is a bean, will do it after injections have been complete.
     */
    @PostConstruct
    public void init() {
        treeBean.setMainSampleViewBean(this);
        orderPartsBean.setMainSampleViewBean(this);
        List<OrderEntity> list = orderEntityFacade.findAll();
        if (list != null && !list.isEmpty()) {
            orderEntity = orderEntityFacade.getOrderEntity(list.get(0));
        } else {
            orderEntity = null;
        }
        treeBean.buildTree(orderEntity);
        orderPartsBean.init();
    }
    /**
     * called before the object is put up for garbage collection by the container
     * Notice it cleans up the stateful bean.
     */
    @PreDestroy
    public void close() {
        userLogin.logout();
    }
    /**
     * This will invalidate the session and close the sessionscoped bean.
     * Next time the page is loaded, the session bean will be recreated as opposed
     * to being retrieved from the container's managed beans.
     * 
     * You can see the effects of this by opening two browser tabs to the same
     * @return index 
     */
    public String logout() {
        FacesContext context = FacesContext.getCurrentInstance();
        ExternalContext ec = context.getExternalContext();


        final HttpServletRequest request = (HttpServletRequest) ec.getRequest();
        request.getSession(false).invalidate();


        return "index";
    }
    /**
     * listener method - persists the order to the database
     * @param event
     */
    public void save(ActionEvent event) {
        orderEntity.setCounter(orderEntity.getCounter() + 1);
        orderEntityFacade.edit(orderEntity);
        msg = "Order was saved.";
    }
    /**
     * Action method to prep the order for checkout
     * @return the page to navigate to for checkout
     */
    public String checkout() {
        msg = "Checking out.";
        return "checkout";
    }
    /**
     * Action method to process the order
     * @return the page to navigate to
     */
    public String processOrder() {
        msg = "Order : " + this.orderEntity + " was processed.";
        init();
        return "startPage";
    }
    public String getName() {
        return this.getOrderEntity().getName() + orderEntity.getCounter();
    }
    public TreeBean getTreeBean() {
        return treeBean;
    }
    public void setTreeBean(TreeBean treeBean) {
        this.treeBean = treeBean;
    }
    public void setOrderEntity(OrderEntity orderEntity) {
        if (orderEntity == null) {
            return;
        }
        this.orderEntity = orderEntityFacade.getOrderEntity(orderEntity);
    }
    public void orderSelected(OrderEntity orderEntity) {
        this.orderEntity = orderEntityFacade.getOrderEntity(orderEntity);
        treeBean.buildTree(this.orderEntity);
        orderPartsBean.init();
    }
    public void partSelected() {
        orderPartsBean.init();
    }
    // Standard getters and setters below this line
    public OrderEntity getOrderEntity() {
        return orderEntity;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public PartEntityFacade getPartEntityFacade() {
        return partEntityFacade;
    }
    public void setPartEntityFacade(PartEntityFacade partEntityFacade) {
        this.partEntityFacade = partEntityFacade;
    }
    public OrderEntityFacadeInterface getOrderEntityFacade() {
        return orderEntityFacade;
    }
    public void setOrderEntityFacade(OrderEntityFacadeInterface orderEntityFacade) {
        this.orderEntityFacade = orderEntityFacade;
    }
    public LoginBean getUserLogin() {
        return userLogin;
    }
    public void setUserLogin(LoginBean userLogin) {
        this.userLogin = userLogin;
    }
    public OrderPartsBean getOrderPartsBean() {
        return orderPartsBean;
    }
    public void setOrderPartsBean(OrderPartsBean orderPartsBean) {
        this.orderPartsBean = orderPartsBean;
    }
}


Although this bean looks more complicated, it is doing much more and it is the central bean used in our application.  We started by declaring it an @Named and gave it the name.  Then we declared it @SessionScoped so that it would retain its state even if we navigate to other pages.  We inject 3 EJBs with @Inject and then we use @Inject to inject 2 CDI beans.  We declare some fields that we want the page to have access to, so later we declare the getters and setters.

Then we have our default constructor.  Remember, all beans are going to have a default constructor.  (Although, they don't have to be explicitly defined if we don't have any other constructors.)

And then we come to a very important annotation.  @PostConstruct.  At the time the bean is created, dependency injection has not occured.  If you try to initialize your bean at instantiation, you will not have access to all your resources.  To get around that, the method annotated with @PostConstruct will be called once the injections have occured.  This is where you do your initializations.  You will notice at this point, we call some setter methods with the this reference, why?  Because injection doesn't do very well with circular, bidirectional, or recursive injections.  i.e.  Bean A contains Bean B which contains Bean A.  To avoid problems, we create Bean A, inject Bean B, and in the initializer, we tell Bean B about Bean A.  Because we just now finished setting up our bidirectional beans, we now call their initializers.  i.e. treeBean.buildTree(), and orderPartsBean.init();

The close() method is annotated with @PreDestroy which means, before the method is sent to garbage collection, this method will be called.

When we get down to the logout method, we see how we can close a session and destroy the session scoped beans.  The logout method is also an action method, which means that it returns a navigation directive.  The return string of "index" will have the .xhtml appended and the browser will redirect to the index page.  We'll go over the JSF side with actionListeners, actions, redirection, etc. later.

I chose not to handle persistence in this bean, but instead to implement all database access via EJBs.  If you wanted to handle persistence in the Named bean, you would inject a persistence unit and manage your transactions as we did with the testBean:
    @PersistenceContext(unitName = "JSFDemoPU")     EntityManager em;    
    @Resource     UserTransaction utx;    

Getters and setters are not required on the injected beans for normal functionality, but when we do the JUnit tests, I chose not to use container managed injection which will require we inject our own instances.  It is not always a good idea to have access to these beans, so don't use the above as a best pratices approach, but more of a tutorial approach.

And so, there you have your typical JSF beans.

COMMON PROBLEMS :

Issue: When using a SessionScoped bean, you notice it isn't holding the state.  Problem: The scopes are specific to ManagedBean and CDI.  If you import the wrong one, your sessions will not work properly.  Make sure for ManagedBean, your scope imports start with javax.faces.bean.  If you are using CDI then the imports should start with javax.enterprise.context.

Issue: An exception occurs saying your bean doesn't have a property.  Problem:  You forgot your getters and setters, or the privileges on them are not public.  Remember,  beans are accessed through their getters and setters.

Issue:  The log file says a method isn't found, or when you execute a listener, you don't get the right method being called.  Again, check your imports.  The annotations and event handlers should be coming from packages starting with javax.faces.  It is easy to accidently import an AWT ActionEvent which will not work, and the errors you get are not intuitive and can be buried within log files on the app server.

No comments:

Post a Comment