niedziela, 20 czerwca 2010

Layout w JSP

Jeśli zaczynasz tworzyć własną aplikację z użyciem JSP na pewno warto pomyśleć nad szablonami. Jeśli jakieś strony wykorzystują ten sam układ, to samo menu, stopkę, itd. to warto poświęcić trochę czasu na stworzenie oddzielnych plików w których będziemy umieszczać wspólne kawałki kodu.

Prosta konfiguracja web.xml
Zacznijmy od modyfikacji pliku web.xml. Umieszczamy w nim fragment kodu który pozwoli nam używać plików .jspx i .tagx.
<context-param>
 <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
 <param-value>.jspx</param-value>
</context-param>

Pliku tagx
Nasz szablon będzie prostą biblioteką znaczników (taglib), która będzie zawierała podstawowy układ widoku. W folderze WEB-INF tworzymy folder tags a w nim folder gdzie będziemy trzymać nasze szablony. U mnie folder z szablonami będzie nazywał się layout. Następnie tworzymy prosty plik main.tagx w którym będzie znajdował się główny układ naszej aplikacji.
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns:jsp="http://java.sun.com/JSP/Page"> 
 
 <jsp:output doctype-root-element="html"
  doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" />
 <jsp:directive.attribute name="title" required="true"/> 
 
 <head>
     <title>${title}</title>
 </head>
 
 <body> 
  <jsp:directive.include file="/include/menu.jsp" />

  <div style="clear: both;">Layout góra</div>
   <jsp:doBody />  
   <div style="clear: both;">Layout dół</div>    
 </body>
</html>
Plik z szablonem wygląda na standardową stronę jsp. Dzięki elementowi jsp:directive.attribute możemy przekazywać do naszego szablonu różnego rodzaju atrybuty, takie jak tytuł strony. Tworząc atrybut ustawiamy nazwę (name) oraz definiujemy czy atrybut jest wymagany (required). Do atrybutu odwołujemy się poprzez ${NAZWA_ATRYBUTU}. W powyższym przykładzie tworzony jest atrybut title który określa tytuł strony (atrybut użyty jest w sekcji head w elemencie title).
Elementem <jsp:doBody /> wskazuje miejsce gdzie zostanie wstawione ciało naszej bazowej strony, tzn. strony która będzie korzystać z szablonu.
Elementem jsp:directive.include możemy załączać inne pliku jsp do naszego szablonu. U nas w przykładzie załączamy jeden plik w którym docelowo będzie znajdować się menu aplikacji (dla celów testowych wypiszemy tylko napis Menu).Plik ten nazywa się menu.jsp i znajduje się w katalogu include.

Pliki jsp i jspx
Zacznijmy od stworzenia pliku menu.jsp z którego korzysta nasz szablon. W katalogu WebContent tworzymy folder include a w nim plik menu.jsp. Plik ten powinien zawierać kawałek kodu html i/lub jsp. U mnie wygląda to tak:
<div>
 Menu.
</div>

Kiedy mamy już stworzony pierwsze szablon, stwórzmy prosty plik który z niego skorzysta. W katalogu WebContent tworzymy plik main.jspx.
<layout:main xmlns:layout="urn:jsptagdir:/WEB-INF/tags/layout"
 xmlns:jsp="http://java.sun.com/JSP/Page">

 <jsp:attribute name="title">Tytuł strony z atrybutu</jsp:attribute>
 <jsp:directive.page contentType="text/html;charset=UTF-8" language="java" />

 Hello World !!!
 
</layout:main>
Na początku strony deklarujemy że używamy taglibu layout (nazwa naszego folder z szablonami). Nasz szablon zawierał jeden atrybut title, który musi zostać przekazany, w przeciwnym wypadku dostaniemy komunikat błędu. Atrybut możemy przekazać w dwojaki sposób:
  • poprzez dodanie nowego elementu jsp:attribute gdzie deklarujemy nazwę i wartość naszego atrybutu (tak jak w przykładzie)
  • poprzez dodanie do naszego główne elementu, atrybutu: nazwa_atrybutu_w_szablonie="wartość" (czyli: <layout:main title="Tytuł strony")

Po uruchomieniu aplikacji i wejściu na stronę main.jspx zobaczymy napis Hello World!!! pochodzący z naszej strony wraz z napisem Menu. (z pliku menu.jsp) oraz Layout góra, Layout dół.

czwartek, 10 czerwca 2010

JPA - Encje

W poniższym artykule zajmiemy się tworzeniem komponentów encyjnych. Encje reprezentują dane składowane w bazie danych w postaci zwykłej klasy javowej. Pola klasy są polami które odpowiadają polom w bazie danych, a metody get i set pozwalają nam pobierać i zmieniać dane w encji.

Tworzenie prostej encji
Aby obiekt stała się obiektem encyjnym potrzebujemy opatrzyć go adnotacją @Entity oraz zadeklarować klucz główny, czyli adnotacja @Id przy deklaracji pola lub przy metodzie get. Ja w moim kodzie wszystkie adnotacje związane z polami (ustalenie klucza głównego, relacje, dodatkowe zależności) będę umieszczał przy deklaracji pól, natomiast warto pamiętać że możemy używać ich również przy metodach get. Jeśli jednak zdecydujemy się umieścić adnotacje @Id przy polu, to konsekwentnie musimy trzymać się tego stylu do końca. W praktyce wygląda to tak.
package pl.mkorwel.exam.entities;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Osoba {

 @Id
 private long pole1;

 private String pole2;

 public long getPole1() {
  return this.pole1;
 }

 public String getPole2() {
  return this.pole2;
 }

 public void setPole1(final long pole1) {
  this.pole1 = pole1;
 }

 public void setPole2(final String pole2) {
  this.pole2 = pole2;
 }
}
Nasza encja nazywa się Osoba i zawiera dwa pole, pole1 jest kluczem głównym o typie numerycznym, a pole2 jest polem tekstowym. Obiekt encyjny zadeklarowany w ten sposób reprezentuje tabelę OSOBA z bazy danych z dwoma pola, POLE1 które jest kluczem głównym o typie numerycznych (BIGINT) oraz z polem POLE2 które jest zwykłym polem tekstowym VARCHAR(255).

Nazwy tabel i kolumn
Zadeklarowani encji bez wykorzystywania adnotacji @Table, @Column sprawi że nazwa klasy odpowiada nazwie tabeli, a nazwy pól odpowiadają nazwie kolumn w bazie danych. Jeśli jednak z jakiegoś powodu chcemy aby nasza encja miała inne nazewnictwo (lub innego powodu) niż tabela jaką reprezentuje jesteśmy zmuszeni dodać do klasy adnotację @Table, a do pól adnotację @Column.
package pl.mkorwel.exam.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "OSOBA_TABLE")
public class Osoba {

 @Id
 @Column(name = "POLE_1", nullable = false)
 private long pole1;

 @Column(name = "POLE_2", length = 100)
 private String pole2;

 public long getPole1() {
  return this.pole1;
 }

 public String getPole2() {
  return this.pole2;
 }

 public void setPole1(final long pole1) {
  this.pole1 = pole1;
 }

 public void setPole2(final String pole2) {
  this.pole2 = pole2;
 }
}
Adnotacje @Table i @Column posiadają szereg atrybutów, dzięki którym możemy doprecyzować jak nasza tabela ma być odwzorowana w bazie danych.
@Table
  • name określa nazwę tabeli
  • catalog określa katalog w jakim znajduje się tabela
  • schema określa schemę w jakim znajduje się tabela
  • uniqueConstraints umożliwia definiowane kodu DDL

@Column
  • name nazwa kolumny
  • uniwue czy kolumna ma być unikalna
  • nullable czy wartość kolumny jest wymaga
  • insertable czy kolumna ma być brana pod uwagę podczas zapisu
  • updateable czy kolumna ma być brana pod uwagę podczas aktualizacji
  • uniqueConstraints umożliwia definiowane kodu DDL
  • length określa długość kolumny typu VARCHAR
  • table nazwa tabeli w jakiej znajduję się pole
  • precision dla kolumn o typie numerycznym, ustalamy ilość liczb po lewej stronie przecinka
  • scale dla kolumn o typie numerycznych, ustalamy ilość liczb po prawej stronie przecinka

Klczy główny
Ostatnim podstawowym zagadnieniem związanym z deklaracją encji jest klucz główny. Jak już wiemy każda encja musi mieć taki klucz, a pole które nim jest posiada adnotacje @Id. Klucz główny możemy oczywiście wpisywać ręcznie, ale istnieje też możliwość automatycznego generowania. Aby tego dokonać wystarczy umieścić adnotację @GeneratedValue.
package pl.mkorwel.exam.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "OSOBA_TABLE")
public class Osoba {

 @Id
 @GeneratedValue
 @Column(name = "POLE_1", nullable = false)
 private long pole1;

 @Column(name = "POLE_2", length = 100)
 private String pole2;

 public long getPole1() {
  return this.pole1;
 }

 public String getPole2() {
  return this.pole2;
 }

 public void setPole1(final long pole1) {
  this.pole1 = pole1;
 }

 public void setPole2(final String pole2) {
  this.pole2 = pole2;
 }
}

Niektóre bazy danych oferują mechanizm sekwencyjnego generowania identyfikatorów, co powoduje ze musimy wskazać odpowiednią sekwencję, której będziemy używać do generowania. W takim celu musimy jeszcze umieścić adnotację @SequenceGenerator.
package pl.mkorwel.exam.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "OSOBA_TABLE")
public class Osoba {

 @Id
 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "OSOBA_SEQ")
 @SequenceGenerator(name = "OSOBA_SEQ", sequenceName = "OSOBA_SEQ_W_BAZIE")
 @Column(name = "POLE_1", nullable = false)
 private long pole1;

 @Column(name = "POLE_2", length = 100)
 private String pole2;

 public long getPole1() {
  return this.pole1;
 }

 public String getPole2() {
  return this.pole2;
 }

 public void setPole1(final long pole1) {
  this.pole1 = pole1;
 }

 public void setPole2(final String pole2) {
  this.pole2 = pole2;
 }
}

W adnotacji @SequenceGenerator za pomocą atrybutu name nadajemy nazwę dla naszej sekwencji, a atrybutem sequenceName wskazujemy tabelę sekwencji w bazie danych. Następnie W adnotacji @GeneratedValue ustawiamy strategię generowania (atrybut strategy) na GenerationType.SEQUENCE i przy pomocy atrybutu generator wskazujemy która sekwencja ma zostać użyta.

niedziela, 23 maja 2010

Integracja JSF i Spring

Przygotowanie środowiska
Tradycyjną drogą integracji JSF i Springa było odwoływanie się z managed beans do spring beans z użyciem tradycyjnych metod wstrzykiwania kontekstu aplikacji. W poniższym artykule pokażę jak można zastąpić managed beans których używamy w technologi JSF, spring beanami które tworzy i udostępnia Spring.
Zacznijmy więc od ściągnięcia odpowiednich bibliotek do Springa które możemy umieścić w katalogu WebContent/WEB-INF/lib naszej aplikacji (zakładam że dysponujesz już jakąś aplikacją z wykorzystaniem JSF, jeśli nie to polecam zajrzeć najpierw do artykułu JSF + Tomcat 6 + Eclipse - Hello World). Linki do potrzebnych bibliotek zamieszam poniżej:

Spring - stworzenie beana
Zacznijmy od stworzenia prostego beana springowego. Do tego celu potrzebować będziemy prostej klasy javowej z jedną metodą (getHello()) która wypisz na ekran napisz powitalny (pamiętajmy o tym że bean ten będzie wykorzystany w JSF dlatego metody wypisujące napis na ekran mają prefiks get).
package com.blogspot.mkorwel.spring;

import org.springframework.stereotype.Component;

/**
 * 
 * @author Mateusz Korwel
 *
 */
@Component
public class HelloBean {

 public String getHello() {
  return "Hello Spring";
 }

}

Klasa HelloBean jest opatrzona adnotacją @Component co sugeruje że będzie to bean springowy. Następnym krokiem konfiguracji springa będzie stworzenie pliku konfiguracyjnego. W moim przykładzie plik ten znajduję się w katalogu WebContent/WEB-INF i nazywa się applicationContext.xml.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

 <context:component-scan base-package="com.blogspot.mkorwel.spring" />

</beans>

Jest to tradycyjny plik konfiguracyjne do springa, z wpisem który wskazuje springowi w jakich pakietach znajdują się spring beany (czyli wszystkie klasy które opatrzone są adnotacją @Component o której już wspomniałem).

Integracja
Do integracji Springa i JSF będziemy musieli zmodyfikować plik WebContent/WEB-INF/web.xml i WebContent/WEB-INF/faces-config.xml. Do pliku web.xml dodajemy informacje o springowym kontekście aplikacji:
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <listener>
  <listener-class>
   org.springframework.web.context.request.RequestContextListener</listener-class>
 </listener>

 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext.xml</param-value>
 </context-param>
a do pliku faces-config.xml informację o wykorzystanie spring beanów jako managed beanów
<application>
  <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
 </application>

Użycie nowej funkcjonalności
Kiedy wszystko jest już skonfigurowane możemy wykorzystywać nasze spring beany jako managed beany, do tego celu stworzyłem prostą stronkę hello.jsp (w katalogu WebContent):
<%@ 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>JSF Spring</title>
</head>
<body>
<f:view>
<h:outputText value="#{helloBean.hello}"></h:outputText>
</f:view>
</body>
</html>

Po umieszczeniu naszej aplikacji (nazwa mojej aplikacji to JSFSpring) na serwerze i uruchomieniu (http://localhost:8080/JSFSpring/hello.jsf) na stronie powinien pojawić się napisz Hello Spring.

środa, 19 maja 2010

Adnotacje

Adnotacje (ang. annotations) są dostępne w javie od wersji 1.5 i służą do opisywania klas. W poniższym artykule opowiem jak szybko stworzyć i wykorzystać własne adnotacje.

Tworzenie adnotacji
Definicja pustej adnotacji nie różni się specjalnie od definicji pustego interfejsu. Pomijając znaczek @ w słówku @interface w zasadzie nie ma żadnej różnicy.
package com.blogspot.mkorwel.annotation;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationMethod {

}
Stworzenie adnotacji wymaga jeszcze użycia metaadnotacji. Poniżej umieszczam krótki opis:
  • @Target określa miejsce stosowania adnotacji
  • @Retention określa trwałość adnotacji
  • @Documented ujęcie danej adnotacji w dokumencie Javadoc
  • @Inherited zezwolenie na dziedziczenie adnotacji w podklasach

Możliwe argumenty dla metaadnotacji @Target to:
  • ElementType.TYPE - w deklaracji klasy, interfejsu, adnotacji, enum
  • ElementType.CONSTRUCTOR - w deklaracji konstruktora
  • ElementType.FIELD - w deklaracji pola
  • ElementType.LOCAL_VARIABLE - w deklaracji zmiennej lokalnej
  • ElementType.METHOD - w deklaracji metody
  • ElementType.PACKAGE - w deklaracji pakietu
  • ElementType.PARAMETER - w deklaracji parametru

Możliwe argumenty dla metaadnotacji @Retention to:
  • RetentionPolicy.SOURCE - adnotacje unieważnione przez kompilator
  • RetentionPolicy.CLASS - adnotacje widoczne w plikach klas, unieważnione przez maszynę wirtualną
  • RetentionPolicy.RUNTIME - adnotacje podtrzymywane w maszynie wirtualnej w czasie wykonania, a więc nadające się do odczytu za pośrednictwem refleksji

Poniżej stworzymy jeszcze jedną adnotację która będzie używana przy deklaracji klas (@Target(ElementType.TYPE)). Adnotacja będzie zawierała dwa elementy. Poniżej kod:
package com.blogspot.mkorwel.annotation;

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotationClass {

 public int id() default -1;
 
 public String name();
 
}
Elementy w adnotacjach deklarujemy podobnie do metod w intefejsie. Każdy element może mieć wartość domyślną, aby ustalić taką wartość używamy słówka default.

Wykorzystanie adnotacji
Po stworzeniu dwóch naszych adnotacji, możemy użyć ich w naszej klasie. Kod klasy nie jest zbyt skomplikowany, więc nie będę go opisywał.
package com.blogspot.mkorwel;

import com.blogspot.mkorwel.annotation.MyAnnotationClass;
import com.blogspot.mkorwel.annotation.MyAnnotationMethod;

@MyAnnotationClass(id = 1, name = "Mateusz")
public class MyClass {
 
 @MyAnnotationMethod
 public void test(){
  
 }

}

Kiedy mamy już adnotacje i klasę która jest nimi opisana, możemy stworzyć klasę która wykorzysta nasze adnotacje i wypisze komunikat na ekranie. Kod klasy która to robi znajduje się poniżej:
package com.blogspot.mkorwel;

import com.blogspot.mkorwel.annotation.MyAnnotationClass;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
public class Test {

 public static void main(String[] args) {

  // tworzenie klasy
  MyClass myClass = new MyClass();
  Class clazz = myClass.getClass();

  // pobranie adnotacji w klasie
  for (int i = 0; i < clazz.getAnnotations().length; i++) {
   // wypisanie adnotacji
   System.out.println("Nazwa adnotacji: " + clazz.getAnnotations()[i]);
   System.out.println("Wartość atrybutu id: "
     + ((MyAnnotationClass) clazz.getAnnotations()[i]).id());
   System.out.println("Wartość atrybutu name: "
     + ((MyAnnotationClass) clazz.getAnnotations()[i]).name());
  }

  // pobranie wszystkich publicznych/pakietowych metod z klasy MyClass
  for (int i = 0; i < clazz.getMethods().length; i++) {
   // pobranie wszystkich adnotacji przypisanych do metody
   for (int j = 0; j < clazz.getMethods()[i].getAnnotations().length; j++) {
    // nazwa metody z adnotacją
    System.out.println("\nNazwa metody z adnotacją: "
      + clazz.getMethods()[i].getName());
    // wypisanie adnotacji
    System.out.println("Nazwa adnotacji: "
      + clazz.getMethods()[i].getAnnotations()[j]);
   }
  }
 }
}
Krótko opiszę co powyższa klasa robi. W pierwszej pętli for pobieramy wszystkie adnotacje które są przypisane do klasy. W naszym przypadku jest to tylko jedna adnotacja @MyAnnotationClass z dwoma elementami które zostaną również wypisane. Druga pętla for pobiera metody jakie znajdują się w klasie, a następnie pobiera adnotacje przypisane do kolejnych metod. W naszym przypadku będzie wypisana jedna metoda test() z jedną adnotacją @MyAnnotationMethod.

niedziela, 9 maja 2010

EJB + Servlet: Prosta aplikacja enterprise

Przygotowanie środowiska pracy.
Do stworzenia aplikacji użyłem:
- Eclipse jako IDE
- JBoss jako serwer aplikacji na którym będzie uruchamiana cała aplikacji

Projekt EAR
Zaczniemy od stworzenia Enterprise Application Project (EAR). Aplikacja będzie zawierała dwa moduły:
  • EJB - projekt zawierajacy ziarna EJB
  • WAR - projekt zaweirający cały widok aplikacji
To właśnie tą aplikację będziemy deployować na serwerze JBoss.

W Eclipse Enterprise Application Project tworzymy w następujący sposób. Klikamy w File -> New -> Other ... i wybieramy Enterprise Application Project.

Następnie wybieramy Target runtime, wpisujemy nazwę aplikacji i klikamy Finish.

Projekt jest na razie pusty i zostanie uzupełniony w dalszej części artykułu.

Projekt EJB
EJB czyli Enterprise JavaBeans, aby móc korzystać z tej technologi musimy stworzyć projekt EJB. W Eclipse tworzymy go w następujący sposób: Klikamy w File -> New -> Other ... i wybieramy EJB Project.

Następnie wpisujemy nazwę projektu, wybieramy wersję EJB (my będziemy korzystać z wersji 3.0) oraz zaznaczamy że nasz projekt będzie częścią dużej aplikacji enterprise.

Po kliknięciu Next > odznaczamy chęć stworzenia EJB Client JAR.

W momencie gdy mamy już przygotowany projekt możemy utworzyć pierwsze ziarno EJB. Tak więc tworzymy zwykły interfejs w dowolnym pakiecie. U mnie wygląda to tak:
package com.blogspot.mkorwel.ejb;

import javax.ejb.Local;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
@Local
public interface HelloLocal {

 public String getHelloWorld();
}

Interfejs kończy się sufiksem Local, co sugeruje że interfejs jest lokalny(w EJB występuje również interfejs Zdalny (Remote)). Rodzaj interfejsu deklarujemy używając adnotacji @Local lub @Remote.

Teraz tworzymy klasę która będzie implementację interfejsu HelloLocal.
package com.blogspot.mkorwel.ejb;

import javax.ejb.Stateless;

@Stateless
public class HelloBean implements HelloLocal {

 @Override
 public String getHelloWorld() {

  return "Hello World";
 }

}

Aby klasa stała się ziarnem EJB wystarczy użyć adnotacji @Stateless lub @Stateful. Adnotację określają czy ziarno EJB jest bezstanowe (Stateless) czy stanowe (Stateful). My tworzymy komponent bezstanowy dlatego użyliśmy adnotacji @Stateless. Nasza metoda getHelloWorld() zwraca prosty napis Hello World który wypiszemy w serwlecie.

Projekt WAR
Następnym krokiem do stworzenia naszej przykładowej aplikacji jest utworzenie projektu dla warstwy widoku, czyli zwykłego projektu WEB'owego w którym będą znajdowały się strony HTML, JSP, Servlety itd. W Eclipse klikamy w File -> New -> Other ... i wybieramy Dynamic Web Project.

Wpisujemy nazwę i nie zapominamy o dodaniu projektu do naszej aplikacji enterprise.

Następnie klikamy Finish. Teraz musimy jeszcze ustawić zależność w projekcie, tak aby mieć dostęp do klas z projektu EJB. Robimy to poprzez kliknięcie lewym przyciskiem myszy na projekcie helloWAR, wybranie Java Build Path a następnie Projects.

Klikamy w Add aby dodać zależność do projektu. Wybieramy nasz project EJB czyli helloEJB. Klikamy OK.


Teraz tworzymy zwykły servlet (w mnie nazywa się Hello), który wypisze prosty napis Hello World który zdefiniowaliśmy w projekcie EJB. Kod servletu wygląda następująco.
package com.blogspot.mkorwel.ejb;

import java.io.IOException;
import java.io.PrintWriter;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
public class Hello extends HttpServlet {
 private static final long serialVersionUID = 1L;

 @EJB
 private HelloLocal hello;

 public Hello() {
  super();
 }

 protected void doGet(HttpServletRequest request,
   HttpServletResponse response) throws ServletException, IOException {
  PrintWriter out = response.getWriter();

  out.println(hello.getHelloWorld());
 }

}

Jak widzimy jest to zwykły servlet który wywoła nam metodę getHelloWorld() z interfejsu HelloLocal. Za pomocą adnotacji @EJB wstrzykniemy implementację naszego interfejsu (klasę HelloBean.java która znajduje się w projekcie helloEJB).

Tak przygotowaną aplikację może zdeployować na serwerze JBoss. Aplikacja będzie znajduje się po linkiem 127.0.0.1:8080/helloWAR/Hello. Zauważmy że deployujemy aplikację EAR, a w przeglądarce odwołujemy się projektu WAR. Dzieje się tak dlatego że projekt helloWAR jest modułem aplikacji helloEAR.

sobota, 1 maja 2010

JavaMail: Wysłanie emaila

W poniższym artykule zademonstruje jak łatwo można napisać kod, który wysyła wiadomości email. Wysyłanie wiadomości odbędzie się przy pomocy naszego konta na gmail'u, więc nie będzie potrzebne instalowanie serwerów smtp, itd.

Przygotowanie projektu
Pierwszym krokiem jakim musisz wykonać jest założenie sobie konta na googlach. Następnie potrzebujemy jeszcze biblioteki Java Mail dzięki której będziemy mogli wysyłać maile. Plik jar możemy ściągnąć z oficjalnej strony Java Mail Api. Po ściągnięciu i rozpakowaniu, plik mail.jar przenosimy do katalogu bibliotek w naszym projekcie.

Proste wysłanie maila
Oto prosty kod dzięki któremu wyślemy bardzo prostą wiadomość:
package com.blogspot.mkorwel.mail;

import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
public class SendMail {

 private static final String HOST = "smtp.gmail.com";
 private static final int PORT = 465;
 // Adres email osby która wysyła maila
 private static final String FROM = "mateusz.korwel@gmail.com";
 // Hasło do konta osoby która wysyła maila
 private static final String PASSWORD = "xxx";
 // Adres email osoby do której wysyłany jest mail
 private static final String TO = "mateusz.korwel@gmail.com";
 // Temat wiadomości
 private static final String SUBJECT = "Hello World";
 // Treść wiadomości
 private static final String CONTENT = "To mój pierwszy mail wysłany za pomocą JavaMailAPI.";

 public static void main(String[] args) {
  try {
   new SendMail().send();
  } catch (MessagingException e) {
   e.printStackTrace();
  }
 }

 public void send() throws MessagingException {

  Properties props = new Properties();
  props.put("mail.transport.protocol", "smtps");
  props.put("mail.smtps.auth", "true");

  // Inicjalizacja sesji
  Session mailSession = Session.getDefaultInstance(props);

  // ustawienie debagowania, jeśli nie chcesz oglądać logów to usuń
  // linijkę poniżej lub zmień wartość na false
  mailSession.setDebug(true);

  // Tworzenie wiadomości email
  MimeMessage message = new MimeMessage(mailSession);
  message.setSubject(SUBJECT);
  message.setContent(CONTENT, "text/plain; charset=ISO-8859-2");
  message.addRecipient(Message.RecipientType.TO, new InternetAddress(TO));

  Transport transport = mailSession.getTransport();
  transport.connect(HOST, PORT, FROM, PASSWORD);

  // wysłanie wiadomości
  transport.sendMessage(message, message
    .getRecipients(Message.RecipientType.TO));
  transport.close();
 }
}

Powyższy kod wysyła bardzo prostą wiadomość email. A co jeśli chcieli byśmy sformatować naszą wiadomość przy pomocy znaczków html? Powyższy kod wysłał by całą zawartość maila bez żadnej transformacji, ale wystarczy że zmienimy typ mime naszej wiadomości i treść zostanie sformatowana. Obecnie mamy text/plain a potrzebujemy text/html. Po takiej zmianie możemy wysyłać maila z kodem html.

Wysłanie maila z załącznikiem
Oto prosty kod dzięki któremu wyślemy wiadomość z załącznikiem:
package com.blogspot.mkorwel.mail;

import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.FileDataSource;
import javax.activation.DataHandler;

import java.util.Properties;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
class SendMailAttachment {

 private static final String HOST = "smtp.gmail.com";
 private static final int PORT = 465;
 // Adres email osby która wysyła maila
 private static final String FROM = "mateusz.korwel@gmail.com";
 // Hasło do konta osoby która wysyła maila
 private static final String PASSWORD = "xxx";
 // Adres email osoby do której wysyłany jest mail
 private static final String TO = "mateusz.korwel@gmail.com";
 // Temat wiadomości
 private static final String SUBJECT = "Hello World";
 // Treść wiadomości
 private static final String CONTENT = "To mój pierwszy mail z załącznikami wysłaby za pomocą JavaMailAPI.";
 // Ścieżka do pliku
 private static final String PATH_FILE = "zalacznik.txt";

 public static void main(String[] args) {
  try {
   new SendMailAttachment().send();
   System.out.println("Wiadomość wysłana");
  } catch (MessagingException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 void send() throws MessagingException {

  Properties props = new Properties();
  props.put("mail.transport.protocol", "smtps");
  props.put("mail.smtps.auth", "true");

  // Pobranie sesji
  Session mailSession = Session.getDefaultInstance(props, null);

  // Tworzenie wiadomości
  MimeMessage message = new MimeMessage(mailSession);
  message.setSubject(SUBJECT);

  // Stworzenie części wiadomosci z treścią
  MimeBodyPart textPart = new MimeBodyPart();
  textPart.setContent(CONTENT, "text/html; charset=ISO-8859-2");

  // Stworzenie części z załacznikami
  MimeBodyPart attachFilePart = new MimeBodyPart();
  FileDataSource fds = new FileDataSource(PATH_FILE);
  attachFilePart.setDataHandler(new DataHandler(fds));
  attachFilePart.setFileName(fds.getName());

  // Zestawienie obydwu części maila w jedną wieloczęściową
  Multipart mp = new MimeMultipart();
  mp.addBodyPart(textPart);
  mp.addBodyPart(attachFilePart);

  // Dodanie treści maila
  message.setContent(mp);
  message.addRecipient(Message.RecipientType.TO, new InternetAddress(TO));

  Transport transport = mailSession.getTransport();
  transport.connect(HOST, PORT, FROM, PASSWORD);

  // Wysąłnei maila
  transport.sendMessage(message, message
    .getRecipients(Message.RecipientType.TO));
  transport.close();
 }
}

Powyższego kodu chyba nie trzeba komentować. Jedną różnicą maila z załącznikami od maila bez załączników jest to, że wiadomość składa się z kilku części.

wtorek, 27 kwietnia 2010

Wysłanie SMS w Javie

Zaczniemy od ściągnięcia dwóch niezbędnych bibliotek:
Po ściągnięciu i rozpakowaniu pliki .jar kopiujemy do projektu i ustawiamy zależności.

Założenie bezpłatnego konta na ipipi.com
Do wysyłania sms będziemy potrzebowali serwera smtp. Do tego celu możemy założyć konta na ipipi.com. Założenie konta jest darmowe. Do konta dostaniemy możliwość wysłania 3 testowych wiadomości, za kolejne trzeba już niestety zapłacić (kupno pakietu).
Wchodzimy na stronę rejestracji i wypełniamy wszystkie potrzebne pola (login, hasło, email, telefon, imię, nazwisko i kraj). Na podany numer telefony zostanie nam wysłana wiadomość z kodem dostępu który będziemy musieli podać w następnym etapie rejestracji.
Strona rejestracji

Kod klasy
Po założeniu konta możemy napisać naszą pierwsza klasę do wysyłania sms'ów za pośrednictwem ipipi.com.

package com.blogspot.mkorwel.sms;

import java.util.Properties;
import java.util.Date;
import javax.mail.*;
import javax.mail.internet.*;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
public class SMSSend {

 // login z portlau ipipi.com
 private static String USERNAME = "xxx";
 // hasło z portalu ipipi.com
 private static String PASSWORD = "xxxyyyzzz123";
 private static String SMTP_HOST = "ipipi.com";
 // podajemy nazwe swojego konta w serwicie ipipi.com
 private static String FROM = "xxx@ipipi.com";
 // podajemy numer na który wysyłamy sms'a
 private static String TO = "+48123456789@sms.ipipi.com";
 // Wpisujemy temat naszej wiadomości
 private static String SUBJECT = "Moja pierwsza wiadomość";
 // Wpisujemy temat naszej wiadomości
 private static String BODY = "Hello World!!!";

 public static void main(String[] args) {

  SMSSend smsSend = new SMSSend();
  smsSend.sendSMS();
  System.out.println("WIADOMOSZ WYSLANA");

 }

 public void sendSMS() {

  Transport tr = null;

  try {
   // ustawiamy parametry
   Properties props = System.getProperties();
   props.put("mail.smtp.auth", "true");

   // pobieramy sesję obiektu
   Session mailSession = Session.getDefaultInstance(props, null);

   // Tworzymy wiadomość
   Message msg = new MimeMessage(mailSession);
   msg.setFrom(new InternetAddress(FROM));
   msg.setSubject(SUBJECT);
   msg.setText(BODY);
   msg.setSentDate(new Date());

   // ustawiamy odbiorcę wiadomości
   InternetAddress[] address = { new InternetAddress(TO) };
   msg.setRecipients(Message.RecipientType.TO, address);

   tr = mailSession.getTransport("smtp");
   tr.connect(SMTP_HOST, USERNAME, PASSWORD);
   msg.saveChanges();

   // wysyłamy sms'a
   tr.sendMessage(msg, msg.getAllRecipients());
   tr.close();
  } catch (Exception e) {
   System.out.println("Error");
   e.printStackTrace();
  }
 }
}

Jeśli wszystko poszło dobrze możemy cieszyć się wysyłaniem sms'ów.

czwartek, 15 kwietnia 2010

Spring Hello World

W poniższym artykule pokaże jak w bardzo prosty sposób użyć Springa. Do tworzenia kodu użyłem edytora Eclipse z zainstalowaną wtyczką SpringIDE (podaję linka za pomocą którego możemy zainstalować plugin w eclipse http://dist.springframework.org/release/IDE).


Przygotowanie środowiska
Na początek musimy ściągnąć z internetu następujące jary:


Całą paczkę jarów ze springiem można sciągnąć z oficjalnej strony springa.
Kiedy już będziemy mieli je na dysku, proponuję stworzyć w projekcie oddzielny katalog na biblioteki np. lib i umieścić tam ściągnięte jary. Następnie dodajemy biblioteki do projektu.

Tworzenie beana
Zaczniemy od stworzenia prostej klasy która będzie springowym beanem. Tak więc tworzymy pakiet com.blogspot.mkorwel a w nim klase Osoba.java. W klasie Osoba tworzymy pole imie typu String wraz z metodami get i set. Całość powinna wyglądać tak:

package com.blogspot.mkorwel;

/**
 * 
 * @author Mateusz Korwel
 *
 */
public class Osoba {

 private String imie;

 public void setImie(String imie) {
  this.imie = imie;
 }

 public String getImie() {
  return imie;
 }

}

Prosta klasa jest gotowa, aby stała się springowym beanem musimy umieścić odpowiedni wpis w odpowiednim pliku konfiguracyjnym. Tak wiec za pomocą wtyczki SpringIDE tworzymy Spring Bean Configuration File. Klikamy na projekcie prawym przyciskiem, wybieramy New -> Other... -> Spring -> Spring Bean Configuration File




klikamy Next > 




po wpisaniu nazwy (u mnie beans.xml) i kliknięciu Next >




musimy jeszcze wybrać schemę, beans -> spring-beans-3.0.xsd. Po tym wszystkim powinien stworzyć się prosty plik xml w którym będziemy umieszczać wszystkie beany. Cały plik po dodaniu wpisu z naszą klasą Osoba powinien wyglądać tak:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

 <bean id="osoba" class="com.blogspot.mkorwel.Osoba">
  <property name="imie" value="Mateusz"></property>
 </bean>

</beans>

W elemencie bean tworzymy instancje danej klasy według wzorca Singleton (czyli obiekt jednej instancji wykorzystywany w całej aplikacji). W atrybucie id deklarujemy identyfikator beana, a w class podajemy ścieżkę do pliku. Po takiej deklaracji mamy już zainicjalizowaną naszą klasę i możemy jej używać wszędzie w aplikacji. Element property jest opcjonalny i mówi o wartościach z jakimi tworzony jest bean (wywołanie metody setImie (atrybut name) z wartością Mateusz (atrybut value)).

Stworzenie prostego JUnite
Aby przetestować czy bean tworzy się poprawnie, tworzymy prosty JUnite test w którym będziemy wstrzykiwać naszą instancję klasy i sprawdzać czy wartość w polu imię jest Mateusz.
Cały kod testu ma wygląda tak:

package com.blogspot.mkorwel;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:beans.xml" })
public class OsobaTest {

 @Autowired
 @Qualifier("osoba")
 private Osoba osoba;

 @Test
 public void testImie() {

  Assert.assertEquals("Mateusz", osoba.getImie());

 }

}


Jak widzimy jest to zwykły test JUnit z kilkoma springowymi adnotacjami. Najważniejsza adnotacja to oczywiście @ContextConfiguration która wstrzykuje nam kontekst naszej aplikacji. Możemy sami podać miejsce gdzie jest zlokalizowany nasz plik konfoguracyjny beans.xml poprzez ustawienie pola locations, jesli tego nie zrobimy to aplikacja odwoła się do pliku który znajduje się w tym samym pakiecie co klasa o nazwie naszej klasy z końcówką -context.xml (w naszym wypadku był by to plik com/blogspot/mkorwel/OsobaTest-context.xml). Kolejne dwie ważne adnotacje to @Autowired@Qualifier. Adnotacja @Autowired wstrzykuje nam instancję klasy Osoba do pola osoba, a @Qualifier wskazuje na konkretne id instancji (w wypadku gdy plik beans.xml zawierał by kilka instancji klasy Osoba) która ma zostać wstrzyknięta. Adnotacja @RunWith wskazuje nam klasę z jaką ma się uruchomić test.

poniedziałek, 12 kwietnia 2010

Proste parsowanie XML w Javie

Co to jest XML?
XML (Extensible Markup Language) czyli niezależny od platformy sposób przechowywania, przekazywania pewnej porcji informacji.
Poniżej bardzo prosty przykład pliku XML zawierającego informacje o osobach (imię, nazwisko, płeć):
<?xml version="1.0"?>
<osoby>
  <osoba plec="mężczyzna">
   <imie>Jan</imie>
   <nazwisko>Kowalski</nazwisko>
  </osoba>
  <osoba plec="kobieta">
   <imie>Anna</imie>
   <nazwisko>Nowak</nazwisko>
  </osoba>
</osoby>


Jak skutecznie przetworzyć XML wykorzystując język Java?
Jest wiele API których można użyć do parsowania XML'i, ja pokaże dwa z nich:


  • DOM
  • SAX
DOM
Pierwszy sposób przetwarzania plików XML to Document Object Model, w skrócie DOM. Prosty API dzięki któremu możemy w łatwy sposób przeszukać plik XML i wyciągnąć z niego interesujące nas informacje. Plik XML jest wczytywane w całości do pamięci i odpowiednimi metodami możemy wyciągać z niego informacje nas interesujące. Metoda ta jest oczywiście niezalecana w przypadku dużych plików XML. Zaletą tego rozwiązania jest to, że w każdej chwili mamy dostęp do całego drzewa dokumentu, czyli do wszystkich elementów, atrybutów, wartości, itd.
Poniżej prosty kawałek kodu który czyta plik XML podany we wstępie artykułu.
package com.blogspot.xml.dom;

import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 
 * @author Mateusz Korwel
 *
 */
public class SampleDOM {

  public static void main(String argv[]) {

   try {
   File file = new File("myFile.xml");
   DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
   DocumentBuilder db = dbf.newDocumentBuilder();
   Document doc = db.parse(file);
   doc.getDocumentElement().normalize();

    System.out.println("Element główny: "
     + doc.getDocumentElement().getNodeName());
   
   NodeList nodeLst = doc.getElementsByTagName("osoba");

    for (int s = 0; s < nodeLst.getLength(); s++) {

     Node fstNode = nodeLst.item(s);

     if (fstNode.getNodeType() == Node.ELEMENT_NODE) {

      Element fstElmnt = (Element) fstNode;

      NodeList fstNmElmntLst = fstElmnt
       .getElementsByTagName("imie");
     Element fstNmElmnt = (Element) fstNmElmntLst.item(0);
     NodeList fstNm = fstNmElmnt.getChildNodes();

      NodeList lstNmElmntLst = fstElmnt
       .getElementsByTagName("nazwisko");
     Element lstNmElmnt = (Element) lstNmElmntLst.item(0);
     NodeList lstNm = lstNmElmnt.getChildNodes();

      System.out.println("\n***");
     System.out.println("Atrybut plec: "
       + fstElmnt.getAttribute("plec"));
     System.out.println("Element Imię : "
       + ((Node) fstNm.item(0)).getNodeValue());
     System.out.println("Element Nazwisko : "
       + ((Node) lstNm.item(0)).getNodeValue());
    }

    }
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}
SAX
Poznaliśmy już bardzo prostą metodę parsowania XML'i łającą pliki do pamięci operacyjnej komputera. Ale co jeśli mamy bardzo duży plik XML którego nie chcieli byśmy w całości wczytywać? W takim wypadku możemy użyć z SAX'a, czyli Simple API for XML. Określamy szereg metod, które wywołują się podczas parsowania pliku XML. Przetwarzanie metodą SAX jest jednokierunkowe.
Poniżej proste dwie klasy korzystające z SAX:
package com.blogspot.xml.sax;

import org.xml.sax.*;
import org.xml.sax.helpers.*;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
public class Handler extends DefaultHandler {

  @Override
 public void startElement(String namespaceURI, String localName,
   String qualifiedName, Attributes att) throws SAXException {
  System.out.println("Znalezione element: " + qualifiedName);
  if (qualifiedName.equals("employee")) {
   System.out.println("Znalezione atrybut: " + att.getValue("name"));
  }
 }

  @Override
 public void characters(char ch[], int start, int length)
   throws SAXException {

   System.out.println("Treść elementu: " + start + " to "
    + (start + length - 1) + ": " + new String(ch, start, length));
 }

}
package com.blogspot.xml.sax;

import javax.xml.parsers.*;
import org.xml.sax.XMLReader;

/**
 * 
 * @author Mateusz Korwel
 * 
 */
public class Sample {

  public static void main(String args[]) throws Exception {

   // Tworzenie parsera
  SAXParserFactory spf = SAXParserFactory.newInstance();
  spf.setNamespaceAware(true);
  SAXParser saxParser = spf.newSAXParser();
  XMLReader parser = saxParser.getXMLReader();

   // Tworzenie klasy Handler
  Handler handler = new Handler();
  parser.setContentHandler(handler);

   // Zaczęćie parsowania dokumentu
  parser.parse("myFile.xml");
 }
}

czwartek, 25 marca 2010

JAAS - prosty moduł

W jednym z poprzednim wpisie, pokazywałem jak wykorzystać JAAS'a w aplikacji webowej. Teraz chciałbym się skupić na tym, jak napisać własny moduł, dzięki któremu będziemy mogli wprowadzić własne zasady uwierzytelnianie i autoryzacji. Na końcu pokażę jak użyć tego modułu w aplikacji webowej, uruchomionej na serwerze aplikacji JBoss.


Kilka słów o samej strukturze
Prosty moduł będzie składał się z 3 klas:

  • MyPrincipal
  • MyGroup
  • MyLoginModule
Klasy należy umieścić gdzieś w naszej aplikacji, u mnie będzie to pakiet com.blogspot.mkorwel.jaas




MyPrincipal
MyPrincipal jest prostą klasą implementującą interfejs java.security.Principal. Klasa przechowuje identyfikator (login, publiczne id, email) który pozwala jednoznacznie określić użytkowania lub rolę.
package com.blogspot.mkorwel.jaas;


import java.io.Serializable;
import java.security.Principal;


/**
 * 
 * @author mkorwel
 *
 */
public class MyPrincipal implements Principal, Serializable {


 /**
  * 
  */
 private static final long serialVersionUID = 1933073722894960007L;
 private final String name;


 public MyPrincipal(String name) {
  this.name = name;
 }


 public String getName() {
  return name;
 }


 public boolean equals(Object o) {
  if (o == null)
   return false;


  if (this == o)
   return true;


  if (!(o instanceof MyPrincipal))
   return false;
  MyPrincipal that = (MyPrincipal) o;


  if (this.getName().equals(that.getName()))
   return true;
  return false;
 }


 public int hashCode() {
  return name.hashCode();
 }
}
MyGroup
MyGroup jest prostą klasą implementującą interfejsy java.security.acl.Groupjava.io.Serializable. Klasa przechowuje kolekcję ról.
package com.blogspot.mkorwel.jaas;


import java.io.Serializable;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;


/**
 * 
 * @author mkorwel
 *
 */
public class MyGroup implements Group, Serializable {


 /**
  * 
  */
 private static final long serialVersionUID = -3978735590793434379L;
 private final String name;
 private final Set users = new HashSet();


 public MyGroup(String name) {
  this.name = name;
 }


 public boolean addMember(Principal user) {
  return users.add(user);
 }


 public boolean removeMember(Principal user) {
  return users.remove(user);
 }


 public boolean isMember(Principal member) {
  return users.contains(member);
 }


 public Enumeration members() {
  return Collections.enumeration(users);
 }


 public String getName() {
  return name;
 }


 public boolean equals(Object o) {
  if (o == null)
   return false;


  if (this == o)
   return true;


  if (!(o instanceof MyGroup))
   return false;
  MyGroup that = (MyGroup) o;


  if (this.getName().equals(that.getName()))
   return true;
  return false;
 }


 public int hashCode() {
  return name.hashCode();
 }


}

MyLoginModule
Klasa MyLoginModule jest miejscem gdzie implementujemy całą logikę modułu. Klasa implementuje interfejs LoginModule, czyli 5 metod:
  • initialize
  • login
  • commit
  • abort
  • logout
Initialize 
Metoda initialize wywoływana jest przy każdej próbie logowania. Zajmuje się wczytaniem wszystkich niezbędnych parametrów (takich jak miejsce zapisu loginów, haseł, itd.)

Login
Metoda odpowiada za poprawną uwierzytelnianie. W tradycyjnym modelu uwierzytelniania metoda sprawdza login i hasło.

Commit
Metoda wykonuje się jeżeli uwierzytelnianie powiodło się. Zapisuje osobę i role do obiektu Subject.

Abort
Metoda wykonuje się jeżeli uwierzytelnianie się nie powiodło.

Logout
Metoda wywoływana podczas wylogowania użytkownika.

package com.blogspot.mkorwel.jaas;


import java.util.HashMap;
import java.util.Map;


import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;


/**
 * 
 * @author mkorwel
 * 
 */
public class MyLoginModule implements LoginModule {


 private Map usernameAndPassword = new HashMap() {


  private static final long serialVersionUID = -2695491152140640122L;


  {
   put("user1", "user");
   put("admin", "admin");
  }
 };
 private Map usernameAndRoles = new HashMap() {


  private static final long serialVersionUID = 1136203053244663646L;


  {
   put("user1", "user");
   put("admin", "admin");
  }
 };


 // initial state
 private Subject subject;
 private CallbackHandler callbackHandler;
 @SuppressWarnings("unused")
 private Map sharedState;
 @SuppressWarnings("unused")
 private Map options;


 // username and password
 private String username;
 private String password;


 private MyPrincipal userPrincipal;


 @Override
 public void initialize(Subject subject, CallbackHandler callbackHandler,
   Map sharedState, Map options) {


  this.subject = subject;
  this.callbackHandler = callbackHandler;
  this.sharedState = sharedState;
  this.options = options;


 }


 @Override
 public boolean login() throws LoginException {


  Callback[] callbacks = new Callback[2];
  callbacks[0] = new NameCallback("user name: ");
  callbacks[1] = new PasswordCallback("password: ", false);


  try {
   // pobranie loginu
   callbackHandler.handle(callbacks);
   username = ((NameCallback) callbacks[0]).getName();


   // pobranie hasla
   char[] tmpPassword = ((PasswordCallback) callbacks[1])
     .getPassword();
   if (tmpPassword == null) {
    tmpPassword = new char[0];
   }
   password = new String(tmpPassword);


   ((PasswordCallback) callbacks[1]).clearPassword();


  } catch (java.io.IOException ioe) {
   throw new LoginException(ioe.toString());
  } catch (UnsupportedCallbackException uce) {
   throw new LoginException("Error: " + uce.getCallback().toString()
     + " not available to garner authentication information "
     + "from the user");
  }


  // weryfikacje loginu i hasla
  return (usernameAndPassword.containsKey(username) && password
    .equals(usernameAndPassword.get(username)));
 }


 @Override
 public boolean commit() throws LoginException {


  userPrincipal = new MyPrincipal(username);


  if (!subject.getPrincipals().contains(userPrincipal)) {
   subject.getPrincipals().add(userPrincipal);
   MyGroup role = new MyGroup("Roles");
   role.addMember(new MyPrincipal(usernameAndRoles.get(username)));
   subject.getPrincipals().add(role);
  }


  return true;
 }


 @Override
 public boolean abort() throws LoginException {


  username = null;
  password = null;
  userPrincipal = null;


  return true;
 }


 @Override
 public boolean logout() throws LoginException {


  subject.getPrincipals().remove(userPrincipal);


  username = null;
  password = null;
  userPrincipal = null;


  return true;
 }
}
Nasz prosty moduł nie posiada żadnych parametrów przekazywanych z zewnątrz. Korzysta z bardzo prostego mechanizmu uwierzytelniania login/hasło które są zapisane w dwóch mapach na początku klasy.
Ważna uwaga!!!
Jeśli używamy serwera aplikacyjnego JBoss, tak jak ja w przykładzie, musimy pamiętać o tym  że wszystkie role muszę być wewnątrz obiektu "Roles".

Własny moduł pod JBoss'em
Tak jak wspominałem na początku, pokaże jak szybko wykorzystać nasz moduł w aplikacji uruchomionej na serwerze aplikacji JBoss.
Jeśli korzystamy ze standardowego modułu uwierzytelniania i autoryzacji jaki przedstawiłem w poprzednim wpisie, to musimy podmienić tylko plik jboss-service.xml. Zmiany dotyczą tylko wskazania klasy modułu i usunięciu wszystkich dodatkowych parametrów takich jak: wskazanie zapytania SELECT dzięki któremu pobieramy login/hasło/role, itd.
Aktualny jboss-service.xml powinien wyglądać tak:
<?xml version="1.0" encoding="UTF-8"?>
<server>
 <mbean code="org.jboss.security.auth.login.DynamicLoginConfig"
  name="jboss.seminarium:name=seminarium.login,service=DynamicLoginConfig">


  <attribute name="PolicyConfig" serialDataType="jbxb">
   <jaas:policy
    xsi:schemaLocation="urn:jboss:security-config:4.1 resource:security-config_4_1.xsd"
    xmlns:jaas="urn:jboss:security-config:4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <jaas:application-policy name="my-auth-module">
     <jaas:authentication>
      <jaas:login-module code="com.blogspot.mkorwel.jaas.MyLoginModule"
       flag="required">
      </jaas:login-module>
     </jaas:authentication>
    </jaas:application-policy>
   </jaas:policy>
  </attribute>
  <depends optional-attribute-name="LoginConfigService">
   jboss.security:service=XMLLoginConfig
        </depends>
  <depends optional-attribute-name="SecurityManagerService">
   jboss.security:service=JaasSecurityManager
        </depends>
 </mbean>
</server>

niedziela, 7 marca 2010

Użycie recaptcha w javie

Recaptcha
Zaczynamy od stworzenia sobie konta na recaptcha i dodajemy domenę na której nasz kod będzie używany. Jeśli nie zamierzamy udostępniać naszej aplikacji w internecie, to możemy wymyślić sobie tymczasową domeną, np. global-key.pl. Musimy tylko pamiętać aby zaznaczyć checkbox (Enable this key on all domains (global key)) dzięki któremu będziemy mogli korzystać z naczego klucza także na serwerach produkcyjnych czy testowych (takich jak localhost czy 127.0.0.1). Następnie klikamy Create Key.
Wszystko powinno wyglądać mniej więcej tak:


Do implementacji recaptcha będziemy potrzebowali biblioteki recaptcha4j-0.0.7.jar. Plik jar należy umieścić w katalogi WebContent\WEB-INF\lib lub w innym katalogu gdzie przechowujemy biblioteki dla naszej aplikacji. 

Przygotowanie formularza
Kiedy mamy już wygenerowany prywatny i publiczny klucz, czas wstawić do formularza pola które będą przechowywać wartości recaptcha. Do tego celu użyjemy tego skryptu napisanego w JavaScript:
<script type="text/javascript"
   src="http://api.recaptcha.net/challenge?k=<your_public_key>">
</script>


<noscript>
   <iframe src="http://api.recaptcha.net/noscript?k=<your_public_key>"
       height="300" width="500" frameborder="0"></iframe><br>
   <textarea name="recaptcha_challenge_field" rows="3" cols="40">
   </textarea>
   <input type="hidden" name="recaptcha_response_field" 
       value="manual_challenge">
</noscript>

W powyższym skrypcie wstawiamy nasz Public Key w miejsce <your_public_key> i tak przygotowany fragment kodu wklejamy do naszego formularza. U mnie wygląda to tak:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Login</title>
</head>
<body>
 <form action="success" method="post">


  <div>
   Value1: <input type="text" name="value1"/>
  </div>
  
  <div>
   Value2: <input type="text" name="value2"/>
  </div>
  
  <div>
   <script type="text/javascript"
      src="http://api.recaptcha.net/challenge?k=<publicKey>">
   </script>
   <noscript>
       <iframe src="http://api.recaptcha.net/noscript?k=<publicKey>"
           height="300" width="500" frameborder="0"></iframe><br />
       <textarea name="recaptcha_challenge_field" rows="3" cols="40">
       </textarea>
       <input type="hidden" name="recaptcha_response_field" 
           value="manual_challenge" />
   </noscript>
  </div>
  
  <div>
   <input type="submit" value="Wyslij" />
  </div>
 
 </form>
</body>
</html>





Możemy teraz uruchomić naszą aplikację i sprawdzić czy recaptcha działa, na tym etapie powinna się tylko wyświetlić na stronie.
Nadszedł czas na stworzenie logiki biznesowej która odbierze formularz z recaptchą. Ja do tego celu użyję serwletu. 


package com.blogspot.mkorwel.servlet;


import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
/**
 * 
 * @author mkorwel
 *
 */
public class Success extends HttpServlet {
  private static final long serialVersionUID = 1L;


  protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {


  }


  protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {


   //Pobranie i wypisanie naszych danych z formularza
   System.out.println(request.getParameter("value1"));
   System.out.println(request.getParameter("value2"));
   
   //Pobranie danych dla recaptcha
   String challengeReCaptcha = request.getParameter("recaptcha_challenge_field");
   String responseReCaptcha = request.getParameter("recaptcha_response_field");
   String remoteAddr = request.getRemoteAddr(); //Address użytkonika
      
   //Wypisanie danych dla recaptcha
   System.out.println(challengeReCaptcha);
   System.out.println(responseReCaptcha);
   System.out.println(remoteAddr);
   
   ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
   //ustawiamy nasz klucz prywatny
      reCaptcha.setPrivateKey("<privateKey>");
      
      //sprawdzamy czy użytkownik wpisał pobrany wyraz w recaptcha
      ReCaptchaResponse reCaptchaResponse =
          reCaptcha.checkAnswer(remoteAddr, challengeReCaptcha, responseReCaptcha);
      
      if (!reCaptchaResponse.isValid()) {
       response.sendRedirect("/index.jsp");
      } else {
       getServletConfig().getServletContext().getRequestDispatcher(
    "/result.jsp").forward(request, response);
      }
   
  }
}

Krótkie wyjaśnienie co dzieje się w servlecie.
W metodzie doPost na początku pobieramy nasze dane z formularza aby wypisać je na ekran. Następnie pobieramy dane dla recaptcha, tj. dwa pola które umieściliśmy na stronie: recaptcha_challenge_field i recaptcha_response_field oraz adres użytkownika który wpisywał recaptcha. Po wczytaniu potrzebnych danych tworzymy obiekt ReCaptchaImpl i ustawiamy nasz klucz prywatny. W tym momencie mamy już wszystkie dany które potrzebne nam są do zweryfikowania poprawnoście recaptcha, dlatego tworzymy obiekt ReCaptchaResponse na którym wywołujemy metode checkAnswer z klasy ReCaptchaImpl którą kilka linijek temu stworzyliśmy. Do sprawdzenia czy użytkownik wpisał pobrany wyraz dla recaptcha używamy metody isValid. W Serwlecie w razie błędnego wpisania kodu, użytkownik zostaje przekierowany do strony z formularzem, a jeśli kod jest poprawny, idzie do strony która wyświetla ekran powitalny.

wtorek, 23 lutego 2010

JSF + Tomcat 6 + Eclipse - Hello World

Konfiguracja Apache Tomcat 6
Zaczniemy od przygotowani środowiska pracy. Potrzebujemy oczywiście zainstalowanego Tomcat'a oraz pliku jstl-1.2.jar. Plik jar ściągamy i kopiujemy do katalogu tomcat_home\lib.

Pierwsza aplikacja Java Serwer Faces
Uruchamiamy eclipse i tworzymy nowy Dynamic Web Project.

Wpisujemy nazwę naszego produktu i ustawiamy:

  • Target runtime: na Apache Tomcat v6.0
  • Dynamic web model version: na 2.5
  • Configuration: na JavaSerwer Faces v1.2 Project

Wszystko powinno wyglądać mniej więcej tak:

Następnie klikamy Next > do momentu aż pojawi się okienko z ustawieniami JSF. Klikamy na Download library.
Po krótkiej chwili pojawi się okienko z dostępnymi bibliotekami do JSF. Wybieramy JSF 1.2 (Apache MyFaces) i klikamy Next >.
Akceptujemy licencję i klikamy Finish.

Dodajemy do URL Mapping Patterns: *.jsf.

Jeśli wszystkie kroki wykonaliśmy prawidłowo wszystko powinno wyglądać tak:


Właśnie stworzyliśmy strukturę projektu. Jak pewnie już zauważyliście jest to normalny Web Projekt z katalogiem Web Content w którym znajduje się katalog WEB-INF z plikiem web.xml. Jest jednak mała różnica, w katalogu WEB-INF znajduje się plik o nazwie faces-config.xml. Jest to plik gdzie znajduje się cała konfiguracja odnośnie JSF. Na razie plik ten jest pusty. 
Zaczniemy od stworzenia prostej klasy zarządzającej treścią strony, tzw. bean, warto zwrócić tutaj uwagę że klasa powinna być napisana zgodnie z prostymi zasadami nazewniczymi dla metod zwracających (getNazwaZmiennej) i ustawiających (setNazwaZmiennej) . W katalogu src tworzymy pakiet com.blogspot.mkorwel.jsf a w nim klasę MyBean. Klasa będzie posiadać funkcję która zwróci nam napis "Hello World !!!".

package com.blogspot.mkorwel.jsf;


/**
 * 
 * @author mkorwel
 * 
 */
public class MyBean {


  private String hello = "Hello World !!!";


  public String getHello() {


   return this.hello;
  }

}

Żeby klasę MyBean móc wykorzystywać w JSF musimy dodać ją w faces-config.xml. Otwiera plik XML na zakładce ManagedBean,


zaznaczamy w lewym okienku session i klikamy Add.


Klikamy przycisk Browse... i wybieramy klasę com.blogspot.mkorwel.jsf.MyBean.


W tym momencie możemy już kliknąć Finish. Zapisujemy wprowadzone zmiany i zabieramy się do stworzenia podstawowego widoku, który wyświetli nam napis "Hello World !!!" z klasy MyBean.
W katalogu WebContent tworzymy hello.jsp w którym wyświetlimy napis. Plik u mnie wygląda tak:
<%@ taglib prefix="f"  uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h"  uri="http://java.sun.com/jsf/html"%>
<html>
<head>
<title>JSF Tutorial</title>
</head>
<body>
<f:view>
<h:outputText value="#{myBean.hello}"></h:outputText>
</f:view>
</body>
</html>
W pierwszych dwóch linkach naszej strony definiujemy dwa zbiory znaczników.
Wszystkie znaczniki technologii JSF znajdują się pomiędzy znacznikiem <f:view>.
Znacznik <h:outputText value="#{myBean.hello}"></h:outputText> wyświetla tekst na ekranie, w atrybucie value precyzujemy skąd ma być wzięta treść. W naszym przypadku będzie to klasa która w pliku faces-config.xml nazywa się myBean (czyli klasa com.blogspot.mkorwel.jsf). Po kropce definiujemy metode typu get która ma się wykonać, w naszym przypadku będzie to metoda getHello(), która zwróci "Hello World !!!".

W tym momencie cała aplikacja jest gotowa, wiec możemy ją zdeployować na serwerze. Stronę możemy obejrzeć pod adresem http://localhost:8080/JSFHelloWorld/faces/hello.jsp , http://127.0.0.1:8080/JSFHelloWorld/faces/hello.jsphttp://localhost:8080/JSFHelloWorld/hello.jsf, lub http://127.0.0.1:8080/JSFHelloWorld/hello.jsf.