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

Používáme Java Server Faces a Hibernate

Z nepřeberného množství nástrojů určených k vývoji aplikací v Javě nyní zkoušíme kombinaci Java Server Faces a Hibernate. Proč právě tyto technologie?

Java Server Faces:
+ technologie vyvíjená a podporovaná Sunem
+ perfektní oddělení view od logiky aplikace
+ jednoduchost
+ možnost tvorby vlastních komponent
- nová technologie, nedostatek tutoriálů a zkušeností při vývoji
- chybí některé základní komponenty (file upload), nicméně byly již napsány třetími stranami (Oracle ADF, MyFaces)

Hibernate:
+ výkonné řešení persistence v Javě
+ relativní jednoduchost používání
+ široká vývojářská základna - mám dojem, že všichni používají Hibernate ;)
- chabá oficiální dokumentace
- trvá dlouho, než pochopíte souvislosti mezi všemi objekty a naučíte se je efektivně používat
- není od Sunu (ano, pro mě je tato maličkost docela důležitá :)

Instalace JSF:
1. Stáhneme JSF 1.1 ze stránek Sunu.
2. Soubory {JSF}/lib/*.jar zkopírujeme do adresáře knihoven aplikačního serveru (viz. konfigurace Tomcatu).

Instalace JSTL:
1. Stáhneme Jakarta Taglibs Standard Library 1.1.2
2. Zkopírujeme {JSTL}/lib/*.jar do adresáře knihoven aplikačního serveru (viz. konfigurace Tomcatu).

Konfigurace Tomcatu:
$CATALINA_HOME/shared/lib - zde se nachazi sdilene knihovny, ktere budou pouzivat vsechny nase web aplikace.
$CATALINA_HOME/common/lib - knihovny umístěné sem jsou viditelné jak našim aplikacím, tak internímu kódu Tomcatu.
Pro JSF a JSTL jsem zvolil $CATALINA_HOME/shared/lib

Instalace JDBC:
1. JAR soubor s ovladači k rozhraní JDBC ( např. u PostgreSQL soubor pg74.215.jdbc2ee.jar) zkopírujeme do $CATALINA_HOME/common/lib

Instalace Hibernate:
1. Stáhneme Hibernate 2.1.6 a Hibernate-extensions 2.1.2 ze Sourceforge.
2. Hibernate vyžaduje, aby všechny jeho knihovny byly součástí WEB-INB/lib u každého projektu. Prý by pak nastaly problémy s nástroji jako Log4j, commons-logging a dalšími (ještě jsem nezkoušel) .
Znamená to, že pokud vyvíjíme Hibernate aplikaci, kterou potřebujeme opakovaně zkoušet-deployovat na vzdálený Tomcat server, tak vytvořený war archiv bude neúměrně velký právě díky těmto knihovnám :). Pokud nemáme rychlé inet připojení, je vhodné instalovat Tomcat lokálně.
Není však třeba kopírovat všechny knihovny z {HIBERNATE}/lib/, stačí jen ty, které potřebujeme. Seznam vyžadovaných a volitelných najdeme v {HIBERNATE}/lib/README.txt}
Jak řešíte instalaci Hibernate knihoven vy?

Hibernate-extensions:
Tady najdeme pomocné nástroje k Hibernate, např. hbm2java (CodeGenerator) a class2hbm (MapGenerator). Tyto knihovny většinou nepoužíváme přímo v našich aplikacích, spíše k počáteční inicializaci - vytvoření základních java souborů z hibernate mappings, nebo skeletonu hibernate mappings ze zkompilovaných tříd (pomocí reflexe)-jedná se však jen o zakladní generaci, která vyžaduje ruční doladění.

Odkazy:
Java Server Faces Technology
Hibernate
Jakarta Tomcat
JSP(tm) Standard Tag Library
Oracle ADF
MyFaces

UPDATE 24.11.2004: Doplněna instalace JSTL a zbývajících knihoven JSF.

Publikoval Jiří Hradil • 21.11.2004 v 22:11 • pod kategorií NezařazenéŽádné komentáře