Monthly Archives: October 2009

Zawieszenie bloga

W związku z przeprowadzką oraz remontem zawieszam prowadzenie bloga na okres mniej więcej dwóch tygodni.

Co prawda miałem jeszcze nadzieję, że zdołam napisać drugą część materiału o rejestrowaniu użytkowników w ProgramBash, jednakże fizycznie nie dałem rady. W związku z czym materiał sobie poczeka. Przeprowadzam się do Pruszkowa (tak, tam gdzie kończą się Reguły 🙂 – dostęp do sieci oferuje firma, która wygląda na w miarę porządną. Obiecują 4 Mbit i zewnętrzne IP dostarczyć w kablu w przeciągu kilku dni, zatem istnieje opcja, że w następną niedzielę poranną kawę wypiję kończąc wyżej wspomniany wpis. Istnieje jednakże opcja, że będzie inaczej w związku z czym zakładam bezpieczny termin 2 tygodni. Do przeczytania.

Advertisements

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

Wokół użytkowników kręci się cały ten internetowy biznes. Oczywistą zatem rzeczą jest obecnie implementacja procesu rejestracji użytkowników, a także ich logowania do aplikacji, co daje rzecz jasna o wiele większe możliwości niż bierne przeglądanie serwisu. Do dzieła.

Oczywiście jak każą dobre praktyki zaczniemy od modelu. Podobny proces już przerabiałem w przypadku HowToJava, a zgodnie z zasadą DRY wypadałoby cofnąć się do doświadczeń z poprzedniego projektu. To pierwsze założenie. Drugie jednakoż mówi o tym, aby w każdym kolejnym projekcie posuwać się dalej niż w poprzednim, zrobić coś bardziej ciekawego i zaawansowanego. I tak będzie tym razem. Poza standardowymi danymi użytymi w HowToJava planuję zaimplementować jeszcze następujące funkcje:

  • avatary użytkowników – rzecz jasna wraz z wgrywaniem plików. Sarkałem na obsługę uploadu plików w JSF, teraz będę miał okazję rzecz sprawdzić w praktyce.
  • określanie płci – to nie jest żart! Konkretnie możnaby w mapowaniu tabeli bazy danych zrobić rzecz na zwykłym SMALLINT i zgodzić się, że 1 to panie, a 2 panowie. Ale to nie bardzo profesjonalne. Poruszę temat typów wyliczeniowych w mapowaniu Hibernate i zobaczymy co z tego wyniknie.
  • kody aktywacyjne – w przypadku HowToJava rzecz była bardzo prosta, gdyż ograniczała się do wpisania adresu poczty elektronicznej, dwa razy hasła no i rzecz jasna CAPTCHY. Niektórych jednakże ta ostatnia niemożliwie denerwuje, zatem tym razem pobawię się w wysyłanie mejli wraz z kodami aktywacyjnymi.

Założenia dość interesujące, zatem trzeba zabrać się do dzieła. Postanowiłem zacząć od rozeznania tematu związanego z typami wyliczeniowymi (ENUM), zarówno jako konstrukcji języka, jak i typu w bazie danych MySQL.

Dla przypomnienia – typ wyliczeniowy w MySQL to specjalny typ kolumny przeznaczony do przechowywania ściśle określonych typów. Warto wspomnieć, iż MySQL konwertuje sobie zawartość takiej kolumny na odwzorowania typu INT. Co za tym idzie znacząco przyspiesza wyszukiwanie po tejże kolumnie, pomimo pozorów używania wartości tekstowych. Jeśli zaś chodzi o ENUM w Javie, jest to rzecz wprowadzona wraz z pojawieniem się wersji 1.5 języka. Temat jest dość obszerny, zatem zainteresowanych odsyłam na strony tutoriala firmy SUN. Użycie tego typu kolumny nasuwa się od razu, kiedy będziemy operować na ledwie kilku potencjalnych wartościach. Czyli określenie płci ( ‘m’,’f’ ), statusu konta ( ‘banned’,’active’,’deleted’,’awaiting’ ) czy choćby poziomu uprawnień ( ‘admin’, ‘user’, ‘guest’ ) to podręcznikowe wręcz przyłady sensowności implementacji typu wyliczeniowego. W przypadku użytkowników ProgramBash skupimy się na dwóch wymienionych powyżej typach – płci oraz statusie konta.

Konwersacja z wujkiem G zaowocowała dwoma linkami – blogiem dangertree, a także znajdującym się tam odnośnikiem do oficjalnej dokumentacji Hibernate.
Rzecz jest dość prosta – musimy stworzyć własny typ wyliczeniowy, aby dało się go zastosować jako typ składowej obiektu odwzorowania. Kiedy będziemy już mieli typ wyliczeniowy, wówczas stworzymy plik odwzorowań dla Hibernate, zaś na końcu stworzymy gotowy obiekt.

Oto kod dla naszego typu wyliczeniowego:

public enum SexEnum {
    MALE(0), FEMALE(1);

    private int id;

    private SexEnum(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public static SexEnum valueOf(int id) {
        switch (id) {
            case 0: return MALE;
            case 1: return FEMALE;
            default: return MALE;
        }
    }
}

Okazuje się jednakże, że to nie wszystko. Potrzeba jeszcze stworzyć klasę, która będzie imlpementowała interfejs UserType oraz ParameterizedType, które są potrzebne naszemu ORMowi. Kod dość długi:

public class SexEnumUserType implements UserType, ParameterizedType {

    private Class<? extends Enum> enumClass;
    private Class<?> identifierType;

    private Method identifierMethod;
    private Method valueOfMethod;

    private static final String defaultIdentifierMethodName = "getId";
    private static final String defaultValueOfMethodName = "valueOf";

    private NullableType type;
    private int [] sqlTypes;

    public void setParameterValues(Properties parameters) {
        String enumClassName = parameters.getProperty("enumClass");
        try {
            enumClass = Class.forName(enumClassName).asSubclass(Enum.class);
        }
        catch (ClassNotFoundException exception) {
            throw new HibernateException("Enum class not found", exception);
        }

        String identifierMethodName =
            parameters.getProperty("identifierMethod", defaultIdentifierMethodName);

        try {
            identifierMethod = enumClass.getMethod(identifierMethodName, new Class[0]);
            identifierType = identifierMethod.getReturnType();
        }
        catch(Exception exception) {
            throw new HibernateException("Failed to optain identifier method", exception);
        }

        type = (NullableType)TypeFactory.basic(identifierType.getName());

        if(type == null)
            throw new HibernateException("Unsupported identifier type " + identifierType.getName());

        sqlTypes = new int [] {type.sqlType()};

        String valueOfMethodName =
            parameters.getProperty("valueOfMethod", defaultValueOfMethodName);

        try {
            valueOfMethod = enumClass.getMethod(
                    valueOfMethodName, new Class[] { identifierType });
        }
        catch(Exception exception) {
            throw new HibernateException("Failed to optain valueOf method", exception);
        }
    }

    public Class returnedClass() {
        return enumClass;
    }

    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
                        throws HibernateException, SQLException {
        Object identifier=type.get(rs, names[0]);
        try {
            return valueOfMethod.invoke(enumClass, new Object [] {identifier});
        }
        catch(Exception exception) {
            throw new HibernateException(
                    "Exception while invoking valueOfMethod of enumeration class: ", exception);
        }
    }

    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
        try {
            Object identifier = value != null ? identifierMethod.invoke(value, new Object[0]) : null;
            st.setObject(index, identifier);
        }
        catch(Exception exception) {
            throw new HibernateException(
                    "Exception while invoking identifierMethod of enumeration class: ", exception);

        }
    }
    public int[] sqlTypes() {
        return sqlTypes;
        //There was a logical bug within the set-up phase of any user type
        //I reported the issue and it got instantly solved (Thanks again Garvin!)
        //But it might still exist in your Hibernate version. So if you are
        //facing any null-pointer exceptions, use the return statement below.
        //Note: INTEGER works even for String based mappings...
        //return new int [] {Types.INTEGER};
    }

        public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable)value;
    }

    public boolean equals(Object x, Object y) throws HibernateException {
        return x==y;
    }

    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    public boolean isMutable() {
        return false;
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
}

Oto jak wygląda kod klasy odwzorowującej dla Hibernate:

<?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">

    <typedef class="com.wordpress.chlebik.mappings.enums" name="sexEnum">
        <param name="enumClassName">com.wordpress.chlebik.mappings.enums.SexEnum</param>
        <param name="identifierMethod">getId</param>
    </typedef>

  <class name="com.wordpress.chlebik.User" table="users">
    <id column="id" name="id" type="long">
      <generator class="native"/>
    </id>
    <property column="adddate" name="adddate" type="integer"/>
    <property column="registerdate" name="registerdate" type="integer"/>
    <property column="avatarpath" name="avatarpath" type="string"/>
    <property column="email" name="email" type="string"/>
    <property column="nick" name="nick" type="string"/>
    <property column="pass" name="pass" type="string"/>
    <property column="sex" name="sex" type="sexEnum"/>
  </class>
</hibernate-mapping>

Kilka słów wyjaśnienia. addDate różni się od registerDate tym, iż ta pierwsza jest datą pojawienia się konkretnego rekordu w bazie danych, zaś registerDate jest zapisaną datą aktywacji konta. Jak widać na początku pliku odwzorowania trzeba było poinformować Hibernate o tym, iż w użyciu będzie nasz specjalny typ (deklaracja typedef).

Na sam koniec kod klasy reprezentującej naszego użytkownika:

@Entity
@Table(name = "users")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String nick;
    private String email;
    private String avatarpath;
    private Integer adddate;
    private Integer registerdate;
    private SexEnum sex;
    private String pass;
    private String passconfirmation;

    public User() {
    }

    public User(String nick, String email, String pass, String passconfirmation, String avatarpath, Integer adddate, Integer registerdate, SexEnum sex) {
        this.nick = nick;
        this.pass = pass;
        this.passconfirmation = passconfirmation;
        this.email = email;
        this.avatarpath = avatarpath;
        this.adddate = adddate;
        this.registerdate = registerdate;
        this.sex = sex;
    }

    // Gettery
    public String getNick() {
        return nick;
    }

    public String getPass() {
        return pass;
    }

    public String getPassconfirmation() {
        return passconfirmation;
    }

    public String getEmail() {
        return email;
    }

    public String getAvatarPath() {
        return avatarpath;
    }

    public Date getAdddate() {
        Date dateDateObject = new Date();
        long dateInMilis = ((long) adddate) * 1000;
        dateDateObject.setTime(dateInMilis);
        return dateDateObject;
    }

    public Date getRegisterdate() {
        Date dateDateObject = new Date();
        long dateInMilis = ((long) registerdate) * 1000;
        dateDateObject.setTime(dateInMilis);
        return dateDateObject;
    }

    public SexEnum getSex() {
        return sex;
    }

    protected Long getId() {
        return id;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setSex(SexEnum sex) {
        this.sex = sex;
    }

    public void setNick(String nick) {
        this.nick = nick;
    }

    public void setPass(String pass) {
        this.pass = pass;
    }

    public void setPassconfirmation(String passconfirmation) {
        this.passconfirmation = passconfirmation;
    }

    public void setRegisterdate(Integer registerdate) {
        this.registerdate = registerdate;
    }

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

    public void setAvatarpath(String avatarpath) {
        this.avatarpath = avatarpath;
    }
}

Tak to wygląda póki co. Teraz jednakże wypada stworzyć trochę kodu, który umożliwi nam wprowadzenie stosownych danych do aplikacji. Po małych zabawach z CSSem i HTMLem do odpowiedniego pliku widoku dorzuciłem taki oto kod:

<%@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>

	<fieldset>
		<legend>Podaj swoje dane</legend>
			<div>
                            <label for="nick">Nick</label>
                            <h:inputText value="#{user.nick}" id="nick" />
                        </div>

			<div>
                            <label for="email">E-mail</label>
                            <h:inputText value="#{user.email}" id="email" />
                        </div>

                        <div>
                            <label for="pass">Hasło</label>
                            <h:inputSecret value="#{user.pass}" id="pass" />
                        </div>

                        <div>
                            <label for="pass_confirmation">Powtórz hasło</label>
                            <h:inputSecret value="#{user.pass_confirmation}" id="pass_confirmation" />
                        </div>

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

<!--   Inne pola póki co pominąłem, chodzi o pokazanie tematu -->

	</fieldset>

	<div class="buttonrow">
            <h:commandButton type="submit" value="Zarejestruj" id="register" action="#{userController.register}"  />
	</div>

        </h:form>
</div>

Rzecz jest wstępnie skrócona do podstawowych własności by zobaczyć czy nasz przykład działa. Jak widać w znaczniku <h:commandButton> podajemy akcję zaimplementowaną w klasie userController. Albowiem gdzieś te nasze requesty muszą zostać obsłużone. Bean o nazwie user będzie nam służył do reprezentacji obiektu obecnie zalogowanego użytkownika.  Plik faces-config.xml dla aplikacji wygląda póki co tak:

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

 <managed-bean>
  <managed-bean-name>userController</managed-bean-name>
  <managed-bean-class>com.wordpress.chlebik.controllers.UserController</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
 </managed-bean>

  <managed-bean>
  <managed-bean-name>user</managed-bean-name>
  <managed-bean-class>com.wordpress.chlebik.User</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
 </managed-bean>

 <managed-bean>
  <managed-bean-name>sexBean</managed-bean-name>
  <managed-bean-class>com.wordpress.chlebik.beans.SexBean</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
 </managed-bean>


<navigation-rule>
    <from-view-id>/rejestruj.jsp</from-view-id>
    <navigation-case>
        <from-outcome>registered</from-outcome>
        <to-view-id>/glowna.jsp</to-view-id>
        <redirect />
    </navigation-case>
    <navigation-case>
        <from-outcome>register-failure</from-outcome>
        <to-view-id>/rejestruj.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

Jak widać poza klasami zajmującymi się logiką, w pliku konfiguracyjnym umieściłem reguły nawigacji. Mówią one jedno – z widoku o adresie /rejestruj.jsp (w regułach podajemy nazwy pliku widoku, a nie mapowania np. rejestruj.faces będzie błędne) należy w razie powodzenia przejść do widoku strony głównej (poprzez REDIRECT), zaś w przypadku pomyłki należy ponownie wyświetlić formularz rejestracji. Kod naszego kontrolera na razie będzie przykładowy – akcja register będzie po prostu zwracała wartość ‘registered’.

public class UserController {
   
   /**
    * Akcja obslugujaca rejestracje w systemie
    *
    * @return String
    * @author Michał Piotrowski
    */
    public String register()   {
       return "registered";
    }
}

Uruchamiamy aplikację, przechodzimy pod adres rejetruj.faces i naszym oczom pokazuje się śliczny formularz rejestracyjny. Niezależnie od tego czy wpiszemy cokolwiek w pola formularza czy też nie, po naciśnięciu przycisku ‘REJESTRUJ’ zostaniemy przekierowani na główną stronę aplikacji. Póki co rzecz jest posta jak konstrukcja gwoździa, jednakże wpis stał się cokolwiek za długi, zatem kolejna część następnym razem. Spróbujemy zwalidować nasz formularz oraz zapisać coś do bazy.

Sprzęt to podstawa

Podstawa. I dlatego ostatnio ucichło na blogu – padł mi monitor. No może nie od razu umarł, ale zaczął wyczyniać cuda niewidy.

No i stąd konieczność nowego zakupu – mój wysłużony CRT Sony odstawiłem na bok, zaś na moim biurku pojawił się taki oto wynalazek:

iiyama-ProLite-E2407HDSV

Jak na razie spisuje się świetnie, na biurku przybyło mi miejsca, a ja mam nadzieję, że nowe wpisy pojawią się już niedługo.

Java exPress, a sprawa Chlebika po raz drugi

Tadam! Po miesięcznym poślizgu wydawniczym “nadejszła wielkopomna chwila” jak mówił pewien znany człowiek – pojawił się kolejny numer Java exPress!

Co w tym takiego wielkiego? Ano to, iż znaleźć tam można artykuł, który popełniłem jakiś czas temu, a który jest kompilacją wpisów dotyczących SCJP. Wszystko zebrane w jednym miejscu, ładnie poskładane i wydane. Miłej lektury.

Michał Piotrowski Sun Certified Java Programmer

W końcu po prawie miesiącu od zdania dostałem fajną przesyłkę od firmy Sun. A w niej pierwszy dyplom do zawieszenia na moim ‘Wall of Fame’.

Certyfikaty SUNa mają to do siebie, że dostaje się kilka bajerków po ich zdaniu. Pierwszy z nich to fajna wizytówka w formie karty kredytowej (twardy plastik). Dzięki temu (o ile w ogóle będzie potrzeba) nie ma konieczności targania ze sobą certyfikatu. Zaś sam dyplomik wygląda tak:

mojSCJP

Trochę krzywo się zeskanował, ale przyczyną tego problemu jest… poczta. Otóż dostałem przesyłkę od SUNa zwykłym listem przysłanym z zagranicy (Malmo, 16 września, odebrałem wczoraj zatem trochę im zeszło). Wszystko fajnie, tylko czemu koperta była z gatunku ‘tekturowych’, bez jakiegokolwiek zabezpieczenia w srodku! I właśnie dlatego otrzymana przesyłka w kilku miejsach wygląda jak z gardła wyciągnięta (zakładam, że to wina wilgoci). Nierówna faktura objawia się niemożnością poprawnego zeskanowania niestety. Mam przynajmniej nadzieję, że kiedy włożę ten dyplom do antyramy to się sprasuje i będzie wyglądał cokolwiek lepiej. Pozostaje tylko mieć nadzieję, że kolejne certyfiakty zostaną dostarczone w trochę lepszym stanie.

Do kopania łopata, zaś do Javy…

javapn No właśnie co do Javy? Ostatnio jakoś cicho na blogu, gdyż przerabiam wydaną w czerwcu książkę, o której już na blogu pisałem – “Java. Praktyczne narzędzia”.

Rzecz jest niebagatelna – prawie 900 stron wiedzy, tym cenniejszej, iż z definicji znalezienie konkretnej literatury dotyczącej poruszanych tematów jest dość trudne. Dla kogo jest ta książka? Zasadniczo dla każdego – początkujący w ogóle zobaczą o co chodzi, zaawansowani ugruntują wiedzę, a i czasem znajdą coś, o czym nie mieli pojęcia. Takie są moje odczucia póki co po lekturze rozdziałów dotyczących Anta i Mavena, a także szybkim przejrzeniu tematu związanego z Subversion.

Twórcy książki skupili się na narzędziach właśnie – nie na omówieniu kolejnej biblioteki służącej do niewiadomoczego. Takiej książki jeszcze na rodzimym rynku nie było i trzeba przyznać,  że tym razem Helion się naprawdę postarał.

Całość jest zorganizowana w działy, których to tematyka prezentuje się następująco:

  • narzędzia do budowy projektów – nazwa może mało precyzyjna, ale jeśli napiszę, że mowa tutaj o Ancie i Mavenie to każdy zrozumie o co chodzi.
  • systemy kontroli wersji – tutaj póki co mam zastrzeżenia. O ile jeszcze jestem w stanie zrozumieć wybór SVNa, o tyle omawianie CVS mija się z celem. Nie lepiej było poruszyć coś bardziej nowoczesnego – GITa, Mercuriala czy Bazarek?
  • Continous Integration – temat mi również ostatnio bliski, gdyż w pracy zaczęliśmy używać od pewnego czasu PHPUnderControl. Rzecz na początku wkurzająca, z czasem staje się wręcz naturalnym elementem tworzenia aplikacji. Autorzy rzecz jasna koncentrują się na Javie – Continuum, Openfire, Hudson i CruiseControl.
  • Testy – wspominałem, że może to narzędzia, a nie konkretne biblioteki, no ale testy są w sumie narzędziem wspomagającym programistów, a nie kolejną rzeczą do doklepania. O testach sporo się pisze, gdyż mowa zarówno o testach jednostkowych (JUnit, TestNG), jak i integracyjnych oraz wydajnościowych. Duuużo stron do przeczytania.
  • Mierzenie poziomu jakości aplikacji – to temat cokolwiek drażliwy dla programistów. Co robić by kod był ładniejszy? Albo miał mniej błędów? Tutaj można sobie o tym poczytać.
  • Narzędzia do zgłaszania błędów – każdy kto uważa, że pisanie komentarzy do TODO w BaseCampie nie jest skuteczną metodą pracy z błędami z pewnością powinien zajrzeć – Trac i Bugzilla to narzędzia omawiane w tej sekcji.
  • Dokumentacja – wszystkim jest potrzebna, wszyscy chcieliby aby istniała, nikt za to nie chce jej tworzyć. Tak, tak, to o dokumentacji mowa. Okazuje się, że w tej syzyfowej pracy nie jesteśmy skazani na porażkę!

Na koniec tego szybkiego przedstawienia należy jednakże wspomnieć o dwóch felerach. Pierwszy to cena – choć niestety za wiedzę trzeba płacić, a tutaj wiedzy mamy całkiem sporo. Drugi zarzut to momentami dość poważne niedopatrzenia w kodzie prezentowanych przykładów. Zdarza się tak, że mowa o jakimś nowym ficzerze, zaś w prezentowanym listingu owego ficzera ani widu, ani słychu. Może to i metoda wspomagania zapamiętywania, jednakże w porządnie wydanej książce nie powinny takie rzeczy mieć miejsca. Mimo to – warto!