Wednesday, December 24, 2008

JSF with GET Request Parameters

Here's a way to display a data from a database using straight JSF with get parameters. This approach allows bookmarking, multiple window support, and use of the back button, without storing anything in the session. All parameters for every request appear in the browser's url window. Missing parameters are set to default values in the request bean.

This is useful in the common scenario of querying a list of items displaying the results, and then displaying the details of a particular item.

These are the key concepts:

  • A request scope managed bean with one managed property for each "input" parameter.
  • Use <h:outputlink> with embedded <f:param> to render links.
  • For form input use hidden input fields to save non form "input" parameters.
  • Any form action method constructs and calls a redirect complete with query string, and then calls response complete on the faces context.
  • Page content is retrieved with a PostConstruct method on the managed bean. If you are not using Java 5 EE, the PostConstruct method can be called just once by adding some logic to determine if all setters have been called. (See commented variables and "if statement" in postConstruct method in bean code for one way to do this).
  • Phase listener records phase. Bean's PostConstruct method then checks the phase and only executes code to retrieve page content if the bean was created in the Render Response phase.

Below is an example.

(This is a from a hibernate application that uses a session bean to hold a hibernate session. The page is rendered with Richfaces. Neither of these implementation details are important.)


faces-config.xml

<?xml version="1.0" encoding="windows-1252"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
"http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config xmlns="http://java.sun.com/JSF/Configuration">

<managed-bean>
<managed-bean-name>sessionBean</managed-bean-name>
<managed-bean-class>crgio.pubs.SessionBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>authorsBean</managed-bean-name>
<managed-bean-class>crgio.pubs.AuthorsBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>sessionBean</property-name>
<value>#{sessionBean}</value>
</managed-property>
<managed-property>
<property-name>pubId</property-name>
<value>#{param.pubId}</value>
</managed-property>
<managed-property>
<property-name>start</property-name>
<value>#{param.start}</value>
</managed-property>
<managed-property>
<property-name>count</property-name>
<value>#{param.count}</value>
</managed-property>
<managed-property>
<property-name>title</property-name>
<value>#{param.title}</value>
</managed-property>
<managed-property>
<property-name>author</property-name>
<value>#{param.author}</value>
</managed-property>

</managed-bean>
<lifecycle>
<phase-listener>crgio.pubs.RecordPhase</phase-listener>
</lifecycle>
</faces-config>


index.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://richfaces.org/a4j" prefix="a4j"%>
<%@ taglib uri="http://richfaces.org/rich" prefix="rich"%>
<f:view>
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=windows-1252"/>
<title>Publication Data</title>
</head>
<body>
<rich:panel header="Publication Data">
<h:form>
<h:inputHidden value="#{authorsBean.pubId}"/>
<h:inputHidden value="#{authorsBean.start}"/>
<h:inputHidden value="#{authorsBean.count}"/>
<h:panelGrid columns="3">
<h:panelGroup>
<h:outputText value="Title:"/>
<h:inputText value="#{authorsBean.title}"/>
</h:panelGroup>
<h:panelGroup>
<h:outputText value="Author:"/>
<h:inputText value="#{authorsBean.author}"/>
</h:panelGroup>
<h:commandButton action="#{authorsBean.doSearch}" value="Go"/>
</h:panelGrid>
</h:form>


<rich:panel header="Publications">
<rich:dataTable value="#{authorsBean.publications}" var="cur">


<h:column>
<f:facet name="header">
<h:outputText value="Id"/>
</f:facet>
<h:outputLink>
<h:outputText value="#{cur.publicationId}"/>
<f:param value="#{cur.publicationId}" name="pubId"/>
<f:param value="#{authorsBean.start}" name="start"/>
<f:param value="#{authorsBean.count}" name="count"/>
<f:param value="#{authorsBean.title}" name="title"/>
<f:param value="#{authorsBean.author}" name="author"/>
</h:outputLink>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Title"/>
</f:facet>
<h:outputText value="#{cur.publicationTitle}"/>
</h:column>

</rich:dataTable>

<h:outputLink>
<h:outputText value="Previous"/>
<f:param value="#{authorsBean.pubId}" name="pubId"/>
<f:param value="#{authorsBean.start - authorsBean.count}" name="start"/>
<f:param value="#{authorsBean.count}" name="count"/>
<f:param value="#{authorsBean.title}" name="title"/>
<f:param value="#{authorsBean.author}" name="author"/>
</h:outputLink>
<h:outputText value=" "/>
<h:outputLink>
<h:outputText value="Next"/>
<f:param value="#{authorsBean.pubId}" name="pubId"/>
<f:param value="#{authorsBean.start + authorsBean.count}" name="start"/>
<f:param value="#{authorsBean.count}" name="count"/>
<f:param value="#{authorsBean.title}" name="title"/>
<f:param value="#{authorsBean.author}" name="author"/>
</h:outputLink>
</rich:panel>

<rich:panel header="Authors for Publication Id #{authorsBean.pubId}">

<rich:dataTable value="#{authorsBean.authors}" var="cur">

<h:column>
<f:facet name="header">
<h:outputText value="Last Name"/>
</f:facet>
<h:outputText value="#{cur.lastName}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="First Name"/>
</f:facet>
<h:outputText value="#{cur.firstName}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Middle Name"/>
</f:facet>
<h:outputText value="#{cur.middleName}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Suffix"/>
</f:facet>
<h:outputText value="#{cur.suffix}"/>
</h:column>
</rich:dataTable>
</rich:panel>
</rich:panel>
</body>
</html>
</f:view>



AuthorsBean.java
package crgio.pubs;

import java.math.BigDecimal;

import java.util.Iterator;
import java.util.List;

import java.util.Vector;

import org.hibernate.Query;
import org.hibernate.Session;

import javax.annotation.PostConstruct;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;

public class AuthorsBean {

private SessionBean sessionBean;
private String pubId;
private String start;
private String count;
private String title;
private String author;

//private boolean pubIdSet;
// private boolean startSet;
// private boolean countSet;

private List authors = new Vector();
private List publications = new Vector();

public AuthorsBean() {
}

@PostConstruct
private void postConstruct() {
/*
if(pubIdSet && startSet && countSet){
System.out.println("in postConstruct " + pubId + " " + start+ " " + count);
if(start == null || start.length()==0) {start = "0";}
if(count == null || count.length()==0) {count = "10";}

*/
System.out.println("++++++++++++++++++++++++++++++++++PostConstruct+++++++++++++++++++++++++++++++++++++++");
System.out.println( toString());

FacesContext facesContext = FacesContext.getCurrentInstance();
PhaseId phaseId = (PhaseId)facesContext.getExternalContext().getRequestMap().get("myPhaseId");

if(phaseId.equals(PhaseId.RENDER_RESPONSE)){

validateParameters();
loadPublications();
loadAuthors();
}

// }


}

private void validateParameters(){
try{Integer.parseInt(pubId);}catch(Exception e){pubId="-1";}
try{Integer.parseInt(start);}catch(Exception e){start="0";}
try{Integer.parseInt(count);}catch(Exception e){count="10";}
if(title==null){title="";}
if(author==null){author="";}


}



public String toString(){
return "pubId " + pubId + " start " + start + " count " + count + " title " + title + " author " + author;
}

public String doSearch(){

System.out.println(toString());
//Build the redirect string with hidden input and regular input from the form.
//Page content retrievel will be done in the Render Response phase when another
//request bean is constructed for the redirect.
//Remember that only phase 1, restore view, and phase 6, Render Response are done by
//the JSF lifecycle on a redirect.
try{
FacesContext.getCurrentInstance().getExternalContext().redirect("index.jsp?pubId=" + pubId + "&count=" + count + "&start=" + start + "&title=" + title + "&author=" + author);
}catch (Exception e){}
FacesContext.getCurrentInstance().responseComplete();


return null;

}

private void loadPublications() {
try {
Session session = sessionBean.getHibernateSession();
Query query = session.createQuery("from crgio.pubs.Publications order by publicationId");
if(title.length()>0 || author.length()>0){
query = session.createQuery("from crgio.pubs.Publications where publicationTitle like '%"+title+"%' and publicationAuthor like '%"+author+"%'order by publicationId");
//query.setString("title", title);
//query.setString("author", author);
}
query.setFirstResult(Integer.parseInt(start));
query.setMaxResults(Integer.parseInt(count));
publications = query.list();
} catch (Exception e) {
System.out.println("loadPublications threw " + e);
e.printStackTrace();
}
}

private void loadAuthors() {
try {
Session session = sessionBean.getHibernateSession();
Publications pub =
(Publications) session.get("crgio.pubs.Publications",
new BigDecimal(pubId));
authors = pub.getPublicationAuthors();
Iterator iter = authors.iterator();
System.out.println("authors size " + authors.size());
while (iter.hasNext()) {
PublicationAuthors cur = (PublicationAuthors) iter.next();
System.out.println(cur.getLastName());
}

} catch (Exception e) {
System.out.println("loadAuthors threw " + e + " pubId = " + pubId);

}
}

public void setSessionBean(SessionBean sessionBean) {
this.sessionBean = sessionBean;
}

public SessionBean getSessionBean() {
return sessionBean;
}

public void setPubId(String pubId) {
this.pubId = pubId;
//pubIdSet = true;;
//postConstruct();
}

public String getPubId() {
return pubId;
}

public void setStart(String start) {
this.start = start;
// startSet = true;
// postConstruct();
}

public String getStart() {
return start;
}

public void setCount(String count) {
this.count = count;
// countSet = true;
// postConstruct();
}

public String getCount() {
return count;
}

public List getPublications() {
return publications;
}

public List getAuthors() {
return authors;
}

/**
* @return the title
*/
public String getTitle() {
return title;
}

/**
* @param title the title to set
*/
public void setTitle(String title) {
this.title = title;
}

/**
* @return the author
*/
public String getAuthor() {
return author;
}

/**
* @param author the author to set
*/
public void setAuthor(String author) {
this.author = author;
}
}



RecordPhase.java

package crgio.pubs;


import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;


public class RecordPhase implements PhaseListener {

public RecordPhase() {
}

public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}

public void beforePhase(PhaseEvent event) {
FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put("myPhaseId", event.getPhaseId());
System.out.println("*** Before Phase " + event.getPhaseId());
}

public void afterPhase(PhaseEvent event) {

System.out.println("*** After Phase " + FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get("myPhaseId"));

}
}