16 czerwca 2007

f:view beforePhase i wywołanie metody przed wyświetleniem strony w JSF

Często przyglądam się dyskusjom na pl.comp.lang.java i są one świetną inspiracją do zgłębiania tajników specyfikacji Java EE 5. Wiele razy można przeczytać ciekawe spostrzeżenia ludzi na codzień zajmujących się tematami, które mnie interesują.

Ostatnio dużo było pytań i dyskusji nt. JPA i EJB 3 i już myślałem, że JSF całkowicie poszedł w odstawkę, kiedy rozgorzała dyskusja na temat możliwości wywoływania metod przed uruchomieniem strony JSF w wątku JSF dynamiczne inicjalizowanie zmiennych autorstwa melona. Początkowo miałem kilka pomysłów na rozwiązanie tematu, ale jak się okazało w JSF 1.2 pojawiła się ciekawsza opcja - wprowadzono nowy atrybut beforePhase do elementu f:view.

Czując słabnącą pozycję JSF w uproszczeniu życia twórcy aplikacji internetowych podczas dyskusji i ostatecznie sprowokowany stwierdzeniem Piotra Nowaka:

W asp.net jest to banalnie rozwiazane, mianowicie callback Page_load jest wywolywany przy kazdym wejsciu na strone, ciekawe dlaczego w jsf nie ma czegos takiego, tylko trzeba stosowac jakies hacki..

, postanowiłem przeszukać specyfikację JSF 1.2 w celu odnalezienia odpowiedzi. Nie chciało mi się wierzyć, że skoro istnieje coś w ASP.Net to nie ma tego w JSF 1.2. Nie długo trwało, kiedy podczas lektury zmian między JSF 1.1 a 1.2 (rozdział What’s Changed Since the Last Release) przeczytałem o nowych metodach klasy javax.faces.component.UIViewRoot. W szczególności jedna z nich wzbudziła moją ciekawość - UIViewRoot.getBeforePhaseListener. Krótki rekonesans po dokumentacji javadoc metody, gdzie mogłem przeczytać:

getBeforePhaseListener

public MethodExpression getBeforePhaseListener()
Returns:
the MethodExpression that will be invoked before this view is rendered.

i już wiedziałem, że jest to dokładnie to, czego szukałem. Klasy nasłuchujące zdarzeń związanych z etapami przetwarzania zlecenia JSF - javax.faces.event.PhaseListener - rejestrowane są m.in. w pliku faces-config.xml. Skoro tak, pomyślałem, to może i znajdę coś związanego ze wspomnianą metodą. Postanowiłem przeszukać specyfikację pod kątem klasy UIViewRoot i po chwili natrafiłem na rozdział 4.1.17 UIViewRoot, a tam na sekcję 4.1.17.2 Properties, gdzie moim oczom ukazała się właściwość beforePhaseListener, która może być również ustawiana per strona w znaczniku <f:view> za pomocą atrybutu beforePhase. Jeśli dodać do tego, że wartością właściwości jest wyrażenie wskazujące na metodę (typu javax.el.MethodExpression) to nie miałem już więcej złudzeń, że znalazłem rozwiązanie. Pozostało jedynie sprawdzić znalezisko w działaniu.

Szybko stworzyłem prostą aplikację JSF w NetBeans IDE 6, którą rozszerzyłem o następujące elementy.

1. Stworzyłem klasę pl.jaceklaskowski.jsf.PageLoadCallback

package pl.jaceklaskowski.jsf;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;

public class PageLoadCallback {

public void beforePhase(PhaseEvent event) {
FacesContext facesContext = event.getFacesContext();
facesContext.addMessage(null, new FacesMessage("Komunikat z PageLoadCallback.beforePhase"));
UIOutput outputText = (UIOutput) facesContext.getViewRoot().findComponent("komunikat");
outputText.setValue(outputText.getValue() + " (zmodyfikowano w PageLoadCallback.beforePhase)");
}

}
2. Zarejestrowałem powyższą klasę jako komponent zarządzany w 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">

<managed-bean>
<managed-bean-name>pageLoadCallback</managed-bean-name>
<managed-bean-class>pl.jaceklaskowski.jsf.PageLoadCallback</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>

3. Użyłem atrybutu beforePhase w f:view ze wskazaniem na komponent zarządzany pageLoadCallback

<%@page contentType="text/html"%> <%@page 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>JSP Page</title>
</head>
<body>
<f:view beforePhase="#{pageLoadCallback.beforePhase}">
<h1>
<h:outputText id="komunikat" value="JavaServer Faces" />
</h1>
<hr>
<h:messages layout="list" />
</f:view>
</body>
</html>

4. Uruchomiłem przykład na Apache Geronimo 2.0M6

A to jest właśnie efekt, którego poszukiwał melon. Dzięki za zwrócenie uwagi na tę kwestię!