Stronnice Chlebika – Newbie Java Blog

listopad 5, 2009

Rejestrowanie użytkowników w ProgramBash – część 2

Zaszufladkowany do: Hibernate, JavaServer Faces, ProgramBash — chlebik @ 12:47 am

Wracając do poprzedniego wpisu – rejestracja to rzecz dość istotna. Dla przypomnienia – na razie zasadniczo poza wygenerowaniem formularza i napisaniem od cholery kodu , który nie za wiele jest w stanie zrobić, przyszedł czas na coś bardziej spektakularnego.

Zaczniemy od walidacji. Zawsze uważałem, że bawienie się w bajery w stylu walidacji po stronie klienta (JavaScript) to tak naprawdę strata czasu – i tak ostatecznie walidacja po stronie serwera będzie miała miejsce, zaś ilość potencjalnych danych, które można źle wpisać nie jest aż tak wielka, aby uznać ewentualny request do serwera za jakoś specjalnie drogi. Dlatego też skupię się tylko na mechanizmach walidacyjnych wbudowanych w sam mechanizm JSF. Poki co mamy do czynienia z takim oto formularzem:

formularz

Co powinno być walidowane? Ano z całą pewnością nick i adres e-mail. Załóżmy, że to e-mail jest identyfikującą składową dla naszych użytkowników i przez to musi być unikalny. Do tego adres e-mail to adres e-mail. Musi spełniać wymogi poprawnego formatu (opisanego w którymś z RFC) – tutaj bez wyrażeń regularnych raczej się nie obejdzie. Długość nicka oraz haseł jest zależna od nas samych – dodamy jednakże ograniczenie minimum jak i maksimum dla tych wartości.

Jednakże zanim to nastąpi należy zastanowić się nad formą wyświetlania komunikatów walidacyjnych. W praktyce stosuje się dwa podejścia – albo listuje się stosowne komunikaty na górze strony, albo też wyświetla się wszystkie po kolei obok walidowanego pola/wartości. Myślę, że to drugie podejście jest o tyle bardziej odpowiednie, iż od razu wiadomo gdzie coś poszło nie tak. Przeglądanie rozbudowanej listy błędów walidacji nie jest zbyt dobrym podejściem, zwłaszcza kiedy nagle komunikaty zajmują połowę ekranu. To jedna sprawa. Druga zaś dotyczy samej treści komunikatów. Nie podchodzę nawet do zagadnienia internacjonalizacji aplikacji, gdyż byłyby to perły rzucane przed wieprze i nawet edukacyjny charakter projektu nie usprawiedliwiałby takich ozdobników. Jednakże posiadanie absolutnie unikalnych i zgromadzonych w jednym miejscu komunikatów jest w każdej aplikacji dość istotne. Dlatego też napiszę kilka słów o pakietach komunikatów.

Podobną rzecz opisywałem już wcześniej przy okazji projektu HowToJava. Jednakże w przypadku Grails istnieją już tam gotowe do wypełnienia pliki komunikatów – wystarczy pobawić się w copywritera i gotowe. W przypadku JSF wymaga to troszeczkę więcej pracy. Podstawą jest rzecz jasna stworzenie pliku komunikatów. W przypadku mojej aplikacyjki dorzuciłem takowy plik (msg.properties) do pakietu z klasami narzędziowymi. Proste. Oczywiście nasz pakiet musimy dopisać do konfiguracji w pliku faces-config.xml. Robimy to w ten oto sposób:

<application>
     <message-bundle>
        com.wordpress.chlebik.util.msg
    </message-bundle>

    <resource-bundle>
        <base-name>com.wordpress.chlebik.util.msg</base-name>
        <var>msgs</var>
    </resource-bundle>
  </application>

Dlaczego wpisujemy dwa razy ten sam plik? Albowiem przy pominięciu pierwszej definicji ( tej z message-bundle ) nie będą działały nadpisania dla standardowych komunikatów! Warto o tym wiedzieć, mi zajęło to parę minut rzucania mięchem :) Samo posiadanie pliku nie jest rzecz jasna do niczego przydatne. Należy jeszcze zaimplementować klasę, która będzie potrafiła coś z naszego pliku wyciągnąć. I tutaj zaczynają się schody. Wyciągnięcie bowiem czegokolwiek z takiego pliku wymaga bowiem trochę zabawy – mianowicie należy uzyskąc referencję do bieżących ustawień regionalnych, potem mechanizm ładowania klas (by zlokalizować nasz plik z komunikatami), zaś na końcu otrzymać dostęp do właściwego pakietu dla wybranych ustawień regionalnych. Wyciągnięcie w tym momencie konkretnego komunikatu to już fraszka. Ufff. Trochę tego za dużo. Dlatego też zgodnie z zasadą DRY zamierzam użyć kodu, który autorzy mojej książki o JSF łaskawie udostępnili dla szerokiej rzeszy programistów. Oto on:

package com.wordpress.chlebik.util;

import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

public class Messages {
   public static FacesMessage getMessage(String bundleName, String resourceId,
      Object[] params) {
      FacesContext context = FacesContext.getCurrentInstance();
      Application app = context.getApplication();
      String appBundle = app.getMessageBundle();
      Locale locale = getLocale(context);
      ClassLoader loader = getClassLoader();
      String summary = getString(appBundle, bundleName, resourceId,
         locale, loader, params);
      if (summary == null) summary = "???" + resourceId + "???";
      String detail = getString(appBundle, bundleName, resourceId + "_detail",
         locale, loader, params);
      return new FacesMessage(summary, detail);
   }

   public static String getString(String bundle, String resourceId,
         Object[] params) {
      FacesContext context = FacesContext.getCurrentInstance();
      Application app = context.getApplication();
      String appBundle = app.getMessageBundle();
      Locale locale = getLocale(context);
      ClassLoader loader = getClassLoader();
      return getString(appBundle, bundle, resourceId, locale, loader, params);
   }

   public static String getString(String bundle1, String bundle2,
         String resourceId, Locale locale, ClassLoader loader,
         Object[] params) {
      String resource = null;
      ResourceBundle bundle;

      if (bundle1 != null) {
         bundle = ResourceBundle.getBundle(bundle1, locale, loader);
         if (bundle != null)
            try {
               resource = bundle.getString(resourceId);
            } catch (MissingResourceException ex) {
            }
      }

      if (resource == null) {
         bundle = ResourceBundle.getBundle(bundle2, locale, loader);
         if (bundle != null)
            try {
               resource = bundle.getString(resourceId);
            } catch (MissingResourceException ex) {
            }
      }

      if (resource == null) return null; // brak dopasowania
      if (params == null) return resource;

      MessageFormat formatter = new MessageFormat(resource, locale);
      return formatter.format(params);
   }

   public static Locale getLocale(FacesContext context) {
      Locale locale = null;
      UIViewRoot viewRoot = context.getViewRoot();
      if (viewRoot != null) locale = viewRoot.getLocale();
      if (locale == null) locale = Locale.getDefault();
      return locale;
   }

   public static ClassLoader getClassLoader() {
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      if (loader == null) loader = ClassLoader.getSystemClassLoader();
      return loader;
   }
}

Listing nie jest specjalnie skomplikowany – to po prostu żonglerka dużymi partiami jakże często powtarzającego się kodu. Dla programisty istotne jest to, iż by wyciągnąć jakikolwiek komunikat wystarczy użyć statycznej metody getMessage przekazując jej odpowiednie parametry, aby otrzymać stosowny komunikat. Mogłoby to wyglądać na przykład tak:

FacesMessage msg = com.wordpress.chlebik.util.Messages.getMessage ( "com.wordpress.chlebik.msg", "niepoprawnyFormatAdresuEmail", null );

Podanie wartości NULL jako trzeciego parametru dla metody wskazuje na fakt, iż w komunikacie nie będą występowały symbole zastępcze (o nich później). Zaś gdybyśmy jednak chcieli by wystąpiły, wówczas należy przekazać do metody tablicę Object[]. Jak widać tę klasę również trzeba było wyszczególnić w pliku konfiguracyjnym!

Teraz zajmiemy się pisaniem własnego walidatora. Standardowe walidatory JSF nadają się do prozaicznych rzeczy – sprawdzania czy dana wartość została wpisana, walidacji długości łancucha i tym podobnych rzeczy. Same w sobie są to rzeczy całkiem potrzebne i używane zasadniczo bardzo często. Jednakże nie w tym rzecz by zadowalać się prostymi rozwiązaniami. Wszak na walidację czeka adres e-mail, chyba jeden z najbardziej “przerobionych” walidatorów na świecie. Samo wyrażenie regularne, które będzie walidowało wpisaną wartość ściągniemy od innych, natomiast ja podam przepis jak opakować porównanie w naszą klasę walidującą.

Wszystkie niestandardowe walidatory muszą implementować interfejs javax.faces.validator.Validator, a także zostać dodane do pliku faces-config.xml. Implementacja interfejsu polega na nadpisaniu tylko jednej metody – validate(), która w razie poprawnej walidacji nie zwraca nic, natomiast w przeciwnym wypadku zgłasza wyjątek ValidatorException z komunikatem błędu wyciągniętym z naszego pakietu komunikatów. Oto klasa walidująca:

public class EmailValidator implements Validator {

    /**
     * Metoda walidujaca
     *
     */
    public void validate(FacesContext context, UIComponent component, Object value) {

        if( value == null ) return;
        if( !(value instanceof String) ) return;

        if( !checkEmail( (String) value) )  {
            FacesMessage msg = com.wordpress.chlebik.util.Messages.getMessage( "com.wordpress.chlebik.util.msg" , "emailNotValid", null);
            msg.setSeverity( FacesMessage.SEVERITY_ERROR );
            throw new ValidatorException( msg );
        }
    }

    /**
     * Prywatna metodka gdzie mielimy temat
     *
     * @param  String   email
     * @return boolean
     */
    private boolean checkEmail( String email ) {

      Pattern p = Pattern.compile(".+@.+\\.[a-z]+");
      Matcher m = p.matcher(email);

      boolean result = m.matches();

      if ( result )
        return true;
      else
        return false;
    }

}

By zaś nie przedłużać – oto kod formularza rejestracyjnego z dodanymi wbudowanymi walidatorami JSF.

<fieldset>
		<legend>Podaj swoje dane</legend>
			<div>
                            <label for="nick">Nick</label>
                            <h:inputText value="#{user.nick}" id="nick" required="true" requiredMessage="#{msgs.nickRequired}">
                                <f:validateLength maximum="60" minimum="3" />
                            </h:inputText>
                            <h:message for="nick" errorClass="error" />
                        </div>

			<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}"/>
                            <h:message for="pass" errorClass="error"/>
                        </div>

                        <div>
                            <label for="passconfirmation">Powtórz hasło</label>
                            <h:inputSecret value="#{user.passconfirmation}" id="passconfirmation"  required="true"  requiredMessage="#{msgs.passconfRequired}"/>
                            <h:message for="passconfirmation" errorClass="error"/>
                        </div>

                        <div>
                            <label for="sex">Płeć</label>
                            <h:selectOneMenu value="#{user.sex}" id="sex" >
                                <f:selectItems value="#{sexBean.sexes}" />
                            </h:selectOneMenu>
                        </div>

	</fieldset>

Wysłanie źle wypełnionego formularza powoduje ponowne wyświetlenie strony wraz z komunikatami o błędach. Komunikaty te są składowane w pliku msg.properties, który wygląda w ten sposób (podano komunikaty walidacji, a także nadpisane standardowe powiadomienia).

javax.faces.component.UIInput.REQUIRED=Wypełnienie tego pola jest obowiązkowe!
javax.faces.validator.LengthValidator.MAXIMUM=Podana wartość jest dłuższa niż {0} znaków
javax.faces.validator.LengthValidator.MINIMUM=Podana wartość jest krótsza niż {0} znaki
nickRequired=Podanie nicka jest obowiązkowe!
emailRequired=Podanie adresu e-mail jest obowiązkowe!
passRequired=Podanie hasła jest obowiązkowe!
passconfRequired=Potwierdzenie hasła jest obowiązkowe!
emailNotValid=Podany adres e-mail jest niepoprawny!

I tak mniej więcej może wyglądać nasz formularz po wypełnieniu go złymi danymi:

walidacja

Jednakże kiedy nasz formularz przejdzie już walidację – dotrze on ostatecznie do klasy UserController. Ona z kolei bieże na siebie kwestię jeszcze jednej walidacji (czy oba hasła wpisane w formularzu są zgodne), po czym w zależności od wyniku zapisuje nowy rekord w bazie danych, albo też ponownie wyświetla formularz z błędem walidacji. Oto kod:

public class UserController {

    private User user;

   /**
    * Akcja obslugujaca rejestracje w systemie
    *
    * @return User
    * @author Michał Piotrowski
    */
    public String register() {

        FacesContext context = FacesContext.getCurrentInstance();
        Map params = context.getExternalContext().getRequestParameterMap();

        String pass       =    (String) params.get("pass");
        String passconf   =    (String) params.get("passconfirmation");
        String nick       =    (String) params.get("nick");
        String email      =    (String) params.get("email");
        Date addDate = new Date();

        if( pass.equals( passconf ) )  {

            try {

                pass = MD5.MD5(pass);
                User newUser = new User( null, nick, email, pass, "/bedzie/path/do/pliku", Math.round(addDate.getTime()/1000), 0, SexEnum.valueOf( (String) params.get("sex") ) );

                SessionFactory sessionFactory = com.wordpress.chlebik.util.ProgramBashUtil.getSessionFactory();
                Session session = sessionFactory.openSession();
                session.save( newUser );

                context.addMessage( null, new FacesMessage( FacesMessage.SEVERITY_INFO, "Rejestracja zakończyła się sukcesem!", "") );
             }
             catch( Exception e ) {
                 context.addMessage( null, new FacesMessage( FacesMessage.SEVERITY_ERROR, "Rejestracja nie powiodła się!", "") );
                 return "register-failure";
             }

             return "registered";
        }
        else {
             FacesMessage msg = com.wordpress.chlebik.util.Messages.getMessage( "com.wordpress.chlebik.util.msg" , "passconfFailed", null);
             msg.setSeverity( FacesMessage.SEVERITY_ERROR );
             context.addMessage("passconfirmation", msg );
             return "register-failure";
        }

    }
}

Kod jest dość oczywisty. Wpierw odczytujemy z mapy parametrów przekazanych w tabeli POST potrzebne nam wartości. Porównujemy hasła, a na końcu tworzymy obiekt i zapisujemy go w bazie. Dorzucamy także stosowny komunikat w zależności od tego, czy rejestracja zakończyła się sukcesem czy nie. W pliku layoutu dorzuciłem taki oto mały HTML:

 <div id="messagesWrapper">
            <h:messages globalOnly="true" infoClass="flashMessageInfo" errorClass="flashMessageError" />
 </div>

Parę zmian w CSSie i pięknie wszystko śmiga. Ah byłbym zapomniał. W regule nawigacji w pliku faces-config w przypadku powodzenia operacji by wyświetlił się nam komunikat, należy wyrzucić znacznik <redirect />. Jeśli tego nie zrobimy nasz komunikat nie zostanie wyświetlony, gdyż po fazie wizualizacji odpowiedzi zostanie wysłany kolejny request. Rozwiązanie to nastręcza jednakże pewien problem, a mianowicie ponownego wysłania tych samych danych. Tym jednakże zajmiemy się następnym razem. Póki co cieszę się z istniejącego (w bardzo skromnym wymiarze) rejestrowania użytkowników.

 

walidacjaSukces

sierpień 5, 2009

ProgramBash – Hibernate wkracza do gry

Zaszufladkowany do: Hibernate, JavaServer Faces, ProgramBash — chlebik @ 12:44 am

Jak pisałem w poprzednim wpisie na podpięcie bazy danych miał przyjść czas. No i ten czas właśnie nadszedł. W dzisiejszym wpisie zajmiemy się podpięciem do naszej aplikacji ORMa Hibernate i spróbujemy go zmusić do pracy z PostgreSQL. A kiedy już zmusimy to i może coś ładnego da się z tego wykrzesać. Zaczynamy.

Pierwszą rzecz, a mianowicie zaimportowane biblioteki do obsługi Hibernate mamy już za sobą. Podczas tworzenia projektu zaznaczyłem opcję mówiącą o imporcie tej jakże przydatnej biblioteki. Domyślnie ustawiłem dialekt na MySQL, jednakże po pobieżnym researchu okazało się, że u mnie na hostingu da się bez problemu zainstalować Postgresa w związku z tym zaczniemy od zmiany pliku konfiguracyjnego – wszak i lokalnie będę pracował z tym systemem bazodanowym.

Obslugiwany dialekt Hibernate znajdziemy spokojnie w źródłach tejże biblioteki, jednakże sterownik dla Postgresa już niekoniecznie. Zatem wypada ściągnąć najnowszą wersję sterownika i dodać do bibliotek w naszym projekcie (PPM na Libraries -> Add i wskazujemy plik ). Ja wrzuciłem swój do katalogu \WEB-INF\lib wraz z kilkoma innymi bibliotekami.
Edytujemy zatem plik hibernate.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
<property name="hibernate.connection.url">jdbc:postgresql://localhost/bash:5432</property>
<property name="hibernate.connection.username">chlebik</property>
<property name="hibernate.connection.password">chlebik</property>

     <mapping resource="/hibernate/mapping/News.hbm.xml"/>

  </session-factory>
</hibernate-configuration>

Jak widać obok standardowych zapisów mówiących o dialekcie i takich tam dodałem już z góry oddzielny zapis o pliku odwzorowania. Pakiet, który będzie zawierał pliki odwzorowań dla Hibernate (jest to najzwyklejszy pakiet utworzony w katalogu ze wszystkimi innymi pakietami) będzie nazywał się com.wordpress.chlebik.mappings. Na pierwszy ogień zaimplementujmy coś prostego – choćby listę newsów pojawiających się zaraz po wejściu do serwisu. Plik News.hbm.cfg prezentuje się następująco:

<hibernate-mapping package="hibernate.mappings">
 <class name="com.wordpress.chlebik.News" table="news">
 <id name="id" type="long" column="id">
 <generator />
 </id>
<property name="adddate" column="adddate" type="integer" />
<property name="header" column="header" type="string" />
<property name="content" column="content" type="string" />
 <class>
</hibernate-mapping>

Jak widać nie jest on specjalnie skomplikowany. Relacji nie ma, raptem 4 pola, wszystko w miarę proste i oczywiste. Jest to odwzorowanie zwykłego POJO, którego zresztą trzeba stworzyć.

package com.wordpress.chlebik;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * Klasa, ktora ma za zadanie byc beanem wystepujacym jako news na portalu
 *
 * @author Michal Piotrowski
 */
@Entity
@Table(name="news")
public class News {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String header;
    private String content;
    private Integer adddate;

    public News() {
    }

    public News( String header, String content, Integer adddate ) {
        this.header = header;
        this.content = content;
        this.adddate = adddate;
    }

    // Gettery
    public String getHeader()
    {
        return header;
    }

    public String getContent()
    {
        return content;
    }

    //  To jest konieczne by moc formatowac date za pomoca znacznikow JSF
    public Date getAdddate()
    {
        Date dateDateObject = new Date();
        long dateInMilis = ((long)adddate)*1000;
        dateDateObject.setTime(dateInMilis);
        return dateDateObject;
    }

    protected Long getId()
    {
        return id;
    }

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

    public void setContent( String content )
    {
        this.content = content;
    }

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

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

Mamy odwzorowanie, mamy POJO, co teraz? Normalnie wypadałoby zrobić dwie rzeczy – zapewnić sobie jeszcze jedną klasę, która zajęła by się tylko dostępem do danych. Czyli np. wyciąganiem obiektów, ich zapisywaniem i takimi tam. W przypadku zwykłych newsów, które na razie będą dodawane bezpośrednio przez bazę (tworzenie panelu administracyjnego dla mojej skromnej edukacyjnej aplikacji uważam za pozbawione sensu), nie ma absolutnie potrzeby rozgraniczanie logiki biznesowej na kolejne warstwy. Przykład jest póki co banalny, myślę że nie popełnię większego naduzycia jeśli po prostu wrzucę metodę wyciągającą ostatnie newsy w komponencie zarządzanym i na tym poprzestaniemy.

Teraz sprawa druga – dostęp do bazy danych. Jak piszą autorzy mojej książki o Hibernate obiekt, który zawiaduje połączeniami do bazy danych i całym tym jarmarkiem to SessionFactory. Jest to jednakże obiekt “ciężki” (heavyweight object), dlatego najlepiej mieć go raptem jeden na całą aplikację. Co zatem trzeba zrobić? Ano jak piszą mądrzy ludzie najlepiej zaimplementować klasę, która będzie trzymała takowy “ciężki” obiekt w formie zmiennej statycznej i w ten sposób po uruchomieniu aplikacji będziemy mogli wyciągać tylko obiekty sesji z istniejącej już fabryki.

Robimy to następująco. Wybieramy sobie pakiet, w którym chcemy takowy obiekt umieścić i prawy przycisk myszy -> New -> Other -> Hibernate -> Hibernate Util. Dzięki temu mamy od razu stworzoną klasę, która wszystko pięknie nam obsłuży. U mnie wygenerowany plik wygląda tak:


package com.wordpress.chlebik;

import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.SessionFactory;

/**
 * Hibernate Utility class with a convenient method to get Session Factory object.
 *
 * @author chlebik
 */
public class ProgramBashUtil {
    private static final SessionFactory sessionFactory;

    static {
        try {
            // Create the SessionFactory from standard (hibernate.cfg.xml)
            // config file.
            sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            // Log the exception.
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

Uff. Mamy chyba wszystko. Pozostaje nam podpiąć pod główną stronę pięknego managed-beana i jesteśmy prawie w domu. Wpierw XML (plik faces-config.xml), potem kod Javy:

<managed-bean>
  <managed-bean-name>NewsBean</managed-bean-name>
  <managed-bean-class>com.wordpress.chlebik.NewsBean</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
 </managed-bean>

No i teraz kod Javy. Nasze ziarenko będzie posiadało tylko jedną metodę – getNews, co pozwoli wylistować wszystkie newsy z bazy. Listing zatem przedstawia się dość prosto:

package com.wordpress.chlebik;

import java.io.Serializable;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

public class NewsBean implements Serializable {

    public NewsBean() {
    }

    public List<News> getNews()
    {
        SessionFactory sessionFactory = com.wordpress.chlebik.ProgramBashUtil.getSessionFactory();
        Session session = sessionFactory.openSession();

        List<News> newsy = session.createQuery("from News order by adddate DESC").list();
        return newsy;
    }
}

Ano i tyle. Od tej pory wystarczy w plikach JSP odwołać się do zapisu #{newsBean.news} i mamy piękną listę newsów. Ja wrzuciłem listowanie newsów na stronę główną w taki oto sposób:

<rich:dataList var="news" value="#{newsBean.news}" rowClasses="newsElement">

  <div>
      <div class="newsHeader"><h:outputText value="#{news.header}"/></div>
      <div class="newsDate">
         <h:outputText value="#{news.adddate}">
             <f:convertDateTime type="date" dateStyle="full" />
        </h:outputText>
      </div>
      <div class="newsContent"><h:outputText value="#{news.content}" /></div>

  </div>

</rich:dataList>

Rzecz jasna klasy CSSa również ładnie podpiąłem i stąd ładny widok. Nieprawda?

glownaHibernateProgramBash

marzec 12, 2009

Wiosna nadchodzi – sprzątamy w modelu

Zaszufladkowany do: Grails, Hibernate, HowToJava, Java — Tagi:, , , — chlebik @ 9:19 am

Odezwało się kilka głosów, iż sensowności wcielania w projekt Grailsowy rozwiązań opartych na Hibernate najtężsi filozofowie by się nie doszukali. Zgodnie zatem z zaleceniami czytelników bloga postanowiłem jasno rozgraniczyć Hibernate od rozwiązania oferowanego nam przez Grails – GORM.

W tym miejscu jeszcze tylko uprzedzę i przypomnę raz jeszcze, gdyż kilkunastokrotnie otrzymywałem mejle z pytaniami. Pisanie notek na blogu odbywa się równolegle z czytaniem odpowiedniej literatury (zarówno papierowej jak i online). Taką formułę bloga wybrałem dlatego, gdyż dopracowanych artykułów poświęconym konkretnym zagadnieniom w sieci można znaleźć masę. Najczęściej są one pisane przez praktyków, którzy daną technikę/technologię wykorzystują w codziennej pracy. Tym samym zapomnieli już dawno, z czym mieli problemy na początku. Dzięki mojemu podejściu (jednoczesnego pisania z poznawaniem omawianych zagadnień) z pewnością powtarzam się w niektórych miejsach, albo też piszę kod, który może pozostawiać sporo do życzenia. Ale to chyba najlepsza metoda nauki – obserwować popełniane błędy i wyciągać z nich wnioski. Dotyczy to zarówno mej skromnej osoby, jak i znacznego procenta czytelników.

Na początek może podstawowe wyjaśnienie – GORM to skrót od Grails Object-Relational Mapping. Pojęcie ORM myślę, że nie musi być rozjaśniane. Specyfiką rozwiązania zastosowanego we frameworku jest wykorzystanie sprawdzonego rozwiązania w tej dziedzinie jakim jest Hibernate z tym tylko, że w przypadku GORMa, framework po prostu wrappuje go w składnię groovy. Tak przynajmniej twierdzi mądra książka. W poprzednim wpisie chcąc, albo też nie chcąc zmieszałem te dwa rozwiązania (niepotrzebnie jak mi wskazano). Zatem bierzemy się za wiosenne porządki w kodzie.

Zaczniemy zatem od konfiguracji. GORM jest nadbudową na Hibernate, zatem proces konfiguracji jest w pełni obsługiwany przez mechanizmy frameworka. Czyli plik hibernate.cfg.xml jest zasadniczo niepotrzebny. Jedynym odnośnikiem dla korzystania z baz danych jest konfiguracyjny plik DataSource.groovy. Nie zmienił się on od czasu poprzedniego wpisu. Co więcej – zgodnie z radami komentujących poprzedni artykuł – wyrzuciłem katalogi z bibliotekami z folderu /lib i zostawiłem tam tylko connector do MySQLa (czysty plik JAR). Plik PreInit.groovy tym samym również poszedł w odstawkę.

Sam kod klas domenowych został rozszerzony poprzez jawne zadeklarowanie pola id, a także stworzenie konstruktora inicjalizującego wszystkie te wartości. To na pewno nie jest potrzebne!. Zmieniłem nazwę klasy na Htj_Links, dzięki czemu łatwiej będzie mi się orientować w kodzie (prefixy do nazw tabeli raczej nie ulegają zmianie). Kod mojej klasy domenowej po zmianach wygląda tak:

class Htj_Links {

static mapping = {
table 'htj_links'
id column: 'link_id'
}

String address;
String description;

I w kontrolerze (jakimkolwiek bądź) można sobie wklepać takie coś:

def lineczki = Htj_Links.list()
lineczki.each{ println(it.address) }

I na konsolce radosnie widac efekty wyciagniecia wszystkich elementów z bazy (słownie jednego testowego rekordu). Przyznaję jednakże – robi wrażenie. To tyle na razie – w następnym wpisie posprzątamy w kontrolerach i w widokach.

marzec 6, 2009

Grailsujemy z bazą, cz. II

Zaszufladkowany do: Grails, Hibernate, HowToJava, Java — Tagi:, , , , — chlebik @ 2:19 am

Nasze ostatnie spotkanie z Grails zakończyliśmy na wygenerowaniu za pomocą scaffoldingu gotowej funkcjonalności CRUD. Pobawiliśmy się trochę, ale…

… ale dalej nasza aplikacja nie ma za wiele funkcjonalności. Dane nie zapisują się w bazie danych, ich odczyt też nie bardzo bo i nie ma czego odczytywać. Tym razem postanowimy coś z tym zrobić. Postaramy się zaimplementować choć część funkcjonalności CRUD w oparciu o Hibernate. Do dzieła.

Scaffolding jest bardzo fajny, jednakże został on stworzony w jednym celu – by ułatwić tworzenie klasy domenowej (lub jak mówi Jacek Laskowski – dziedzinowej). To właśnie pisanie testów dla niej, sprawdzenie poprawności odwzorowań i tak dalej jest celem stosowania rusztowania w aplikacji Grails. Kiedy już mamy pewność, że klasa dziedzinowa jest w porządku, wówczas możemy zabrać się za implementację reszty. Przyjrzyjmy się jeszcze raz naszej klasie dziedzinowej:

class Linki {String adress;
String description;
Date addDate;
Integer category_id;

static constraints = {
adress(blank:false, maxSize: 256, nullable: false)
addDate()
description(blank:false, maxSize: 1024, nullable:false)
category_id(blank:false, nullable:false)
}

}

Tak zdefiniowana klasa daje dość duże pojęcie o tym jak powinna wyglądać tabela w bazie danych. Sama baza jeszcze nie istnieje fizycznie w jakimkolwiek RDBMie, ale to za chwilę się zmieni. By zaś pozostać w zgodzie ze wszystkimi prawidłami sztuki – oto jak będą wyglądały tabele dla linków:

htj_linki_baza1

Mamy zatem podstawową tabelę z linkami i pomocnicza, w której zapiszemy kategorię (dokładnie jedną) danego odnośnika. Pora zaprząc do pracy Hibernate. Zanim jednakże to zrobimy wypada powiedzieć parę słów o samej bazie danych. Otóż standardowo będzie to MySQL, wybrany dlatego, że akurat mam go zainstalowanego na dysku :) O swoich przygodach z driverem do JDBC pisałem wcześniej, zatem problemu być nie powinno. Jeżeli zaś chodzi o samego Hibernate, to nie zamierzam tutaj dawać jakiś rozbudowanych teoretyczych wykładów. Kod niech mówi za siebie. Do nauki używam bardzo fajnej książki, którą polecam każdemu.

Zanim zaczniemy pracę trzeba ściągnąć Hibernate i sterownik MySQLa dla JDBC (connector). Konkretne pliki JAR należy wypakować do katalogu /lib naszej aplikacji i jesteśmy gotowi do dalszej pracy (w przypadku Hibernate potrzebuje on też kilku innych bibliotek do pracy – zachęcam do sięgnięcia po książkę lub do dokumentacji). Plik /conf/DataSource.groovy wyglądał u mnie tak:

dataSource {
pooled = true
driverClassName = "com.mysql.jdbc.Driver"
username = "root"
password = ""
}
hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}
// environment specific settings
environments {
development {
dataSource {
dbCreate = "update" // one of 'create', 'create-drop','update'
url = "jdbc:mysql://localhost/chlebik"
}
}

Próbny rozruch (czy odnajduje klasy JDBC) i… zonk. ClassNotFoundException. Okazało się, że konwencja ponad konfigurację nie dała rady. I teraz zagwozdka – jak cholercia dodać do CLASSPATH Grailsów zewnętrzny plik JAR? Chwil kilka z guglem i cóż się okazuje – wymaga to małego hacka, który dobił mnie totalnie. W katalogu /conf należy utworzyć plik PreInit.groovy i tam wpisać taki oto twór (Update: W bugtrackerze frameworka znalazłem informację, że plik PreInit.groovy zmienia nazwę na BuildSettings.groovy, ale zmiana ta wywala u mnie błąd zatem zostaję przy starej nazwie):

grails.compiler.dependencies = { fileset(dir:'D:/Java/Howtojava/lib/mysql-connector-java-3.1.14/',includes:'*.jar');
fileset(dir:'D:/Java/Howtojava/lib/hibernate-distribution-3.3.1.GA/',includes:'*.jar')}

Tak to wygląda u mnie, ścieżki są bezwzględne (inaczej się jakoś nie dało). Domyślam się, że cała ta zabawa z CLASSPATH i tak dalej wiąże się mocno z wdrażaniem aplikacji, ale póki co nie za bardzo mi się chce zabawiać z meandrami Anta czy innego Mavena. Póki co takie rozwiązanie działa i do prac developerskich w zupełności wystarcza.

Oczywiście skoro mamy już zaimportowane biblioteki trzeba skonfigurować samego Hibernate. Tworzymy plik /conf/hibernate/hibernate.xfg.xml o takiej oto treści:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/chlebik</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.connection.pool_size">10</property>
<property name="show_sql">true</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- Mapping files -->
<mapping resource="grails-app/domain/Linki.hbm.xml"/>
</session-factory>
</hibernate-configuration>

Dane jak widać są zdublowane (występują w konfiguracji DataSource jak i dla Hibernate). Na razie nie wiem co z tych plików ma pierwszeństwo zatem zostawiam dane i tu i tu. Zgodnie z poleceniem z książki teraz wypadałoby stworzyć plik odwzorowań dla klasy Linki. Chwilowo dla jasności przykładu i łatwości zakomentowałem właściwość category_id oraz add_date. Oto kod:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="Linki" table="htj_links">

<id name="id" type="int" column="link_id">
<generator class="native" />
</id>

<property name="address" column="address" type="string" />
<property name="description" column="description" type="string" />

</class>

</hibernate-mapping>

Na szybko utworzona tabelka w bazie:

CREATE TABLE `htj_links` (
`link_id` int(11) NOT NULL auto_increment,
`address` varchar(255) character set utf8 collate utf8_polish_ci NOT NULL,
`description` varchar(255) character set utf8 collate utf8_polish_ci NOT NULL,
PRIMARY KEY (`link_id`)
) ENGINE=InnoDB;

I wrzucony jeden mały link. Do tego oczywiście naszą klasę dziedzinową trzeba przerobić na odpowiednią modłę, aby mogła występować wraz z Hibernate:

public class Linki {

String address;
String description;

static constraints = {
id(blank:false, nullable: false)
address(blank:false, maxSize: 255, nullable: false)
description(blank:false, maxSize: 1024, nullable:false)

}
Linki() {

}

public Linki( Integer id, String address, String description)
{
this.id = id;
this.address = address;
this.description = description;
}

public Integer getId()
{
return id;
}

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

public String getAddress()
{
return address;
}

public void setAddress( String address )
{
this.address = address;
}

public String getDescription()
{
return description;
}

public void setDescription( String description )
{
this.description = description;
}

}

Ufff, tyle roboty, a efektów wciąż nic. Teraz dopiszemy sobie taki oto kod do naszego kontrolera – LinkiController.groovy:

private static final SessionFactory sessionFactory;

static {
try {

sessionFactory = new Configuration ().configure ().buildSessionFactory ();
System.out.println("Identyfikator utworzonego obiektu: " + sessionFactory);
}
catch (Throwable ex) {
System.err.println("Inicjacja SessionFactory nie powiodła się." + ex);
throw new ExceptionInInitializerError (ex);
}
}

public static SessionFactory getSessionFactory () {
return sessionFactory;
}

No i teraz pozostaje tylko uruchomić naszą aplikację. Kiedy w standardowym wyjściu (konsoli) pojawi nam się coś na kształt:

Identyfikator utworzonego obiektu: org.hibernate.impl.SessionFactoryImpl@79404a

To znaczy, że odnieśliśmy sukces. Jak napisałem wcześniej – do istniejącej tabeli z linkami dodałem testowy rekord (wszystkie operacje na bazie dokonywane były przez PHPMyAdmin – nie jest to może podstawowe wyposażenie javowca, no ale ja ułatiwiam sobie życie i póki co pracuję z narzędziami, które wykorzystuję na co dzień). Skoro mamy rekord w bazie danych to wypadałoby na pewno go wyciągnąć. W tym samym kontrolerze pod linijką drukującą informację o naszym obiekcie należy umieścić taki oto kod:

Session session = sessionFactory.openSession();
List linki = session.createQuery("from Linki").list();
System.out.println("Pobrałem rekordów: " + linki.size());

Linki pojedynczyLink = linki.get(0);
System.out.println("Identyfikator rekordu to: " + pojedynczyLink.getId() );
System.out.println("Przypisany link to: " + pojedynczyLink.getAddress() );
System.out.println("Opis rekordu to: " + pojedynczyLink.getDescription() );

Zwrócić należy uwagę, że w naszym przypadku w klauzuli FROM mamy nazwę klasy, a nie tabeli! To dość istotne, inaczej otrzymalibyśmy komunikat mówiący o niemożności znalezienia tabeli. Jako, że rekord będziemy mieli raptem jeden, dostajemy się do niego bezopśrednio poprzez odwołanie się do pierwszego elementu zwróconej listy (oczywiście to tylko przykład, w normalnych aplikacjach takich rzeczy się nie robi). Ja w wyniku otrzymałem ostatecznie taki oto efekt (pogrubienia dodałem dla czytelności):

Identyfikator utworzonego obiektu: org.hibernate.impl.SessionFactoryImpl@1af7e68
Hibernate: select linki0_.link_id as link1_0_, linki0_.address as address0_, linki0_.description as descript3_0_ from htj_links linki0_

Pobrałem rekordów: 1
Identyfikator rekordu to: 1
Przypisany link to: http://www.chlebik.wordpress.com
Opis rekordu to: strona człowieka, który do końca nie wie o co chodzi w tym wszystkim.

I teraz by nie przedłużać (jest trzecia nad ranem) w tymże samym kontrolerze dopiszmy do naszego kodu takie oto coś:

pojedynczyLink.setDescription(" choć może autor i na czymś się zna....");
session.beginTransaction();
session.save(pojedynczyLink);
session.getTransaction().commit();
session.close();

Po uruchomieniu tego kodu zobaczmy efekt, po czym zrestartujmy aplikację. Co otrzymaliśmy z bazy? Może coś takiego:

Identyfikator rekordu to: 1
Przypisany link to: http://www.chlebik.wordpress.com
Opis rekordu to: choć może autor i na czymś się zna....

Cudnie. Jak widać początkowy wysiłek opłacił się i oto w kilku zaledwie linijkach możemy pobrać rekordy, zmienić jeden z nich (lub wszystkie), po czym zapisać je do bazy. Mam nadzieję, że przedstawiony powyżej kod daje choćby mgliste pojęcie o sile Hibernate, czy tez ORMów w ogólności. Następne boje z bazą (zrezygnujemy ze scaffoldingu – tak przy okazji można uruchomić sobie aplikację, przejść do sekcji z linkami i spróbować się pobawić) już niedługo, mam nadzieję, że nie stracicie cierpliwości.

marzec 3, 2009

Grailsujemy z bazą, czyli tworzymy linki cz.1

Zaszufladkowany do: Grails, Hibernate, HowToJava, Java, NetBeans — chlebik @ 12:04 am

Grails implementuje wzorzec MVC, choć do tej pory o owym ‘modelu’ to niewiele napisałem. Pora to zmienić, tym bardziej, że bez modelu to nasza aplikacja nie różni się zbytnio od stronki opartej na tabelkach tak mniej więcej sprzed 10 lat.

W poprzednim wpisie wspomniałem, że tworzenie aplikacji w Grails jest oparte na mechanizmie ’scaffoldingu’. Kolejne kroki polegają na:
- zbudowaniu klasy domenowej (domain class, tłumaczenie moje)
- napisanie do niej testów
- wygenerowanie dla tejże klasy kontrolera

Każdy obeznany ze wzorcem MVC w tym momencie zda sobie sprawę, iż nie jest to byle co. Często samo utworzenie modelu jest żmudną robotą, a do tego jeszcze testy i kontroler – wpis będzie długi z całą pewnością (dla niezorientowanych: piszę kolejne posty jednocześnie czytając i kodując zatem nie wiem co ostatecznie wyniknie na samym końcu :). Na pierwsze zapoznanie się z całym procesem wybrałem implementację strony z linkami. Nie jest ona specjalnie wymagająca ze strony widoku, kontroler też nie za bardzo, zaś model powinien okazać się dość prosty – w końcu cóż skomplikowanego może być w tabelce z linkami?

Wpierw należy stworzyć domain class, co da się łatwo osiągnąć klikając prawym przyciskiem na projekcie i wybierając New->Grails Domain Class. Nadajemy plikowi nazwę Linki i po chwili możemy cieszyć się nową klasą, a także stworzonym automatycznie plikiem do testów integracyjnych (integration tests). O samych testach napiszę w kolejnych wpisach (choć w myśl idei Grails wpierw powinniśmy napisać testy, a potem dopiero logikę), teraz przyjrzyjmy się naszemu modelowi.

class Linki {
String adress;
String description;
Date addDate;
Integer category_id;

static constraints = {
adress(blank:false, maxSize: 256, nullable: false)
addDate()
description(blank:false, maxSize: 1024, nullable:false)
category_id(blank:false, nullable:false)
}

}

Zasadniczo nie wiem czy jest sens cokolwiek wyjaśniać. Nasz model to po prostu reprezentacja konkretnych rekordów w tabeli bazy danych. Tabela ta dopiero powstanie, ale już właśnie na etapie budowy (jak i pisania testów), można przemyśleć strukturę tejże tabeli i opisać ją poprzez konkretne zapisy w klasie modelu. Jak widać na powyższym przykładzie nasz pojedynczy rekord będzie miał na pewno adres (o pewnej maksymalnej długości), opis (też maksymalna długość), datę dodania oraz ID kategorii linków, który to będzie kluczem obcym wskazującym na pojedynczą kategorię, do której link jest przypisany (musi być przypisany, stąd ograniczenie category_id do wartości różnych niż NULL i pustych). Grails (lub też sam ORM – również Hibernate) doda do tego rekordu w sposób automatyczny własność ID, a także wersję – więcej o tym w kolejnych wpisach.

To tyle. Naprawdę. To już koniec, pomijając testy, mamy działający model. Teraz zatem wypadałoby stworzyć kontroler, który ogarnie ten temat. Prawy klik na katalogu z kontrolerami, New->Grails Controller, wpisujemy nazwę Linki i po chwili magii Netbeans mamy klasę kontrolera, kolejny test integracyjny i folder na widoki powiązane z nowym kontrolerem. Oto nasza klasa:

class LinkiController {
def index = { }
}

Nasze zadanie jest proste – trzeba istniejący kod zamienić na taki:

def scaffold = Linki

Uruchamiamy projekt, przechodzimy pod adres http://127.0.0.1/Howtojava/linki/list i co? Taaak, wiem, że widok budujący. Oto Grailsy automatycznie wygenerowały stronkę z pełną funkcjonalnością CRUD. Oczywiście wygląda to… nie wygląda, zwykłe tekstowe linki z parametrami przekazywanymi przez GET, no ale miło zobaczyć coś powstałe z niczego.

linczek

Mamy najzwyklejszą listę – kwestia tylko w tym, iż nie mamy za bardzo czego na niej wylistować. Zawsze jednakże możemy kliknąć New Linki i oto co ukaże się naszym oczom:

createlinki

Formularz wymaga dopracowania – nazwy pól powinny być po polsku, komunikaty o błędzie też (spróbujcie utworzyć nowy link nie wpisując np. liczby całkowitej w polu categoryid). Pojawi się komunikat po wysłaniu formularza. Do tego wybór kategorii powinien być rozwijaną listą, a nie wpisywaniem liczby całkowitej. No ale to temat na najbliższe wpisy. Wpiszmy testowe dane i utwórzmy nowy link. Oto efekt po wysłaniu formularza:

newlink

Pokazał nam się dodany właśnie wpis – możemy go edytować, albo też usunąć. Żyć nie umierać. Wróćmy do listy wszystkich linków – http://localhost:8080/Howtojava/linki/list – jak widać mamy dodany właśnie link pięknie pokazany na pozycji pierwszej. Jedyny minus to to, że po zakończeniu działania aplikacji dane te zostaną utracone. Zajmę się tym w następnym wpisie.

Blog na WordPress.com.