Category Archives: JavaServer Faces

Działający przykład JEE w akcji

Jakoś tak się złożyło, że poza servletami i JPA niespecjalnie miałem w życiu pobawić się w EJB i insze wynalazki. Fakt, że pojawił się kiedyś dawno Spring skutecznie zniechęcał do posiłkowania się JEE w codziennym developmencie. Jednakże sytuacja zmieniła się wraz z wydaniem wersji 6 Javy EE.

Szukając w sieci materiałów dla przygotowań do certyfikatu Java Persistence API Developer Certified Expert (tak tak, pierwsze wpisy z przygotowań już niedługo) znalazłem dość ciekawy tutorial, który umożliwia postawienie w pełni funkcjonalnej aplikacji, na której możnaby przećwiczyć mniej i bardziej zaawansowane tematy związane z tą certyfikacją.

JBoss to nazwa budząca respekt. Jako firma oferuje szeroki wachlarz produktów – począwszy od serwera aplikacyjnego skończywszy na IDE. Oferuje również strasznie fajny tutorial znajdujący się  dokładnie pod tym adresem. Obejmuje on instalację dedykowanego IDE oraz przedstawia JEE w akcji – mamy i usługi sieciowe, mamy JSF, mamy też podpięte Hibernate jako ORM. Nic tylko brać i działać. Jeśli ktoś potrzebuje zobaczyć jak może wyglądać sensowna appka bez miliona zależności w POMie to powyższy adres jest świetnym punktem wyjścia.

Advertisements

Kilka sztuczek w Struts 1

W nowej pracy mam okazję na co dzień pracować z frameworkiem Struts. W moich dotychczasowych projektach nie miałem do tej pory okazji zapoznać się z tym frameworkiem, jednakże p0 kilku tygodniach pisania w nim kodu mam o nim jakieś tam pojęcie. Napotkałem po drodze kilka dość frustrujących “wpadek” i dlatego postanowiłem je opisać na blogu.

1. Tagi z dynamicznymi wartościami.

Podstawą internetu jest magiczny znacznik HTMLa o nazwie FORM. Zasadniczo dość często zdarza się, iż adres akcji, która zostanie wywołana jest zależny od przekazanego parametru. W Strustach zaleca się do takich operacji używanie tagu <bean:write> – w sumie można się z tym nawet zgodzić, problemem jest to, iż nie da się umieścić tego tagu wewnątrz treści innego tagu. Czyli takie coś nie przejdzie:

<html:form action="<bean:write [...] />"  ></html:form>

Wpisanie czegoś takiego do treści strony JSP spowoduje wyrzucenie wyjątku. O dziwo użycie składni skrypletowej ze znacznikami również nie zadziała;

<html:form action="<%=(String)request.getAtrtribute("cost")%>"  ></html:form>

Problem ten można obejść sposobem “niejavowym”. A mianowicie tak:

<html:form action='<%=(String)request.getAtrtribute("cost")%>'  ></html:form>

Widać różnicę? Zamieniamy po prostu cudzysłów na apostrof. I nagle dziwnym trafem wszystko działa.

2. Checkboxy w checkboxie czyli o tagu html:multibox

Załóżmy taką sytuację. Piszemy aplikację do rezerwacji pokoi w hotelu. Jako użytkownik wybieramy sobie kilka opcji spośród dostępnych w hotelu ( np. sejf w pokoju, wypożyczenie auta). I te informacje przechowujemy dla każdej rezerwacji. Z drugiej strony mamy rzecz jasna również wszystkie możliwe opcje – ich lista może być długa. I teraz – jak przy edycji/podglądzie formularza szybko i łatwo zaznaczyć na liście te, które użytkownik wybrał dla swojej rezerwacji?

Rzecz jasna możemy przeiterować po całej kolekcji ze wszystkimi opcjami i następnie iteracyjnie przechodzić po opcjach, które wybrał użytkownik. Jeśli obecnie przetwarzana wartość znajduje się pośród tych, które wybrał użytkownik renderujemy atrybut checked. Jednakże jest to troszeczkę nieefektywne i dlatego też twórcy Strutsa wymyślili tag o nazwie html:multibox. W skrócie – iterujemy po liście wszystkich możliwych opcji, ale zamiast porównywać cokolwiek (skrypletami choćby) wstawiamy do tej pętli ww. znacznik i powinno być dobrze. Dlaczego o tym piszę? Ano rzućmy okiem na dokumentację tagu:

Renders an HTML <input> element of type checkbox, whose “checked” status is initialized based on whether the specified value matches one of the elements of the underlying property’s array of current values.

Co pomyśli sobie programista? Świetnie – iterujemy po liście obiektów i potem do tagu wrzucamy drugą listę tego samego typu obiektów i jest OK. Czyli wystarczy dokonać trochę magii z przesłonięciem metod equals oraz hashCode i jesteśmy w domu. No i nie do końca…

Okazuje się (po 3h mojego rzucania mięsem przed komputerem), iż w tym przypadku jednym rozwiązaniem jest to, że obydwie kolekcje będą zawierały obiekty typu String!. Zapomnijcie o dobrach tego taga w przypadku własnych klas. Ostatecznie może wyglądać to tak:

<logic:iterate id="lancuch" name="BeanFormularza" property="listaLancuchow" >

         <html:multibox property="listaLancuchowDoZaznaczenia">
             <bean:write name="lancuch" />
        </html:multibox>

</logic:iterate>

3. Sprawdzanie parametrów żądania

Generalnie sprawa jest prosta. W akcji zapisujemy zmienną w obiekcie żądania i chcemy dobrać się do niej w widoku. Robimy tak:

request.setAttribute( "klucz", wartoscPodTymKluczem );

Wiadomo jednakże, iż czasami wartoscPodTymKluczem może przyjąć wartość NULL, albo co gorsza w ogóle nie zostać ustawiona (co może, choć nie do końca, znaczyć to samo co NULL). Dobrą praktyką byłoby tym samym sprawdzenie w widoku, czy aby interesująca nas zmienna nie jest właśnie NULLem. I tutaj mała niespodzianka w przypadku tagu logic:present. Tag ten sprawdza, czy podana zmienna istnieje. Jednakże należy uważać, gdyż dokumentacja może być myląca – przyzwyczajeni bowiem jesteśmy do tego, iż atrybuty name oraz property oznaczają to samo w każdym tagu – name zawiera nazwę beana, zaś property to konkretna własność obiektu (choć nie do końca, tak naprawdę liczy się istnienie metody dostępowej). Natomiast w przypadku atrybutów żądania jest odwrotnie! By powyższy przykład zadziałał z ww. tagiem musi to wyglądać następująco:

<logic:present name="klucz" >
  tutaj jest miejsce na nasz kod i wszystko inne jeśli wartość istnieje i nie jest pusta
</logic:present>

Zapominamy o property. Tak to ma wyglądać.

4. Podsumowanie
Duperelki, ale ileż potrafią napsuć zdrowia i dobrego samopoczucia. Rzecz jasna wszystko dotyczy frameworka Struts w wersji 1. Jak wygląda ten temat obecnie nie wiem i raczej niezbyt prędko będę miał okazję poznać drugą odsłonę tego frameworka. Jednakże komentarze na ten temat jak najmilej widziane.

Ostateczne dodanie wpisu w ProgramBash

Umilkło ostatnio na blogu, dopadła mnie mądrze pisząc “prokrastynacja”, a po polsku – nie chciało mi się jak cholera.

Jednakże poczucie obowiązku robi swoje – dziś ostatni odcinek w cyklu “dodajemy wpisy w ProgramBash“. Zasadniczo nie za wiele tutaj nowości – widok, walidacja, zapis do bazy. Pozwoliłem sobie nie przeklejać listingów niezbyt różniących się od tych z poprzedniego wpisu dotyczącego ProgramBash. Jedyne co wypada pokazać, to użycie kolejnego komponentu Richafaces, jakim jest komponent DataScroller. Jest to nie mniej i nie więcej, a paginator dla danych, które zamierzamy wyświetlić. Oto kod:

 <h:form id="topForm">

        <rich:datascroller align="left" for="topList" maxPages="20"
             reRender="sc2, topList" id="sc1" oncomplete="SyntaxHighlighter.highlight()" />
     
        <rich:dataTable width="755" id="topList" rows="2" columnClasses="col"
           value="#{entryBean.topList}" var="entry"> 
            
            <f:facet name="header">
                <rich:columnGroup>
                    <h:column>        
                    </h:column>
                </rich:columnGroup>
            </f:facet>

            <h:column>
                Punktów: <h:outputText value="#{entry.points}" /><br />
                Dodał: <h:outputText value="#{entry.author.nick}" /><br />
                W kategorii <strong><h:outputText value="#{entry.category}" /></strong> w dniu: <strong><h:outputText value="#{entry.adddate}" /></strong><br /><br />

                <pre class="brush: <h:outputText value='#{entry.category.shortname}' />">
                    <h:outputFormat value="#{entry.entry}" />
                </pre>


            </h:column>


        </rich:dataTable>
        <rich:datascroller align="left" for="topList" maxPages="20"
          id="sc2" reRender="sc1, topList"  oncomplete="SyntaxHighlighter.highlight()">
        </rich:datascroller>

      
    </h:form>

Umieszczony w odpowiednim widoku komponent ten zaczytuje ze stosownego beana ( entryBean ) wszystkie wpisy posortowane po ilości posiadanych punktów. Jest to o tyle hardkorowe, że w przypadku np. 100k wpisów mielibyśmy spore narzuty na wydajności bazy i całej aplikacji. Jednakże w środowisku produkcyjnym do takich rzeczy standardowo uzywałoby się jakiegoś cache. Na potrzeby edukacyjne nie ma problemu – możemy zostawić to tak jak jest.

Domyślnie zaś ilość wyświetlanych rekordów na stronę to 2 – nie jest to zbyt duża ilość, ale chodziło mi o pokazanie możliwości paginacji bez musu tworzenia testowych 30 wpisów. Na razie pozostanie to wszystko bez zmian. Należy także zwrócić uwagę na podpięcie formatowania kodu – służą do tego znaczniki pre wraz z odpowiednią klasą. Konkretna klasa jest reprezentowana przez nową składową encji EntryCategory – shortname. Uwaga! Należy zwrócić szczególną uwagę na atrybut oncomplete i wywołanie metody SyntaxHighlighter.highlight()! Bez tego po wgraniu nowej strony z kodem nie zostanie on sformatowany!!! Straciłem na to godzinę szukania i kombinowania.

Problem polega na tym, iż na moim lokalnym kompie rozwiązanie powyższe śmiga pięknie. Co więcej – niezależnie od tego, czy do dataScrollera wrzucę bezpośrednio atrybut page, czy też nie. Niezależnie również od tego czy trzymam tę wartość w beanie o zasięgu sesji, czy pojedynczego requestu. Wszystko jest OK. Zaś na VPSie – ZONK! Naciskając przyciski zmieniające stronę, podgląd odpowiedzi serwera wskazuje na to, iż aplikacja otrzymała AJAXem jak najbardziej kolejną stronę wyników. Niestety, nie jest to odzwierciedlane w oknie programu. Grzebię w tym już chyba od tygodnia i nic nie jestem w stanie na to poradzić mimo rozlicznych kombinacji. Jeśli ktoś miałby jakiś pomysł – będę wdzięczny.

Efekt końcowy wygląda mniej więcej tak:

Do tego niestety pomimo najszczerszych chęci przegrałem z SyntaxHighlighterem jeśli chodzi o formatowanie kodu, w którym znajduje się ENTER na końcu pierwszej linii. Dorzuca on sobie kilkanaście spacji na początku linii, co wygląda dziwnie (obrazek powyżej), ale co więcej – nie daje się usunąć choćby JSem!!! Prototype dostarczany razem z Richfaces wyrzuca mi błędami (np. jego funkcja remove oficjalnie nie istnieje), zaś próba podpięcia jQuery pod aplikację kończy się prawie śmiercią kliniczną Richfaces. Trudno, cóż począć.

To tyle na dziś – niewiele w sumie zostało rzeczy, które chciałbym do aplikacyjki dodać – na pewno ocenianie wpisów (AJAX), a także możliwość otrzymania danych poprzez webservice (to tak celem przećwiczenia takowych w Javie). Zatem do następnego razu.

PS. Nie lubię JSF.

Bardziej rozbudowany Hibernate, czyli dodajemy wpisy w ProgramBash

Po zabawach z rejestracją, uploadami plików i innymi sesyjnymi gadżetami przyszedł czas na trochę bardziej zorientowane na bazę rzeczy. Konkretnie chodzi oczywiście o użycie Hibernate i jego mechanizmu relacji. Do dzieła.

Podstawową rzeczą w serwisie będzie pojedynczy wpis. Upraszczając zagadnienie chodzi nam o wpis do bazy z pewną treścią, datą dodania, autorem, oceną (poprzez głosowanie innych użytkowników serwisu), a także by było ciekawiej przyporządkowanymi kategoriami. Mówiąc krótko mamy tutaj szereg relacji pomiędzy tabelami, no i samych tabel też trzeba będzie dorobić. Do tej pory (jak choćby w przypadku obiektu użytkownika), odwzorowania danych w bazie poprzez obiekt było proste. Mieliśmy zwykły obiekt z kilkoma własnościami – dopisaliśmy metody dostępowe i tyle. Podobną rzecz robiliśmy już przy okazji pisania HowToJava, jednakże tam relacje obsługiwał DSL z Grails, zatem rzecz była maksymalnie łatwa i uproszczona. W przypadku Hibernate rzecz jest trochę bardziej skomplikowana. Jednakże zanim weźmiemy się za kodowanie trzeba przedstawić kod SQLa, który posłuży nam do pracy. Do tej pory tabele były tworzone przez ORM, co wymagało nie raz ich poprawiania. Teraz damy Hibernate gotowe tabele do działania.

Istnieje już tabela reprezentująca użytkowników w serwisie. Użytkownicy dodają wpisy, które tym samym posiadają tylko 1 autora (wiele do jednego z perspektywy wpisu). Wpisy są również przypisane do jednej z kategorii (by było łatwiej wyszukiwać i segregować – relacja jeden do jednego). Tworzymy tabele:

-- Tabela dla wpisów
CREATE TABLE `entries` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` BIGINT NOT NULL ,
`category_id` BIGINT NOT NULL ,
`add_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
`content` TEXT NOT NULL ,
`points` INT NOT NULL ,
INDEX ( `user_id` )
) ENGINE = MYISAM ;

-- Tabela dla kategorii
CREATE TABLE `entry_categories` (
`id` BIGINT  NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`category` VARCHAR( 64 ) NOT NULL ,
PRIMARY KEY ( `id` )
) ENGINE = MYISAM ;


INSERT INTO `entry_categories` (`id`, `category`) VALUES
(1, 'Java'),
(2, 'MySQL'),
(3, 'PostgreSQL'),
(4, 'C#'),
(5, 'PHP'),
(6, 'Groovy'),
(7, 'Python'),
(8, 'Ruby'),
(9, 'RoR'),
(10, 'C++'),
(11, 'C'),
(12, 'Bash'),
(13, 'Life');

Teraz trochę teorii, gdyż zrozumienie relacji w Hibernate wcale nie jest takie proste. Kilka rzeczy, które wypadałoby wiedzieć:

  • relacje jednostronne i dwustronne – relację uznajemy za jednostronną, kiedy tylko jedna z encji ma pojęcie o istnieniu drugiej. Prosty przykład – mam akwarium i w nim rybki. Jestem ich właścicielem i powiedzmy, że posiadam zbiór obiektów rybek i wiem dokładnie, która jest która. Natomiast moje rybki ni cholery nie wiedzą skąd się wzięły, co je to zresztą obchodzi. Relacja dwustronna ma miejsce wówczas, gdy obydwie strony relacji wiedzą o sobie. Idealnym przykładem jest rodzic, który posiada kilkanaście dzieci. Doskonale zdaje sobie sprawę z ich istnienia, potrafi je jednoznacznie rozróżnić, ale także każde z dzieci ma informacje o swoich rodzicach (w sumie jest to relacja wiele-do-wielu).
  • właściciel relacji – posiłkując się przykładami powyżej należy w każdej relacji wskazać jej właściciela. Jest to ta encja, która ma prawo do ustawiania klucza głównego w tabeli po stronie “wiele”. Czyli w przykładzie z rodzicem i dziećmi to rodzic za każdym razem ustawia swoje ID w tabeli dzieci. W naszym przypadku to użytkownik będzie właścicielem relacji z wpisem.
  • kaskadowość – bardzo przydatna rzecz, a konkretniej zapis, który pozwala na wskazanie czy przy zapisywaniu właściciela relacji utrwaleniu w bazie danych mają również być poddane wszystkie zależne encje. Jest to o tyle wygodne, iż wystarczy dodać dzieci do rodzica, zapisać rodzica w bazie i viola – informacje o dzieciach również zostały zapisane.

Rozpatrzmy teraz nasz przykład. Zaczniemy od tej łatwiejszej części, czyli relacji pomiędzy wpisem, a kategorią do której jest przypisany. Kategoria jako taka istnieje, ale nie ma pojęcia do czego jest przypisywana (albo co do niej) i zasadniczo taki stan rzeczy wszystkich zadowala. Zatem będziemy mieli do czynienia z relacją jednostronną, której to właścicielem będzie wpis – bo właśnie w tej klasie będzie zapisana informacja o kategorii. Z drugiej strony mamy użytkownika, który może być autorem pewnej ilości wpisów. Zatem wypadałoby, aby użytkownik miał dostęp do swoich wpisów (by wyświetlić je np. na stronie użytkownika). Z drugiej strony kiedy wyświetlimy jeden konkretny wpis to dobrze byłoby też poznać jego autora – skoro umieścił fajnego loga, to pewnie innego jego wpisy są/mogą być równie ciekawe. Czyli wpis musi znać swojego autora. Tym samym od razu wiadomo, że jest to relacja dwustronna. Kto ma być właścicielem? Z zaprezentowanego schematu bazy danych łatwo wyczytać, iż będzie nim użytkownik. Obiekt uzytkownika wciąż wisi u nas w sesji – i kiedy będziemy dodawać wpis, będziemy tenże obiekt aktualizować, a dzięki konfiguracji zachowania kaskadowego automatycznie aktualizacji dokonamy w tabeli z wpisami.

Dość teorii na razie – rzućmy okiem na kod:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="hibernate.mappings">
  <class name="com.wordpress.chlebik.EntryCategory" table="entry_categories">
    <id column="id" name="id" type="long">
      <generator class="native"/>
    </id>
    <property column="category" name="category" type="string"/>
  </class>
</hibernate-mapping>

A oto i klasa Javy:

package com.wordpress.chlebik;

import java.io.Serializable;
import javax.persistence.Id;


/**
 * Klasa, ktora ma za zadanie byc beanem wystepujacym jako kategoria wpisu
 *
 * @author Michal Piotrowski
 */
public class EntryCategory implements Serializable {

    @Id
    private Long id;
    private String category;
    
    public EntryCategory() {  }

    public EntryCategory( String category ) {
        this.category = category;
    }

    @Override
    public String toString() {
        return category;
    }

    // Gettery
    public String getCategory()
    {
        return category;
    }
    
    protected Long getId()
    {
        return id;
    }

     // Settery - bardziej dla poprawnosci niz potrzeby
    public void setCategory( String category )
    {
        this.category = category;
    }

    protected void setId( Long id )
    {
        this.id = id;
    }
}


Rzecz już bardziej prosta być nie może. Mapowanie tej klasy jest póki co podobne do obecnego obiektu usera. Zajmijmy się teraz wpisem.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="hibernate.mappings">
  <class name="com.wordpress.chlebik.Entry" table="entries">
    <id column="id" name="id" type="long">
      <generator class="native"/>
    </id>

    <!-- relacja -->
    <many-to-one name="category" class="com.wordpress.chlebik.EntryCategory" column="category_id" />

    <!--  zwykle kolumny -->
    <property column="content" name="entry" type="string"/>
    <property column="points" name="points" type="integer"/>
    <property column="add_date" name="adddate" type="timestamp"/>
  </class>
</hibernate-mapping>

No i jej kod:

package com.wordpress.chlebik;

import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Id;


/**
 * Klasa, ktora ma za zadanie byc beanem wystepujacym jako wpis
 *
 * @author Michal Piotrowski
 */
public class Entry implements Serializable {

    @Id
    private Long id;
    private EntryCategory category;
    private Timestamp adddate;
    private String entry;
    private Integer points;

    /**
     * Bezargumentowy konstruktor
     */
    public Entry() {  }

    /**
     * Konstruktor z danymi
     *
     * @param id
     * @param adddate
     * @param entry
     * @param points
     * @param category
     */
    public Entry( Long id, Timestamp adddate, String entry, Integer points, EntryCategory category ) {
        this.id          =   id;
        this.category    =   category;
        this.adddate     =   adddate;
        this.entry       =   entry;
        this.points      =   points;
    }

    @Override
    public String toString() {
       return entry + "\n Kategoria: " + category.toString();
    }

    // Gettery
    public EntryCategory getCategory()  {
        return category;
    }
    
    public Long getId()  {
        return id;
    }

    public String getEntry()  {
        return entry;
    }

    public Timestamp getAdddate()  {
        return adddate;
    }

    public Integer getPoints()  {
        return points;
    }

     // Settery 
    public void setCategory( EntryCategory category )  {
        this.category = category;
    }

    public void setId( Long id )  {
        this.id = id;
    }

    public void setPoints( Integer points )  {
        this.points  = points;
    }

    public void setAdddate( Timestamp adddate ) {
        this.adddate = adddate;
    }

    public void setEntry( String entry ) {
        this.entry  =  entry;
    }

}

Nie zapomnijmy również dodać stosownych wpisów w pliku hibernate.cfg.xml:

    <mapping resource="/com/wordpress/chlebik/mapping/EntryCategory.hbm.xml"/>
    <mapping resource="/com/wordpress/chlebik/mapping/Entry.hbm.xml"/>

Encja Entry posiada ID kategorii, dzięki czemu wrzucając mapowanie do pliku konfiguracyjnego Hibernate bez problemu wie, co i skąd wyciągnąć. Jak widać w encjach nadpisałem metodę toString, dzięki czemu dodając do bazy danych przykładowy wpis mogłem po małej magii otrzymać w widoku takie oto cudo:

Od razu postanowiłem podpiąć formatowanie kodu, o którym pisałem niedawno. Zważywszy na edukacyjny charakter aplikacji,a tym samym małe obciążenie postanowiłem linkować do zewnętrznych źródeł. Podpięcie biblioteki, wrzucamy wyciągnięty z bazy kod i oto taki widok:

Ślicznie. Wróćmy jednakże do głównego wątku naszych rozważań. Nasz wpis posiada przypisaną kategorię. Posiada również autora, którym jest jeden z użytkowników w bazie danych. Zajmiemy się teraz stworzeniem odpowiedniego mapowania dla pokazania tej relacji. Jak napisałem powyżej jest to relacja dwustronna – wpis zna swojego autora, zaś użytkownik również jest świadom swoich wpisów.

W pliku Users.hbm.xml dopisujemy taki oto kod:

<set table="entries" name="entries">
         <key column="user_id"/>
         <one-to-many class="com.wordpress.chlebik.Entry" not-found="ignore" />
</set>

Mówi on tyle, że w przypadku obiektu User mamy dostęp do zbioru unikalnych wpisów, które są składowane w tabeli entries. Identyfikującym kluczem, który pozwoli wyciągnąć tylko te wpisy, które przynależą do naszego użytkownika jest kolumna user_id. No i ostatecznie obiekty, które będą w tej kolekcji są obiekty typu Entry. Rzecz dość czytelna. Oczywiście dopisujemy również metody dostępowe do tej kolekcji w klasie User!!!.

Z kolei by obiekt wpisu zdawał sobie sprawę z istnienia swego autora i mógł się do niego odwoływać należy do pliku Entry.hbm.xml dorzucić taką deklarację:

<many-to-one name="author" class="com.wordpress.chlebik.User" column="user_id"/>

W klasie Entry również trzeba dopisać metody dostępowe dla właściwości author. Uruchamiamy naszą aplikację i testowo gdzie nam wygodnie umieszczamy takie coś:

        try
        {
            org.hibernate.SessionFactory sessionFactory = com.wordpress.chlebik.util.ProgramBashUtil.getSessionFactory();
            Session session = sessionFactory.openSession();
            Entry wpis = (Entry) session.get( Entry.class, new Long(1) );
            return wpis;
        }
        catch( Exception e )
        { };

Używam metody get, gdyż nie rzuca ona wyjątku przy wyciąganiu obiektu z bazy tylko zwraca NULL w przypadku braku poszukiwanego rekordu. Akurat jego istnienia jestem pewny (wklepałem go ręcznie), ale dobrze jest wyrabiać sobie dobre nawyki. Zmodyfikowałem trochę metodę toString w klasie Entry i po uruchomieniu aplikacji pokazał się mym oczom taki widoczek:

Ślicznie. Od strony pojedynczego wpisu działa. Teraz testowo zamieniamy kod wyciągający pojedynczy wpis na taki oto kawałek:

 User author = (User) session.get( User.class, new Long(49) );
 return author;

Zaś w pliku Users.hbm.xml zmieniamy definicję na:

 <bag table="entries" name="entries" order-by="add_date">
         <key column="user_id"/>
         <one-to-many class="com.wordpress.chlebik.Entry" not-found="ignore" />
 </bag>

W widoku wrzuciłem taki kod dla spróbowania:

<rich:dataList var="elem" value="#{testowyBean.author.entries}">
    <h:outputText value="#{elem.entry}" />
</rich:dataList>

I pokazuje się nam piękna lista wpisów, które stworzył nasz użytkownik (na razie jego ID wklepane na sztywno, ale przerobienie tego to za chwilkę). Pytanie czemu użyłem znacznika bag, a nie np. Set lub List. Odpowiedź na to pytanie można znaleźć pod tym adresem – nie będę tworzył dodatkowego taga dla operacji, które powinny być oczywiste i powszechnie dostępne w widoku. Co do List dostawałem błąd parsowania XMLa – pomimo wklapania struktury znacznika prosto z dokumentacji Hibernate. Dopiero ww. konstrukcja z bag dała radę i posłusznie współpracuje ze znacznikiem rich:dataList.

Wpis trochę się zrobił rozwlekły zatem część widoku oraz samej logiki w następnym odcinku.

Formatowanie kodu w aplikacjach webowych w najprostszy mozliwy sposób

Przymierzając się wprowadzenia funkcjonalności dodawania wpisów w ProgramBash natknąłem się na problem wprowadzenia formatowania kodu. No i mały problem.

Domyślnie do aplikacji będzie można wklejać dowolny niemalże język programowania. Oczywiście pojawia się problem formatowania kodu źródłowego – zgodzimy się, iż czyste wskaźniki <pre> to nie jest dobre rozwiązanie. Oczywiście istnieje masa skryptów, które zajmują się tego typu konwersją, jednakże najcześciej są one napisane w PHP, zaś przepisywanie tego typu skryptów to benedyktyńska praca i niezbyt do tego rozwojowa. Java pod tym względem (dostępności takich skryptów) jest o wiele bardziej uboga. Jednakże okazuje się, iż istnieje inny sposób – można do tego typu zadań zaprząc JavaScript!. Jest to najzwyklejszy skrypt w tymże języku, dodane pliki stylowania kodu i viola – po wrzuceniu do skryptu łańcucha z kodem powinniśmy mieć pięknie sformatowany kod.

Narzędzie to nazywa się niezbyt oryginalnie SyntaxHighlighter, zaś jego strona domowa to wiki, gdzie znajdziemy wszystkie możliwe informacje. Na razie wygląda to bardzo obiecująco – jak tylko wezmę się za kolorowanie składni to na pewno skorzystam z tego skryptu.

[NAPRAWIONY] Błąd sesji przy starcie ProgramBash

Zdziwiłem się kiedy przy okazji restartu serwera odwiedziłem stronkę ProgramBash. Ni stąd ni z owąd dostałem ekran błędu (rzuconego wyjątku). Hmmm, coś nie bardzo.

Szybkie spojrzenie do sysloga i co się okazuje? To wina mojego phaseListenera, który jest wywoływany po raz pierwszy i sprawdza czy mamy widok do odtworzenia. Kod odpowiedzialny za rzucanie wyjątku jest związany z sesją – śliczny NullPointerException. Pomaga ponowne odświeżenie okna przeglądarki. Bug niemożliwy i to z mojej winy. Poprawię go pewnie jak dotrę do domu.

No i poprawiłem. Zmieniony kod można znależć w ostatnim wpisie poświęconym ProgramBash

Sesja i zmiana layoutu w ProgramBash

Po rejestracji/zalogowaniu w jakimkolwiek serwisie internetowym w zdecydowanej większości przypadków zmianie ulega layout strony. Czasem prawie wcale, jednakże najczęściej pojawia się całkiem sporo nowych rzeczy – pasek z linkami, informacje o sesji i różne takie. Nie ma w tym nic zdrożnego zatem i w przypadku ProgramBash wypadałoby zrobić coś podobnego.

Prace zakończyliśmy w momencie, w którym rejestrujemy nowego użytkownika i automatycznie jest on logowany do serwisu. Jak również pisałem poprzednio po fakcie rejestracji jesteśmy przerzucani (póki co) na stronę główną z radosnym komunikatem o powodzeniu operacji. Jednak poza tym komunikatem, absolutnie nic nie pokazuje nam, że jesteśmy zalogowani, zaś o takich detalach jak login choćby nie wspomnę. Czyli musimy pobawić się w logikę widoku.

Dla przypomnienia – w przypadku ProgramBash warstwą widoku rządzi biblioteka Apache Tiles. Przypomnieć również wypada, iż obecnie istniejący layout został zdefiniowany w taki oto sposób (plik faces-config.xml):

<tiles-definitions>
    <definition name="guestLayout" template="/WEB-INF/layout/guestLayout.jsp">
   
         <put-attribute name="upperMenu" value="/WEB-INF/layout/upperMenuTemplate.jsp" />
         <put-attribute name="content" />
         <put-attribute name="footer" value="/WEB-INF/layout/footerTemplate.jsp" />

    </definition>
</tiles-definitions>

Nazwy mówią za siebie – domyślnie mamy do czynienia z guestLayout, czyli widokiem przeznaczonym dla niezalogowanych użytkowników. Zadanie jest proste – wyświetlić kilka dodatkowych informacji bazując na fakcie, iż jesteśmy zalogowani. I tutaj zasadniczo problem się rozwiązał, gdyż na dłuższą metę rzecz w 2 instrukcjach warunkowych, które umieszczone w widoku (konkretniej w kafelku z menu) będą określały czy coś pokazać, czy nie. Dlatego też ten zapis pozostanie niezmieniony, zaś przegrzebiemy kilka rzeczy w odpowiednim pliku JSP, a konkretnie upperMenuTemplate.jsp. Wyglądać on będzie po tej operacji tak:

 <div id="menu">
                <ul>
                    <li><a href="/glowna.faces" title="home">GŁÓWNA</a></li>
                    <li><a href="/top" title="top">TOP</a></li>
                    <li><a href="/szukajka" title="Szukaj">SZUKAJ</a></li>

                     <h:panelGroup rendered="#{userController.logged == true}">
                         <li><a href="/dodajwpis" title="Dodaj wpis">DODAJ WPIS</a></li>
                         <li><a href="/logout.faces" title="Wyloguj">WYLOGUJ</a></li>
                    </h:panelGroup>

                    <h:panelGroup rendered="#{userController.logged == false}">
                        <li><a href="/rejestruj.faces" title="Rejestracja">REJESTRACJA</a></li>
                        <li><a href="/login.faces" title="Zaloguj">ZALOGUJ</a></li>
                    </h:panelGroup>
                    
                    <li>
                        <a href="#" onclick="document.getElementById('mp').component.show(); return false;" tabindex="0">O AUTORZE</a>
                    </li>
                    <li><a href="mailto:michpio@gazeta.pl" title="Contact">KONTAKT</a></li>

                     <h:panelGroup rendered="#{userController.logged}">
                         <li>
                             <h:form>
                                 Zalogowany jako: <strong><h:commandLink action="#{userController.showUser}" ><h:outputText value="#{user.nick}" /></h:commandLink></strong>
                                 <h:inputHidden id="userId" value="#{user.id}" />
                             </h:form>
                         </li>
                    </h:panelGroup>
                </ul>
        </div>

Jak widać do klasy UserController dodano własność logged wraz ze stosownymi metodami dostępowymi. Rzecz jest dość prosta. Bardziej skomplikowaną rzeczą jest stworzenie czegoś na kształt ACLa, który pilnowałby użytkownika przed wejściem pod wskazane adresy. Skoro bowiem ktoś jest już zalogowany, to po co miałby wchodzić ponownie na ekran rejestracji? Takie sprawdzenie pewnych warunków (konkretniej zalogowania, przynajmniej na razie) musi odbywać się za każdym żądaniem skierowanym do aplikacji. Tutaj przydałby się nam znów jakiś phaseListener. Jednakże by ułatwić sobie pracę wpierw dodamy możliwość zalogowania się oraz wylogowania – bez konieczności rejestrowania się w serwisie za każdym razem. Dodamy zatem widok związany z logowaniem. Oto jak powinien wyglądać:

<%@taglib uri="http://richfaces.org/a4j" prefix="a4j"%>
<%@taglib uri="http://richfaces.org/rich" prefix="rich"%>

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

<%@taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>

<div class="form-container">
  
    <h:form id="registrationForm" prependId="false">

	<fieldset>
		<legend>Zaloguj się do serwisu</legend>
		
			<div>
                            <label for="email">E-mail</label>
                            <h:inputText value="#{user.email}" id="email" required="true"  requiredMessage="#{msgs.emailRequired}">
                                <f:validateLength maximum="256" />
                                <f:validator validatorId="com.wordpress.chlebik.Email" />
                            </h:inputText>
                            <h:message for="email" errorClass="error"/>
                        </div>

                        <div>
                            <label for="pass">Hasło</label>
                            <h:inputSecret value="#{user.pass}" id="pass"  required="true"  requiredMessage="#{msgs.passRequired}">
                                 <f:validateLength maximum="30" minimum="5" />
                            </h:inputSecret>
                            <h:message for="pass" errorClass="error"/>
                        </div>
              
	</fieldset> 

	<div class="buttonrow">
            <h:commandButton type="submit" value="Zaloguj" id="login" immediate="false" action="#{userController.login}"  />
	</div>

        </h:form>

</div>

Jak widać rzecz jest prosta jak konstrukcja snopowiązałki. 2 pola, pod które podpięto walidatory i akcja to wywołania. Akcję wrzucamy do naszej starej i znajomej klasy userController. Dzięki temu część kodu będzie można pewnikiem zrefaktoryzować i sprawić by służył zarówno rejestracji jak i logowaniu. Kod akcji prezentuje taki oto listing:

 /**
    * Akcja obslugujaca logowanie w systemie
    *
    * @return String
    * @author Michał Piotrowski
    */
    public String login() {

        FacesContext context = FacesContext.getCurrentInstance();
        Map params = context.getExternalContext().getRequestParameterMap();
       
        String pass       =    (String)  params.get("pass");
        String email      =    (String)  params.get("email");

         SessionFactory sessionFactory = ProgramBashUtil.getSessionFactory();
         Session session = sessionFactory.openSession();
         String toReturn  =   "login-failed";

        try {
               Query zapytanie = session.createQuery( "FROM User WHERE email = '" + email + "' AND pass = '" + MD5.MD5( pass ) + "'" );
               User existingUser = (User) zapytanie.uniqueResult();

               if( existingUser instanceof User ) {
                   context.getExternalContext().getSessionMap().put("user", existingUser );
                   logged = true;
                   context.addMessage( null, new FacesMessage( FacesMessage.SEVERITY_INFO, "Logowanie powiodło się", "") );
                   toReturn = "login-success";
               } else {
                   context.addMessage( null, new FacesMessage( FacesMessage.SEVERITY_ERROR, "Podano złe dane!", "") );
                   toReturn = "login-failure";
               }
    
               return toReturn;

        } catch( Exception e ) {
            context.addMessage( null, new FacesMessage( FacesMessage.SEVERITY_ERROR, "Logowanie nie powiodło się!", "") );
            return "login-failure";
        } finally {
            session.close();
        }
    }

I takie oto działanie podpięte zostaje pod wywołanie widoku /login.faces. Możemy się zatem spokojnie zalogować. Efektem będzie taki oto choćby widok:

Wylogowanie z kolei podpięliśmy pod stosowny link w górnym menu. Kod w kontrolerze użytkownika jest prościutki:

 /**
     * Metoda sluzaca do wylogowania z serwisu
     *
     * @return String
     */
    public String logout() {

         FacesContext context = FacesContext.getCurrentInstance();
         context.getExternalContext().getSessionMap().put( "user", null );
         logged = false;
         context.addMessage( null, new FacesMessage( FacesMessage.SEVERITY_INFO, "Poprawnie wylogowano Cię z serwisu!", "") );
         return null;
    }

Teraz kiedy już mamy możliwość zalogowania się i wylogowania kiedy tylko chcemy możemy przystąpić do implementacji czegoś na kształt ACLa. Jak już wspomniałem wyżej potrzebny nam będzie do tego kolejny phaseListener, który będzie sprawdzał czy user jest zalogowany czy nie, a tym samym czy pewne akcje są dla niego dostępne, a które nie są.

package com.wordpress.chlebik.filters;

import com.wordpress.chlebik.controllers.UserController;
import java.util.Arrays;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpSession;

/**
 * Klasa obslugujaca ACL
 *
 * @author Michał Piotrowski
 */
public class ACLHandler implements PhaseListener {

    String[] loggedNotAllowed    =   new String[]{ "/login.jsp", "/rejestruj.jsp" };
    String[] loggedNeeded        =   new String[]{};

    public PhaseId getPhaseId() {
         return PhaseId.ANY_PHASE;
    }

    @Override
     public void beforePhase(PhaseEvent e) {

        if( e == null ) {
            return;
        }

        if( e.getPhaseId()== PhaseId.RENDER_RESPONSE )
        {
            FacesContext facesContext = e.getFacesContext();

            HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
            UserController controller = (UserController) session.getAttribute( "userController" );
            String viewId = facesContext.getViewRoot().getViewId();
            Arrays.sort( loggedNotAllowed );
            Arrays.sort( loggedNeeded );

            if( controller != null && controller.isLogged() ) {

                int viewIndex = Arrays.binarySearch( loggedNotAllowed, viewId );

                if( viewIndex > 0 ) {
                    facesContext.addMessage( null, new FacesMessage( FacesMessage.SEVERITY_ERROR, "Musisz się wylogować by móc skorzystać z tej opcji!", "") );
                    facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, "", "logoutNeeded" );
                }

            }  else {

                int viewIndex = Arrays.binarySearch( loggedNeeded, viewId );

                if( viewIndex > 0 ) {
                    facesContext.addMessage( null, new FacesMessage( FacesMessage.SEVERITY_ERROR, "Musisz się zalogować by móc skorzystać z tej opcji!", "") );
                    facesContext.getApplication().getNavigationHandler().handleNavigation(facesContext, "", "loginNeeded" );
                }
            }
        }
    }

    @Override
    public void afterPhase(PhaseEvent event) { }

}

Rzecz jasna klasę należy dorzucić do konfiguracji. Modyfikujemy zatem plik faces-config.xml:

 <lifecycle>
        <phase-listener> com.wordpress.chlebik.filters.ACLHandler</phase-listener>
 </lifecycle>

I tym samym mamy śliczny pseudo-ACL. Z premedytacją użyłem przedrostka “pseudo”, gdyż w duzych i skalowalnych aplikacjach ACL opiera się najczęściej na rolach/grupach przywilejów, często z możliwością ich modyfikacji, zaś to wszystko wyciągane z bazy danych i równie często trzymane w cache. Jednakże na potrzeby naszej aplikacji powyższe rozwiązanie działa bardzo dobrze – zasadniczo niewiele rzeczy (tak planuję) będzie wymagało zalogowania/wylogowania. Grunt, iż podane rozwiązanie działa.