Java Server Faces a Custom Converter
Problematika vytváření vlastních konverterů v Java Server Faces je nedostatečně zdokumentovaná a začátečníka pronikajícího do této skvělé technologie může stát zbytečně mnoho času.
V J2EE tutoriálu je ukázka jednoduchého konverteru pro úpravu čísel kreditních karet, avšak jedná se o převod String->String. V některých případech bychom však potřebovali převést String na náš vlastní objekt.
Proto se pojďme podívat, jak takový konverter vytvoříme. Předpokládejme, že chceme vytvořit select menu s výběrem našich objektů a tyto postoupit do backing beanu:

Nejdříve vytvoříme objekt Postava, který chceme nabízet k výběru v selectOneMenu. Důležité je překrytí metody equals() - tak, abychom dokázali porovnat objekt Postava podle parametrů (i když se bude jednat o 2 různé instance). Pokud equals() nepřekryjeme, nebude konverter fungovat, protože seznam objektů nabízených v selectOneMenu nebude stejný jako objekt, který v konverteru předáme podle předaného String argument (viz. konverter):
Postava.java (objekt, který chceme konvertovat):
/*
* Vytvoreno 23.11.2004
*/
package cz.hradil.blog;
/**
* Trida pro vytvareni objektu Postava. Kazda postava ma sve ID a jmeno.
*
* @author Jirka Hradil
*/
public class Postava {
private Short idcko;
private String jmeno;
/**
* Vytvori novou postavu.
*
* @param noveIdcko - ID postavy
* @param noveJmeno - jeji jmeno
*/
public Postava(Short noveIdcko, String noveJmeno) {
this.idcko = noveIdcko;
this.jmeno = noveJmeno;
}
/**
* @return Vrati ID postavy.
*/
public Short getIdcko() {
return idcko;
}
/**
* @return Vrati jmeno postavy.
*/
public String getJmeno() {
return jmeno;
}
/**
* @param idcko
* Nastavi ID postavy.
*/
public void setIdcko(Short idcko) {
this.idcko = idcko;
}
/**
* @param jmeno
* Nastavi jmeno postavy.
*/
public void setJmeno(String jmeno) {
this.jmeno = jmeno;
}
/*
* 2 postavy jsou si rovne, pokud maji stejne ID a jmeno.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Postava)) {
return false;
}
Postava postava = (Postava) obj;
if (postava.getIdcko() == null || postava.getJmeno() == null) {
return false;
}
return this.getIdcko().equals(postava.getIdcko()) &&
this.getJmeno().equals(postava.getJmeno());
}
/*
* Hashovaci kod pocitame jako: int result=17;
* result=37*result+idcko.hashCode(); result=37*result+jmeno.hashCode();
* Duvod: rovnomerne rozlozeni objektu v hashovacich sektorech.
*/
public int hashCode() {
int result = 17;
result = 37 * result + idcko.hashCode();
result = 37 * result + jmeno.hashCode();
return result;
}
/*
* Vraci retezec ID a jmena osoby.
*
* @see java.lang.Object#toString()
*/
public String toString() {
return idcko.toString() + ":" + jmeno;
}
}
Pak vytvoříme backing bean, který obsahuje get/set pro uložení zvoleného objektu Postava a rovněž nabízí List objektů SelectItem, který budeme načítat z JSP stránky (viz. view). Pro zvědavce-ano, míchám tady dohromady controller a model, v praxi bychom zřejmě načítali seznam jinak:
VyberPostavyBean.java (backing bean):
/*
* Vytvoreno 22.11.2004
*/
package cz.hradil.blog;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.faces.model.SelectItem;
/**
* Controller pro vyber Postavy.
*
* @author Jirka Hradil
*
* TODO To change the template for this generated type comment go to Window -
* Preferences - Java - Code Style - Code Templates
*/
public class VyberPostavyBean {
private Postava zvolenaPostava;
/**
* @return Vraci zvolenou postavu.
*/
public Postava getZvolenaPostava() {
return zvolenaPostava;
}
/**
* @param zvolenaPostava
* Nastavi postavu.
*/
public void setZvolenaPostava(Postava zvolenaPostava) {
this.zvolenaPostava = zvolenaPostava;
}
/**
* Metoda se zavola po stisknuti tlacitka pro ulozeni.
*/
public void ulozit() {
System.out.println("Jsem v metode ulozit(). Predany objekt: " + zvolenaPostava);
//tady si uz s objektem udelame co chceme
}
/**
* @return Vraci seznam 3 vzorovych postav.
*/
public List getSeznamPostav() {
List seznamPostav = new ArrayList();
Postava bugs = new Postava(new Short("1"), "Bugs Bunny");
Postava runner = new Postava(new Short("2"), "Road Runner");
Postava coyote = new Postava(new Short("3"), "Wile E Coyote");
seznamPostav.add(bugs);
seznamPostav.add(runner);
seznamPostav.add(coyote);
return seznamPostav;
}
/**
* @return Vraci seznam postav jako List polozek SelectItem.
*/
public List getSeznamPostavMenu() {
List seznamPostavMenu = new ArrayList();
for (Iterator iter = this.getSeznamPostav().iterator(); iter.hasNext();) {
Postava el = (Postava) iter.next();
//Zde si vsimneme, ze do SelectItem musime ukladat cely objekt Postava, ktery pak chceme ziskat zpatky
seznamPostavMenu.add(new SelectItem(el, el.getJmeno())); //2. argument je text, ktery chceme zobrazit
}
return seznamPostavMenu;
}
}
Konečně se dostáváme ke konverteru. Tady je důležité, aby objekt, který chceme vrátit (převod String->Object) byl stejný jako objekt, který jsme nabízeli v selectOneMenu. A protože se nedokážeme dostat přímo na zvolený objekt v selectOneMenu, musíme na základě předaného argumentu String prohledat seznam původních objektů, vyhledat shodný objekt a vrátit jej. JSF kontrolují, abychom vrátili objekt, který byl na výběr v selectOneMenu. Proto jsme také překrývali equals-abychom zajistili, že původní objekt v selectOneMenu a námi vrácený objekt jsou stejné. Pokud bychom vrátili jiný objekt Postava, který se v selectOneMenu vůbec nenabízel, pak bychom viděli Exception :).
VyberPostavyConverter.java (konverter):
/*
* Vytvoreno 22.11.2004
*
*/
package cz.hradil.blog;
import java.util.Iterator;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
/**
* Convertor String->Postava a zpet.
*
* @author Jirka Hradil
*/
public class VyberPostavyConverter implements Converter {
/*
* Z predaneho String vytvori objekt Postava.
*
* @see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext,
* javax.faces.component.UIComponent, java.lang.String)
*/
public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) {
Postava zvolenaPostava = null;
for (Iterator iter = new VyberPostavyBean().getSeznamPostav().iterator(); iter.hasNext();) {
Postava el = (Postava) iter.next();
if (el.getIdcko().toString().equals(arg2)) {
zvolenaPostava = el;
break;
}
}
return zvolenaPostava;
}
/*
* Objekt Postava prevede na String.
*
* @see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext,
* javax.faces.component.UIComponent, java.lang.Object)
*/
public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) {
return (((Postava) arg2).getIdcko().toString());
}
}
Teď zaregistrujeme bean a konverter do faces-config.xml:
faces-config.xml:
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
<!-- BEANY -->
<managed-bean>
<description>Vyber postavy</description>
<managed-bean-name>VyberPostavyBean</managed-bean-name>
<managed-bean-class>cz.hradil.blog.VyberPostavyBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<!-- KONVERTERY -->
<converter>
<converter-id>VyberPostavyConverter</converter-id>
<converter-class>cz.hradil.blog.VyberPostavyConverter</converter-class>
</converter>
</faces-config>
A můžeme vytvořit view:
formular.jsp (view):
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:view>
<h:form>
<h:outputText value="Postava" />
<h:selectOneMenu value="#{VyberPostavyBean.zvolenaPostava}" converter="VyberPostavyConverter">
<f:selectItems value="#{VyberPostavyBean.seznamPostavMenu}" />
</h:selectOneMenu>
<h:commandButton id="ulozit" action="#{VyberPostavyBean.ulozit}" value="Ulozit" />
</h:form>
<h:messages />
</f:view>
A je to. Po zvolení položky v menu se nám do backing beanu přenese zvolený objekt Postava.
ODKAZY:
J2EE 1.4 Tutorial