poniedziałek, 11 stycznia 2010

Servlety w OSGi

Platforma OSGi pozwala na dodanie do naszej aplikacji możliwości jakie oferują serwery aplikacji - uruchamianie własnych servletów.

Po utworzeniu nowego projektu tworzymy klasę servletu - czyli klasę dziedziczącą z "javax.servlet.http.HttpServlet". Prawdopodobnie w MANIFEST.MF będziemy musieli dodać deklarację importującą pakiet "javax.servlet.http" oraz "javax.servlet". Może ona wyglądać np tak:


package sample.servlet;

import java.io.IOException;

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

public class SampleServlet extends HttpServlet{
private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().println("Hello world");
resp.getWriter().close();
}
}


niedziela, 10 stycznia 2010

EMF, edytory graficzne i generowanie kodu: Część I

To pierwszy odcinek z serii postów dotyczących modelowania i generowania kodu przy użyciu narzędzi skupionych wokół Eclipse Modeling Project. Ich celem będzie pokazanie jak w łatwy sposób można zrezygnować z tradycyjnego programowania na rzecz modelowania i generowania kodu. Zaznaczę tylko, że nie mam tutaj na myśli tworzenia modeli przy pomocy ogromnych kombajnów do UMLa - modelujemy w edytorach graficznych(które sami stworzymy) w języku dziedzinowym specjalnie stworzonym do rozwiązania danego problemu.

poniedziałek, 21 grudnia 2009

Testowanie pakunkow OSGI

Ostatni swój projekt realizuje w oparciu o platformę OSGI. Tym razem nie korzystałem z mavena, pax-* i tym podobnych narzędzi - skorzystałem z możliwości tworzenia pakunków jako projektów plug-in'ów eclipse (wskazując jako "Target Platform" Osgi framework zamiast Eclipse).

Utworzyłem pakunki, zacząłem je konfigurować i stwierdziłem że potrzebuję testów jednostkowych (zamiast sprawdzać czy aplikacja działa po prostu uruchamiając ją wolę pisać testy jednostkowe). I w tym miejscu pojawił się problem związany z platformą OSGI. Umieszczanie kodu testowego w tym samym projekcie w którym znajduje się kod aplikacji nie wydaje się ładnym rozwiązaniem. W dodatku moje pakunki byłyby zależne od bibliotek takich jak JUnit czy Mockito... Brrr... Naturalnym było więc utworzenie oddzielnych projektów zawierających testy dla każdego z pakunków. W ten sposób trafiłem z deszczu pod rynnę, gdyż umieszczając testy w oddzielnym pakunku mam dostęp jedynie do wyeksportowanych pakietów, czyli w przypadku gdy postępuję zgodnie ze sztuką (a postępuję ;) ) - samych interfejsów. W ten sposób testy jednostkowe przestają być jednostkowe. :|

Na pierwszy rzut oka sytuacja patowa. Na szczęście udało mi się znaleźć rozwiązanie. Polega ono na umieszczeniu kodu testowego w "Project Fragment" - specjalnym rodzaju projektu plug-in'u pozwalającego na jego dekompozycję. Tak więc mając projekt (typu "Plug-in Project") o nazwie abc zawierający pakunek tworzę nowy projekt (typu "Fragment Project") o nazwie abc.test wskazując jako Host Plug-in projekt abc. W ten sposób projekt abc.test posiada pełen dostęp do pakunku abc (włącznie z niewyeksportowanymi pakietami), a zarazem pozwala na wydzielenie testów (i ich zależności) z pakunku zawierającego logikę aplikacji.

środa, 9 grudnia 2009

Testowanie aplikacji Swing

Ostatnio postanowiłem uruchomić testy funkcjonalne dużej aplikacji napisanej w Swingu. Założenie było takie, aby testy naśladowały interakcję prawdziwego użytkownika.

Biblioteka Fest-Swing okazała się być idealna do tego celu. Co takiego umożliwia? Moim zdaniem dwie najważniejsze rzeczy to:

  • łatwe przeszukiwanie drzewa komponentów w celu znalezienia tego z którym chcemy podjąć interakcję

  • możliwość symulowania akcji użytkownika



Najpierw uruchamiamy aplikację:

Robot robot = BasicRobot.robotWithCurrentAwtHierarchy();
application("pakiet.aplikacji.Main").start();
FrameFixture mainFrame = findFrame(new GenericTypeMatcher(JFrame.class) {
protected boolean isMatching(JFrame frame) {
return frame.getTitle().startsWith("Tytul aplikacji")
&& frame.isShowing();
}
}).using(robot);


Najpierw tworzymy instancję klasy org.fest.swing.core.Robot - jest to wrapper na java.awt.Robot, gdyż to za jej pomocą odbywa się interakcja z aplikacją.
Następnie przy pomocy metody org.fest.swing.launcher.ApplicationLauncher.application tworzymy obiekt ApplicationLauncher jako parametr wskazując nazwę klasy, w której znajduje się metoda main naszej aplikacji, a następnie uruchamiamy go metodą start.

Następnie odnajdujemy obiekt klasy javax.swing.JFrame. Robimy to przy pomocy metody org.fest.swing.finder.WindowFinder.findFrame, jako argument przekazując instancję klasy org.fest.swing.core.GenericTypeMatcher, w której możemy określić dodatkowe warunki jakie ma spełniać obiekt JFrame.

Warto wspomnieć o klasach *Fixture - są to wrappery na standardowe elementy interfejsu graficznego takie jak przyciski, checkboxy, tabele, listy... to właśnie za ich pomocą możemy podejmować interakcję z komponentem (przykład to metoda click() w klasie ButtonFixture) lub wyszukiwać komponenty zawarte w kontenerze (przykladowo na obiekcie mainFrame możemy wywołać metode comboBox() w celu pobrania obiektu ComboboxFixture.

Jest tylko jedna uwaga: metoda wyszukująca obiekt musi odnaleść dokładnie jeden rezultat. W sytuacji, gdy kontener zabierałby więcej comboboxów, mysiałby zostać wybrany jeden z nich np. przy pomocy obiektu typu GenericTypeMatcher.

czwartek, 3 grudnia 2009

Zmiękczanie wyjatków za pomocą AspectJ

Kolejną ciekawą możliwością jaką daje AspectJ jest automatyczna zamiana twardych (checked) wyjątków na miękkie (unchecked) - rozszerzających klasę RuntimeException.

Czemu ma służyć taki manewr? Oczywiście wygodzie programisty. ;) Czasem zdarza się, że pomiędzy miejscem powstania wyjątku, a miejscem jego obsługi jest po drodze wywoływanych wiele metod. Teraz jeśli rzucanym wyjątkiem jest wyjątek twardy to każda metoda po drodze musi zadeklarować ze może ewentualnie taki wyjątek rzucić.


package wyjatki;

public class Wyjatki {
static void test() throws NoSuchMethodException{
Wyjatki.class.getMethod("abc", String.class);
}
static void test2() throws NoSuchMethodException{
test();
}
public static void main(String[] args) {
try {
test2();
} catch (NoSuchMethodException e) {
//obsluga wyjatku
e.printStackTrace();
}
}
}


Jeśli pisanie deklaracji throws jest dla nas uciążliwe możemy obejść problem opakowywując twardy wyjątek w jego miękki odpowiednik.


package wyjatki;

public class Wyjatki {
static void test(){
try{
Wyjatki.class.getMethod("abc", String.class);
}catch(NoSuchMethodException e){
throw new RuntimeException(e);
}
}
static void test2(){
test();
}
public static void main(String[] args) {
try {
test2();
} catch (RuntimeException e) {
Throwable e2 = e.getCause();
//obsluga wyjatku
e2.printStackTrace();
}
}
}


Kod w działaniu zachowuje się tak samo, ale metody test oraz test2 nie muszą deklarować NoSuchMethodException. W każdym razie programista musi ręcznie opakować wyjątek. I tutaj na scenę wkracza AspectJ. Posiada on deklarację declare soft, która odpowiada za opakowanie twardego wyjątku jego miękkim odpowiednikiem. Dla naszego przykładu wygląda ona następująco:


package wyjatki;

public aspect WyjatkiAspect {
declare soft : NoSuchMethodException : execution(void wyjatki.Wyjatki.test());
}


Natomiast klasa Wyjatki prezentuje się następująco:


package wyjatki;

public class Wyjatki {
static void test(){
Wyjatki.class.getMethod("abc", String.class);
}
static void test2(){
test();
}
public static void main(String[] args) {
try {
test2();
} catch (RuntimeException e) {
Throwable e2 = e.getCause();
//obsluga wyjatku
e2.printStackTrace();
}
}
}


Jak widać nie dość, że pozbyliśmy się deklaracji throws to jeszcze wyeliminowaliśmy opakowywanie NoSuchMethodException w metodzie test.

Na koniec dosyć ważna uwaga: twarde wyjątki po istnieją, aby były jawnie obsługiwane, dlatego ten mechanizm nie służy temu aby opakować wyjątek i o nim zapomnieć. Trzeba pamietać, aby go właściwie obsłużyć.

środa, 2 grudnia 2009

Static crosscutting w AspectJ

Każdy kto cokolwiek słyszał o AspectJ wie, że można za jego pomocą modyfikować wywołania metod (dynamic crosscutting), jednak mało kto wie, że za jego pomocą można także modyfikować statyczne właściwości klas, np dodawać atrybuty lub metody. To jest właśnie static crosscutting. Jak wykorzystać static crosscutting zaprezentuję na prostym przykładzie.

Tak więc mamy klasę reprezentująca wpis w serwisie blogowym.

package blog;

import java.util.ArrayList;
import java.util.List;

public class Wpis {
private String tresc;
private List komentarze = new ArrayList();
public Wpis() {
}
Wpis(String tresc){
this.tresc = tresc;
}
public String getTresc() {
return tresc;
}
public void setTresc(String tresc) {
this.tresc = tresc;
}
public List getKomentarze() {
return komentarze;
}
public void setKomentarze(List komentarze) {
this.komentarze = komentarze;
}
public void addKomentarz(String komentarz){
this.komentarze.add(komentarz);
}
@Override
public String toString() {
return "Wpis o tresci: " + tresc;
}
}


Dla celów demonstracyjnych utworzyliśmy także klasę Test demonstrującą użycie klasy Wpis.


package blog;

public class Test {
public static void main(String[] args) {
Wpis w = new Wpis("Hello world");
System.out.println(w.getTresc());
System.out.println(w.toString());
}
}


Załóżmy teraz, że chcemy dodać do niej atrybut reprezentujący datę utworzenia, automatycznie inicjowany bieżącym czasem.


package blog;

import java.text.MessageFormat;
import java.util.Date;

public aspect WpisAspect {
private Date blog.Wpis.dataUtworzenia;

public Date blog.Wpis.getDataUtworzenia(){
return this.dataUtworzenia;
}

after(Wpis wpis) returning() : execution(blog.Wpis.new(..)) && target(wpis) {
wpis.dataUtworzenia = new Date();
}

String around(Wpis wpis) : execution(public String blog.Wpis.toString()) && target(wpis) {
return MessageFormat.format("Wpis z dnia {0} o tresci: {1}", wpis.getDataUtworzenia(), wpis.getTresc());
}
}


Najpierw definiujemy nowy atrybut w klasie Wpis - jak widać nie różni się to od zwykłego utworzenia atrybutu, z wyjątkiem tego, że nazwa atrybutu zawiera też nazwę typu w którym będzie zdefiniowany. Następnie do klasy Wpis dodajemy metodę umożliwiającą pobranie wartości nowo-utworzonego atrybutu. Dokonujemy też modyfikacji wszystkich konstruktorów, tak by go odpowiednio inicjowały. Ostatnią modyfikacją jest "nadpisanie" metody toString() własną implementacją, która uwzględnia atrybut dataUtworzenia.

Oto rezultat uruchomienia klasy test:

Hello world
Wpis z dnia 02.12.09 19:33 o tresci: Hello world

Jak widać wszystko działa zgodnie z naszymi zamierzeniami.

Do czego można wykorzystać static crosscutting?

Pierwszą rzeczą jaka się nasuwa w odpowiedzi jest możliwość modyfikacji kodu do którego nie posiadamy źródeł. W takiej sytuacji możemy dokonać modyfikacji w kodzie bez nieetycznej dekompilacji.

Drugim, chyba sensowniejszym przykładem jego użycia będzie dodanie do klasy elementów, które chcemy mieć, ale które są typowo technicznymi jej składnikami, np logger, czy połączenie z bazą danych.

czwartek, 12 listopada 2009

Ejb dependency injection w JBoss AS

Pol dnia męczyłem się ze wstrzykiwaniem zaleznosci w Jboss AS.

Miałem bezstanowe ziarno ejb implementujące lokalny interface:


@Stateless(mappedName="ABC", name="ABC")
public class ABCService implements ABCLocal{
...
}


, które próbowałem wstrzyknąć do servletu używając


@EJB
ABCLocal abc;


Ponadto próbowałem ustawiać parametry adnotacji EJB

@EJB(beanName="ABC)
ABCLocal e1;
@EJB(beanName="ABC/local")
ABCLocal e2;
@EJB(mappedName="ABC")
ABCLocal e3;
@EJB(mappedName="ABC/local")
ABCLocal e4;
@EJB(name="ABC")
ABCLocal e5;
@EJB(name="ABC/local")
ABCLocal e6;


- każdorazowo wartością był null.

Zgodnie z dokumentacją JBossa ziarna są rejestrowane w JNDI pod nazwa_ear/nazwa_ejb/local oraz nazwa_ear/nazwa_ejb/remote. Niestety to rozwiązanie nie odpowiadało mi, gdyż nie chciałem uzależniać działania aplikacji od nazwy archiwum ear.

Ostatecznie zdecydowałem sie na dodanie do ziarna adnotacji


@LocalBinding(jndiBinding = "ABCApp/ABC/local")


dzięki czemu mogę sie odwoływać do ziarna za pomocą


@EJB(mappedName="ABCApp/ABC/local")
ABCLocal abc;