Introduction
This is a simple CRUD example with JSF.
Background
JSF is an MVC framework, but it is very different from the Spring MVC and ASP.NET MVC. It actually has a strong ASP.NET Web Form "POSTBACK" flavor. This example is based on a stack overflow example.
The attached is a Maven project. I have tested it with Maven 3.2.1, Java 1.8.0_45, Tomcat 7, and Eclipse Java EE IDE for Web Developers Luna Service Release 2. It actually has two examples.
- The "simplecrud.xhtml" is the simple CRUD example;
- The "freshsafecrud.xhtml" is to address the problems due to the JSF "POSTBACK" nature.
The "welcome.xhtml" is the index page of the two examples.
If you are not familiar with how to import Maven projects into Eclipse, you can check out this link. When I tried to import the project, I noticed that Eclipse showed me some error message telling me some validation errors. You can simply ignore these messages and delete the markers from Eclipse.
The pom.xml and the web.xml
The "pom.xml" declared the dependencies needed to create a JSF application.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.song.example</groupId>
<artifactId>JSF-Example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<tomcat.version>7.0.55</tomcat.version>
<jsf.version>2.1.7</jsf.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>${jsf.version}</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>${jsf.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>true</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
The "web.xml" of the example application is the following.
<?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"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>JSF Example</display-name>
<welcome-file-list>
<welcome-file>welcome.xhtml</welcome-file>
</welcome-file-list>
<filter>
<filter-name>nocachefilter</filter-name>
<filter-class>
com.song.web.filter.NocacheFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>nocachefilter</filter-name>
<url-pattern>
- From a servlet container point of view, all the JSF pages will be mapped to the single servlet implemented by the "javax.faces.webapp.FacesServlet" class;
- In this example application, we mapped all the requests to all the "xhtml" pages to the "javax.faces.webapp.FacesServlet".
The Simple CRUD Example
The manage bean of the simple example is implemented in the "SimpleCrudBean" class.
package com.song.jsf.example;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class SimpleCrudBean implements Serializable {
private static final long serialVersionUID = 1L;
private List<Student> list;
private Student item = new Student();
private Student beforeEditItem = null;
private boolean edit;
@PostConstruct
public void init() {
list = new ArrayList<Student>();
}
public void add() {
item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
list.add(item);
item = new Student();
}
public void resetAdd() {
item = new Student();
}
public void edit(Student item) {
beforeEditItem = item.clone();
this.item = item;
edit = true;
}
public void cancelEdit() {
this.item.restore(beforeEditItem);
this.item = new Student();
edit = false;
}
public void saveEdit() {
this.item = new Student();
edit = false;
}
public void delete(Student item) throws IOException {
list.remove(item);
}
public List<Student> getList() {
return list;
}
public Student getItem() {
return this.item;
}
public boolean isEdit() {
return this.edit;
}
}
- The "add", "saveEdit", and "delete" methods need to synchronize the data with a persistent storage like a database;
- In this example, the database operation is skipped for simplicity.
The "Student" class is implemented in the "Student.java" file.
package com.song.jsf.example;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
public Student() {}
public Student(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public Student clone() {
return new Student(id, name);
}
public void restore(Student student) {
this.id = student.getId();
this.name = student.getName();
}
}
The "SimpleCrudBean" class is bound to the "simplecrud.xhtml" file to make it fully functional.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<head>
<title>Simple CRUD</title>
</head>
<body>
<h3>List students</h3>
<h:form rendered="#{not empty simpleCrudBean.list}">
<h:dataTable value="#{simpleCrudBean.list}" var="item">
<h:column><f:facet name="header">ID</f:facet>#{item.id}</h:column>
<h:column><f:facet name="header">Name</f:facet>#{item.name}</h:column>
<h:column>
<h:commandButton value="edit" action="#{simpleCrudBean.edit(item)}" />
</h:column>
<h:column>
<h:commandButton value="delete" action="#{simpleCrudBean.delete(item)}" />
</h:column>
</h:dataTable>
</h:form>
<h:panelGroup rendered="#{empty simpleCrudBean.list}">
<p>No students! Please add students.</p>
</h:panelGroup>
<h:panelGroup rendered="#{!simpleCrudBean.edit}">
<h3>Add student</h3>
<h:form>
<p>Name: <h:inputText value="#{simpleCrudBean.item.name}" /></p>
<p>
<h:commandButton value="add" action="#{simpleCrudBean.add}" />
<h:commandButton value="reset" action="#{simpleCrudBean.resetAdd}" />
</p>
</h:form>
</h:panelGroup>
<h:panelGroup rendered="#{simpleCrudBean.edit}">
<h3>Edit student #{simpleCrudBean.item.id}</h3>
<h:form>
<p>Name: <h:inputText value="#{simpleCrudBean.item.name}" /></p>
<p>
<h:commandButton value="save" action="#{simpleCrudBean.saveEdit}" />
<h:commandButton value="cancel" action="#{simpleCrudBean.cancelEdit}" />
</p>
</h:form>
</h:panelGroup>
<p>
<a href="#{appUrlStore.baseUrl}">Go back to index</a>
</p>
</body>
</html>
If you now load the "simplecrud.xhtml" page, you can find that the CRUD operations all work fine. But if you click the refresh button on the browser after adding a student, you will see this ugly popup message.
If you click the "Continue" button and proceed with the refresh, you will notice that the student just added gets added again. This is definitely not a good behavior. This kind of behavior is due to the JSF's "POSTBACK" nature. In the next example, we will try to resolve this issue.
The Refresh-safe CRUD Example
In order to address the problem in the simple CRUD example, I created the "CommonUtils" class.
package com.song.jsf.example.util;
import java.io.IOException;
import java.io.Serializable;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
@ManagedBean(name="commonUtils")
@ApplicationScoped
public class CommonUtils implements Serializable {
private static final long serialVersionUID = 1L;
public void redirectWithGet() {
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
HttpServletRequest request = (HttpServletRequest)externalContext.getRequest();
StringBuffer requestURL = request.getRequestURL();
String queryString = request.getQueryString();
if (queryString != null) {
requestURL.append('?').append(queryString).toString();
}
String url = requestURL.toString();
try {
externalContext.redirect(requestURL.toString());
} catch (IOException e) {
throw new RuntimeException("Unable to rerirect to " + url);
}
facesContext.responseComplete();
}
}
The "redirectWithGet" method is to simply send a redirect request to the browser to refresh the browser with a GET request. The "CommonUtils" object is injected into the "FreshsafeCrudBean" class, and the "redirectWithGet" method is called whenever a "POSTBACK" is performed.
package com.song.jsf.example;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import com.song.jsf.example.util.CommonUtils;
@ManagedBean
@SessionScoped
public class FreshsafeCrudBean implements Serializable {
private static final long serialVersionUID = 1L;
private List<Student> list;
private Student item = new Student();
private Student beforeEditItem = null;
private boolean edit;
@ManagedProperty(value="#{commonUtils}")
private CommonUtils util;
public void setUtil(CommonUtils util) {
this.util = util;
}
@PostConstruct
public void init() {
list = new ArrayList<Student>();
}
public void add() {
item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
list.add(item);
item = new Student();
util.redirectWithGet();
}
public void resetAdd() {
item = new Student();
util.redirectWithGet();
}
public void edit(Student item) {
beforeEditItem = item.clone();
this.item = item;
edit = true;
util.redirectWithGet();
}
public void cancelEdit() {
this.item.restore(beforeEditItem);
this.item = new Student();
edit = false;
util.redirectWithGet();
}
public void saveEdit() {
this.item = new Student();
edit = false;
util.redirectWithGet();
}
public void delete(Student item) throws IOException {
list.remove(item);
util.redirectWithGet();
}
public List<Student> getList() {
return list;
}
public Student getItem() {
return this.item;
}
public boolean isEdit() {
return this.edit;
}
}
The "freshsafecrud.xhtml" is exactly the same as the "simplecrud.xhtml" file, except that it is bound to the "FreshsafeCrudBean" class.
If you now load the "simplecrud.xhtml", you should feel free to refresh you page without seeing the side effects of the "POSTBACK". Of course the cost is a round trip to the web server.
Points of Interest
- This is a simple CRUD example with JSF;
- A true CRUD operation normally involves the database operations, but this example skipped them for simplicity;
- JSF has a strong ASP.NET Web Form flavor, it heavily relies on "POSTBACK" for its functionalities;
- An additional example is also provided to address the side effects of the "POSTBACK" with the cost of an additional server round-trip.
History
First Revision - 9/16/2015