04 kwietnia 2008

Własny JSF renderer oraz krótka przygoda z NetBeans Eclipse Importer

Dostałem zadanie od Andrzeja K., który poprosił mnie o napisanie artykułu "odnośnie renderkit-ów w JSF". Kiedyś tam czytałem na ten temat, ale nic poza tym i dodając do tego, że trochę minęło od mojej ostatniego spotkania z JSF postanowiłem przyjąć "zaproszenie". Rozpocząłem lekturę specyfikacji JSF 1.2, szczególnie rozdziału 8. Rendering Model. Okazało się, że lektura specyfikacji to jedno, a uruchomienie przykładu to drugie. Najbardziej istotne znaczenie ma z jaką implementacją przyjdzie nam pracować. Ja zacząłem od Apache MyFaces 1.2.2, które dystrybuowane jest z Apache Geronimo 2.1. Do pracy potrzebne było mi IDE i padło na Eclipse 3.3.2 z wtyczką Geronimo Eclipse Plugin (GEP).

Utworzyłem projekt typu Dynamic Web Project z rozszerzeniem JSF i utworzyłem stronę index.jsp.
 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<!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>Mój własny JSF Renderer</title>
</head>
<body>
<f:view>
<h:outputText value="Pewien tekst"></h:outputText>
</f:view>
</body>
</html>
Następnie stworzyłem własnego renderera, który odpowiadałby za wyrysowywanie kontrolki h:outputText w mój wymyślny sposób - po prostu wyświetlił sam tekst.
 package pl.jaceklaskowski.jsf.renderers;

import java.io.IOException;
import java.util.logging.Logger;

import javax.faces.component.UIComponent;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;
import javax.servlet.ServletContext;

public class DodajTekstRenderer extends Renderer {

Logger logger = Logger.getLogger(DodajTekstRenderer.class.getName());

public DodajTekstRenderer() {
super();
logger.info("Utworzono renderer");
}

@Override
public void decode(FacesContext facesContext, UIComponent component) {
logger.info("Wywołano decode");
super.decode(facesContext, component);
}

@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
logger.info("Wywołano encodeBegin");

ServletContext servletContext = (ServletContext) context.getExternalContext().getContext();
ResponseWriter writer = context.getResponseWriter();
writer.write("Zmieniono: " + ((UIOutput) component).getValue());

super.encodeBegin(context, component);
}

@Override
public void encodeEnd(FacesContext facesContext, UIComponent component) throws IOException {
logger.info("Wywołano encodeEnd");
super.encodeEnd(facesContext, component);
}

}
Jeszcze konfiguracja renderera i związanych z nim kontrolek JSF w pliku 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">
<render-kit>
<renderer>
<display-name>Mój własny JSF renderer</display-name>
<component-family>javax.faces.Output</component-family>
<renderer-type>javax.faces.Text</renderer-type>
<renderer-class>pl.jaceklaskowski.jsf.renderers.DodajTekstRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
i skończone. Projekt jest gotowy do uruchomienia.

Kilka słów wyjaśnienia, po co i dlaczego tak. Postawione zadanie polegało na stworzeniu renderera dla dostarczanej domyślnie przez JSF kontrolki h:outputText. Każda kontrolka JSF ma związany z nią renderer, który wie, w jaki sposób wyrysować ją w danym środowisku graficznym, np. przeglądarce HTML. Można sobie wyobrazić sytuację, w której chcielibyśmy stworzyć renderera dla technologii Mozilla XUL, PDF, czy SWT/JFace, czy innych. Zaletą tworzenia aplikacji opartych o JSF jest to, że nie zależy ona od sposobu wyświetlania (renderowania) i to właśnie rendererowi pozostawia się obowiązek jej wyrysowywania. Twórca aplikacji JSF odpowiada (jedynie) za rozkład elementów - kontrolek JSF - na stronie. Specyfikacja JSF zobowiązuje dostawców implementacji JSF, aby domyślny zestaw rendererów dla kompletu kontrolek JSF obsługiwał HTML. Wsparcie dla innych technologi wyrysowywania pozostawione są w gestii danej implementacji JSF.

Po kilku nieudanych próbach uruchomienia przykładu było już wiadomo, że wtopiłem. Renderer nie działał (!) Zacząłem poszukiwać odpowiedzi w źródłach MyFaces i zapoznałem się z artykułem Extending Trinidad’s default renderers. Niestety nic nie pomogło. Kompletnie zniechęcony napisałem na grupę użytkowników Apache MyFaces przedstawiając mój problem - Custom renderer for h:outputText - possible?. Niestety, mając nadzieję, że otrzymam odpowiedź w krótkim czasie, po dniu zniechęcony postanowiłem napisać na forum Web Tier APIs - JavaServer Faces na Sun Forums - Custom renderer for h:outputText - possible?. Tam odpowiedź nadeszła w przeciągu 2 godzin (!) Okazało się, że problemem mogła być implementacja JSF, którą do tej pory był Apache MyFaces, a sugerowano użycie JSF RI dystrybuowanego przez GlassFisha.

Yes it is possible. Have you tried running your code on GlassFish to rule out an implementation bug in MyFaces or Geronimo? You can download glassfish and deploy your web application directly on it to test: https://glassfish.dev.java.net/public/downloadsindex.html

Good luck!

Ken Paulsen
https://jsftemplating.dev.java.net


Zaraz wziąłem się za próbowanie z NetBeans IDE 6.1 (wersja rozwojowa) i GlassFish. Ku mojemu zdziwieniu...nie jeszcze tego nie napiszę, to byłoby jak poznać zakończenie kryminału zanim się jeszcze zacznie go czytać ;-)

Utworzyłem projekt w NetBeans i już miałem przełożyć klasy z projektu w Eclipse, kiedy przypomniałem sobie, aby właśnie w tym momencie popróbować się z wtyczką Eclipse Project Importer do importowania projektów z Eclipse do NetBeans. Lektura Migrating your Eclipse projects to NetBeans i już było wiadomo, że muszę ją najpierw zainstalować, bo domyślnie nie jest dostępna w NetBeans. Korzystam z wersji rozwojowej NetBeans IDE 6.1, więc to dodatkowo pozwalało "wierzyć", że coś może się (delikatnie mówiąc) zepsuć. Nic to, jak nie spóbuję, to nie będę wiedział.

Wybieram menu Tools > Plugins w NetBeans, gdzie wyszukuję wtyczki.

Po instalacji (przycisk Install w lewym dolnym rogu okienka dialogowego Plugins) nawet nie musiałem restartować NetBeansa. Zaczynałem wierzyć, że pójdzie gładko.

Pojawiło się nowe menu w menu File - Import Project > Eclipse Project....

Wybieram je, a po nim projekt do zaimportowania (a właściwie przestrzeń roboczą Eclipse'a, w którym znajdują się projekty),


wybieram interesujący mnie projekt - jsf-renderer i


zatwierdzam wybór przyciskiem Finish. Potwierdzam jeszcze informację, że chyba nie będzie lekko i import to niełatwa rzecz.


I faktycznie, efekt końcowy mierny. Import zakończył się utworzeniem nowego projektu typu Java Application zamiast oczekiwanego przeze mnie projektu aplikacji webowej. Spodziewałem się więcej. No cóż, nie dane mi było skorzystać z dobrodziejstw wtyczki. Mówię jej Do następnego razu! i wracam do tematu głównego, jakim jest mój zacny renderer przenosząc projekt z Eclipse do NetBeans ręcznie. Nie ma tego wiele, więc nie zajmie mnie na długo.

Nadeszła chwila prawdy. Cały projekt znajduje się w NetBeans, więc definiuję konfigurację uruchomieniową (wskazanie Relative URL na /faces/index.jsp) i można uruchamiać.


Pora na długooczekiwany Run. Jak to zwykle bywa w takich momentach, wstrzymuję oddech i...


Działa! Na konsoli pojawiły się następujące komunikaty:
 Wywołano encodeBegin
Wywołano encodeEnd
Bardzo pomocna była lektura artykułu Creating an AJAX enabled JSF component - Part 1. Gorąco zachęcam do jego lektury wszystkim zainteresowanym aspektami tworzenia własnych rendererów.

Pytanie dla wytrwałych: Jaką klasę należy rozszerzyć podczas tworzenia własnego renderera? oraz bardziej dociekliwe: Jakie metody renderera uczestniczą w wyrysowywaniu kontrolki podczas budowania widoku JSF?. Nagród nie ma.

1 komentarz:

  1. Trochę szkoda ze nie ma nagród :) ale i tak spróbuję odpowiedzieć.

    1. Jaką klasę należy rozszerzyć podczas tworzenia własnego renderera?

    Odp: A to bardzo zależy.Możemy zacząć pisanie naszego renderera od zera i wtedy rozszerzyć Klasę Renderer - tak jak Ty to robisz. Ale jeżeli rozszerzamy funkcjonalność jakiegoś innego komponentu to możemy wtedy dziedziczyć po konkretnej klasie renderera. Jakiej? To oczywiście zależy od komponentu. Osobiście korzystam ze strony internetowej która podaje dla wszystkich standardowych komponentów klasę komponentu, taga oraz renderera (nie mogę jej teraz znaleźć :))

    Niestety przy rozszerzaniu standardiowych komponentów wpadamy na minę przygotowaną przez twórców specyfikacji 1.2. Nie rozumiem czemu ale klasy komponentów są "generowane automatycznie" (nie dokońca wiem co to znaczy). I nie ma ich w API a tylko w IMPL. a do tego są to klasy finalne. więc albo pozostaje nam kompozycja i karkołomna delegacja wszystkich metoda, albo przekopiowanie koduj z konkretnej implementacji.

    OdpowiedzUsuń