Full Web Application Tomcat JSF Primefaces JPA Hibernate – Part 1
We created this post that will show how to create a full web application using the following tools: Tomcat7, JSF2 (Facelets and Libraries) with Primefaces (with AutoComplete), JPA / Hibernate (with a relationship NxN), Login with Filter.If you want to see a full web application with JSF + EJB + JPA + JBoss 7 click here.
To run the code of this post you will need the artifacts bellow (all jar file you will find in the last page of this post):
■Eclipse Indigo
■Tomcat7
■Mojarra
■Primefaces
■Hibernate
■Driver Postgres
You can use the any database you want, you will need the specific driver of the database and to edit the URL connection at the persistence.xml.
At the end of this post you will find the source code with all needed libraries to download.
What you will see today in this post:
■Entity classes of the model. A relationship NxN (@ManyToMany), NamedQueries with JoinFetch, Enum as attribute.
■Generic DAO, application transaction methods, generic methods to populate query parameters.
■Façades an transactions, using the method findReferenceOnly, care using the entityManager.merge() method.
■Filters.
■ManagedBeans. How to inject a ManagedBean inside another ManagedBean, observations about @ViewScoped.
■JSFMessageUtil.
■Configurations file: log4j.properties, messages.properties.
■xhtml pages, Facelets.
■Primefaces AutoComplete, JSF Converter with “forClass”.
■Easiest way to use CSS/javascript/images with JSF.
■“web.xml” configurations.
■Increasing the security of your application.
■Running the application.
The application that you will find in here will have a Dog and Person CRUD (Create, Read, Update, and Delete); only the ADMIN will have access to the Dog CRUD. Before you run this application you should create a database named “JSFCrudDB”.
Model classes
The following classes are the model classes and they should be in the “com.model” package:
package com.model;
public enum Role {
ADMIN, USER;
}
package com.model;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = 'USERS')
@NamedQuery(name = 'User.findUserByEmail', query = 'select u from User u where u.email = :email')
public class User implements Serializable {
private static final long serialVersionUID = 1L;
public static final String FIND_BY_EMAIL = 'User.findUserByEmail';
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(unique = true)
private String email;
private String password;
private String name;
@Enumerated(EnumType.STRING)
private Role role;
// get and set
public boolean isAdmin() {
return Role.ADMIN.equals(role);
}
public boolean isUser() {
return Role.USER.equals(role);
}
@Override
public int hashCode() {
return getId();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof User) {
User user = (User) obj;
return user.getId() == id;
}
return false;
}
}
package com.model;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
@Entity
@NamedQuery(name = 'Person.findUserByIdWithDogs', query = 'select p from Person p left join fetch p.dogs where p.id = :personId')
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
public static final String FIND_USER_BY_ID_WITH_DOGS = 'Person.findUserByIdWithDogs';
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private int age;
private String name;
@ManyToMany
private List<Dog> dogs;
// get and set
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person person = (Person) obj;
return person.getId() == id;
}
return false;
}
}
package com.model;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
@Entity
public class Dog implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private int age;
private String name;
@ManyToMany(mappedBy='dogs')
private List<Person> persons;
// get and set
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Dog) {
Dog dog = (Dog) obj;
return dog.getId() == id;
}
return false;
}
@Override
public String toString() {
return name;
}
}
About the above code:
■The Person class has the namedQuery “Person.findUserByIdWithDogs”. This query will eagerly load the Dog list of the Person class. If we try to access the Dog list without a transaction and this query a LazyInitializationException will happen. Other solution to this situation would be the OpenSessionInView pattern; this pattern may generate the N+1 queries effect. If you want to know more about this exception click here.
■It is very easy to map an Enum with JPA/Hibernate. As you can see there is an option with the @Enumerated annotation that will set the database table field as String. If you want to read more about this annotation and see how to map an Enum as ORDINAL click here.
■Always override the equals/hashCode methods. These methods are often invoked by several frameworks. A lot of Primefaces “bugs” were solved by implementing these methods.
You will need to create the “persistence.xml” file inside the “src/META-INF” folder:
<?xml version='1.0' encoding='UTF-8'?>
<persistence version='2.0'
xmlns='http://java.sun.com/xml/ns/persistence' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xsi:schemaLocation='http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd'>
<persistence-unit name='JSFCrudPU' transaction-type='RESOURCE_LOCAL'>
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name='javax.persistence.jdbc.driver' value='org.postgresql.Driver' />
<property name='javax.persistence.jdbc.url' value='jdbc:postgresql://localhost:5432/JSFCrudDB' />
<property name='javax.persistence.jdbc.user' value='postgres' />
<property name='javax.persistence.jdbc.password' value='postgres' />
<property name='hibernate.dialect' value='org.hibernate.dialect.PostgreSQLDialect' />
<property name='hibernate.connection.shutdown' value='true' />
<property name='hibernate.hbm2ddl.auto' value='update' />
<property name='hibernate.show_sql' value='false' />
<property name='hibernate.format_sql' value='false'/>
</properties>
</persistence-unit>
</persistence>
About the above code:
■The “hibernate.hbm2ddl.auto” property with the value “update” indicates to the JPA to update, whenever necessary, the database according the model classes. It is not a good practice to leave this option as “update” or with any other value that may update the production/customer database. It is always good to use the “validate” option in the production environment and to create and execute the sql script manually.
■The hibernate.show_sql and the hibernate.format_sql properties are used to display the query in the console. In the file log4j.properties (page 08) there is an option that will allow displaying the query parameters values.
DAO
In the package “com.dao” you will need to create the following classes:
package com.dao;
import java.io.Serializable;
import java.util.*;
import java.util.Map.*;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaQuery;
abstract class GenericDAO<T> implements Serializable {
private static final long serialVersionUID = 1L;
private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory('JSFCrudPU');
private EntityManager em;
private Class<T> entityClass;
public void beginTransaction() {
em = emf.createEntityManager();
em.getTransaction().begin();
}
public void commit() {
em.getTransaction().commit();
}
public void rollback() {
em.getTransaction().rollback();
}
public void closeTransaction() {
em.close();
}
public void commitAndCloseTransaction() {
commit();
closeTransaction();
}
public void flush() {
em.flush();
}
public void joinTransaction() {
em = emf.createEntityManager();
em.joinTransaction();
}
public GenericDAO(Class<T> entityClass) {
this.entityClass = entityClass;
}
public void save(T entity) {
em.persist(entity);
}
public void delete(T entity) {
T entityToBeRemoved = em.merge(entity);
em.remove(entityToBeRemoved);
}
public T update(T entity) {
return em.merge(entity);
}
public T find(int entityID) {
return em.find(entityClass, entityID);
}
public T findReferenceOnly(int entityID) {
return em.getReference(entityClass, entityID);
}
// Using the unchecked because JPA does not have a
// em.getCriteriaBuilder().createQuery()<T> method
@SuppressWarnings({ 'unchecked', 'rawtypes' })
public List<T> findAll() {
CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return em.createQuery(cq).getResultList();
}
// Using the unchecked because JPA does not have a
// query.getSingleResult()<T> method
@SuppressWarnings('unchecked')
protected T findOneResult(String namedQuery, Map<String, Object> parameters) {
T result = null;
try {
Query query = em.createNamedQuery(namedQuery);
// Method that will populate parameters if they are passed not null and empty
if (parameters != null && !parameters.isEmpty()) {
populateQueryParameters(query, parameters);
}
result = (T) query.getSingleResult();
} catch (NoResultException e) {
System.out.println('No result found for named query: ' + namedQuery);
} catch (Exception e) {
System.out.println('Error while running query: ' + e.getMessage());
e.printStackTrace();
}
return result;
}
private void populateQueryParameters(Query query, Map<String, Object> parameters) {
for (Entry<String, Object> entry : parameters.entrySet()) {
query.setParameter(entry.getKey(), entry.getValue());
}
}
}
package com.dao;
import java.util.*;
import com.model.Person;
public class PersonDAO extends GenericDAO<Person> {
private static final long serialVersionUID = 1L;
public PersonDAO() {
super(Person.class);
}
public Person findPersonWithAllDogs(int personId) {
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put('personId', personId);
return super.findOneResult(Person.FIND_USER_BY_ID_WITH_DOGS, parameters);
}
}
package com.dao;
import java.util.*;
import com.model.User;
public class UserDAO extends GenericDAO<User> {
private static final long serialVersionUID = 1L;
public UserDAO() {
super(User.class);
}
public User findUserByEmail(String email){
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put('email', email);
return super.findOneResult(User.FIND_BY_EMAIL, parameters);
}
}
package com.dao;
import com.model.Dog;
public class DogDAO extends GenericDAO<Dog> {
private static final long serialVersionUID = 1L;
public DogDAO() {
super(Dog.class);
}
}
About the above code:
■The DAO classes has methods to allow the Façades to control the transaction. This pattern is being applied to avoid the OpenSessionInView pattern; the OpenSessionInView pattern may cause the N+1 effect of queries. With the transaction controlled by the developer it is easier to understand the transaction flow, but we have a more verbose code. If you want to know more about the OpenSessionInView and the N+1 effect you can click here.
■You will find a method named “populateQueryParameters” in the GenericDAO class. This method will populate dynamically all the parameters of a query if needed. The classes PersonDAO and UserDAO have a sample code of how to invoke this method.
Façades
In the package “com.facade” you will need to create the classes bellow:
package com.facade;
import com.dao.UserDAO;
import com.model.User;
public class UserFacade {
private UserDAO userDAO = new UserDAO();
public User isValidLogin(String email, String password) {
userDAO.beginTransaction();
User user = userDAO.findUserByEmail(email);
if (user == null || !user.getPassword().equals(password)) {
return null;
}
return user;
}
}
package com.facade;
import java.io.Serializable;
import java.util.List;
import com.dao.DogDAO;
import com.model.Dog;
public class DogFacade implements Serializable{
private static final long serialVersionUID = 1L;
private DogDAO dogDAO = new DogDAO();
public void createDog(Dog dog) {
dogDAO.beginTransaction();
dogDAO.save(dog);
dogDAO.commitAndCloseTransaction();
}
public void updateDog(Dog dog) {
dogDAO.beginTransaction();
Dog persistedDog = dogDAO.find(dog.getId());
persistedDog.setAge(dog.getAge());
persistedDog.setName(dog.getName());
dogDAO.update(persistedDog);
dogDAO.commitAndCloseTransaction();
}
public Dog findDog(int dogId) {
dogDAO.beginTransaction();
Dog dog = dogDAO.find(dogId);
dogDAO.closeTransaction();
return dog;
}
public List<Dog> listAll() {
dogDAO.beginTransaction();
List<Dog> result = dogDAO.findAll();
dogDAO.closeTransaction();
return result;
}
public void deleteDog(Dog dog) {
dogDAO.beginTransaction();
Dog persistedDog = dogDAO.findReferenceOnly(dog.getId());
dogDAO.delete(persistedDog);
dogDAO.commitAndCloseTransaction();
}
}
package com.facade;
import java.io.Serializable;
import java.util.List;
import com.dao.DogDAO;
import com.dao.PersonDAO;
import com.model.Dog;
import com.model.Person;
public class PersonFacade implements Serializable {
private static final long serialVersionUID = 1L;
private PersonDAO personDAO = new PersonDAO();
private DogDAO dogDAO = new DogDAO();
public void createPerson(Person person) {
personDAO.beginTransaction();
personDAO.save(person);
personDAO.commitAndCloseTransaction();
}
public void updatePerson(Person person) {
personDAO.beginTransaction();
Person persistedPerson = personDAO.find(person.getId());
persistedPerson.setName(person.getName());
persistedPerson.setAge(person.getAge());
personDAO.commitAndCloseTransaction();
}
public void deletePerson(Person person){
personDAO.beginTransaction();
Person persistedPersonWithIdOnly = personDAO.findReferenceOnly(person.getId());
personDAO.delete(persistedPersonWithIdOnly);
personDAO.commitAndCloseTransaction();
}
public Person findPerson(int personId) {
personDAO.beginTransaction();
Person person = personDAO.find(personId);
personDAO.closeTransaction();
return person;
}
public List<Person> listAll() {
personDAO.beginTransaction();
List<Person> result = personDAO.findAll();
personDAO.closeTransaction();
return result;
}
public Person findPersonWithAllDogs(int personId) {
personDAO.beginTransaction();
Person person = personDAO.findPersonWithAllDogs(personId);
personDAO.closeTransaction();
return person;
}
public void addDogToPerson(int dogId, int personId) {
personDAO.beginTransaction();
dogDAO.joinTransaction();
Dog dog = dogDAO.find(dogId);
Person person = personDAO.find(personId);
person.getDogs().add(dog);
dog.getPerson().add(person);
personDAO.commitAndCloseTransaction();
}
public void removeDogFromPerson(int dogId, int personId) {
personDAO.beginTransaction();
dogDAO.joinTransaction();
Dog dog = dogDAO.find(dogId);
Person person = personDAO.find(personId);
person.getDogs().remove(dog);
dog.getPerson().remove(person);
personDAO.commitAndCloseTransaction();
}
}
About the above code:
■Every transaction is controlled by the developer. The developer has the responsibility of never forgetting an opened transaction.
■The class PersonFacade uses a method named “findReferenceOnly“. This method has a better performance than the “find” method with operations that involves only operations where only the entity id is needed. A query will be triggered in the database to get only the ID of that entity. If any other attribute is invoked a new query will be fired in the database to bring that information. According the book “Pro JPA 2: Mastering the Java™ Persistence API” this method could also be used to edit items in a list. e.g. In the method Person.addDoAgToPerson the “find” method could be replace for the “findReferenceOnly“
■Both methods ” Person.addDogToPerson” and “ Person.removeDogFromPerson” invoke the method “joinTransaction” in the DogDAO class. The EntityManager in the DogDAO will use the same transaction started at the PersonDAO.
Cares about the EntityManager.merge()
It would be easier just invoke the method like this: “entityManager.merge(dog)” instead of querying for the persisted object and updating the data. If you invoke the merge method in a file received from the view you could damage the integrity of your database data. The developer must take care with this merge method; if a lazy relationship is not loaded and this relationship is null in the merged entity the JPA/Hibernate will erase this relationship in the database. e.g. If the developer invokes the method entityManager.merge(dog) and dog.getPersons() == null the JPA/Hibernate will delete all relationship between them in the database.
The best practice is to find the object in the database than edit it.
In this project the object were loaded from the database and it is in the detached state; for this specific situation where you find the object detached and the lazy relationship is not null the JPA will not erase the database data relationship. If the object were manually created and the relationship has the value List<Person> == null the JPA would have deleted the database data.
Filter
In the “com.filter” package you will need to create the classes bellow:
package com.filter;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
public class AbstractFilter {
public AbstractFilter() {
super();
}
protected void doLogin(ServletRequest request, ServletResponse response, HttpServletRequest req) throws ServletException, IOException {
RequestDispatcher rd = req.getRequestDispatcher('/pages/public/login.xhtml');
rd.forward(request, response);
}
protected void accessDenied(ServletRequest request, ServletResponse response, HttpServletRequest req) throws ServletException, IOException {
RequestDispatcher rd = req.getRequestDispatcher('/pages/public/accessDenied.xhtml');
rd.forward(request, response);
}
}
package com.filter;
import java.io.IOException;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.model.User;
/**
* Servlet Filter implementation class UserCheckFilter
*/
public class LoginCheckFilter extends AbstractFilter implements Filter {
private static List<String> allowedURIs;
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
if(allowedURIs == null){
allowedURIs = new ArrayList<String>();
allowedURIs.add(fConfig.getInitParameter('loginActionURI'));
allowedURIs.add('/JSFCrudApp/javax.faces.resource/main.css.xhtml');
allowedURIs.add('/JSFCrudApp/javax.faces.resource/theme.css.xhtml');
allowedURIs.add('/JSFCrudApp/javax.faces.resource/primefaces.js.xhtml');
allowedURIs.add('/JSFCrudApp/javax.faces.resource/primefaces.css.xhtml');
allowedURIs.add('/JSFCrudApp/javax.faces.resource/jquery/jquery.js.xhtml');
allowedURIs.add('/JSFCrudApp/javax.faces.resource/messages/messages.png.xhtml');
allowedURIs.add('/JSFCrudApp/javax.faces.resource/images/ui-icons_2e83ff_256x240.png.xhtml');
allowedURIs.add('/JSFCrudApp/javax.faces.resource/images/ui-icons_38667f_256x240.png.xhtml');
}
}
/**
* @see Filter#destroy()
*/
public void destroy() {
}
/**
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession();
if (session.isNew()) {
doLogin(request, response, req);
return;
}
User user = (User) session.getAttribute('user');
if (user == null && !allowedURIs.contains(req.getRequestURI())) {
System.out.println(req.getRequestURI());
doLogin(request, response, req);
return;
}
chain.doFilter(request, response);
}
}
package com.filter;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import com.model.User;
public class AdminPagesFilter extends AbstractFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
User user = (User) req.getSession(true).getAttribute('user');
if (!user.isAdmin()) {
accessDenied(request, response, req);
return;
}
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
package com.filter;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import com.model.User;
public class DefaultUserPagesFilter extends AbstractFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
User user = (User) req.getSession(true).getAttribute('user');
if(!user.isUser() && !user.isAdmin()){
accessDenied(request, response, req);
return;
}
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
About the above code:
■The classes that implements the Filter interface are responsible for the Authorization and Authentication of the user. At the page 13 will have more information about why do we need to check if the user is logged in and its role at every request.
■The method “fConfig.getInitParameter(“loginActionURI”);” that is invoked inside the class LoginCheckFilter will load a property from the “web.xml” file. The “loginActionURI” property has a value to indicate the login page to the Filter; the Filter will use this property to allow every user (logged or not) to access that specific page (you will see the property value in the page 12). This property value is added to a list of URLs that are allowed to be called without the need of a logged user. It is a good practice to have this list of allowed URLs in the “web.xml” file, but for commodity only one were added in the “web.xml” in this post to display how to do it.
■Each filter watches over a directory that an user may access. Other approach would be to have a file with all allowed URLs and its allowed roles, e.g. “JSFCrud/protected/manager;admin-manager”.
from:
http://www.javacodegeeks.com/2012/07/full-web-application-tomcat-jsf.html