piątek, 30 stycznia 2009

Encje, Managery i MBeany Część 3

Witam!
Pozostały nam już tylko:

Managed Beany

Są to beany warstwy webowej. Służą przede wszystkim do przechowywania danych, zarówno tych wprowadzonych przez użytkownika w formularzach, jak i innych potrzebnych przy wyświetlaniu naszych stron, pobranych na przykład z bazy danych.
Co wydaje mi się być warte podkreślenia, NIE MA TUTAJ ŻADNYCH BEZPOŚREDNICH ODNIESIEŃ DO BAZY DANYCH!
Nie definiujemy tutaj żadnych relacji, zapytań, entityManagere-ów, inicjalizacji "Lazy" pól.
Po prostu niczego co by w sposób bezpośredni sczytywało lub zapisywało dane do bazy.
Wszystkie te czynności powinny być wykonywane przez Session Beany lub encje, a MBeany tylko z nich korzystać.
Mamy trzy rodzaje MBeanów: session, request, application. Nazwy te oznaczają zasięg (scope) w jakim one istnieją.

Przykładowy MBean mógłby wyglądać tak:
public class OsobaBean {

  /* Definicje wymaganych przez nas Session Beanów z warstwy biznesowej
   * Adnotacja @EJB musi się znaleźc przy każdym z osobna!
   */
  @EJB
  protected OsobaManagerLocal om; /* Podajemy nazwe interfejsu nie implementacji! */

  /* Pola MBeana powinny byc prywatne */
  private String imie;
  private String login;

  /* Metody wykorzystujące metody biznesowe.
   * Ich implementacje znajdziecie w poprzedniej części cyklu.
   */
  public void zarejestruj(){
    Osoba o = new Osoba(); /* Powiedzmy, że Osoba jest prostą encją mającą tylko dwa pola */
    o.setImie(imie);
    o.setLogin(login);
    om.dodajUsera(o);
  }

  public void usun(){
    Osoba o = new Osoba();
    o.setImie(imie);
    o.setLogin(login);
    om.usunUsera(o);
  }

  public List<Osoba> szukaj(String login){
    return om.szukajUsera(login);
  }

  /* Skoro pola są prywatne to konieczne są settery gettery
   * (chyba, że nie chcemy by były dostępne na stronach)
   */
  public void setImie(String imie) {
    this.imie = imie;
  }

  public String getImie() {
    return imie;
  }

  public String getLogin() {
    return login;
  }

  public void setLogin(String login) {
    this.login = login;
  }

}
Więc idąc od góry mamy...
Adnotacja @EJB pozwala "wstrzykiwać" Managery do naszych MBeanów. Wystarczy podać Interfejs, a implementacja zostanie automatycznie znaleziona.
Pola mogą być zarówno typami prostymi jak i klasami czy encjami nie ma tutaj ograniczeń. Powinny być jednak prywatne. Settery i gettery i tak będą musiały być stworzone jeśli chcemy by strona mogła się z naszym MBeanem komunikować.
Metody, zależnie od tego do czego służą mogą zwracać dowolne typy. Jednak warto zauważyć, że można w faces-config.xml zdefiniować przekierowania (navigation-rule) do innych podstron, na podstawie zwracanego przez metodę String-a.

Aby móc korzystać z naszego MBean-a należy dodać jeszcze wpis do faces-config.xml:
<managed-bean>
  <managed-bean-name>osobaBean</managed-bean-name>
  <managed-bean-class>com.blogspot.javazpiwnicy.bean.OsobaBean</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
managed-bean-name jest to nazwa przez, którą będziemy mogli odwoływać się na stronach do naszego MBean-a.
managed-bean-class klasa naszego beana.
managed-bean-scope czyli wspomniany wcześniej zasięg istnienia naszego beana.

Teraz już na naszych stronach możemy odwoływać się do naszych pól i metod przez EL.
Naprzykład <h:outputText value="#{osobaBean.imie}" /> czy <h:inputText value="#{osobaBean.imie}" />.
Można też w pewien sposób oszukać MBean-a. Jeśli chcemy zwracać jakąś wartość bezpośrednio z bazy i nie przechowywać jej w MBean-ie wystarczy stworzyć metodę przedrostkiem get.
Przykładowo public String getAllImiona() i odwoływać się do niej przez #{osobaBean.allImiona}.

Zazwyczaj metody wykorzystujemy w formularzach jako akcje przycisków.
<h:commandButton value="Zarejestruj" action="#{osobaBean.zarejestruj}" />

To już koniec tej trylogii ;)
Miłego pisania i zapraszam do komentowania.

środa, 28 stycznia 2009

Dostęp do JBoss-a spoza komputera lokalnego

Dzisiaj postanowiłem pokazać wynik mojej pracy koledze. Niestety po wpisaniu przez niego adresu http://<moj_ip>:8080/itd pokazywała się informacja o niemożliwości połączenia. Po chwili poszukiwania okazało się, że problem można łatwo rozwiązać. Wystarczy dodać parametr "-b 0.0.0.0" do parametrów uruchamiania serwera. Jeśli mamy Eclipsa z wtyczka od Jbossa klikamy 2x na nasz serwer->Open launch configuration i wpisujemy powyższy parametr do arguments w miejsce "-b localhost". Czyniąc to powinniśmy pamiętać o bezpieczeństwie naszego serwera, w tym celu zachęcam do zapoznania się z tą stroną.

Dominik:
U mnie okazało się to być niewystarczające i w ustawieniach serwera (2x click na serwerze) musiałem zamienić Host name z localhost na 0.0.0.0 .
Pozdrawiam

sobota, 24 stycznia 2009

Hibernate Validator + DataSource

Witam, dziś będzie o dostępie do dany z bazy, w hibernate validatorach. Przed przeczytaniem polecam zajrzeć na jeden z poprzednich postów, link. Pomysł, aby tym się zająć zrodził się podczas tworzenia formularza rejestracyjnego w moim projekcie. Uznałem, że fajnie byłoby, gdyby automatycznie po wpisaniu jakiegoś loginu, validator sprawdzał czy takowy istnieje już w naszej bazie i informował nas o tym.
Musimy zacząć od zmodyfikowania pliku persistance.xml, dodamy nowe property, dzięki któremy będziemy mogli pobrać obiekt typu EntityManagerFactory z pomocą JNDI. Ten zaś z kolei da nam dostęp do EntityManagera. Tak więc dodajemy wpis:

...
<properties>
...
<property name="jboss.entity.manager.factory.jndi.name" value="java:/myEntityManagerFactory"/>
</properties>
</persistence-unit>

Tworzymy teraz nowy validator:

@Documented
@Target(FIELD)
@Retention(RUNTIME)
@ValidatorClass(UsernameValidator.class)
public @interface Username {
String message() default "Podany login juz istnieje";
}

oraz

public class UsernameValidator implements Validator<Username>, Serializable
{
private static final long serialVersionUID = 1L;
EntityManager entityManager;

public void initialize(Username parameters)
{
}

public boolean isValid(Object value)
{
Context ctx = null;
String login = (String) value;
try
{
ctx = new InitialContext();
} catch (NamingException e)
{
e.printStackTrace();
}
EntityManagerFactory entityManagerFactory = null;
try
{
entityManagerFactory = (EntityManagerFactory) ctx.lookup("java:/myEntityManagerFactory");
} catch (NamingException e)
{
e.printStackTrace();
}
entityManager = entityManagerFactory.createEntityManager();

Query q = entityManager.createQuery("FROM Osoba WHERE login = '" + login + "'");
try
{
q.getSingleResult();
return false;
} catch (final NoResultException e)
{
return true;
}
}
}

Jak widzimy używając JNDI pobieramy EntityManagerFactory. Z pomocą entityManagera tworzymy zapytanie, a w bloku try catch, próbujemy uzyskać wynik tego zapytania, jeżeli metoda wyrzuci wyjątek znaczy to, że podany login nie istnieje w bazie. Zakładam, że loginy przechoowywane sa w tabeli Osoba w kolumnie login. Aby skorzystać z naszego validatora wystarczy nad odpowiednim polem dać adnotacje @Username.

czwartek, 22 stycznia 2009

Encje, Managery i MBeany Część 2

Witam
Bez zbędnego gadania zapraszam do kolejnej części cyklu. Dzisiaj będą:

Managery

Są to beany warstwy EJB, poprawniej byłoby je nazwać Session Beanami (jakieś inne nazwy?).
Nazwałem je managerami ze względu na pewną konwencje nazewniczą, która nakazuje nazywać je np OsobaManager.
Dostarczają metod do komunikacji z bazą danych (zapis, odczyt, modyfikacje).
Tak jak poprzednio proponuje zrobić osobną paczkę (np. pl.dzmitrow.beany ).

Manager to tak naprawdę dwa pliki. Pierwszym jest interfejs:
@Local
public interface OsobaManagerLocal {

public void dodajUsera(Osoba osoba);
public void usunUsera(Osoba osoba);
public List<Osoba> szukajUserow(String login);

}
Od razu widać, że klasa, która będzie implementować ten interfejs będzie dostarczać możliwość dodawania, usuwania i szukania userów (encja Osoba).
Adnotacja @Local mówi, że implementacja metod może być wykorzystywana tylko przez inne klasy tego EAR-a (jeżeli się mylę poprawcie mnie).
Istnieje również @Remote dająca możliwość udostępniania metod. Wykonana przez nas implementacja będzie dostarczana przez serwer aplikacji pod odpowiednią nazwą JNDI.
Nie będę się rozpisywał na temat @Remote, gdyż temat zasługuje na osobnego posta.
Konwencja nakazuje zaznaczać w nazwie czy dany interfejs dotyczy zdalnego czy lokalnego użytku (stąd OsobaManagerLocal).

Przejdźmy do drugiego pliku, implementującego ten interfejs:
@Stateless
public class OsobaManager implements OsobaManagerLocal {

@PersistenceContext(unitName="ProjektEJB")
EntityManager em;
...
}
@Stateless jak sama nazwa wskazuje wskazuje na bez stanowość beana (nie posiada on żadnych pól!).
Są również @Statefull, ale nie mam z nimi żadnego doświadczenia więc je pominę.
Zawiera jedynie definicje metod umożliwiających komunikacje z bazą. Poza warstwą EJB udostępnione są tylko te metody, które są podane w interfejsie.
@PeristenceContext, z którego persistence unit (o ile mamy ich zdefiniowanych więcej) ma korzystać entity manager (nazwę zdefiniowaliśmy w persistence.xml).
EntityManager em; pobiera entity managera ze zdefiniowanego kontekstu.
Niżej powinny oczywiście się znaleźć podane w interfejsie metody.

Zaimplementujmy dodawanie userów:
@Override
public void dodajUsera(Osoba osoba) {
em.persist(osoba);
}
Widzimy, że została wykorzystana metoda entity managera persist. Powoduje ona zapisanie do bazy danych instancji encji (w tym wypadku encji Osoba).
Pojawiła się też adnotacja @Override, większości z nas powinna być znana z standardowej javy. Oznacza ona metodę jako implementacje metody z interfejsu (lub nadpisanie metody z klasy, po której dziedziczymy).
Dzięki temu możemy być pewni, że pozostanie spójność naszej implementacji z interfejsem.
Drobna uwaga. Przy tworzeniu obiektu osoba nie ustawiamy id! Zostanie on automatycznie dodany (o ile encja została tak skonfigurowana) przy zapisywaniu do bazy danych.

Zaimplementujmy teraz usuwanie:
@Override
public void usunUsera(Osoba osoba) {

Osoba o = em.merge(osoba);
em.remove(o);
em.flush();
}
Pojawiło się kilka nowych metod entity managera.
merge - bardzo ważna metoda gdyż odpowiada za kilka spraw. Po pierwsze odpowiada za wciąganie instancji encji
do kontekstu entity managera. To znaczy, jeżeli taki obiekt istnieje w bazie danych to nasza instancja będzie z nim skojarzona.
Po drugie, jeśli obiekt był już w kontekście i zostały wprowadzone w nim zmiany to zostaną one zapisane do bazy danych. W przeciwnym wypadku zostanie on stworzony (jednak nie zaleca się z korzystania z merge zamiast persist).
remove(obiekt) - usuwa obiekt z bazy danych. Obiekt musi być w kontekście!
flush() - niektóre operacje na bazie danych mogą być kolejkowane. Jeżeli chcemy być pewni by nasze polecenie zostało wykonane od razu wykonujemy flush() i wszystkie zmiany zostają zapisane.

Pozostało nam zaimplementować szukanie:
@SuppressWarnings("unchecked")
@Override
public List<Osoba> szukajUsera(String userName) {

Query query = em.createQuery("SELECT o FROM Osoba AS o where o.login like :login");
query.setParameter("login",userName+"%");

return query.getResultList();
}
lub może trochę mniej bezpieczny, ale krótszy kod:
@SuppressWarnings("unchecked")
public List<Osoba> szukajUsera(String userName) {
return (em.createQuery("SELECT o FROM Osoba AS o where o.login like '"+userName+"%'")).getResultList();
}
Znów coś nowego. Query jest to klasa pozwalająca na wykonywanie poleceń na bazie danych w języku PODOBNYM do SQL.
Przykładem może byc Hibernate-owy HQL (Hibernate Query Language). Opis jak konstruować zapytania można znaleźć np. w dokumentacji hibernate-a.
W naszym przykładzie zapytanie tłumacząc na ludzki mówi:" z tabeli zawierającej ENCJE Osoba (FROM Osoba) pobierz takie instancje (SELECT o), których login jest jak zawartość zmiennej login (like :login)".
Po czym za login podstawiamy userName+"%" query.setParameter("login",userName+"%");.
Dzięki +"%" zwrócone będą osoby, których loginy zaczynają się od zawartości zmiennej userName. Korzystanie z setParameter() może być trochę mniej wygodne niż drugi przykład, ale jest bezpieczniejsze gdyż zmienne w ten sposób wprowadzane zmienne są czyszczone z potencjalnie niebezpiecznych wartości. Chroniąc nas w ten sposób przed atakami typu "SQL Injection".
FROM Osoba AS o pozwala na odnoszenie się w zapytaniu do pól osoby przez o.nazwaPola.
Należy zapamiętać, że FROM odnosi się do nazwy encji, a nie do nazwy tabeli.
query.getResultList() zwraca wynik zapytania jako listę. Jako, że metoda ta zwraca List, a nie List<Osoba> kompilator domaga się sprawdzenia czy zwracanie jest poprawne.
Niestety nic sensownego nie wymyśliłem poza zmienną lokalną lub dodaniem @SuppressWarnings("unchecked") (co zostało zastosowane). Jeżeli ktoś ma lepszy pomysł proszę o komentarz.
Oczywiście nie zawsze trzeba zwracać listę. Można zawsze zwrócić pojedynczy wynik przez getSingleResult(); (zwracany jest obiekt klasy Object więc konieczne są rzutowania).
Istnieją jeszcze "Named Query", ale co to i jak tego używać pozostawiam na pastwę ciekawości czytelnika.

Na deser sprawa poruszona w poprzednim poście czyli inicjalizacja pól z fetch ustawionym na FetchType.LAZY.
W hibernacie wystarczy wykonać:
Hibernate.initialize(d.getProdukty());
W ten sposób zostanie zainicjalizowane pole produkty i będziemy mogli pobrać jego zawartość (jeżeli ktoś wie jak zrobić to w OpenJPA proszę pisać w komentarzach).
To by było chyba wszystko.
Jak zwykle zapraszam do komentowania.

Nowy autor

Powitajmy w naszych szeregach nowego autora, Szymona Szypulskiego <fanfary>
Oby to była owocna współpraca ^^
Pozdrawiam

środa, 21 stycznia 2009

Encje, Managery i MBeany Część 1

Witam
Zauważyłem ze niektórzy mają z tym problem więc postaram się przybliżyć jakie elementy powinny się znaleźć w encjach, managerach, i mbeanach (managery i mbeany w następnych postach).

Encje

Są one elementami warstwy EJB i warto przechowywać je w osobnym pakiecie (np pl.dzmitrow.projekt.encje).
W encjach umieszczamy definicje tabel i powiązania między nimi.
Tworzymy je jako zwykłe klasy javowe implementujące Serializable, z odpowiednimi adnotacjami.

Każda encja musi posiadać adnotacje @Entity i implementować interfejs Serializable
@Entity
public class Kategoria implements Serializable{
  private static final long serialVersionUID = 8211793733544613374L; /* dowolna liczba z L na koncu */
...
}
Oczywiście, by były przechowywane jakieś dane musimy zdefiniować odpowiednie pola np. wcześniej podana klasa Kategoria ma pola:
...
private int id;
private String nazwa;
private List<Produkt> produkty; /*definicja tej encji nie obchodzi nas w tym miejscu */
...
Pola powinny być prywatne ze względów bezpieczeństwa, a skoro są prywatne to nie ma do nich dostępu z zewnątrz bez setterów/getterów.
Możemy je wpisać ręcznie... , albo użyć Eclipsowego narzędzia. Aby wygenerować w otwartym pliku klikamy prawym w dowolnym miejscu i wybieramy Source -> Generate Getters and Setters.
Wybieramy pola dla których chcemy je utworzyć, klikamy OK i gotowe.

Musimy teraz zdefiniować, które pole jest kluczem głównym naszej encji. Zapewne jest to id więc przy getId() dodajemy:
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
  return id;
}
Ogólnie większość definicji w encji podaje się przy getterach i tylko one będą się pojawiać w poście.
Co zrobiliśmy? Powiedzieliśmy, że id ma być kluczem głównym (@Id) oraz klucze mają by unikalne i generowane automatycznie ( @GeneratedValue(strategy=GenerationType.IDENTITY) )

Możemy ustawić również unikalność pól. Dzięki czemu choćbyśmy chcieli nie będzie dwóch kategorii i takiej samej nazwie.
Są na to dwa sposoby. Pierwszy to:
@Entity
@Table(
name="Kategoria",
uniqueConstraints={@UniqueConstraint(columnNames={"id"}),@UniqueConstraint(columnNames={"nazwa"})})
public class Kategoria implements Serializable{
...
Przy czym warto zauważyć, że @UniqueConstraint(columnNames={"id", "nazwa"}) oznaczałoby unikalność pary a nie nazwy i id z osobna.
Drugi to dodać odpowiednią adnotacje przy getterze pola:
@Column(unique=true) /* takich opcji jest dużo więcej! */
public String getNazwa() {
  return nazwa;
}
Ważne jest by od razu zdefiniować wszystkie unikalności i inne właściwości, gdyż zmiany wejdą w życie tylko przy ponownym utworzeniu tabeli.

Jeszcze jedna drobna uwaga zanim przejdziemy do relacji między encjami.
Gdy tworzymy jakieś metody w encjach nie będące setterami/getterami, a nie chcemy dostawać warningów, należy oznaczyć je adnotacją @Transient będą one ignorowane przez EntityManager-a.

Teraz najgorszy koszmar definicje powiązań między encjami ;).
Do dyspozycji mamy następujące powiązania:
  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany
Definiujemy je jak poprzednio przy getterach np. w encji Osoba mogłoby się znaleźć coś takiego:
@OneToOne(cascade={CascadeType.ALL}, fetch=FetchType.LAZY)
public Adres getAdres() {
  return adres;
}
Rzecz jasna wystarczyłoby samo @OneToOne, ale przy okazji wytłumaczę co to jest fetch (cascade zostało wytłumaczone w tym poście).
fetch ma dwie możliwe wartości FetchType.LAZY i FetchType.EAGER . Są one odpowiednio predefiniowane dla pewnych połączeń, jednak nie będę teraz tu wszystkich wymieniał i odeśle do dokumentacji Hibernate-a.
Pierwszy pozwala na odroczenie pobrania danych pola do wybranego przez nas momentu, a drugi ustala by dane były pobierane od razu z całym obiektem. Przy czym przy używaniu Lazy trzeba w odpowiedni sposób zainicjalizować pole (o czym będzie w poście o managerach).

Gdy chcemy zdefiniować połączenie jednostronne @OneToMany warto zrobić tabele łączącą:
@OneToMany
@JoinTable(name="KATEGORIA_PRODUKT",
 joinColumns={@JoinColumn(name="KATEGORIA_ID")},
 inverseJoinColumns={@JoinColumn(name="PRODUKT_ID")})
public List getProdukty() {
  return produkty;
}
Gdy chcemy zdefiniować dwustronnie takie połączenie to (jak przy każdym dwustronnym) musimy zdefiniować stronę posiadającą.
Najłatwiej (w tym przypadku) jest zrobić jako stronę posiadającą @ManyToOne .
Jako, że chcę połączyć produkty z kategoriami wpierw strona trzymająca czyli produkty:
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="kategoria_fk")
public Kategoria getKategoria() {
  return kategoria;
}
A w encji Kategoria:
@OneToMany(mappedBy="kategoria", fetch=FetchType.LAZY)
public List getProdukty() {
  return produkty;
}
Co to jest to mappedBy? Kompilator przez to rozumie, że definicja połączenia znajduje się po drugiej stronie (w encji Produkt) i jest określona dla pola kategoria. Fetch został ustawiony jako lazy bo pobieranie wszystkich produktów z danej kategorii zabiera sporo czasu a nie zawsze potrzebujemy mieć do nich dostęp.

W drugą stronę robi się zupełnie inaczej i ponownie odsyłam do dokumentacji hibernate-a.

Po zdefiniowaniu encji nigdzie więcej nie musimy ponownie ustawiać zależności i innych właściwości w managerach czy mbeanach.

Wszystkie adnotacje wykorzystane w tym poście są wytłumaczone i wszystkie ich właściwości podane w powyższej dokumentacji.
Zachęcam do komentowania i zadawania pytań.

poniedziałek, 19 stycznia 2009

Co nieco o Hibernate Validator

Witam, dziś parę słów na temat walidacji, a dokładniej walidacji przy użyciu Hibernate Validator. Walidacja jest niezwykle przydatną funkcją podczas tworzenia wszelkiego rodzaju formularzy, możemy kontrolować to, co użytkownik może wprowadzić. Jeśli chodzi o walidatory JSF to dobry tutorial znajduje się tutaj. My jednak będziemy zajmować się narzędzie od Hibernate'a. Stronę projektu znajdziecie tutaj, zaś tutaj dokumentacje. Jeśli chodzi o jakieś dodatkowe JARy to w JBossie nie martwimy się o nie, zaś w Geronimo możliwe, że trzeba je będzie ściągnąć ze stronki Hibernate. W poprzednim poście napisałem nieco o RichFaces, więc teraz użyjemy go tworząc bardzo prosty formularz.

Jeśli chodzi o plik web.xml to nie zmienił się on od pooprzeniego razu.

Przejdźmy do zdefiniowania mbeana:

package com.blogspot.javazpiwnicy.mbeans;

import java.util.Date;

import org.hibernate.validator.Email;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotEmpty;
import org.hibernate.validator.Past;
import org.hibernate.validator.Pattern;

import com.blogspot.javazpiwnicy.validator.Urodziny;

public class Osoba {
@NotEmpty(message = "Pole nie moze byc puste")
@Length(min = 2, max=15, message = "Minimalna dlugosc to 2, maksymalna 15")
private String login;

@NotEmpty(message = "Pole nie moze byc puste")
@Email(message = "To nie jest adres email")
private String email;

@Pattern(regex = "[0-9]{2}( |-)?[0-9]{3}", message = "Niepoprawny kod pocztowy")
private String kodPocztowy;

@Past(message = "Data nie jest z przeszlosci")
@Urodziny
private Date urodziny;

public String getLogin() {
return login;
}

public void setLogin(String login) {
this.login = login;
}

public String getEmail() {
return email;
}

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

public String getKodPocztowy() {
return kodPocztowy;
}

public void setKodPocztowy(String kodPocztowy) {
this.kodPocztowy = kodPocztowy;
}

public Date getUrodziny() {
return urodziny;
}

public void setUrodziny(Date urodziny) {
this.urodziny = urodziny;
}

public String dodaj() {
return "success";
}
}

Widzimy tutaj nad poszczególnymi polami Osoby różne adnotacje. Ich opis można znaleźć w dokumentacji. Do wyświetlania informacji służy "message", bez tego użyte zostaną standardowe informacje o błędzie. Oczywiście można te informacje pobierać z plików properties wystarczy wpisać "message={naszKluczWPliku}". Adnotacja Urodziny to nasz własny walidator sprawdzający czy osoba ma 18 lat.
Przejdźmy teraz do stworzenia własnego walidatora. Najpierw tworzymy deskryptor(adnotacja):

package com.blogspot.javazpiwnicy.validator;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.hibernate.validator.ValidatorClass;

@Documented
@Target(FIELD)
@Retention(RUNTIME)
@ValidatorClass(UrodzinyValidator.class)
public @interface Urodziny {
String message() default "Nie masz 18 lat";
}

A następnie plik walidatora:

package com.blogspot.javazpiwnicy.validator;

import java.util.Calendar;
import java.util.Date;
import org.hibernate.validator.Validator;

public class UrodzinyValidator implements Validator<Urodziny>{

public void initialize(Urodziny parameters) {
}

public boolean isValid(Object value){
Calendar dataUrodzin = Calendar.getInstance();
dataUrodzin.setTime((Date)value);
Calendar kiedys = Calendar.getInstance();
kiedys.add(Calendar.YEAR, -18);

if(dataUrodzin.before(new GregorianCalendar(kiedys.get(Calendar.YEAR),
kiedys.get(Calendar.MONTH), kiedys.get(Calendar.DAY_OF_MONTH))))
return true;
return false;
}
}
Dziękuje przy okazji Szymonowi za wskazówkę dotyczącą Calendar'a.
Tak więc zostaje nam jeszcze napisanie formularza:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@ taglib uri="http://richfaces.org/a4j" prefix="a4j"%>
<%@ taglib uri="http://richfaces.org/rich" prefix="rich"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>javazpiwnicy.blogspot.com</title>
</head>
<body>
<f:view>
<center><rich:panel header="Formularzyk" style="width: 400px">
<a4j:form>
<h:panelGrid columns="3">
<h:outputText value="Login" />
<h:inputText id="login" value="#{osoba.login}">
<rich:ajaxValidator event="onblur" />
</h:inputText>
<rich:message for="login"/>

<h:outputText value="Email" />
<h:inputText id="poczta" value="#{osoba.email}">
<rich:ajaxValidator event="onblur" />
</h:inputText>
<rich:message for="poczta"/>

<h:outputText value="Kod pocztowy" />
<h:inputText id="kod" value="#{osoba.kodPocztowy}">
<rich:ajaxValidator event="onblur" />
</h:inputText>
<rich:message for="kod"/>

<h:outputText value="Data urodzin" />
<rich:calendar id="dataUrodzenia" value="#{osoba.urodziny}">
<rich:ajaxValidator event="onclick" />
</rich:calendar>
<rich:message for="dataUrodzenia" />

<h:outputText value="" />
<a4j:commandButton value="Dodaj" action="#{osoba.dodaj}" />
</h:panelGrid>
</a4j:form>
</rich:panel></center>
</f:view>
</body>
</html>

I to byłoby na tyle. Po wpisaniu błędnych danych w formularzu powinna pojawić się stosowna informacja.

Pobieranie loginu z kontekstu

Podobno wiele osób o to prosiło (jak coś chcecie to bezposrednio do nas, nie gryziemy ^^), więc zamieszczam kod autorstwa Szymona Szypulskiego, pozwalający wyciągnąć z kontekstu login aktualnie zalogowanego użytkownika. Jeżeli chcemy używać tej funkcji musi być w beanie zdefinowane pole login, inaczej przy używaniu jej możemy dostać error o treści Property not found.
public String getLogin() {
FacesContext kontekst = FacesContext.getCurrentInstance();
ExternalContext ekontekst = kontekst.getExternalContext();
String user = ekontekst.getRemoteUser();
if( user != null)
return user;
return null;
}

Możemy też skorzystać z kodu zaproponowanego przez Bartka:
Principal user = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
String login = user.toString();


To by było chyba na tyle jeśli idzie o pobieranie loginu.
Pozdrawiam i zapraszam do komentawania

Drzazga:
Od siebie dodam jeszcze sprawdzanie czy user jest zalogowany w określonej roli:

public boolean getIsInAdminRole() {
return FacesContext.getCurrentInstance().getExternalContext().isUserInRole("admin");
}

piątek, 16 stycznia 2009

Pierwsze kroki z RichFaces

Witam, dzisiaj zajmiemy się ciekawym zagadnieniem, jakim jest biblioteka Richfaces. Umożliwia ona nam m.in. łatwą integrację naszej aplikacji z Ajax-em, czy stworzenie ładnego layoutu strony. Dokładny opis co jeszcze potrafi znajdziecie tutaj . Głównym konkurentem Richfaces-a na rynku jest IceFaces, ich zestawienie można znaleźć tutaj. Tak więc chcąc rozpocząć pracę musimy sciągnać biblioteke na dysk, pobieramy stąd potrzebne pliki. Następnie potrzebne nam będą jeszcze 2 pliki, a dokładnie commons-digester-1.8.jar oraz commons-beanutils-1.8.0.jar

Na początku dodajmy potrzebne pliki jar do projektu, a więc wrzucamy do katalogu WebContent/WEB-INF/lib pliki z katalogu lib RichFaces-a, jak również ściągniete pliki commons-digester-1.8.jar oraz commons-beanutils-1.8.0.jar.

Teraz zdefiniujmy plik web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>JavaZPiwnicy-Richfaces</display-name>

<context-param>
<param-name>org.richfaces.SKIN</param-name>
<param-value>blueSky</param-value>
</context-param>

<filter>
<display-name>RichFaces Filter</display-name>
<filter-name>richfaces</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
</filter>

<filter-mapping>
<filter-name>richfaces</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>

<listener>
<listener-class>com.sun.faces.config.ConfigureListener</listener-class>
</listener>

<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
</web-app>
Stwórzmy bardzo prostego mbean-a:

package com.blogspot.javazpiwnicy;

public class Osoba {
private String imie;

public String getImie() {
return imie;
}

public void setImie(String imie) {
this.imie = imie;
}
}
Następnie dopisujemy do faces-config.xml

<managed-bean>
<managed-bean-name>osoba</managed-bean-name>
<managed-bean-class>com.blogspot.javazpiwnicy.Osoba</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
I ostatnie co nam zostało to prosta strona jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@ taglib uri="http://richfaces.org/a4j" prefix="a4j"%>
<%@ taglib uri="http://richfaces.org/rich" prefix="rich"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Witaj ;)</title>
</head>
<body>
<f:view>
<center><rich:panel header="Powitanie" style="width: 400px">
<a4j:form>
<h:panelGroup>
<h:panelGrid columns="3">
<h:outputText value="Podaj imie" />
<h:inputText value="#{osoba.imie}" />
<a4j:commandButton value="Zatwierdz" reRender="powitanie" />
</h:panelGrid>
</h:panelGroup>
</a4j:form>

<h:panelGroup id="powitanie">
<h:outputText value="Witaj " rendered="#{not empty osoba.imie}"/>
<h:outputText value="#{osoba.imie}" />
</h:panelGroup>

</rich:panel></center>
</f:view>
</body>
</html>
Na początku pliku dodaliśmy nowe tagliby, abyśmy mogli korzystac z dobrodziejstw RichFaces i Ajaxa

<%@ taglib uri="http://richfaces.org/a4j" prefix="a4j"%>
<%@ taglib uri="http://richfaces.org/rich" prefix="rich"%>
Stworzyliśmy prostą stronę witającą użytkownika po wpisaniu imienia. atrybut reRender commandButton'a powoduje ponowne wyrenderowanie komponentu o wskazanym id. Napis "Witaj" wyświetli się w naszym przypadku tylko wtedy, kiedy po kliknięciu na Zatwiedz pole osoby nie będzie puste.
W celu dalszego dokształcania się polecam stronę z liveDemo

piątek, 9 stycznia 2009

Kaskady pól Encji

Witam.
Każdy z nas pisząc encje z wykorzystaniem adnotacji, używał do określania relacji między nimi @OneToMany, @OneToOne i ustawiał kaskadę (jak w większości przykładów) cascade={CascadeType.ALL} (czyli przykładowo @OneToMany( cascade={CascadeType.ALL} ). Powoduje ona "przenoszenie" komend z obiektu, na którym wywołaliśmy polecenia EntityManger-a (persist, merge, remove) na zawartość pól kaskadowanych. Wszystko jest dobrze dopóki aplikacje i powiązania między encjami są proste. Jeżeli mamy na przykład sklepik z encjami Osoba i Bilet i ustawimy pełną kaskadowość. To przy usuwaniu osoby usuniemy też wszystkie bilety tej osoby. W tym wypadku wystarczy wyłączyć kaskadowość, ale w większych projektach może to być za mało.
Zobaczmy więc co za typy kaskadowania kryją się pod CascadeType.ALL.
Najważniejsze to:
  • CascadeType.PERSIST

  • CascadeType.MERGE

  • CascadeType.REMOVE

Pełna lista pod http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/#entity-hibspec-cascade

Tak Więc jeśli chcemy by wraz z stworzeniem(w bazie) jakiegoś obiektu wiązało się z stworzeniem wszystkich jego pól "encyjnych" ustawiamy CascadeType.PERSIST gdy chcemy by update lub pobranie elementu powodowało update wszystkich pól, które są związane z encjami ustawiamy CascadeType.MERGE. Gdy chcemy ustawić kilka typów kaskadowania naraz wpisujemy je po przecinku. Przykładowo @OneToMany( cascade={CascadeType.MERGE, CascadeType.PERSIST})

W razie problemów z adnotacjami polecam wcześniej już podlinkowany poradnik do Hibernate-a http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/. Będzie również pomocny przy korzystaniu z OpenJPA.
To tyle co miałem do powiedzenia jeżeli idzie o kaskadowanie.
Pytania (lub propozycje co do artykułów) pisać w komentarzach.
Pozdrawiam

poniedziałek, 5 stycznia 2009

Zabezpieczenie strony przy użyciu JAAS

Odpowiednie zabezpieczenie strony jest bardzo ważnym aspektem pisania projektu, nie chcemy przecież, aby przypadkowe osoby miały dostęp do funkcji zarządzających naszą strona/baza danych. W celu uwierzytelnienia i autoryzacji użytkowników posłużymy się narzędziem Java Authentication and Authorization Service, w skrócie JAAS.
Zacznijmy od zdefiniowania połączenia z naszą bazą, to co opisywał Dominik w swoim poście. Mój plik mysql-ds.xml wygląda następująco:

<datasources>
<local-tx-datasource>
<jndi-name>myBase</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/sklep</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password></password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>

Następnie w pliku <nasz_jboss>/server/default/conf/login-config.xml dodajemy wpis:

<application-policy name="sklepDomain">
<authentication>
<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
<module-option name="dsJndiName">java:/myBase</module-option>
<module-option name="principalsQuery">select password from osoba where login=?</module-option>
<module-option name="rolesQuery">select rola,'Roles' from role where login=? </module-option>
<module-option name="debug">true</module-option>
</login-module>
</authentication>
</application-policy>

Widzimy w tym pliku zapytania do bazy danych, tak więc przydałoby się teraz zdefiniować odpowiednie encje. W moim przypadku mamy 2 tabele, pierwsza przechowuje login oraz hasło usera, a druga login i role tego usera. Tak wiec encja Osoba musi zawierać pola tekstowe: login oraz password, a encja Role pola: login oraz rola.
Przechodzimy teraz do warstwy web'owej naszej aplikacji. W folderze WEB-INF tworzymy plik jboss-web.xml i wpisujemy:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<security-domain>java:/jaas/sklepDomain</security-domain>
</jboss-web>

Przy czym "sklepDomain" pochodzi z pliku login-config.xml
Teraz musimy zdefiniować które obszary naszej strony będą chronione oraz kto będzie miał do nich dostęp, a także gdzie znajdują się strony do logowania oraz w przypadku błędu logowania strona z błędem, trzeba także poinformować jakie role mamy zdefiniowane w naszej aplikacji. W tym celu w pliku web.xml dodajemy:

<security-constraint>
<web-resource-collection>
<web-resource-name>WRC1</web-resource-name>
<url-pattern>/faces/protected/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>

<login-config>
<auth-method>FORM</auth-method>
<realm-name>sklepDomain</realm-name>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/loginError.html</form-error-page>
</form-login-config>
</login-config>

<security-role>
<role-name>admin</role-name>
</security-role>

Nie pozostaje nam nic innego jak napisać formularz logowania:

<html>
<body>
<form action="j_security_check" method="post">
<input type="text" name="j_username" /><br />
<input type="password" name="j_password" /><br />
<input type="submit" value="login" />
</form>
</body>
</html>

I to wszystko :) Teraz próbując wejść na którąkolwiek ze stron z folderu protected zostaniemy przeniesieni na stronę logowania, tylko osoba z rola admin będzie mogła tam wejść.

update:
Razem z Dominikiem wykminiliśmy jak jak obsługiwać hasło haszowane algorytmem md5.
Dodajemy do pliku login-config.xml:

<module-option name="hashAlgorithm">MD5</module-option>
<module-option name="hashEncoding">base64</module-option>

Zmodyfikujemy także nieco funkcje podaną w poprzednim poście, która haszowala nasze hasło.
Teraz będzie wyglądać następująco:

private String makeMD5(String pass) throws NoSuchAlgorithmException
{
MessageDigest md = null;
String passwordHash = new String("");

try {
md = MessageDigest.getInstance("MD5");
byte[] passwordBytes = pass.getBytes();
byte[] hash = md.digest(passwordBytes);
passwordHash = Base64Encoder.encode(hash);
}catch (Exception e) {
e.printStackTrace();
}
return passwordHash;
}

sobota, 3 stycznia 2009

Wysyłanie maili z pomocą Gmaila

Dzisiaj zajmiemy się wysyłaniem maili przy użyciu konta na Gmailu. Stworzymy prostą aplikację JSF, mogącą pomóc w procesie rejestracji użytkownika i potwierdzenia poprawności wpisanego przez niego adresu e-mail.
Zacznijmy od napisania klasy obsługującej wysyłanie maili:
package pl.javazpiwnicy;

import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class MailManager
{
String addressFrom = "<nasze_konto>@gmail.com";
String temat = "Rejestracja sie powiodla!";

public void wyslij(String login, String haslo, String email) throws AddressException, MessagingException
{
String tresc = "Twoj login: " + login + "\n" + "Twoje haslo: " + haslo;
Properties props = System.getProperties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.port", "465");
props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.fallback", "false");

Authenticator auth = new SMTPAuthenticator();
Session sesja = Session.getInstance(props, auth);

MimeMessage wiadomosc = new MimeMessage(sesja);
wiadomosc.setFrom(new InternetAddress("<nasze_konto>@gmail.com"));
wiadomosc.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
wiadomosc.setSubject(temat);
wiadomosc.setText(tresc);

Transport.send(wiadomosc);
}

private class SMTPAuthenticator extends javax.mail.Authenticator
{
public PasswordAuthentication getPasswordAuthentication()
{
try
{
String username = "<nasz_login_na_gmaila>";
String password = "<nasze_haslo>";
return new PasswordAuthentication(username, password);
}catch (Exception e) {
}
return null;
}
}
}
Następnie napiszemy beana do zarządzania użytkownikiem:
package pl.javazpiwnicy;

import java.util.Random;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;

public class User
{
private String login;
private String email;
private String haslo;

private MailManager mm = new MailManager();

public String getLogin()
{
return login;
}
public void setLogin(String login)
{
this.login = login;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
private void setHaslo()
{
Random r = new Random();
haslo = Long.toString(Math.abs(r.nextLong()), 27).substring(0, 8);
}

public String rejestruj() throws AddressException, MessagingException
{
setHaslo();
mm.wyslij(login, haslo, email);
return "success";
}
}
Teraz przyszła kolej na dodatkowy wpis w faces-config.xml :
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>pl.javazpiwnicy.User</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Na koniec stworzymy bardzo prosty formularz, zawierający dwa pola do wpisania: login i adres maila:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>

</head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="2">
<h:outputText value="Login"/><h:inputText value="#{user.login}"/>
<h:outputText value="E-mail"/><h:inputText value="#{user.email}"/>
<h:commandButton action="#{user.rejestruj}" value="Rejestruj"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
Klikniecie przycisku rejestruj powinno spowodować wysłanie losowo wygenerowanego hasła na maila użytkownika. W ten sposób także łatwo potwierdzimy autentyczność adresu e-mail. Kod wydaje się w miarę łatwy, więc nie będę go wyjaśniał, jakby były pytania to proszę pisać w komentarzach. A oto jak wygląda struktura katalogów tego projektu u mnie:



Na koniec dodam jeszcze prostą funkcję na szyfrowanie hasła algorytmem md5, przyda się kiedy będziecie wygenerowane hasło chcieli wrzucić do bazy:
private String makeMD5(String password) throws NoSuchAlgorithmException
{
MessageDigest m = MessageDigest.getInstance( "MD5" );
m.update( password.getBytes(), 0, password.length() );
return new BigInteger( 1, m.digest() ).toString( 16 );
}