środa, 6 kwietnia 2011

Własne komponenty JSF 2.0

Od dłuższego czasu chodzi mi napisanie biblioteki komponentów JSF zawierającej zestaw flash'owych wykresów FusionCharts Free. Zabierałem się już do tego kilkakrotnie ale zawsze miałem coś pilniejszego do zrobienia. Tym razem jednak zebrałem się w sobie i po lekturze książki, o której pisałem ostatnio postanowiłem przebrnąć przez problematykę tworzenia własnych bibliotek JSF. Początkowo, po wprowadzeniu wersji 2.0, myślałem o wykorzystaniu do tego celu komponentów złożonych (composite components), jednak po głębszym przemyśleniu tematu, doszedłem do wniosku, że bardziej elastyczne będzie utworzenie własnej biblioteki.
Postanowiłem zacząć od czegoś najprostszego – komponentu, który wygeneruje mi HTML'owy element DIV ze wstawionym przez aplikację kliencką tekstem. Komponent taki, może w zasadzie składać się tylko z jednaj klasy – klasy komponentu, dziedziczącej z klasy UIComponent. W praktyce stosuje się głównie dziczenie po jednej z trzech klas: UIOutput, UIInput, UIComand.
Twórcy technologii JSF założyli, że formatem wyjściowym może być nie tyko HTML (generowany domyślnie prze klasę komponentu) dlatego też, wprowadzili możliwość wydelegowania zadania wizualizacji do odrębnego mechanizmu. Ja jednak, aby nie komplikować tematu rendererów, do którego pewnie jeszcze wrócę, skupię się na podstawowym rozwiązaniu. Poniżej klasa mojego komponentu
package kuba.demo.customcomp;

import java.io.IOException;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

@FacesComponent("com.blogspot.javaspotlight.Div")
public class UIDiv extends UIOutput{

   @Override
   public void encodeBegin(FacesContext context) throws IOException {

      ResponseWriter writer = context.getResponseWriter();
      String clientId = getClientId(context);

      String compId   = (String)getAttributes().get("id");
      String divStyle = (String)getAttributes().get("style");
      String divText  = (String)getAttributes().get("divText");

      writer.startElement("div", this);

      if (compId != null){
         writer.writeAttribute("id", compId, "id");
      }else{
         writer.writeAttribute("id", clientId, null);
      }

      if (divStyle != null){
         writer.writeAttribute("style", divStyle, null);
      }

      if (divText != null){
         writer.writeText(divText, null, null);
      }

      writer.endElement("div");
   }
}

Jej zadaniem jest wygenerowanie elementu div z trzema atrybutami pochodzącymi z a aplikacji klienckiej: id, style, divText.
Warto też zwrócić uwagę na adnotację @FacesComponent("com.blogspot.javaspotlight.Div"). Zawiera ona identyfikator klasy komponentu JSF. We wcześniejszych wersjach odwzorowanie identyfikatora na klasę komponentu umieszczało się w pliku faces-config.xml






Od wersji 2.0 można używać obu metod odwzorowań.
Drugim krokiem tworzenia własnego komponentu JSF jest utworzenie w katalogu WEB-INF pliku deskryptora biblioteki zawierającego: przestrzeń nazw, nazwę znacznika i typ komponentu. Jego nazwa musi posiadać zakończenie .taglib.xml
   



























Warto zwrócić uwagę na jedną rzecz – deklarację atrybutów znacznika. Nie jest ona wymagana, klasa komponentu i tak będzie „widziała” atrybuty. Jednak bez tej deklaracji tworząc aplikację z wykorzystaniem własnej biblioteki, nie będzie ich rozpoznawać nasze środowisko programistyczne – w moim przypadku NetBeans.
Wreszcie krok trzeci – ostatni. W pliku web.xml musimy wskazać na położenie deskryptora






Teraz pozostaje już tylko użycie komponentu na stronie.













Teoretycznie to już wszystko, tylko po co nam taki komponent ? Celem bibliotek jest możliwość ich wielokrotnego wykorzystania w różnych projektach. Pozostaje jeszcze spakowanie naszego komponentu. Dopiero tutaj zaczął się problem :)
Przygotowanie komponentu JSF do dystrybucji
  1. utworzyć archiwum .jar
  2. przekopiować deskryptor .taglib.xml do katalogu META-INF
  3. w katalogu META-INF musi znajdować się plik faces-config.xml – nawet jeśli nie zawiera żadnych wpisów (tutaj właśnie utknąłem – przecież JSF 2.0 załatwia wszystko przez adnotacje i teoretycznie ten plik nie jest potrzebny. Jakże się myliłem – JEST POTRZEBNY :))
  4. plik web.xml nie jest potrzebny
Struktura biblioteki po spakowaniu powinna wyglądać następująco

Własny język wyrażeń (Expression Language) w JSF 2.0

Dzisiejszy wpis ma na celu pokazanie na przykładzie dwóch prostych przypadków, jak rozszerzyć istniejący język wyrażeń EL.

Przykład 1 – rozszerzenie języka wyrażeń

Załóżmy, że zaimplementowaliśmy własny mechanizm warunkowego renderowania komponentów JSF (w zależności od posiadanych przez użytkownika ról)
Wyrażenie #{userHasRole['EMPLOYEE, MANAGER']} powinno zwracać true lub false. Wymaga to utworzenia własnego mechanizmu przetwarzającego, który w pierwszym kroku zinterpretuje ciąg znaków userHasRole , a następnie ciąg 'EMPLOYEE, MANAGER'. Mechanizm ten to nic innego jak rozszerzenie klasy ELResolver , której najważniejszą dla nas jest metoda:

public Object getValue(ELContext context, Object base, Object property)

Poniżej kod własnego resolver'a
package kuba.demo.el;

import java.beans.FeatureDescriptor;
import java.util.Iterator;
import javax.el.ELContext;
import javax.el.ELResolver;
import kuba.demo.UserRolesController;

public class UserRolesResolver extends ELResolver{ 

    @Override
    public Object getValue(ELContext context, Object base, Object property) {

        // base=null  property=userHasRole

        if(base==null && "userHasRole".equals(property)){

            context.setPropertyResolved(true);

           return new UserRolesController();

        }

        // base=UserRolesController  property=EMPLOYEE, MANAGER

        if(base instanceof UserRolesController && property instanceof String){

            context.setPropertyResolved(true);

           return UserRolesController.checkUserAccess((String)property);

        }        

        return false;
    }
  

    @Override
    public Class getType(ELContext context, Object base, Object property) {



         if (base instanceof UserRolesController) {

            context.setPropertyResolved(true);

            return UserRolesController.class;

        }

      return null;

    }


    @Override
    public Class getCommonPropertyType(ELContext context, Object base) {

        if (base instanceof UserRolesController) {

            context.setPropertyResolved(true);

            return String.class;

        }

      return null;

    }


    @Override
    public boolean isReadOnly(ELContext context, Object base, Object property) {

        if (base instanceof UserRolesController) {

            context.setPropertyResolved(true);

            return true;
        }
      return false;
    }


    @Override
    public void setValue(ELContext context, Object base, Object property, Object value) {
    }


    @Override
    public Iterator getFeatureDescriptors(ELContext context, Object base) {
       return null;

    }
}

Ja, na potrzeby niniejszego przykładu, dodałem jeszcze klasę pomocniczą UserRolesController przechowującą i sprawdzającą role użytkownika
package kuba.demo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

public class UserRolesController {

    public static boolean checkUserAccess(String roleNames) {

        List userRoles = new ArrayList(Arrays.asList("ADMIN",
                                                     "MANAGER",
                                                     "USER"
                                                    ));

        StringTokenizer token = new StringTokenizer(roleNames,",");
      
        while (token.hasMoreTokens()) {

            String roleName = ((String)token.nextElement()).trim();

            if(userRoles.contains(roleName)){

                return true;

            }
        }
        return false;
    }
}

Aby nasza klasa przetwarzająca EL była widoczna w aplikacji, musimy dodać następujący w pis w pliku faces-config.xml





Jest tutaj pewna niekonsekwencja twórców specyfikacji. Nie istnieje bowiem możliwość rejestracji własnego resolver'a przy pomocy odpowiedniej adnotacji – tak jak odbywa się to w przypadku innych elementów JSF 2.0.  

Przykład 2 – dodanie funkcji do języka wyrażeń

Załóżmy, że chcemy na tronie JSF wywołać przy pomocy EL własną funkcję przyjmującą dwa argumenty



W tym celu musimy zaimplementować statyczną metodę
package kuba.demo.el;

public class SumELFunction {

    public static int sumTwoArgs(int arg1, int arg2){
        return arg1 + arg2;
    }
}

oraz odwzorować ją w znajdującej się w katalogu WEB-INF bibliotece znaczników el.taglib.xml
















pozostaje jeszcze zarejestrować bibliotekę w pliku web.xml


Java Context and Dependency Injection w akcji

Dzisiaj niechcący wygooglowałem projekt poświęcony w całości framewokowi Context and Dependency Injection – CDISource. Muszę przyznać, że pod względem treści wygląda bardzo imponująco. Jak twierdzą autorzy, Andy Gibson i Rick Hightower powstał on w celu promocji CDI w kontekście różnych możliwych zastosowań – nie tylko w Java EE. Myślę, że warto śledzić rozwój tego projektu, tym bardziej, że zawiera on nie tylko opisy rozwiązań, ale także spore ilości przykładowego kodu.

P.S.
Wiem. Od bardzo dawna nic ciekawego nie napisałem. Niestety cierpię na permanentny brak czasu. Szczególne wyrzuty sumienia mam wobec Grześka Kukawskiego - autora Darmowego kursu UML, na który się zapisałem i niestety utknąłem na drugiej części. Na swoje usprawiedliwienie mam jedynie to, że nie przebimbałem tego czasu. W ostatnim czasie nasza firmowa biblioteka wzbogaciła się o kolejną pozycję – trzecie wydanie JavaServer Faces (David Geary, Cay S. Horstmann) – nie mogłem sobie odpuścić i jej przeczytanie stanęło na pierwszym miejscu :)