czwartek, 16 października 2008

Moc dialektów Hibernate

Od czasu do czasu mam przyjemność przeprowadzania rozmów rekrutacyjnych z kandydatami do pracy. Osobom, które deklarują znajomość Oracle PL/SQLa i przebrną przez pierwsze pytanie dotyczące sekwencji (a 2/3 znawców PL/SQLa nie wie, co to jest sekwencja) zadaję następujące zadanko:

Przedstawiam encję Osoby:


i proszę o napisanie zapytania pokazującego 3 najmniej zarabiające osoby. Jeżeli kandydat zna również MySQL, proszę o napisanie tego zapytania w 2 wersjach. Bajer polega na tym, że w MySQLu do ograniczenia listy wyników do trzech wystarczy wykorzystać klauzulę limit:

SELECT * FROM OSOBY ORDER BY OSOBY.PENSJA LIMIT 3;


podczas gdy w Oracle takiej magicznej klauzuli nie ma. Jest tam za to możliwość wyświetlenia wierszy o określonym numerze (indeksie):

SELECT * FROM OSOBY WHERE NUMROW <= 3 ORDER BY OSOBY.PENSJA;

ale numer wiersza jest sprzed sortowania, zatem powyższe zapytanie wyświetli trzech pierwszych użytkowników posortowanych po wielkości pensji. Aby osiągnąć zamierzony cel, trzeba skorzystać z podzapytania:

SELECT * FROM (SELECT * FROM OSOBY OSOBY ORDER BY OSOBY.PENSJA ) WHERE NUMROW <= 3;

Zapytanie o to samo, a jednak inaczej skonstruowane (a niby SQL to język deklaratywny).
Zaczęło mnie zastanawiać, co na to Hibernate. Czy jest na tyle mądry, że potrafi to samo zapytanie HQLowe zapisać na 2 zupełnie różne sposoby? Czy jego dialekty, ograniczają się jedynie do podmiany słów kluczowych i nazw typów, czy są mechanizmem dużo potężniejszym? Przekonajmy się.

Na bazie aplikacji Hibernate Getting Started - Hello World stworzyłem prosty projekt w środowisku Eclipsie. Skasowałem pliki Message.java oraz Message.hbm.xml.

Stworzyłem za to klasę Person:

public class Person {
private Long id;
private String name;
private int salary;

public Person() {

}

public Person(String text, int salary) {
this.name = text;
this.salary = salary;
}

public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}
public void setName(String text) {
this.name = text;
}

public int getSalary() {
return salary;
}

public void setSalary(int salary) {
this.salary = salary;
}

}

oraz plik Person.hbm.xml odzwierciedlający zawartość klasy Person w bazie danych:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="hello.Person" table="PEOPLE">

<id name="id" column="PERSON_ID">
<generator class="native">
</generator>
</id>

<property name="name" column="NAME" type="string" />

<property name="salary" column="SALARY" type="integer" />

</class>

</hibernate-mapping>

Skonfigurowałem w pliku hibernate.cfg.xml połączenie z bazą danych MySQL:

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>

<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>

<property name="hibernate.connection.username">test</property>
<property name="hibernate.connection.password">test</property>

<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>

<!-- SQL to stdout logging -->
<property name="show_sql">true</property>

<property name="hibernate.hbm2ddl.auto">update</property>
<mapping resource="hello/Person.hbm.xml" />
</session-factory>
</hibernate-configuration>

Dzięki parametrowi
<property name="hibernate.hbm2ddl.auto">update</property>
schemat bazy danych zostanie dostosowany do aplikacji (zostaną utworzone lub zmodyfikowane odpowiednie tabele) przy jej starcie.
Natomiast parametr
<property name="show_sql">true</property>
pozwala wyświetla w konsoli zapytań SQL, które wykonuje Hibernate.

Parametr
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
określa tzw. dialekt Hibernate, który odpowiada za tłumaczenie zapytań Hibernate na zapytania SQLowe zrozumiałe dla konkretnego systemu zarządzania bazami danych.

Do tego zmieniłem zawartość metody main() klasy HelloWorld:

public static void main(String[] args) {
Session session = HibernateUtil.getSessionFactory().openSession();
Collection people = session.createCriteria(Person.class).list();
if (people.size() < 5) {
Transaction tx = session.beginTransaction();

Person p1 = new Person("Adam", 100);
Person p2 = new Person("Marek", 200);
Person p3 = new Person("Adrian", 150);
Person p4 = new Person("Konrad", 180);
Person p5 = new Person("Romek", 120);

session.persist(p1);
session.persist(p2);
session.persist(p3);
session.persist(p4);
session.persist(p5);

tx.commit();
}

Query query = session.createQuery("FROM Person p ORDER BY p.salary").setMaxResults(3);
List list = query.list();
for (Person person : list) {
System.out.println(person.getName() + " " + person.getSalary());
}

session.close();
HibernateUtil.shutdown();
}

Na początek prostsze zadanie - zapytanie do bazy MySQL. Odpalenie aplikacji kończy się spodziewanym wynikiem:

Hibernate: select person0_.PERSON_ID as PERSON_ID0_, person0_.NAME as NAME0_,
person0_.SALARY as SALARY0_ from PEOPLE person0_ order by person0_.SALARY limit ?
Adam 100
Romek 120
Adrian 150

Czas więc na uruchomienie aplikacji z bazą Oracle. Na początku, trzeba zmienić odrobinę plik Person.hbm.xml tak, aby unikalny identyfikator osoby generowany był z sekwencji:

<id name="id" column="PERSON_ID">
<generator class="sequence">
<param name="sequence">people_seq</param>
</generator>
</id>

oraz plik hibernate.cfg.xml, konfigurując połączenie z bazą Oracle:

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<property name="hibernate.connection.url">jdbc:oracle:thin:@127.0.0.1:1521/xe</property>
<property name="dialect">org.hibernate.dialect.OracleDialect</property>

<property name="hibernate.connection.username">test</property>
<property name="hibernate.connection.password">test</property>


<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>

<!-- SQL to stdout logging -->
<property name="show_sql">true</property>

<property name="hibernate.hbm2ddl.auto">update</property>

<mapping resource="hello/Person.hbm.xml" />

</session-factory>
</hibernate-configuration>

Uruchamiam aplikację i ...

Hibernate: select * from
( select person0_.PERSON_ID as PERSON_ID0_, person0_.NAME as NAME0_,
person0_.SALARY as SALARY0_ from PEOPLE person0_ order by person0_.SALARY )
where rownum <= ?
Adam 100
Romek 120
Adrian 150

Czyli bez niespodzianki. Dialekty Hibernate są więc dość potężnym mechanizmem. Pozwalają nie tylko na zmianę pojedynczych słów kluczowych, ale także konstrukcji całych zapytań. Trudno się zresztą dziwić. Hibernate to już bardzo dopracowany produkt.

Niech no mi się teraz nawinie ktoś na rozmowie rekrutacyjnej, ze znajomością Oracle PL/SQLa i Hibernate...

niedziela, 21 września 2008

Mój ulubiony Exception - java.lang.ArrayIndexOutOfBoundsException

a wszytko zaczęło się od popełnienia przeze mnie aplikacji. JSF, Facelets, Spring, JPA. Nic specjalnego... Poza tym, że na owej aplikacji od czasu do czasu (co kilka dni) wyskakiwał wyjątek java.lang.ArrayIndexOutOfBoundsException, a dokładniej:

09:17:54,784 ERROR [ajp-0.0.0.0-8009-11] (LifecycleDecorator.java:75) - javax.faces.FacesException: java.lang.ArrayIndexOutOfBoundsException: 1
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:306)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:144)
at pl.matt.common.LifecycleDecorator.render(LifecycleDecorator.java:71)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:245)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at pl.matt.common.filter.ProcessingTimeFilter.doFilter(ProcessingTimeFilter.java:29)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104)
at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:241)
at org.apache.coyote.ajp.AjpProcessor.process(AjpProcessor.java:437)
at org.apache.coyote.ajp.AjpProtocol$AjpConnectionHandler.process(AjpProtocol.java:381)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at com.sun.faces.renderkit.RenderKitUtils.buildTypeArrayFromString(RenderKitUtils.java:719)
at com.sun.faces.renderkit.RenderKitUtils.determineContentType(RenderKitUtils.java:572)
at com.sun.faces.renderkit.RenderKitImpl.createResponseWriter(RenderKitImpl.java:219)
at com.sun.facelets.FaceletViewHandler.createResponseWriter(FaceletViewHandler.java:380)
at com.sun.facelets.FaceletViewHandler.renderView(FaceletViewHandler.java:550)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:106)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:251)
... 24 more


Hmm... O co chodzi? Co ciekawe, błąd występował zawsze, gdy na stronę wchodziła przeglądarka Yanga WorldSearch Bot v1.1/beta (http://www.yanga.co.uk/). Robi się coraz bardziej interesująco, prawda? Przy wystąpieniu błędu, miałem też ustawione logowanie nagłówków HTTP. Oto one:

user-agent: Yanga WorldSearch Bot v1.1/beta (http://www.yanga.co.uk/)
accept: text/html;q=1.0, text/plain;q=1.0, text/;q=0.5, */*;q=0.1
accept-charset: utf-8;q=1.0, windows-1251;q=0.8, cp1251;q=0.8, koi8-r;q=0.8, *;q=0.5
accept-encoding: gzip;q=1.0, deflate;q=1.0, identity;q=0.5, *;q=0
content-length: 0


Niby nic specjalnego (no poza przeglądarką) ale sytuacja dziwna. Aplikacja odpalana jest na JBossie 4.2.1.GA, gdzie implementacją JSFów jest implementacja referencyjna SUNa w wersji 1.2.04-p02. Pobrałem źródła, aby podejrzeć 719 linię klasy RenderKitUtils


private final static String CONTENT_TYPE_SUBTYPE_DELIMITER = "/";

//...

// now split type and subtype
if (typeSubType.indexOf(CONTENT_TYPE_SUBTYPE_DELIMITER) >= 0) {
String[] typeSubTypeParts = Util.split(typeSubType.toString(), CONTENT_TYPE_SUBTYPE_DELIMITER);
type = typeSubTypeParts[0].trim();
subtype = typeSubTypeParts[1].trim();
} else {
type = typeSubType.toString();
subtype = "";
}


linia 719 to
subtype = typeSubTypeParts[1].trim();

Klasa parsuje zatem jeden z nagłówków HTTP gdzie spodziewa się dwóch napisów rozdzielonych "/". Od razu moją uwagę zwrócił nagłówek:
accept: text/html;q=1.0, text/plain;q=1.0, text/;q=0.5, */*;q=0.1 a dokładniej jego część: text/;.

Trop był, trzeba było go jeszcze sprawdzić. Zainstalowałem zatem w FireFoxie wtyczkę Modify Headers która umożliwia... zmodyfikowanie nagłówków HTTP. Skonfigurowałem ją tak, aby zastępowała nagłówek accept wartością text/html;q=1.0, text/plain;q=1.0, text/;q=0.5, */*;q=0.1.



uruchomiłem aplikację, wszedłem na nią Firefoksem i...
bingo. Aplikacja się wywala. Zaglądam w logi:

00:03:13,862 ERROR [LifecycleDecorator] javax.faces.FacesException: java.lang.ArrayIndexOutOfBoundsException: 1
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:306)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:144)
at pl.matt.common.LifecycleDecorator.render(LifecycleDecorator.java:71)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:245)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at pl.matt.common.filter.ProcessingTimeFilter.doFilter(ProcessingTimeFilter.java:29)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104)
at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:241)
at org.apache.coyote.ajp.AjpProcessor.process(AjpProcessor.java:437)
at org.apache.coyote.ajp.AjpProtocol$AjpConnectionHandler.process(AjpProtocol.java:381)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at com.sun.faces.renderkit.RenderKitUtils.buildTypeArrayFromString(RenderKitUtils.java:719)
at com.sun.faces.renderkit.RenderKitUtils.determineContentType(RenderKitUtils.java:572)
at com.sun.faces.renderkit.RenderKitImpl.createResponseWriter(RenderKitImpl.java:219)
at com.sun.facelets.FaceletViewHandler.createResponseWriter(FaceletViewHandler.java:380)
at com.sun.facelets.FaceletViewHandler.renderView(FaceletViewHandler.java:550)
at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:106)
at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:251)
... 24 more

Jest mój ulubiony Exception.

Tak jak w tenisie stołowym serwis to pół punktu, tak w programowaniu powtórzenie błędu to połowa jego poprawienia. Teraz tylko... no właśnie...

odpaliłem aplikację w trybie odpluskiwania (debug).



Analizując stos wywołań natrafiłem na 203. linię klasy RenderKitImpl:
String[] typeArray = context.getExternalContext().getRequestHeaderValuesMap().get("Accept");

O co chodzi? Przydała się wiedza z zakresu... serwletów! Na obiekcie HttpServletRequest wywołana jest metoda getHeaders(), która zwraca obiekt klasy Enumeration reprezentujący nagłówki HTTP o zadanej nazwie, w tym przypadku "Accept".

Jak teraz podmienić nagłówek w obiekcie HttpServletRequest? Nie da się...
Da się natomiast, przefiltrować żądanie HTTP, podmieniając obiekt je reprezentujący na obiekt innej klasy (oczywiście implementującej interfejs HttpServletRequest). Brzmi skomplikowanie? Chyba nie jest tak źle.

Zatem po kolei:

tworzę klasę filtra:

public class ModifyRequestHeaderFilter implements Filter {

public void init(FilterConfig filterConfig) throws ServletException {
}

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws java.io.IOException,
ServletException {
if (req instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest) req;
req = new HttpServletRequestWrapper(request);
}

chain.doFilter(req, res);
}

public void destroy() {
}
}


metoda
chain.doFilter(req, res);
przekazuje żądanie do dalszego przetworzenia, ale wcześniej, żądanie jest opakowywane:

HttpServletRequest request = (HttpServletRequest) req;
req = new HttpServletRequestWrapper(request);

w obiekt mojej klasy, która przeciąża metodę getHeaders() klasy HttpServletRequest.

Klasa HttpServletRequestWrapper rozszerza klasę javax.servlet.http.HttpServletRequestWrapper, która w zasazdie tylko opakowuje żądanie HTTP. Delegując wywołania wszystkich metod do opakowanego obiektu, właśnie po to, żeby można było przeciążyć wybrane z nich. To przy okazji świetny przykład wzorca dekorator .Tworzę zatem swoją klasę HttpServletRequestWrapper która przy żądniu nagłówków o nazwie "Accept", podmienia w ich treści "text/;" na "text/*;".


public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {

private String substitutedHeaderName = "Accept";
private String toReplace = "text/;";
private String replacement = "text/*;";

public HttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}

@SuppressWarnings( { "unchecked" })
@Override
public Enumeration getHeaders(String name) {
if (substitutedHeaderName.equals(name)) {
Enumeration headers = super.getHeaders(name);
List out = new ArrayList();
while (headers.hasMoreElements()) {
String header = (String) headers.nextElement();
if (header.contains(toReplace)) {
header = header.replaceAll(toReplace, replacement);
}
out.add(header);
}
return new IteratorEnumeration(out.iterator());
}
return super.getHeaders(name);
}

}


Trzeba jeszcze zarejestrować całe rozwiązanie w deskryptorze wdrożenia - pliku web.xml. Filtr konfiguruję tak, aby obsługiwał wszystkie żądania odwołujące się do stron JSF.


<filter>
<filter-name>ModifyRequestHeaderFilter</filter-name>
<filter-class>pl.matt.common.filter.ModifyRequestHeaderFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>ModifyRequestHeaderFilter</filter-name>
<url-pattern>*.jsf</url-pattern>
</filter-mapping>


Restart aplikacji i... działa. Serwlety wiecznie żywe. Mateusz wiecznie zadowolony.

czwartek, 4 września 2008

JBoss Tools - nowa jakość w tworzeniu aplikacji opartych o JSF

Całkiem niedawno, Piotrek Buczek, jeden z moich zafascynowanych Seamem kolegów pokazał mi kilka sztuczek związanych z tą technologią. Oprócz sztuczek, bardzo zresztą widowiskowych uwagę moją przykuło środowisko na jakim pracował. Był to Eclipse 3.3 z wtyczką JBoss Tools. Wsparcie dla Seama - bardzo pierwsza liga, jak to mawia pan Kazimierz. Jak się ma wsparcie dla samych JSFów, zaraz zobaczymy. Zdradzę tylko, że wcale nie gorzej...

Odkąd dostałem olbrzymią emkę za udział w konkursie Ganymede Around the World Contest nie przystoi mi używać innej wersji Eclipse. Na nim zatem zainstaluję wtyczkę JBoss Tools. Z Ganymede działa tylko rozwojowa wersja wtyczki (JBossTools 3.0.0.Alpha1) więc tą właśnie zainstaluję, ale po kolei...

Wtyczkę można pobrać ze strony projektu JBoss Tools lub zainstalować wykorzystując Eclipsowego zarządcę wtyczek. Wybieram to drugie rozwiązanie. Otwieram menu
Help / Software Updates...
i wybieram zakładkę
Avaliable Software.
Dodaję nową stronę o adresie:
http://download.jboss.org/jbosstools/updates/development



Rozwijam nowo utworzony węzeł drzewka, zaznaczam
JBossTools Development Release 3.0.0.Alpha1
i klikam magiczny przycisk
Install



Eclipse warczy, ściąga, restartuje się... i gotowe. Wypada jeszcze tylko skonfigurować w Eclipse JBossa. Odpalam zatem menu
Window / Preferencess. i tam wybieram pozycję
Server / Runtime Environments następnie klikam
Add... i rozwijam gałąź JBoss, a division of Red Hat.


Kilka razy klikam Next wskazuję położenie na dysku JBossa i gotowe.

Czas przyjrzeć się lepiej możliwościom wtyczki.Tworzę nowy projekt JSF (File / New... / Project... / JSF Project).


Na następnej zakładce wybieram wersję JSF 1.2 z Faceletami oraz szablon FaceletsKickStartWithoutLibs, jako, że biblioteki mam w JBossie.




Zobaczmy co oferuje wtyczka. Otwieram plik WebContent/pages/greeting.xhtml. Pierwsze zaskoczenie - oprócz kodu pliku xhtml, mam zgrabny podgląd.



Klikam dowolne miejsce na podglądzie, podświetla mi się odpowiednie miejsce w kodzie.



Ładnie...
Dodaję nowy tag, zmiany na bieżąco widzę w oknie podglądu.



Jak dla mnie, rewelacja...
Czas na zabawy z klawiszem CTRL. Przyciskam go, najeżdżam myszą na pole name wyrażenia #{person.name}. Klikam..,. i już widzę metodę getName() klasy Person, która w pliku faces-config.xml skonfigurowana jest jako ziarno zarządzane JSF.



Klikam w słówko greeting wyrażenia #{msg.greeting}. No i bomba, jestem już przy edycji pliku messages.properties i jego właściwości greeting.



Plik oczywiście jest skonfigurowany odpowiednio w faces-config.xml.
Przejdźmy zatem do tego pliku. Już pierwszy rzut oka, pozwala nam się zorientować, że mamy możliwość graficznej edycji nawigacji w aplikacji JSF.



Sympatycznie. Tu niestety mały minus... Wtyczka automatycznie formatuje zawartość xmlową pliku faces-config.xml, więc nie bardzo można mieć kontrolę nad wcięciami i kolejnością tagów w pliku. Przynajmniej ja nie potrafię tego osiągnąć.

Wrócę jeszcze na moment do pliku WebContent/pages/greeting.xhtml. Oprócz nawigacji z klawiszem CTRL. Wtyczka JBoss Tools oferuje całkiem niezłe podpowiadanie. Zarówno tagów (wystarczy wpisać <f: i nacisnąć CTRL+spację)



jak i właściwości ziaren zarządzanych



oraz wpisów w pliku messages.properties.



Mało? Zobaczmy, co jeszcze oferuje JBoss Tools. Odpalam projekt (na zakładce JBoss Server View klikam Start the server.



Serwer startuje...
Wchodzę na stronę
http://localhost:8080/jsfProject/

otwieram w Eclipsie plik
WebContent/pages/inputname.xhtml. Zauważyliście, że wykorzystywana jest tu, trochę zapomniana funkcja Faceletów, która umożliwia takie konstruowanie stron JSF, aby były one poprawnie wyświetlane w przeglądarce WWW bez uruchamiania ich na serwerze aplikacji?
Zmieniam tytuł Facelets Hello Application na Witaj!. Zapisuję stronę. Przeładowuję ją w przeglądarce... i gotowe, zmiany od razu są widoczne.



Niestety zmiany w kodzie źródłowym, w plikach messages*.properties lub konfiguracja w faces-config.xml wymagają już restartu serwera.



To zapewne tylko niektóre możliwości wtyczki. Jak dla mnie, są one rewelacyjne. Coś czuję, że JBoss Tools dołączy do mojego zestawu ulubionych wtyczek środowiska Eclipse.

niedziela, 31 sierpnia 2008

koszulka Eclipse, bug naprawiony

Stało się...
Eclipse 3.4.0 ujrzał światło dzienne, a Ganymede Around the World Contest dobiegł końca. Wziąłem udział w tym konkursie, co na szczęście nie umknęło uwadze organizatorów, o czym przekonałem się odbierając przesyłkę:


Niby emka, ale jakaś duża. Przynajmniej nie zrobiona w Chinach a w Kanadzie.

Jakby tego było mało, zgłoszony przeze mnie błąd został już naprawiony. Dołączony zostanie prawdopodobnie do wersji 3.4.1 środowiska.
Poprawiony błąd i koszulka. Chyba warto brać udział w konkursach spod stajni Eclipse.

środa, 13 sierpnia 2008

testy java

Zawsze mnie zastanawiało, skąd się biorą podchwytliwe pytania dotyczące programowania w Javie. Czy wymyślają je programiści, na zasadzie "sztuka dla sztuki", czy pochodzą one z prawdziwych projektów. Moja zagadka pochodzi z życia.

Co będzie wynikiem wywołania funkcji:


public static void main(String[] args) {
BigDecimal arg1 = new BigDecimal("10");
BigDecimal arg2 = new BigDecimal("3");
System.out.println(arg1.divide(arg2).toPlainString());
}


a) na konsole zostanie wypisane: 3,333333333
b) na konsole zostanie wypisane: 3,33
c) na konsole zostanie wypisane: 3
d) zostanie rzucony wyjątek w linii System.out.println(arg1.divide(arg2).toPlainString());
e) na konsole zostanie wypisane: null

no i odpowiedź:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
następnym razem dzieląc obiekty klasy BigDecimal będę uważał.

poniedziałek, 11 sierpnia 2008

Java EE 5 Development using GlassFish Application Server

Korzystając z biblioteki warszawskiego JUGa miałem okazję zapoznać się z książką
"Java EE 5 Development using GlassFish Application Server"
autorstwa Davida R. Heffelfingera. Poniżej przedstawiam recenzję tej pozycji. Recenzja jest po angielsku - takie wymogi wydawnictwa, które wspomaga bibliotekę książkami.

I newer used GlassFish application server so I was really curious about it's features. Reading a book about GlassFish could have been a great possibility to familiarize with it's capabilities. Could have been, but haven't. After first look on the table of contents I realized, that this book is rather about Java EE 5 Development than about Development using GlassFish Application Server. The glass fish on the cover misled me.

So instead of improving my knowledge about GlassFish features I revised my J2EE skills. Hibernate, JDBC, JSP, JSF, EJB, Web Services weren't novelties for me, but I've founded some interesting fragments. Did you know, that you may use Facelets to make JSF-xml pages viewable directly in browser. I didn't. The book contains quite a lot similar more or less useful tricks with example code. Unfortunately it also contains some bugs, like getting new ID by executing
"select max(customer_id) + 1 from customers"
statement.

Generally speaking Java EE 5 Development using GlassFish Application Server by David R. Heffelfinger is a solid 400 pages book about J2EE. If you want to know GlassFish Application Server in details, you will be disappointed, but if you are a newbie in J2EE you will spent a lot of interesting evenings with it. Even more than with one of your girlfriends.

piątek, 1 sierpnia 2008

zdalne debugowanie aplikacji na serwerze aplikacyjnym JBoss

Istnieją jeszcze starej daty programiści którzy lubią pracę z konsolą. Nie uruchamiają oni serwerów aplikacyjnych z poziomu Eclipse, korzystają za to z terminala. Zaliczam się do nich i ja. Podejście takie ma jedną zasadniczą wadę. W Eclipse, aby uruchomić serwer aplikacyjny w trybie debug wystarczy na zakładce Servers kliknąć odpowiednią ikonkę. Z poziomu konsoli jest nieco trudniej. Ale tylko troszeczkę...

Ręczne uruchamianie serwera w trybie debug może się także przydać przy debugowaniu serwera uruchomionego na innym komputerze, niż komputer programisty. Instrukcję jak to zrobić, na przykładzie Eclipse 3.4.0 i serwera JBoss 4.0.5.GA przedstawiam poniżej.

W przykładzie, korzystam z aplikacji akwarium, którą od czasu do czasu posiłkuję się na blogu.

Najpierw zajmę się JBossem. W katalogu bin znajduje się plik run.sh. Kopiuję go na debug.sh. Na początku pliku dodaję:

JAVA_OPTS="$JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n"

To polecenie modyfikuje zmienną JAVA_OPTS, której zawartość dalej w skrypcie staje się parametrami uruchomieniowymi wirtualnej maszyny (JVM) z którą wystartuje serwer. Dobrze jest zapamiętać wartość wpisaną jako address i ustawić suspend=n. W przeciwnym wypadku JBoss nie uruchomi się dopóki nie podłączymy się do niego z IDE.

W JBossie to już wszystko. Czas na Eclipse.

W klasie AquariumManagerImpl ustawiam pułapkę, klikając na lewym marginesie.



Tutaj zatrzyma się aplikacja aby umożliwić śledzenie jej wykonania krok po kroku.

Następnie wybieram z menu Debug pozycję Debug Configurations....




Wybieram z listy po lewej stronie Remote Java Application podając odpowiedni port i nazwę.



Klikam Apply i Debug. Wyskoczy błąd, ponieważ serwer nie jest jeszcze włączony.

Czas zatem go uruchomić, tym razem nie poleceniem
./run.sh
a
./debug.sh

JBoss powienien wypluć z siebie:
Listening for transport dt_socket at address: 8787

Następnie, w Eclipse z menu Debug wybieram pozycję aquarium - debug.

Wchodzę na stronę:
http://localhost:8080/aquarium/info.htm.
Strona się nie ładuje, przechodzę do Eclipse. Zatwierdzam zmianę perspektywy i już mogę przechodzić kod aplikacji linijka po linijce za pomocą klawisza F6
F8 natomiast wznawia dalszy bieg programu.