Jiří Hradil blog

o software


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

Publikoval Jiří Hradil • 25.11.2004 v 02:11 • pod kategorií hibernate, jsfŽádné komentáře