Sunday, November 3, 2013

EJB3/JSF2/primfaces3.5:Cascading selectOneMenu example using Eclipse

hello,how are you?

First of all what is the meaning of cascading selectoneMenu?

The term "Cascading SelectOneMenu" means a dynamic dependent selectMenu that allow a “child” selectMenu to refresh when a selection is made in the “parent” one.

As we already know from the first post that described our tools (Preparing Your Environment),we will use oracle database and its hr schema for all our posts,so in this post we have added two dependent SelectOneMenu to a simple page,the first one displays a list of Regions (Americas,Asia,Europe,etc),once a Region is selected,the second selectmenu is updated to display a list of Countries for the selected Region and of course this second menu will always be disabled if there is no region selected as we see from the figure


AS Regions and Countries tables already exist in hr schema of oracle database so lets go now to explore our application

(once more note that as usual in our post we will explain every detail found in the code then we will explore the application- how it works -and in the end of the post you will find the source code to download but I will not explain the basics about creating the application such as how to connect to the database to generate the entities, how to create JSF or EJB project,how to configure the JNDI datasource for oracle database in glassfish 3.1.2 server, how to package your application in an Ear file and so on because all these basics to build an EJB3/JSF2 Applications had been explained in details in our first posts from here and here)

1-EJB Project(DependentselectEJB):

The EJB module is containing the entity classes,the EJBS,and the presistence file.

Entity classes:

We have two entity classes for our two tables RegionsEntity and CountriesEntity for Regions and Countries tables.The Entities are in the“model.entities”package.For the RegionsEntity we have added a named query ("Regions.findAll") to display a list of all Regions in the First Select One Menu and for the other Entity we have added another named Query ("Countries.findCountriesByRegion") to display a list of the Countries for the selected Region.The code of the two entities looks like the following:

RegionsEntity:
 package model.entities;  
 import java.io.Serializable;  
 import javax.persistence.*;  
 import java.util.List;  
 /**  
  * The persistent class for the REGIONS database table.  
  *   
  */  
 @Entity  
 @Table(name = "REGIONS")  
 @NamedQuery(name = "Regions.findAll", query = "SELECT r FROM RegionsEntity r order by r.regionName")  
 public class RegionsEntity implements Serializable {  
      private static final long serialVersionUID = 1L;  
      @Id  
      @GeneratedValue(strategy = GenerationType.IDENTITY)  
      @Column(name = "REGION_ID")  
      private Long regionId;  
      @Column(name = "REGION_NAME")  
      private String regionName;  
      // bi-directional many-to-one association to CountriesEntity  
      @OneToMany(mappedBy = "region")  
      private List<CountriesEntity> countries;  
      public RegionsEntity() {  
      }  
      public Long getRegionId() {  
           return this.regionId;  
      }  
      public void setRegionId(Long regionId) {  
           this.regionId = regionId;  
      }  
      public String getRegionName() {  
           return this.regionName;  
      }  
      public void setRegionName(String regionName) {  
           this.regionName = regionName;  
      }  
      public List<CountriesEntity> getCountries() {  
           return this.countries;  
      }  
      public void setCountries(List<CountriesEntity> countries) {  
           this.countries = countries;  
      }  
      @Override  
      public int hashCode() {  
           int hash = 0;  
           hash += (regionId != null ? regionId.hashCode() : 0);  
           return hash;  
      }  
      @Override  
      public boolean equals(Object object) {  
           // TODO: Warning - this method won't work in the case the id fields are  
           // not set  
           if (!(object instanceof RegionsEntity)) {  
                return false;  
           }  
           RegionsEntity other = (RegionsEntity) object;  
           if ((this.regionId == null && other.regionId != null)  
                     || (this.regionId != null && !this.regionId  
                               .equals(other.regionId))) {  
                return false;  
           }  
           return true;  
      }  
      @Override  
      public String toString() {  
           return "model.entities.RegionsEntity[ regionId=" + regionId + " ]";  
      }  
 }  
CountriesEntity:
 package model.entities;  
 import java.io.Serializable;  
 import javax.persistence.*;  
 /**  
  * The persistent class for the COUNTRIES database table.  
  *   
  */  
 @Entity  
 @Table(name = "COUNTRIES")  
 @NamedQuery(name = "Countries.findCountriesByRegion", query = "SELECT c FROM CountriesEntity c WHERE c.region = :theRegion order by c.countryName")  
 public class CountriesEntity implements Serializable {  
      private static final long serialVersionUID = 1L;  
      @Id  
      @GeneratedValue(strategy = GenerationType.IDENTITY)  
      @Column(name = "COUNTRY_ID")  
      private String countryId;  
      @Column(name = "COUNTRY_NAME")  
      private String countryName;  
      // bi-directional many-to-one association to RegionsEntity  
      @ManyToOne  
      @JoinColumn(name = "REGION_ID")  
      private RegionsEntity region;  
      public CountriesEntity() {  
      }  
      public String getCountryId() {  
           return this.countryId;  
      }  
      public void setCountryId(String countryId) {  
           this.countryId = countryId;  
      }  
      public String getCountryName() {  
           return this.countryName;  
      }  
      public void setCountryName(String countryName) {  
           this.countryName = countryName;  
      }  
      public RegionsEntity getRegion() {  
           return this.region;  
      }  
      public void setRegion(RegionsEntity region) {  
           this.region = region;  
      }  
      @Override  
      public int hashCode() {  
           int hash = 0;  
           hash += (countryId != null ? countryId.hashCode() : 0);  
           return hash;  
      }  
      @Override  
      public boolean equals(Object object) {  
           // TODO: Warning - this method won't work in the case the id fields are  
           // not set  
           if (!(object instanceof CountriesEntity)) {  
                return false;  
           }  
           CountriesEntity other = (CountriesEntity) object;  
           if ((this.countryId == null && other.countryId != null)  
                     || (this.countryId != null && !this.countryId  
                               .equals(other.countryId))) {  
                return false;  
           }  
           return true;  
      }  
      @Override  
      public String toString() {  
           return "model.entities.CountriesEntity[ countryId=" + countryId + " ]";  
      }  
 }  
Business logic or EJBS:


1-AbstractFacade:
 package model.ejb;  
 import javax.persistence.EntityManager;  
 import javax.persistence.PersistenceContext;  
 public abstract class AbstractFacade<T> {  
      private final static String UNIT_NAME = "selectEJBPU";  
      @PersistenceContext(unitName = UNIT_NAME)  
      protected EntityManager em;  
      private Class<T> entityClass;  
      public AbstractFacade(Class<T> entityClass) {  
           this.entityClass = entityClass;  
      }  
      public T find(Long entityID) {  
           return em.find(entityClass, entityID);  
      }  
 }  
2-RegionService

Region Service EJB class extends AbstractFacade and has two methods.One to execute the named query and get the list of regions and the other method (findById) to be used in our Converter class in the JSF project to convert between Region entity and an unique string representation (which would be the id property) which JSF returns
 package model.ejb;  
 import java.util.List;  
 import javax.ejb.LocalBean;  
 import javax.ejb.Stateless;  
 import javax.persistence.Query;  
 import model.entities.RegionsEntity;  
 /**  
  * Session Bean implementation class RegionService  
  */  
 @Stateless  
 @LocalBean  
 public class RegionService extends AbstractFacade<RegionsEntity> {  
      public RegionService() {  
           super(RegionsEntity.class);  
      }  
      @SuppressWarnings({ "unchecked" })  
      public List<RegionsEntity> getRegionsList() {  
           Query query = em.createNamedQuery("Regions.findAll");  
           List<RegionsEntity> results = query.getResultList();  
           return results;  
      }  
      public RegionsEntity findById(Long Id) {  
           return super.find(Id);  
      }  
 }  
3-CountryService

Country Service EJB class extends AbstractFacade and has one method to get all the countries for the selected Region.
 package model.ejb;  
 import java.util.List;  
 import javax.ejb.LocalBean;  
 import javax.ejb.Stateless;  
 import javax.persistence.Query;  
 import model.entities.CountriesEntity;  
 import model.entities.RegionsEntity;  
 /**  
  * Session Bean implementation class CountryService  
  */  
 @Stateless  
 @LocalBean  
 public class CountryService extends AbstractFacade<CountriesEntity> {  
      public CountryService() {  
           super(CountriesEntity.class);  
      }  
      @SuppressWarnings({ "unchecked" })  
      public List<CountriesEntity> findCountriesByRegion(RegionsEntity Region) {  
           Query query = em.createNamedQuery("Countries.findCountriesByRegion");  
           query.setParameter("theRegion", Region);  
           List<CountriesEntity> results = query.getResultList();  
           return results;  
      }  

 }  
For the presistence.xml file I will not talk in details about it as we already do this in the previous posts but you should know that jdbc/hrConn is the name of our data source which we have configured in glassfish server in our first post from here using the Glassfish administration console to be used as a data source for all our Applications

2-JSF  Project(DependentselectJSF):

Our JSF project which represents the interface or the web logic which depends on EJB project,let us explore it

Java Resources:

com.mb package:

we have a single view scoped managed bean inside this package called SelectController managed bean

SelectController:

SelectController is viewScoped managedbean.We are using @EJB annotation to inject the EJB inside of the MB then we call our EJB method to get the list of Regions for the first select menu and added another method which check the Region that was selected by the user and returns the list of countries for the selected region
 package com.mb;  
 import java.io.Serializable;  
 import java.util.List;  
 import javax.ejb.EJB;  
 import javax.faces.bean.ManagedBean;  
 import javax.faces.bean.ViewScoped;  
 import model.ejb.CountryService;  
 import model.ejb.RegionService;  
 import model.entities.CountriesEntity;  
 import model.entities.RegionsEntity;  
 @ManagedBean  
 @ViewScoped  
 public class SelectController implements Serializable {  
      /**  
       *   
       */  
      private static final long serialVersionUID = 7722275586700049775L;  
      @EJB  
      private RegionService regionService;  
      @EJB  
      private CountryService countryService;  
      private RegionsEntity region;  
      private CountriesEntity country;  
      public RegionsEntity getRegion() {  
           return region;  
      }  
      public void setRegion(RegionsEntity region) {  
           this.region = region;  
      }  
      public CountriesEntity getCountry() {  
           return country;  
      }  
      public void setCountry(CountriesEntity country) {  
           this.country = country;  
      }  
      public RegionService getRegionService() {  
           return regionService;  
      }  
      public List<RegionsEntity> getRegions() {  
           return regionService.getRegionsList();  
      }  
      public List<CountriesEntity> getCountriesForRegion() {  
           if (region == null) {  
                return null;  
           }  
           return countryService.findCountriesByRegion(region);  
      }  
 }  
com.converter package:

inside this package we have our converter class RegionConverter

RegionConverter:

JSF generates HTML. HTML is in Java terms basically one large String. To represent Java objects in HTML, they have to be converted to String. Also,when a HTML form is submitted,the submitted values are treated as String in the HTTP request parameters.What is really happened under the covers is that JSF extracts them from the HttpServletRequest#getParameter() which returns String.To convert between a non-standard Java object (i.e. not a String,Number or Boolean for which EL has built-in conversionsyou have to supply a custom converter for your Entity class.This Converter implements two methods getAsString()method so that the desired Java Object is been represented in an unique String representation and getAsobject() method so that the HTTP request parameter can be converted back to the desired Java Object (RegionEntity in our case) and annotate the Converter with @FacesConverter to hook on the object type in question then JSF will take care of the conversion when RegionEntity type comes into the picture
 package com.converter;  
 import javax.faces.context.FacesContext;  
 import javax.faces.convert.Converter;  
 import javax.faces.convert.FacesConverter;  
 import javax.faces.component.UIComponent;  
 import model.entities.RegionsEntity;  
 import com.mb.SelectController;  
 @FacesConverter(forClass = RegionsEntity.class)  
 public class RegionConverter implements Converter {  
      public static Object getManagedBean(String name) {  
           FacesContext ctx = FacesContext.getCurrentInstance();  
           return ctx.getApplication().evaluateExpressionGet(ctx,  
                     "#{" + name + "}", Object.class);  
      }  
      @Override  
      public Object getAsObject(FacesContext context, UIComponent component,  
                String value) {  
           if (value == null) {  
                return null;  
           }  
           SelectController selectMB = (SelectController) getManagedBean("selectController");  
           // Convert the unique String representation to the actual object.  
           return selectMB.getRegionService().findById(Long.valueOf(value));  
      }  
      @Override  
      public String getAsString(FacesContext context, UIComponent component,  
                Object value) {  
           // Convert the object to its unique String representation.  
           if (value instanceof RegionsEntity) {  
                return String.valueOf(((RegionsEntity) value).getRegionId());  
           } else if (value instanceof String) {  
                return ((String) value);  
           } else if (value == null) {  
                return "";  
           }  
           return null;  
      }  
 }  
The web pages:

We have a single page called select.xhtml page

select.xhtml page :
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
 <html xmlns="http://www.w3.org/1999/xhtml"  
      xmlns:h="http://java.sun.com/jsf/html"  
      xmlns:ui="http://java.sun.com/jsf/facelets"  
      xmlns:p="http://primefaces.org/ui"  
      xmlns:f="http://java.sun.com/jsf/core">  
 <h:head>  
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
      <title>Cascading Menu</title>  
      <h:outputStylesheet name="css/main.css" />  
 </h:head>  
 <h:body>  
      <f:view contentType="text/html">  
           <div class="header">  
                <h:graphicImage library="images" name="JSFSpotlight.png" />  
           </div>  
           <div class="content">  
                <p:panel header="Cascading Menu" style="margin:0 auto; width:70%"  
                     styleClass="shows-panel">  
                     <h:form>  
                          <h:panelGrid columns="2" cellspacing="10">  
                               <p:outputLabel value="Select Region" for="regions" />  
                               <p:selectOneMenu value="#{selectController.region}" id="regions"  
                                    style="width:200px">  
                                    <f:selectItem itemLabel="Select Region" itemValue="#{null}" />  
                                    <f:selectItems value="#{selectController.regions}" var="region"  
                                         itemLabel="#{region.regionName}" itemValue="#{region}" />  
                                    <p:ajax update="countries" process="@this" />  
                               </p:selectOneMenu>  
                               <p:outputLabel value="Select Country" for="countries" />  
                               <p:selectOneMenu value="#{selectController.country}"  
                                    disabled="#{selectController.region eq null}" id="countries"  
                                    style="width:200px">  
                                    <f:selectItem itemLabel="Select Country" itemValue="#{null}" />  
                                    <f:selectItems value="#{selectController.countriesForRegion}"  
                                         var="country" itemLabel="#{country.countryName}"  
                                         itemValue="#{country}" />  
                               </p:selectOneMenu>  
                          </h:panelGrid>  
                     </h:form>  
                </p:panel>  
</div>  
</f:view>
</h:body> </html>
So simple like that we have two primfaces select one menu the second one is refereshed using the <p:ajax> to be updated with a list of countries for the selected region. Note the disabled property of the Countries select Menu when the region is null (No region is selected).We also have a Cascading Style Sheets  file in the Resources folder which we refer in our page by using <h:outputStylesheet> tag.

Running the Application:

Now it is time to deploy our Application in GlassFish Application server (the server we use to deploy our Applications as we described in our tools).To Deploy and access the Application do the following:

1- Create jdbc/hrConn datasource in your glassfish server as we said in our first post preparing your Environment from here
2- Download the application from here,open it in your eclipse by clicking file->import->select existing projects into workspace,and deploy it in your glassfish by selecting the Ear file and Run as->Run on server ->select your glassfish server and then ok (for more information about opening and deploying our Apps in Eclipse read the last section of Master_detail post from here
3-To access our Application use the URLhttp://localhost:8080/DependentselectJSF/ 
(note that 8080 is the port of my Glassfish Application server so you may use a different port depending on the configuration of your glassfish) 

You will see the two primfaces select one Menu ,if you select any region from the first One,The other will be Enabled to display the Countries for the selected region



Select a different region and the second selectOneMenu will be updated to display the list of countries for the new region 




If you have any questions, please post it below.

See you soon and until then Happy JSF Developing.