Monday, February 17, 2014

A Simple JSF Application

I’m creating a Bill-Pay application that

- Display a login screen
- After successful login, displays list of all available Payee in system
- User can select relevant payees from the list / ignore them\
- Add selected payees in their profile

My first facelet (Login.xhtml) displays a web page with two input fields, Login and Password. I’m using JSF standards validation mechanism to validate these fields, appropriate error message will be displayed if any of these fields fail validation.

System will also display appropriate error message if authentication fails. Upon successful login, next screen will display list of available Payee in the application.

Login.xhtml:
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
<h:head>
       <h:outputStylesheet libary="css" name="default.css" />
       <title>Login</title>
</h:head>
<h:body>
       <h:form>
              <h:graphicImage library="images" name="wave.med.gif"
                     alt="Duke waving his hand" />
              <p>
                     <h:outputText value="#{bundle.userNameLabel}"/>
                     <h:inputText id="userName" title="Enter user id"
                           value="#{userAuthenticationBean.userName}">
                           <f:validateRequired/>
                           <f:validateLength minimum="#{userAuthenticationBean.minLengthUserName}" maximum="#{userAuthenticationBean.maxLengthUserName}"/>
                     </h:inputText>
              </p>  
              <h:message showSummary="true" showDetails="false"
                     style="color: #d20005;
                     font-family: 'New Century Schoolbook', serif;
            font-style: oblique;
            text-decoration: overline"
                     id="errors1" for="userName" />
              <p>   
                     <h:outputText value="#{bundle.passwordLabel}"/>
                     <h:inputSecret id="password" titl="Enter password"
                           value="#{userAuthenticationBean.password}">
                           <f:validateRequired/>
                     </h:inputSecret>                        
              </p>
              <h:message showSummary="true" showDetails="false"
                     style="color: #d20005;
                     font-family: 'New Century Schoolbook', serif;
            font-style: oblique;
            text-decoration: overline"
                     id="errors2" for="password" />
              <p>
                     <h:commandButton id="submit" value="Submit" action="showPayeeList" />
              </p>
       </h:form>
</h:body>
</html>

At this point, I created a Managed Bean (UserAuthenticationBean.java) to perform authentication.

UserAuthenticationBean.java
package billpay.web.consumer;

import java.io.Serializable;
import java.util.Random;

import javax.enterprise.context.SessionScoped;
import javax.inject.Named;


@Named
@SessionScoped
public class UserAuthenticationBean implements Serializable{

                private static final long serialVersionUID = 5443351151396868724L;
   
                public int MinLengthUserName=4;
                public int MaxLengthUserName=10;
               
                private String userName;
       private String password;

       public String getPassword() {
              return password;
       }

       public void setPassword(String password) {
              this.password = password;
       }

                public String getUserName() {
                                return userName;
                }

                public void setUserName(String userName) {
                                this.userName = userName;
                }
}


At this point, when I compiled my application, everything looked good, however deployment failed with below error:

org.jboss.weld.exceptions.DefinitionException: WELD-000075 Normal scoped managed bean implementation class has a public field:  [EnhancedAnnotatedFieldImpl] public billpay.web.consumer.UserAuthenticationBean.minLengthUserName
       at org.jboss.weld.bean.ManagedBean.checkBeanImplementation(ManagedBean.java:225)

The reason for this failure was ‘public’ fields in my Bean. After some googling, I looked at this post.

Since I’m using JSF 2, I decided to use omnifaces to import my constants from Bean class. It was quite easy to integrate omnifaces, just drop a jar in WEB-INF/lib, and declare the tag on your facelet.

Read more about Omnifaces here

See changes in my facelet to integrate the omnifaces tags below.

<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:o="http://omnifaces.org/ui"
      xmlns:of="http://omnifaces.org/functions">

<h:body>
<o:importConstants type="billpay.web.consumer.UserAuthenticationBean"/>

                                                <h:inputText id="userName" title="Enter user id"
                                                                value="#{userAuthenticationBean.userName}">
                                                                <f:validateRequired/>
                                                                <f:validateLength minimum="#{UserAuthenticationBean.MinLengthUserName}" maximum="#{UserAuthenticationBean.MaxLengthUserName}"/>

Also I had to make some changes in my Bean class to declare my fields as constants, by making them ‘static’ and ‘final’.

       public static final int MinLengthUserName=4;
       public static final int MaxLengthUserName=10;



I deployed my application successfully in Tomcat, everything looked good and typing this URL (http://localhost:8080/billpay/faces/login.xhtml) in address bar displayed my login facelet.


However I also noticed  that my validation was not working as expected. If I click on Submit without entering any text in Login and Password field, JSF was not throwing any validation error in spite of <f:validateRequired/> tag.

After some reading and scanning APIs, I came to know that we need to add a context parameters that will instruct JSF to perform validation for NULL/EMPTY fields.

       <context-param>
              <param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
              <param-value>true</param-value>
       </context-param>

See javax.faces.validator.Validator Javadoc for more information.

My next task was to set up authentication.  J2EE tutorial discusses several approaches for implementing this functionality.

Since UserAuthenticationBean  already had login and password values, I added a new method to perform login operation:

       public String login(){
              if(this.userName.equals("Vicky") && this.password.equals("USA")){
                     System.out.println("You're good to go");
                     return "showPayeeList";
              }else{
                     FacesContext context = FacesContext.getCurrentInstance();
                     context.addMessage(null, new FacesMessage("Login failed."));
                     return null;
              }
       }

I also had to make some changes in facelet to invoke this operation for login operation.

<h:commandButton id="submit" value="Submit" action="#{userAuthenticationBean.login}" />

If I enter correct credentials, system will display ‘showPayeeList’ facelt that diplays all available payees in the system, however if authentication fails, system displays Login page again with error message ‘Login failed’.


In my login facelet I didn’t add any <h:messge/> tag to display the error message, however it does that automatically because we’ve set PROJECT_STAGE to ‘Development’.
       <context-param>
              <param-name>javax.faces.PROJECT_STAGE</param-name>
              <param-value>Development</param-value>
       </context-param>


WOW!!

Now my next task  is to display list of all available Payee .

In our application, we usually get list of Payees from database, however to simulate that I created a new RequestScoe ManagedBean (PayeeBean) and an Entity (PayeeEntity) to retrieve list of Payees. For this simple application, there was no need of an Entity, however I wanted to create a layer to separate Presentation and Data layers.

@Named
@RequestScoped
public class PayeeBean implements Serializable{

       private static final long serialVersionUID = 5443352251396868724L;
      
       /**
        * Returns all Payees that exists in Bill Payment system. Ideally this method
        * would refer some EJBs or some other data sourcesin actual application.
        * @return List<PayeeVO>
        * @throws Exception
        */
       public List<PayeeVO> getPayeeList(){
              return PayeeEntity.getPayeeList();
       }
}


public class PayeeEntity {

       public static List<PayeeVO> getPayeeList(){
              List<PayeeVO> payeeList = new ArrayList<PayeeVO>();
              payeeList.add(new PayeeVO(1,"Comcast"));
              payeeList.add(new PayeeVO(2,"Sawnee"));
              payeeList.add(new PayeeVO(3,"Macy's"));
              payeeList.add(new PayeeVO(4,"Walgreens"));
              return payeeList;
       }
}


Once I’ve list of Payees, my next task was to display them on a web page that will allow user to select appropriate payees.

Also this page will have option to ‘Add’ selected Payee in consumer’s profile, or clear the selection.

Since all my Payees were stored in a java.util.List, <h:dataTable> was obvious choice, however I had to display a checkbox for every Payee that will allow users to make selection. Depending upon your requirements, we could also use <ui:repeat> or <c:forEach> to display these values, however I stick with JSF standard components to explore their features.

showPayeeList.xhtml

<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core">
<h:head>
       <h:outputStylesheet library="css" name="default.css" />
       <title>Payee List</title>
</h:head>
<h:body>
       <h:form>
              <h:dataTable value="#{payeeBean.payeeList}" var="payee">
                     <f:facet name="caption">List of Payee</f:facet>
                     <h:column>
                           <f:facet name="header">Payee's Name</f:facet>
                            #{payee.name}
                            </h:column>
                     <h:column>
                     <f:facet name="header">Add</f:facet>
                           <h:selectBooleanCheckbox
                                  value="#{consumerBean.selectedPayee[payee.id]}" />
                     </h:column>
              </h:dataTable>
              <h:commandButton value="Submit" action="#{consumerBean.showSelectedPayee}" />
              <h:commandButton value="Clear" action="#{consumerBean.clearSelectedPayee}" />
       </h:form>
</h:body>
</html>

I also created a new bean (ConsumerBean) to store these selected values. Also this bean will be stored in Session scope because it holds consumer specific values.

ConsumerBean.java

@Named
@SessionScoped
public class ConsumerBean implements Serializable{

       private static final long serialVersionUID = 5443351151876868724L;
       private Map<Integer, Boolean> selectedPayee = new HashMap<Integer, Boolean>();
       private List<PayeeVO> selectedPayeeList = new ArrayList<PayeeVO>();
       private List<PayeeVO> payeeList = PayeeEntity.getPayeeList();
      
       public Map<Integer, Boolean> getSelectedPayee(){
              return selectedPayee;
       }
      
       public String showSelectedPayee(){
              for(PayeeVO payee: payeeList){
                     if(selectedPayee.get(payee.getId()))
                                  selectedPayeeList.add(payee);
              }
              selectedPayee.clear();
              return "showSelectedPayee";
       }

       public String clearSelectedPayee(){
              selectedPayeeList.clear();
              return null;
       }     
      
       public List<PayeeVO> getSelectedPayeeList(){
              return selectedPayeeList;
       }
}


After successful login, system displays all available Payees (below)



I selected ‘Comcast’ and ‘Sawnee’, clicked on ‘Add’, and here is my next screen

Details of other config files below:

faces-config.xml
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
    <application>
        <resource-bundle>
            <base-name>billpay.web.messages.Messages</base-name>
            <var>bundle</var>
        </resource-bundle>
        <locale-config>
            <default-locale>en</default-locale>
            <supported-locale>es</supported-locale>
        </locale-config>
    </application>
</faces-config>

Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.faces</welcome-file>
</welcome-file-list>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
</web-app>

Messages_en.properties
userNameLabel=Login:
passwordLabel=Password:

Workspace


Overall I’m amazed with simplicity offered by JSF. It took me approximately 2 hours to complete this simple application. Thanks for reading.




No comments:

Post a Comment