środa, 15 lipca 2009

Zaznaczanie kawałka tekstu w dla pola input=text

O ile problem nie jest specjalnie nowy, o tyle znalezienie rozwiązania stanowiło dla mnie spore wyzwanie. W pierwszym momencie uznałem nawet, że javascript po prostu nie daje takich możliwości. Dopiero gdy postanowiłem znaleźć jak się ustawia kursor w dowolnym miejscu pola input, okazało się, że zaznaczanie tekstu również jest dość proste. Oto rozwiązanie:

if (input.createTextRange) {
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', selectionEnd);
range.moveStart('character', selectionStart);
range.select();
}
else if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
}
}


Rozwiązanie zaczerpnięte ze strony http://faqts.com/knowledge_base/view.phtml/aid/13562

poniedziałek, 29 czerwca 2009

Piractwo komputerowe

W ostatnim czasie pojawiło się sporo aresztowań związanych z piractwem. Ofiarami tego procederu są głównie studenci, chociaż nie tylko. Ponieważ jest to blog dotyczący programowania, postanowiłem przedstawić swój punkt widzenia związany z piractwem.

Tak na prawdę, mamy kilka grup pozyskujących oprogramowanie:
  • Osoby które pobierają oprogramowanie nielegalnie, i nie zrobiły by inaczej.
  • Osoby które pobierają oprogramowanie nielegalnie, bo tak jest prościej.
  • Osoby które pobierają oprogramowanie nielegalnie, bo nie stać ich aby pozyskać je inaczej.
  • Osoby które kupują oprogramowanie, bo nie potrafiły złamać zabezpieczeń
  • Osoby które kupują oprogramowanie, bo uważają, że twórca powinien dostać wynagrodzenie za swój trud.
W zasadzie nie popieram piractwa. Mówię w zasadzie, ponieważ mój punkt widzenia jest uzależniony od tego, o której grupie mówię.

Jeżeli mówimy o grupie pierwszej, do której należą osoby które celowo pobierają oprogramowanie, łamią zabezpieczenia i na przykład sprzedają je dalej, tak aby osiągnąć z tego powodu korzyści, to uważam, że jest to postępowanie karygodne i należy je tępić.

Jeżeli mówimy o osobach wygodnych, to jest to kwestia złej polityki firm wydających oprogramowanie. Nie mówię tutaj o zaostrzeniu polityki bezpieczeństwa, tylko o złych zasadach udostępniania oprogramowania. Jeżeli ktoś z tej grupy ma możliwość pobrania za darmo oprogramowania z ograniczoną funkcjonalnością, to istnieje duża szansa, że takie oprogramowanie pobierze. Dodatkowo, jeżeli w przyszłości będzie potrzebował pełnej funkcjonalności, to istnieje duża szansa, że to oprogramowanie zakupi.

W przypadku osób których nie stać, to są to osoby, które i tak by nie zakupiły naszego oprogramowania i ściganie ich, tak na prawdę nic nie zmienia, a dodatkowo psuje wizerunek naszej firmie. Co komu zawinił biedny student, że się nasyła na niego policję i ze sporym prawdopodobieństwem rujnuje dalsze życie? Ten student i tak by oprogramowania nie zakupił, a co za tym idzie, nie poniesiemy żadnej straty z tytułu tego, że pobierze on nasze oprogramowanie. Warto przygotować dla takiego rynku specjalną ofertę, ale tak na prawdę ściganie mija się z celem.

Dla nie potrafiących złamać zabezpieczenia. Cóż, podstawowe zabezpieczenia w aplikacji warto stosować, właśnie ze względu na tą grupę. Natomiast bardziej zaawansowane algorytmy mijają się z celem. Zastosowanie bardziej skomplikowanych algorytmów spowoduje tylko większe zainteresowanie hakerów pragnących wyzwań, i w ten sposób powstanie w miarę łatwo dostępny crack do naszych zabezpieczeń. Po co dawać takie wyzwanie? Lepsza jest dobra polityka licencyjna dająca dostęp do produktu, nawet jeżeli z biznesowego punktu widzenia nie koniecznie ma to sens.

Dla pozostałej grupy należą się podziękowania za uczciwość. Dzięki tej grupie zarówno biznes oprogramowania jak i muzyczno-filmowy nadal może się rozwijać.

Do tego wszystkiego dochodzi jeszcze kwestia samych zabezpieczeń, które osiągnęły poziom, przy którym wgrywa się cracka tylko po to, żeby łatwiej się korzystało z produktu. Mam kilku znajomych, którzy kupują oryginalne oprogramowanie, a następnie wgrywają cracki, bo na przykład nie chce im się za każdym razem jak chcą pograć w grę, szukać płyty.

Myślę że swój punkt widzenia przedstawiłem. Jestem przeciwny piractwu, ale jestem jednocześnie przeciwny idiotycznym politykom licencyjnym oraz ściganiem ludzi którzy nigdy nie nabyli i nie mogli nabyć danego oprogramowania.

poniedziałek, 18 maja 2009

Hibernate, dziwne zachowanie InheritanceType.SINGLE_TABLE

Dziś trafiłem na bardzo ciekawy problem. Korzystałem z obiektów, z dziedziczeniem oraz mapowaniem z wykorzystaniem pojedyńczej tabeli. Tego typu rozwiązanie ma tą zaletę, że jest szybkie, oraz tą wadę, że potrzeba trochę więcej miejsca w bazie. Dla mnie to drugie nie było problemem w tej konkretnej sytuacji, a ponieważ zależało mi na szybkości, to ta metoda na pozór wyglądała bardzo ciekawie.

W skrócie moje klasy wyglądały tak:


@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorValue("PLAYER_SHORT_INFO")
public class PlayerShortInfo implements IsSerializable {

@Id @GeneratedValue
@Column(name = "ID", unique = true, nullable = false)
private long id;
[...]
}

@Entity
@DiscriminatorValue("PLAYER_INFO")
@PrimaryKeyJoinColumn(name="ID")
public class PlayerInfo extends PlayerShortInfo implements IsSerializable {
@ManyToOne(optional = false, cascade = {CascadeType.REFRESH, CascadeType.PERSIST}, fetch = FetchType.EAGER)
PlayerType type;
[...]
}


Nigdy nie tworzyłem obiektów klasy PlayerShortInfo.Dziedziczneie było zrobione głownie dla ograniczenie ilości danych przesylanych klientowi, gdy potrzebuje tylko danych skróconych (ale na przykład nie dla jednego obiektu, a dla 100). Takie rozwiązanie wydało mi sie proste i wydajne. Niestety, dziś okazało sie inaczej.

Klasa PlayerInfo, jest klasa która nigdy nie jest przekazywana do warstwy WEB. Jest wykorzystywana tylko w logice biznesowej, a wiec nawet nie było potrzeby, aby implementowała IsSerializable (wymagane przez GWT). W czasie testów, wyszło jednak, ze GWT nie może przetransportować listy zawierającej PlayerShortInfo ponieważ PlayerType (!!!) nie implementuje IsSerializable. Po sprawdzeniu, okazało sie, ze zeczywiscie nie implementuje, tylko ze PlayerType nigdy nie miał być przesyłany do warstwy WEB.

Po dłuższych poszukiwaniach, okazało sie ze SELECT: (List)em.createQuery("select d from PlayerShortInfo d order by id").getResultList(); zwraca listę nie PlayerShortInfo ale PlayerInfo

Wnioski:
InheritanceType.SINGLE_TABLE jest nieco mylące. Obiektem ładowanym z bazy jest zawsze obiekt który został zapisany, nawet jeżeli w Select jest jasno podane ze chcemy załadować obiekt wyższego rzędu. Co sie dzieje, jest wykonywane załadowanie obiektu który został zapisany a następnie rzutowanie go na element docelowy. Sprawa jest bardziej skomplikowana, bo jeżeli dziecko jest obiektem ciężkim, to pomimo ze chcemy załadować lekkiego rodzica, załadowany zostanie ciężki obiekt dziecka, razem ze wszystkimi atrybutami które ono posiada (na przykład listami innych obiektów).

Rozwiązanie:
Moim zdaniem nie ma dobrego rozwiązania. Trzeba być świadomym problemu i stosować takie rozwiązania w swojej aplikacji, aby nie było potrzeby stosowania tego rodzaju dziedziczenia.

poniedziałek, 11 maja 2009

Duża ilość elementów w GWT

GWT generalnie jest fajną platformą, ale niestety sam javascript nie jest najszybszy. Okazuje się, że dodanie dużej ilości elementów załadowanych z bazy i wstawienie ich do tabeli stanowi spory problem. Na przykład, ładując 500 elementów i wstawiając je do FlexTable albo do Grid, strona ładuje się dość długo, szczególnie na słabszych komputerach. Ponieważ zależało mi na szybkości ładowania elementów, postanowiłem temat zbadać i spróbować coś z nim zrobić.

Zarówno Grid jak i FlexTable, a w zasadzie również VerticalPanel oraz HorizontalPanel są oparte o tablice. Tablica jako taka jest elementem dość skomplikowanym w budowie, a dodatkowo GWT dodaje dość dużo kodu sprawdzającego, warunkowego oraz debugu. Cały ten kod powoduje, że Całość działa o wiele wolniej.

Ponieważ efekt zbliżony do tabeli można osiągnąć również przy pomocy arkusza styli CSS oraz zwykłych div, postanwoiłem spróbować przerobić standardową tabelę na pseudotabelę zrobioną przy pomocy DIV z flow:left; GWT posiada kilka elementów które mi na to pozwalają. Pierwszy z nich to FlowPanel który jest tłumaczony na DIV i pozwala na dodanie innych komponentów do środka. Drugi to Label, SimplePanel oraz Hyperlink pozwalające na dodanie innych elementów.

Są dwie zasady jeżeli chcemy osiągnąć efekt właściwy.
  • Wszystkie elementy muszą mieć na stałe ustawioną wysokośc oraz szerokość
  • Sumaryczna szerokość elemtnów wewnątrz "tabeli" musi być równa szerokości "tabeli"
Wygenerowany kod będzie wyglądał tak:


<div style="width:200px;display:block;">
<div style="float:left;width:100px;height:20px;">Nazwa 1</div>
<div style="float:left;width:100px;height:20px;">Wartosc 1</div>
<div style="float:left;width:100px;height:20px;">Nazwa 2</div>
<div style="float:left;width:100px;height:20px;">Wartosc 2</div>
</div>


Efekt wyglada tak:
Nazwa 1
Wartosc 1
Nazwa 2
Wartosc 2


Z wygladu mamy tabele, a wiec to o co chodzi.

To samo mozna osiagnac w GWT, tylko style nalezy przeniesc do arkusza styli.

FlowPanel content = new FlowPanel();
Label l1 = new Label("Nazwa 1");
Label w1 = new Label("Wartosc 1");
Label l2 = new Label("Nazwa 2");
Label w2 = new Label("Wartosc 2");

content.setStyleName("table_main");
l1.setStyleName("table_field");
l2.setStyleName("table_field");
w1.setStyleName("table_field");
w2.setStyleName("table_field");

content.add(l1);
content.add(l2);
content.add(w1);
content.add(w2);

initWidget(content);

STYLE:

.table_main{
width:200px;
display:block;
}

.table_field{
float:left;
width:100px;
height:20px;
}



Ponieważ FlowPanel nie wymaga dużo sprawdzeń i inicjalizacji to jego utworzenie jest bardzo proste. Dodanie kolejnych elementów nie wymaga tworzenia struktury drzewa, tak jak w przypadku kontenerów opartych o tablice. Dzięki temu, ilość kodu javascript który musi zostać wykonany jest dużo mniejsza, a co za tym idzie jest mocno zauważalny wzrost wydajności tego rozwiązania w stosunku do standardowej tabeli.
Wada tego rozwiązania jest taka, że jednak nie jest to standardowa tabela, a co za tym idzie, nie ma możliwości skalowania tabeli. Na szczęście w większości dzisiejszych layoutów wielkości są zablokowane na stałe, a więc ten problem dotyczy niewielkiej ilości przypadków.

PS. Nie mieżyłem wzrostu wydajności, ale wzrost wydajności był zauważalny bez mieżenia. Strona oparta o tabele ładowała się wolno. Strona oparta o to rozwiązanie ładowała się w zasadzie odrazu.

wtorek, 21 kwietnia 2009

Richfaces VS Icefaces

Ten artykuł był w sumie jednym z pierwszych które chciałem napisać. Było to spowodowane dylematem który miałem podczas wyboru technologii webowej do następnej aplikacji. W tamtym okresie miałem już doświadczenia z IceFaces i nie byłem do końca przekonany do tej właśnie technologii, natomiast brakowało mi doświadczeń z RichFaces, a był to framework który zapowiadał się bardzo ciekawie. Niestety, nie udało mi się znaleźć na sieci dobrego porównania tych technologii, w związku z tym postanowiłem napisać ten artykuł.

Ogółem

W ogólnym zarysie obie technologie wyglądają podobnie. Posiadają dokładnie to samo zastosowanie, podobny zakres komponentów oraz oparte są na technologii JSF. Można zauważyć pewne drobne różnice jeżeli idzie o dostępne komponenty, ale posiadają podobną ilość. Osobiście odniosłem jednak wrażenie, że IceFaces ma komponenty jednak troche lepiej dobrane i kompletne, podczas gdy w RichFaces są one odrobinę bardziej surowe, ale tak na prawdę jest to raczej subiektywne odczucie. Na podstawie pobieżnego obejrzenia nie byłem w stanie jednak jednoznacznie wybrać bądź odrzucić żadnej z technologii.

Instalacja

Instalacja IceFaces nie należała do najprzyjemniejszych. Co prawda mówię o wersji 1.6, a więc mogło się to zmienić, nie mniej taką właśnie wersję instalowałem. O ile praca odbywała się na serwerze WebLogic problemów nie było. Wszystko działało tak jak trzeba. Niestety na etapie instalacji na JBoss (4.2.2) instalacja nawet przykładowej aplikacji nie była możliwa do wykonania. IceFaces zawiera wbudowaną wersję JSF. Jest to również prawda w przypadku serwera JBoss. Jak można się łatwo domyślić wersje te nie były zgodne i nie chciały ze sobą współpracować. Po wielu próbach rozwiązania problemu, włącznie z postami na forum IceFaces z prośbą o pomoc poddałem się. Dwa miesiące później na forum pojawiły się jakieś pomysły rozwiązań, ale jeżeli mam być szczery, to było to obejście problemu (na poziomie serwera) a nie jego rozwiązanie.

Instalacja RichFaces była stosunkowo prosta. Jedyne co dodatkowo musiałem zrobić, to zainstalować implementacje JSF, ponieważ sam framework był dostarczony bez. Po zainstalowaniu wszystko działało zarówno na serwerze JBoss jak i na WebLogic.

Ajax

Największą zaletą obu frameworków jest obsługa AJAX. Oba podchodzą do problemu trochę inaczej. W przypadku IceFaces Ajax jest wbudowany w komponenty i w zasadzie przezroczysty dla użytkownika. Kontrola nad nim sprowadza sie do kilku ustawień w web.xml oraz parametrach w komponentach. W przypadku RichFaces Ajax jest bardzo widoczny. Jest to rozwiązane za pomocą całej serii dodatkowych komponentów (framework Ajax 4 Java (a4j)). Ten dodatkowy framework jest odpowiedzialny za całą komunikację z serwerem. Niestety oznacza to również, że programista musi napisać dodatkowy kod aby ajax działał w przeciwieństwie do IceFaces, gdzie proces zachodzi automatycznie. Wykorzystanie tego dodatkowego frameworku pozwala jednak na wykorzystanie standardowych komponentów JSF i dodanie do nich obsługi AJAX bez konieczności zastępowania ich. Takie podejście sprawia, że można w łatwy sposób dodać obsługę AJAX do już istniejącej aplikacji. Oba frameworki prezentują również troche inne podejście do odświeżania stanu aplikacji. Oba pozwalają na odpytywanie w celu sprawdzenia czy są nowe dane. Jednak, jeżeli idzie o push, to rozwiązanie zastosowane w obu frameworkach jest nieco inne. W przypadku IceFaces połączenie jest inicjowane ze strony serwera. W przypadku gdy pojawią się jakieś dane, serwer (który utrzymuje cały czas połączenie) wysyła do klienta nowe dane. W przypadku RichFaces klient co jakiś czas odpytuje serwera czy są jakieś dane. Jeżeli serwer przygotował jakąś paczkę danych, to jest ona pobierana. Warto wspomnieć, że RichFaces nie utrzymuje stałego połączenia z serwerem w przeciwieństwie do IceFaces. Osobiście miałem sporo problemów z tym utrzymywanym połączeniem. IceFaces zakładało, że sieć jest idealna, i takie połączenie nie zostanie zerwane. W przypadku zerwania aplikacja nie zachowywała się dobrze. Miało to zostać naprawione w późniejszych wersjach (i jest duża szansa, że obecnie problem już nie występuje).

Problemy

W przypadku obu frameworków natrafiłem na kilka problemów.

IceFaces
Ponieważ sprawa dotyczy starej wersji frameworku myślę, że nie ma sensu opisywać konkretnych problemów z samym frameworkiem, natomiast postaram się oddać zakres problemów z jakim się spotkałem. Po pierwsze były problemy z konkretnymi komponentami, ale były to raczej niewielkie problemy i były raczej uciążliwe, niż uniemożliwiające prace. Rozwiązanie problemów albo było w repozytorium kodu, albo było planowane do wypuszczenia z następną wersją. Niestety problem na który natrafiłem ja był planowany z następną wersją. Mówię niestety, bo o ile po zmianie wersji problem zniknął, o tyle pojawił się inny dużo poważniejszy w komponencie który wcześniej działał prawidłowo. Sama zmiana wersji (z wersji 1.5.x na 1.6) nie przebiegała w sposób prosty i bezproblemowy. Ponieważ zostało zmienione całkowicie nazewnictwo klas w stylach aplikacja straciła cały wygląd. Wymagało to ręcznej zmiany wszystkich styli na docelowe. W wersji 1.6.1 dodane zostało narzędzie do automatycznej migracji styli, ale niestety było to już dłuższy czas po tym, jak style zostały dostosowane. Może to tylko moje odczucia, ale bardzo mi się nie podobało taki załatwienie sprawy.
Dodanie własnego komponentu pracującego razem z AJAX wymagało sporo wysiłku ze strony programisty. Wszelkie niestandardowe rozwiązania, nie przewidziane przez programistów tworzących Framework były dość trudne do zaimplementowania, ponieważ sam framework stanowi dość zamkniętą całość.
Community jest dość rozbudowane i często chętne do pomocy, ale jednak na odpowiedź trzeba trochę poczekać, szczególnie jeżeli problem jest bardziej złożony.
Dokumentacja do projektu jest dość dobra, jest sporo przykładów.

RichFaces
Problemy z samym frameworkiem były podobne do tych z IceFaces. Pojedyńcze elementy nie do końca dobrze funkcjonowały, natomiast znalezienie rozwiązania było znacznie łatwiejsze. Główne problemy natomiast, były nieco innej natury. Do IceFaces można było znaleźć pokaźną ilość przykładów czy HOWTO. Z RichFaces już nie było tak lekko. Ilość przykładów dostarczonych z aplikacją była bardzo skąpa a przykładowy kod demonstrowany w Component Showcase przeklejony żywcem nie zawsze działał. Po analizie kodu źródłowego okazywało się, że źródłowy zawarty na stronie nie posiadał paru elementów koniecznych do działania elementu do którego się odwoływał. Dojście do tego jak dany komponent działał, wymagało przejrzenia kodu źródłowego samej aplikacji demonstracyjnej. Community jest również znacznie mniejsze i trudniej jest uzyskać pomoc w przypadku problemów.

Podsumowanie

Oba frameworki są oprogramowaniem dobrym i stanowią ciekawą ofertę. RichFaces jest napisany tak, że pozwala na zaimplementowanie bardziej specyficznych rozwiązań dużo niższym kosztem, bo kod jest tak pomyślany, że dodanie obsługi Ajax jest proste a same komponenty w większości są standardową implementacją JSF (Sun lub MyFaces). Niestety, to samo rozwiązanie powoduje, że ta dodatkowa kontrola wymaga pisania dodatkowego kodu, a więc dla aplikacji która nie stosuje niestandardowych rozwiązań i liczy się czas, IceFaces jest dużo lepszym rozwiązaniem. Dodatkowo IceFaces jest znacznie lepszy jeżeli idzie o poznanie samego frameworku. Dokumentacja, dobre i duże community sprawia ze ten framework zdobywa coraz większe uznanie. Ja osobiście jednak bardziej polubiłem RichFaces. Miałem z nim znacznie mniej problemów a jednocześnie daje mi dużo większą kontrolę nad tym co się dzieje w aplikacji, co mi osobiście bardzo odpowiada. W przypadku IceFaces czas który został zaoszczędzony na dodatkowym kodzie, został wykorzystany na pisanie łatek do niedziałającego kodu samego frameworku, lub poprawianie nowych problemów po wgraniu kolejnej wersji poprawiającej inne błędy.

Jeżeli miał bym wybierać framework oparty o JSF do następnego projektu to skłaniał bym się bardziej w kierunku RichFaces, jako bardziej dopracowanego i dającego większą swobodę.

środa, 15 kwietnia 2009

GWT oraz Session Beans

Uwaga do tego artykułu.
Kod zawarty w tym wpisie w części nie jest kodem napisanym przezemnie. Sam pomysł został zaczerpnięty z grup googlowych, ja postanowiłem, że pomysł jest na tyle ciekawy, że warto go przedstawić.

Standardowo GWT nie jest zawarte w transakcji. Ten stan raczej nie jest do końca wygodny, szczególnie jeżeli odbywa się jakieś przetwarzanie po stronie serwera. Standardowa struktura aplikacji jest następująca

  • Klient (przeglądarka)
  • Serwer (GWT)
  • Serwer (EJB) (logika biznesowa)
  • DB
Oczywiście ta struktura może być różna, w zależności od aplikacji, ale generalnie wygląda podobnie. Problem polega na tym, że na poziomie Server(GWT) dane nie są objęte transakcjami. Okazuje się jednak, że przy pomocy stosunkowo prostego zabiegu da się tą warstwę całkowicie pominąć.

Standardowo aplikacja GWT wymaga zdefiniowania 2 interfejsów oraz ich implementacji. Interfejsy są zrobione standardowo

Service:

public interface SmapleService extends RemoteService {
// Sample interface method of remote interface
String getMessage(String msg);

/**
* Utility/Convenience class.
* Use SmapleService.App.getInstance () to access static instance of SmapleServiceAsync
*/
public static class App {
private static SmapleServiceAsync app = null;

public static synchronized SmapleServiceAsync getInstance() {
if (app == null) {
app = (SmapleServiceAsync) GWT.create(SmapleService.class);
((ServiceDefTarget) app).setServiceEntryPoint(GWT.getModuleBaseURL() +
"smaple.Smaple/SmapleService");
}
return app;
}
}
}


ServiceAsync:

public interface SmapleServiceAsync {
void getMessage(String msg, AsyncCallback async);
}


Standardowa implementacja wygląda tak:
 
public class SmapleServiceImpl
extends RemoteServiceServlet
implements SmapleService {
// Implementation of sample interface method
public String getMessage(String msg) {
return "Client said: \"" + msg + "\"
Server answered: \"Hi!\"";
}
}


Ten kod niestety nie pozwala na transakcyjność. Wystarczy jednak zrobić kilka niewielkich zmian. Po pierwsze klasa SampleServiceImpl nie powinna implementować interfejsu SampleService. Po drugie, należy przeciążyć metodę processCall odpowiedzialną za obsługę przychodzących wywołąń tak, aby wywoływała metody z SSB.


public class SampleServiceImpl
extends RemoteServiceServlet {
private static final Logger log = Logger.getLogger(SampleServiceImpl.class);

public String processCall(String payload) throws SerializationException {
try {
Object bean = getBean();
synchronized (bean) {
RPCRequest rpcRequest = RPC.decodeRequest(payload, bean.getClass());
log.debug("RPC METHOD CALL: " +
rpcRequest.getMethod() +
" PARAMETERS: " +
Arrays.toString(rpcRequest.getParameters()));
return RPC.invokeAndEncodeResponse(bean, rpcRequest.getMethod(),
rpcRequest.getParameters());
}

} catch (IncompatibleRemoteServiceException ex) {
ex.printStackTrace();
return RPC.encodeResponseForFailure(null, ex);
} catch (NamingException ne) {
ne.printStackTrace();
return RPC.encodeResponseForFailure(null, ne);
}
}

public ISampleBeanService getBean() throws NamingException {
final HttpSession httpSession = getThreadLocalRequest().getSession();
ISampleService bean = (ISampleBeanService) httpSession.getAttribute("SampleBeanService");

if (bean != null) try {
// Call a method to test whether the bean is still live;
// if it's dead, we'll re-create it
synchronized (bean) {
bean.isAlive();
log.debug("Bean alive");
}
} catch (NoSuchEJBException ex) {
log.debug("Bean dead");
bean = null;
httpSession.removeAttribute("SampleBeanService");
}

if (bean == null) {
InitialContext ic = new InitialContext();
Object objRef = ic.lookup("sample/SampleBeanService/local");
bean = (ISampleBeanService) PortableRemoteObject.narrow(objRef,
ISampleBeanService.class);
httpSession.setAttribute("SampleBeanService", bean);
}

return bean;
}


Warto zwrocic uwage, ze nie zgadzaja sie nazwy interfejsow. Bean korzysta z interfejsu ISampleBeanService podczas gdy GWT korzysta z SampleService. To nie jest błąd.


public interface ISampleBeanService extends SampleService {
public boolean isAlive();
}


Ten interfejs jest pomocniczy implementuje tylko metodę isAlive(), wykorzystywaną do sprawdzenia czy SSB zostało stworzone, oraz czy jest nadal dostępne.

Aby całość działała brakuje już tylko samego SSB:


@Stateless(name = "SampleBeanService")
@Local(ISampleBeanService.class)
public class SampleService implements ISampleBeanService {
// Implementation of sample interface method
public String getMessage(String msg) {
return "Client said: \"" + msg + "\"
Server answered: \"Hi!\"";
}
public boolean isAlive() {
return true;
}

}


Bean jak widać jest standardowym beanem. Nie wymaga niczego nadzwyczajnego.

Napotkałem na dwa problemy przy implementacji tego rozwiązania.
  • Problem z sesją. Na dobrą sprawę, jeżeli użyjemy tego rozwiązania pojawia się problem z przechowywaniem danych. Standardowo w GWT dane wrzucane są do sesji HTTP. Niestety w przypadku Statless bean z założenia dane nie są w nim trzymane, a z poziomu SSB nie ma dostępu do Sesji HTTP. Rozwiązaniem jakie się w sumie samo nasuwa są Statefull Session Bean, które w zasadzie właśnie do tego celu były przewidziane.
  • Problem z kompilacją modułów (w Idei). Chodzi o zależności między modułami. W tym przypadku SSB musi być w module WEB, a to nie jest dobre rozwiązanie. Na szczęście znalazłem inne rozwiązanie, które zostało opisane w osobnym wpisie w tym blogu.

środa, 25 marca 2009

Wykorzystanie obiektów z warstw niższych w warstwie WEB GWT.

Jednym z problemów jaki miałem w korzystaniu z GWT było wykorzzystanie tych samych obiektów na poziomie warstwy logiki biznesowej, czy DAO oraz na poziomie warstwy WEB. Teoretycznie są to obiekty POJO, więc problemów być nie powinno, jednak wymaganie, z drugiej strony ich deklaracje znajdują się w module web (a więc w większości przypadków w plikach war).
Okazuje się jednak, że tak być nie musi. Klasy muszą się znajdować w katalogu GWT (w ktatlogu client) w czasie kompilacji kodu GWT, ale później można je przenieść w dowolne miejsce w aplikacji.

Muszą zostać jednak spełnione dwa warunki:
  • Pakiet w którym znajduje się klasa musi być nadal ten sam.
  • Moduł web musi mieć dostęp do skompilowanych źródeł.

W związku z tym struktura, w której całość działała w mojej aplikacji wyglądała tak:

EAR
  • dao.jar
  • business.jar
  • web.war
  • META-INF
WAR
  • images
  • css
  • WEB-INF
  • WEB-INF -> lib -> web.jar
  • META-INF
  • [...].js
  • [...].html
Co istotne, obiekty zawarte były w dao.jar a nie w web.jar.

Oczywiście tego typu struktura wymaga aby biblioteka GWT była importowana w module DAO (ze względu na interfejs IsSerializable), ale jest to niewielkie poświęcenie za możliwość korzystania z tych samych obiektów w module GWT oraz w modulach biznesowych. Dzięki takiemu rozwiązaniu mogę załadować obiekt, na przykład przy pomocy JPA z bazy a następnie przekazać go bezpośrednio do warstwy webowej.

Kompilacja
Całość została przezemnie zautomatyzowana przy pomocy skryptów antowych.
Standardowo obiekty są umieszczone w module DAO.
Przed kompilacją Obiekty są przenszone do modułu GWT.

<move todir="dao/src/pl/project/client/objects">
<fileset dir="webui/src/pl/project/client/objects"/>
</move>


Ant, kompiluje moduł GWT przy pomocy taska antowego dostępnego w bibliotece ant-gwt.

<taskdef resource="dk/contix/ant/gwt/ant-gwt.xml"
classpathref="project.classpath"/>
<gwtcompile destdir="${build.dir}/gwt-web" optimize="true">
<fileset dir="${module.web.basedir}/web/src">
<include name="**/*.gwt.xml"/>
</fileset>
</gwtcompile>

Przenosze spowrotem obiekty do DAO

<move todir="web/src/pl/project/client/objects">
<fileset dir="dao/src/pl/project/client/objects"/>
</move>


Kompiluje pozostałe elementy a następnie składam tworze pliki JAR, WAR oraz EAR

Warto wspomnieć, że GWT nie miało problemów z anotacjami.

Następnym razem napisze jak wymusić na GWT aby wywoływało bezpośrednio Beana EJB (na przyklad Stateless Session Bean), dzięki czemu wszystkie operacje wykonywane są wewnątrz transakcji.

poniedziałek, 23 marca 2009

Obsługa wyjątków w GWT

Sprawa jest stosunkowo prosta od momentu gdy powstało GWT 1.5. Mechanizm RPC zastosowany w GWT pozwala na przekazywanie wyjątków powstałych w warstwach niższych, jednak te wyjątki muszą spełniać pewne warunki.

Wyjątek musi dziedziczyć po Throwable (a więc na przykład Exception). Musi implementować java.io.Serializable oraz co najważniejsze musi implementować com.google.gwt.user.client.rpc.IsSerializable. Ta ostatnia klasa jest wymagana dla przekazywania wszelkich obiektów poprzez RPC, a więc dotyczy to również wyjątków.

Wyjątek jest przechwytywany w metodzie public void onFailure(Throwable caught) i zawarty jest w zmiennej caught. Dalszą obsługę wyjątku możemy robić już w standardowy sposób.

Przykładowy kod:

Klasa błędu

import com.google.gwt.user.client.rpc.IsSerializable;
import java.io.Serializable;

public class MyException extends Exception
implements Serializable, IsSerializable {
String name = getClass().getName();

public String getName() {
return name;
}
}


Obsługa błedu

MyAppService.App.getInstance().method(new AsyncCallback<Void>() {
public void onFailure(Throwable caught) {
if (caught instanceof MyException)
handleException((MyException)caught);
else
handleUnknownException(caught);
}

public void onSuccess(Void result) {
[...]
}
});


Deklaracja metody w MyAppAsync

public interface MyAppAsync(){
void method(AsyncCallback<Void> async);
}


Niestety to rozwiązanie posiada dwa poważne problemy.
Po pierwsze pozwala na przesłanie tylko własnych klas błędów.
Po drugie wymaga aby klasa błędu była w pakiecie client w module zawierającym kod GWT. Oznacza to poważny problem w zależnościach między modułami.

Rozwiązanie drugiego problemu w skrócie:
Jest możliwa kompilacja modułu GWT przy pomocy kompilatora GWT (do postaci javascript), a następnie kompilacja źródeł z innym modułem. To może być na przykład moduł trzymający wszystkie obiekty. Istotne jest tylko, aby moduł GWT posiadał dostęp do tej skompilowanej klasy oraz klasa znajdowała się w odpowiednim pakiecie.

Ten konkretny problem postaram się opisać szerzej następnym razem.

Wstęp

Postanowiłem stworzyć tego bloga, aby podzielić się z szerszym gronem moimi frustracjami oraz spostrzeżeniami. Może ktoś, dzięki moim spostrzeżeniom w przyszłości uniknie błędów, z którymi ja się spotkałem.