08 października 2007

Konwertery w JavaServer Faces część 4

Poprzednio skończyłem na aplikacji, która korzysta z domyślnej konwersji pozycji w menu rozwijalnym h:selectOneMenu do typu atrybutu ziarna zarządzanego #{pracownikAgent.wybranyPracownik}. Atrybut wybranyPracownik jest typu java.lang.String, a pozycje na liście reprezentowne są przez typ long, które odpowiadają numerom pracowników. Wybór typu identyfikatora pozycji w menu rozwijalnym jest decyzją programisty aplikacji JSF i jest wyznaczony przez pierwszy argument wejściowy konstruktora javax.faces.model.SelectItem (w tym przypadku był to long).

Ktoś mógłby zapytać, nad czym się tutaj zastanawiać? Możnaby przecież w miejscu wykorzystania wybranej pozycji menu przez użytkownika, tj. w metodzie obsługującej akcję, po prostu zamienić typ wejściowy na wymagany (w naszym przypadku String na long) i wykonać operację wyszukania pracownika po jego numerze. Właśnie! Wszystko można wykonać samodzielnie, ale czyż nie byłoby przyjemniej, jeśli część pracy możnaby wydelegować - złożyć na barki szkieletu programistycznego, np. JSF? Właśnie w tym tkwi sedno sprawy. Czym więcej wiemy o wykorzystywanym rozwiązaniu/szkielecie, tym więcej możemy z niego wykorzystać, zmniejszając ilość pracy włożonej w stworzenie aplikacji (w końcu to był powód zastosowania tego rozwiązania względem innego, odrzuconego, nieprawdaż?).

Moim zamierzeniem jest stworzenie aplikacji w taki sposób, aby numer pracownika był identyfikatorem pozycji w menu rozwijalnym i był wykorzystywany do tworzenia widoku aplikacji, a w wywołanej akcji (np. po wciśnięciu przycisku Zatwierdź) oczekiwałbym egzemplarza typu Pracownik odpowiadającego wybranej pozycji w menu.

Pierwszą z wprowadzonych zmian w mojej dotychczasowej aplikacji JSF będzie wprowadzenie nowej strony strona4.jsp.

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Mini-aplikacja JSF - konwerter</title>
</head>
<body>
<f:view>
<h:form>
<h:selectOneMenu value="#{pracownikAgent.pracownik}"
converter="konwerter.pracownik">
<f:selectItems value="#{pracownikAgent.pracownicySelectItems}" />
</h:selectOneMenu>
<br/>
<h:commandButton value="Zatwierdź" actionListener="#{pracownikAgent.wykonajAkcje}"/>
<br/>
<h:messages showSummary="true" errorStyle="color: red" />
</h:form>
</f:view>
</body>
</html>
Główną różnicą między stroną strona3.jsp z poprzedniej części, a powyższą, jest skorzystanie z atrybutu converter kontrolki h:selectOneMenu. Atrybut converter wskazuje na konwerter zarejestrowany w konfiguracji aplikacji JSF - faces-config.xml.

<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="1.2"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<converter>
<converter-id>konwerter.pracownik</converter-id>
<converter-class>pl.jaceklaskowski.konwerter.PracownikConverter</converter-class>
</converter>
<managed-bean>
<managed-bean-name>pracownik</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.Pracownik</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>pracownikAgent</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.konwerter.PracownikAgent</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
W Konwertery w JavaServer Faces część 1 przedstawiłem wymagania stawiane konwerterowi - jest to po prostu obiekt realizujący interfejs javax.faces.convert.Converter.

package pl.jaceklaskowski.konwerter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

public class PracownikConverter implements Converter {

public Object getAsObject(FacesContext context, UIComponent component, String value) {
long numerPracownika = Long.valueOf(value);
return TymczasowaBazaDanych.odszukaj(numerPracownika);
}

public String getAsString(FacesContext context, UIComponent component, Object value) {
return Long.toString(((Pracownik) value).getNumer());
}
}
Podczas zatwierdzenia formularza przez użytkownika (wciśnięcie przycisku Zatwierdź) JSF wykona pierwsze stadium przetwarzania zlecenia, w którym wykonane zostaną konwersje danych ze strony do odpowiadającym ich typom atrybutów, tj. identyfikator pozycji w menu (typu String) zostanie zamieniony przez konwerter konwerter.pracownik na typ Pracownik i wyłącznie poprawna konwersja spowoduje przypisanie wyboru atrybutowi wskazanemu przez wyrażenie #{pracownikAgent.pracownik}. Na zakończenie przetwarzania zlecenia JSF wykona związaną z przyciskiem akcję wykonajAkcje, a po pomyślnym jej wykonaniu ponownie wyświetli stronę (w przypadku zastosowania wyłącznie akcji-słuchacza wskazanej przez atrybut actionListener kontrolki h:commandButton zostanie wyświetlona strona inicjująca zlecenie).

Dla pełnego obrazu zmian należy wspomnieć o tych, w ziarnie pracownikAgent reprezentowanym przez klasę pl.jaceklaskowski.konwerter.PracownikAgent.

package pl.jaceklaskowski.konwerter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;

public class PracownikAgent {

private Pracownik pracownik;

public void wykonajAkcje(ActionEvent event) {
System.out.println("Wykonano akcję z pracownikiem: " + pracownik);
}

public Collection<SelectItem> getPracownicySelectItems() {
Collection<SelectItem> pracownicy = new ArrayList<SelectItem>();
for (Pracownik p : getPracownicy()) {
pracownicy.add(new SelectItem(p, p.getImie()));
}
return pracownicy;
}

public List<Pracownik> getPracownicy() {
return TymczasowaBazaDanych.zwrocWszystkichPracownikow();
}

public Pracownik getPracownik() {
return pracownik;
}

public void setPracownik(Pracownik pracownik) {
this.pracownik = pracownik;
}
}
Istotna zmiana w ziarnie pracownikAgent polega na przekazaniu pełnego egzemplarza Pracownik p do konstruktora javax.faces.model.SelectItem podczas tworzenia listy pozycji w menu (dla przypomnienia poprzednio był to wyłącznie numer typu long).

Uruchomienie aplikacji rozpoczyna się od wyświetlenia strony strona4.jsp.

Wybranie pozycji w menu oraz wciśnięcie przycisku Zatwierdź spowoduje wyświetlenie wybranej pozycji typu Pracownik na konsolę serwera aplikacji.

deployed with moduleid = konwerter
Initializing Sun's JavaServer Faces implementation (1.2_04-b20-p03) for context '/konwerter'
Wykonano akcję z pracownikiem: pl.jaceklaskowski.konwerter.Pracownik@56c88c
Jak można zauważyć akcja pracuje już z właściwym egzemplarzem typu Pracownik, więc poprzez zastosowanie konwerterów akcja, a w zasadzie jej autor, nie musi troszczyć się o odszukiwanie potrzebnych do jej działania informacji, co znacząco uprości jego pracę podczas realizacji działania metody biznesowej wykonajAkcje.

Warto oczywiście rozważyć przesłonięcie metody toString(), aby wyświetlić coś sensowniejszego niż pl.jaceklaskowski.konwerter.Pracownik@56c88c.

Po tych zmianach pozostaje usunąć tymczasowe wprowadzenie typu pl.jaceklaskowski.konwerter.TymczasowaBazaDanych i zastąpienie go...właśnie, czy są już przeczucia odnośnie tego, co będzie w kolejnej odsłonie? Dla podpowiedzi napiszę, że będzie to blisko związane z rzeczami, które mnie ostatnimi czasy bardzo absorbują, a które są w ofercie Korporacyjnej Javy 5. Niebawem rozwiązanie zagadki.

Gotowy projekt aplikacji Konwerter dostępny jest jako jsf-konwerter-czesc4.zip.

5 komentarzy:

  1. JA PIERDYKAM. KOMPLETNIE NIE WIEM O CO CHODZI:p ALE I TA granuluję PASJI. SERDECZNIE POZDRAWIAM

    OdpowiedzUsuń
  2. Zastanawiam się jednak, co skłoniło Cię do zajrzenia do mojego Notatnika? W końcu jakoś tutaj trafiłeś. Chyba, że wpisałeś do Google - "strona nieznany temat pasja" ;-) I jeszcze w dodatku ten komentarz - reklama własnej pasji? Postaraj się, bo jeśli tak, to zaraz komentarz pójdzie pod młotek.

    Jacek

    OdpowiedzUsuń
  3. Witam, zgodnie z krokami podanymi w artykule stworzyłem aplikacje, potrzebuje komponentu selectonemenu ktory wyswietlal mi rzeczy zupełnie tak jak zostało to opisane, używam my faces w wersji 1.1.5. Błędy ktore otrzymuje to:
    javax.servlet.ServletException: org.apache.jasper.JasperException
    javax.faces.webapp.FacesServlet.service(FacesServlet.java:154)

    javax.faces.FacesException: org.apache.jasper.JasperException
    org.apache.myfaces.context.servlet.ServletExternalContextImpl.dispatch(ServletExternalContextImpl.java:429)

    org.apache.jasper.JasperException
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:460)

    java.lang.NullPointerException
    examples.PracownikConverter.getAsString(PracownikConverter.java:16)

    Googluje problem mocno, wszedzie otrzymuje podobne rezultaty jak tutaj, znalazłem też problem w ktorym mowa że select onemenu pod myfaces nie działa dobrze:
    http://wiki.apache.org/myfaces/FAQ
    sekcja: Select Items stop working in MyFaces 1.1.5

    Zwykłe selectonemenu z prostym wypisaniem komponentow przez selectIteam działa prawidłowo, problem dotyczy selectItems.

    czy dam rade to przeskoczyc jakos?

    OdpowiedzUsuń
  4. Moje kilka uwag / pytań:
    1. Po co się pisze:
    for (Pracownik p : getPracownicy()) {
    pracownicy.add(new SelectItem(p, p.getImie()));
    }
    skoro wyciągając wartości z listy nie można z tej wartości obiektu 'p' skorzystać (tzn. następuje odwołanie do bazy danych)?

    2. Ten powyższy przykład nie gdy zamienię w converterze 'TymczasowaBazaDanych.odszukaj' na prawdziwą bazę danych, która używa CMP EntityManagera. Z moich obserwacji serwer używając funkcji 'getAsObject' i odwołując się do bazy nie wstrzykuje EM (em jest nullem). I nie mam pojęcia czemu. Jakieś pomysły?

    pozdrawiam

    OdpowiedzUsuń
  5. Witam.
    Podczas uruchamiania własnych konwerterów na serwerze JBoss 4.2.2.GA (implementacja JSF , otrzymywałem upierdliwe message 'Validation Error: Value is not valid'. Żadnych walidatorów nie dodawałem. Lekarstwem okazało się zaimplementowanie już na tym etapie metody equals - w podanym przykładzie dotyczyłoby to klasy Pracownik.

    pzdr

    OdpowiedzUsuń