30 kwietnia 2008

Niuanse Apache Maven 2 - opcja -U

0 komentarzy
Tomek S. prosił wczoraj o pomoc z wykonaniem kroków, które podałem do zestawienia projektu aplikacji JSF z wtyczką myfaces-archetype-trinidad, o której pisałem w Tworzenie projektu aplikacji JSF z pomocą myfaces-archetype-trinidad. Jak to bywa, mimo dołożenia wszelkich starań, aby przykład wykonał się we wszystkich możliwych środowiskach (wliczając i kończąc na Windows XP ;-)) Tomek trafił na następujący problem:
 sancho@Laveno ~/ais/workspace-test/pl-ais-bilard
$ mvn archetype:generate -DarchetypeCatalog=http://myfaces.apache.org
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Required goal not found: archetype:generate
[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Tue Apr 29 22:49:06 CEST 2008
[INFO] Final Memory: 2M/3M
[INFO] ------------------------------------------------------------------------
Po 2h wieczornej walki Tomek ostatecznie rozwiązał problem przez wykonanie mvn z opcją -U.
 mvn -U archetype:generate -DarchetypeCatalog=http://myfaces.apache.org
I tutaj należą się słowa uznania dla wytrwałości Tomka - raz, że wytrwał z lekturą mojego wpisu, dwa, że zechciał spróbować się z tematem samodzielnie, a trzy, że znalazł wystarczająco zacięcia, aby rozwiązać problem, kiedy się w międzyczasie pojawił. I to mimo późnej pory (koło północy)! Gratulacje Tomek!

Po tych wyniesieniach odnośnie wytrwałości Tomka, pora wyciągnąć jakieś wnioski. Tego typu wezwania pomocy zawsze prowokują do zastanowienia nad możliwymi opcjami, aby materiał, jaki zamieszczam, nie był zbyt trywialny, a jednocześnie nie stanowił trudności i to nie tylko dla początkujących. Do kogo powinienem mierzyć? Przy ponad 500-tnemu gronu czytelników trudno wypośrodkować, a tym bardziej, kiedy temat opcji -U było już przeze mnie...przedstawiany! Nie tak dawno, bo we wpisie Zestawienie środowiska do programowania w Scali z Maven 2 i Eclipse IDE napisałem:

Zawsze cieszy mnie, kiedy poznając jedną rzecz dowiaduję się innych, równie istotnych informacji, i tym razem nie obyło się inaczej. W dokumentacji maven-scala-plugin wspomniano o kolejnej cesze mavena, gdzie brak określenia wersji wtyczki przy jej braku w lokalnym repozytorium jest możliwe wyłącznie, jeśli uruchomiono mvn z opcją -U. Pamiętam, że kiedyś miałem z tym problem i właśnie byłem zmuszony określić wersję wtyczki korzystając z http://mvnrepository.com/. Teraz mam i na to rozwiązanie.

Dodam jeszcze do tego, że opcja -U pozwala na skorzystanie ze skróconej postaci wywołania wtyczek maven, które w przeciwnym przypadku musiałyby być wydane w postaci:
 mvn groupId:artifactId:version:goal
która przydaje się w dwóch znanych mi sytuacjach - brak dostępnej wtyczki/artefaktu w lokalnym repozytorium m2 lub mamy potrzebę skorzystania z innej niż bieżąca wersja wtyczki/artefaktu. Tego typu podejście wykorzystałem w Tworzenie projektów Scali z pomocą mavenowego scala-archetype-simple, gdzie wydałem polecenie:
 mvn org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create
do utworzenia projektu Scali i to właśnie z powodów niezgodności między maven-archetype-plugin a scala-archetype-simple.

Oba wpisy dotyczyły Scali, acz było również i o Mavenie. To kolejny raz upewnia mnie, że należy rozwijać się we wszystkie możliwe kierunki (chociaż w moim przypadku wciąż w ramach Javy) i dotykać tematów, które tylko łudząco wyglądają na bezwartościowe, np. poznanie Scali. Warto o tym wspomnieć, gdyż pewne rzeczy należy znać nawet o północy! ;-)

Pytanie konkursowe (i bardzo podchwytliwe): W jaki sposób sprawić, aby polecenie mvn archetype:generate -DarchetypeCatalog=http://myfaces.apache.org -Dmaven.repo.local=c:/puste-m2-repo zakończyło się poprawnie?. Zakładam uruchomienie na Windows ze względu na wartość parametru maven.repo.local, który można zmienić na wybrany przez siebie, byle różny od już istniejącego repozytorium m2 lokalnie.

29 kwietnia 2008

Tworzenie projektu aplikacji JSF z pomocą myfaces-archetype-trinidad

4 komentarzy
Mam wrażenie, że ten tydzień obfituje w rozpoznawanie archetypów mavenowych. Potrzebowałem szybko stworzyć aplikację JSF i mogło być szybko, gdyby nie fakt, że teraz zanim za cokolwiek się zabiorę, to mam tyle opcji, że nie wiem, co ostatecznie wybrać (nie wiem, co gorsze - nie wiedzieć z czego wybrać, bo ma się wiele opcji, czy nie znając żadnej?!). Weźmy jako przykład owe stworzenie aplikacji JSF - mogę skorzystać z NetBeans IDE (Właśnie! Miałem pobrać finalną wersję NetBeans IDE 6.1!), albo Eclipse IDE, albo jeszcze inne IDE, które byłoby dedykowane do pracy z JSF. Mogę również skorzystać z Apache Maven 2 i skorzystać z jednego z wielu archetypów dedykowanych do stworzenia aplikacji JSF dostarczanych przez Apache MyFaces - MyFaces Archetypes for Maven. A to tylko tak na szybko, bo pewnie jest jeszcze kilka innych opcji. Niejednokrotnie jestem pytany o moje wrażenia dotyczące bibliotek kontrolek JSF udostępniane przez MyFaces, więc nie wahając się długo odhaczyłem kolejne zadanie z mojej listy do ewaluacji - sprawdzić utworzenie aplikacji JSF z pomocą archetypu udostępnianego przez MyFaces myfaces-archetype-trinidad.

Archetyp myfaces-archetype-trinidad dostępny jest w repozytorium m2 jako org.apache.myfaces.buildtools.myfaces-archetype-trinidad. Ostatnia wersja to 1.0.1. Jak napisano w MyFaces Archetypes for Maven wystarczy uruchomić mvn archetype:generate -DarchetypeCatalog=http://myfaces.apache.org i wybrać odpowiednią opcję do stworzenia aplikacji JSF z danymi rozszerzeniami - w moim przypadku będzie to 5: remote -> myfaces-archetype-trinidad (Simple Web application using Apache Myfaces and Trinidad).
 jlaskowski@work /cygdrive/c/projs/sandbox
$ mvn archetype:generate -DarchetypeCatalog=http://myfaces.apache.org
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO] task-segment: [archetype:generate] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] Preparing archetype:generate
[INFO] No goals needed for project - skipping
[INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] [archetype:generate]
Choose archetype:
1: remote -> myfaces-archetype-helloworld (Simple Web application using Apache Myfaces)
2: remote -> myfaces-archetype-helloworld-facelets (Simple Web application using Apache Myfaces and Facelets)
3: remote -> myfaces-archetype-helloworld-portlets (Simple Web application using Apache Myfaces and Portlets)
4: remote -> myfaces-archetype-jsfcomponents (Simple JSF Component using Apache Myfaces)
5: remote -> myfaces-archetype-trinidad (Simple Web application using Apache Myfaces and Trinidad)
Choose a number: (1/2/3/4/5): 5
Define value for groupId: : pl.jaceklaskowski.myfaces
Define value for artifactId: : myfaces-witajswiecie
Define value for version: 1.0-SNAPSHOT: : 1.0.0
Define value for package: : pl.jaceklaskowski.myfaces
Confirm properties configuration:
groupId: pl.jaceklaskowski.myfaces
artifactId: myfaces-witajswiecie
version: 1.0.0
package: pl.jaceklaskowski.myfaces
Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating OldArchetype: myfaces-archetype-trinidad:1.0.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: pl.jaceklaskowski.myfaces
[INFO] Parameter: packageName, Value: pl.jaceklaskowski.myfaces
[INFO] Parameter: basedir, Value: c:\projs\sandbox
[INFO] Parameter: package, Value: pl.jaceklaskowski.myfaces
[INFO] Parameter: version, Value: 1.0.0
[INFO] Parameter: artifactId, Value: myfaces-witajswiecie
...
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] OldArchetype created in dir: c:\projs\sandbox\myfaces-witajswiecie
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Teraz wystarczy jedynie mvn package i uruchomić aplikację z Jetty - mvn jetty:run (w zasadzie to mvn package jest wykonywany przez uruchomieniem Jetty, więc mogłoby być zaniechane).

Warto zauważyć, że pobierana wersja MyFaces Trinidad to 1.2.5, podczas gdy zgodnie ze stroną główną projektu mamy już wersję 1.2.7 (March 16, 2008 - MyFaces Trinidad 1.2.7 Released). Zaraz i tym się zajmę.
 jlaskowski@work /cygdrive/c/projs/sandbox/myfaces-witajswiecie
$ mvn package
...
[INFO] [war:war]
[INFO] Packaging webapp
[INFO] Assembling webapp[myfaces-witajswiecie] in [c:\projs\sandbox\myfaces-witajswiecie\target\myfaces-witajswiecie]
[INFO] Processing war project
[INFO] Webapp assembled in[422 msecs]
[INFO] Building war: c:\projs\sandbox\myfaces-witajswiecie\target\myfaces-witajswiecie.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Uruchamiam aplikację.
 jlaskowski@work /cygdrive/c/projs/sandbox/myfaces-witajswiecie
$ mvn -PjettyConfig clean jetty:run
...
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: A custom project using myfaces
[INFO] Webapp source directory = C:\projs\sandbox\myfaces-witajswiecie\src\main\webapp
[INFO] web.xml file = C:\projs\sandbox\myfaces-witajswiecie\src\main\webapp\WEB-INF\web.xml
[INFO] Classes = C:\projs\sandbox\myfaces-witajswiecie\target\classes
2008-04-28 22:53:59.273::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Context path = /myfaces-witajswiecie
[INFO] Tmp directory = determined at runtime
[INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] Webapp directory = C:\projs\sandbox\myfaces-witajswiecie\src\main\webapp
[INFO] Starting jetty 6.1.8 ...
2008-04-28 22:53:59.351::INFO: jetty-6.1.8
2008-04-28 22:53:59.491::INFO: No Transaction manager found - if your webapp requires one, please configure one.
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig getBooleanInitParameter
INFO: No context init parameter 'org.apache.myfaces.PRETTY_HTML' found, using default value true
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig getBooleanInitParameter
INFO: No context init parameter 'org.apache.myfaces.ALLOW_JAVASCRIPT' found, using default value true
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig getBooleanInitParameter
INFO: No context init parameter 'org.apache.myfaces.READONLY_AS_DISABLED_FOR_SELECTS' found, using default value true
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig getBooleanInitParameter
INFO: No context init parameter 'org.apache.myfaces.RENDER_VIEWSTATE_ID' found, using default value true
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig getBooleanInitParameter
INFO: No context init parameter 'org.apache.myfaces.STRICT_XHTML_LINKS' found, using default value true
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig getLongInitParameter
INFO: No context init parameter 'org.apache.myfaces.CONFIG_REFRESH_PERIOD' found, using default value 2
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig getBooleanInitParameter
INFO: No context init parameter 'org.apache.myfaces.VIEWSTATE_JAVASCRIPT' found, using default value false
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig createAndInitializeMyFacesConfig
INFO: Tomahawk jar not available. Autoscrolling, DetectJavascript, AddResourceClass and CheckExtensionsFilter are disabled now.
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.config.MyfacesConfig createAndInitializeMyFacesConfig
INFO: Starting up Tomahawk on the MyFaces-JSF-Implementation
Apr 28, 2008 10:54:00 PM org.apache.myfaces.config.FacesConfigurator feedStandardConfig
INFO: Reading standard config META-INF/standard-faces-config.xml
Apr 28, 2008 10:54:00 PM org.apache.myfaces.config.FacesConfigurator feedClassloaderConfigurations
INFO: Reading config
jar:file:/C:/.m2/org/apache/myfaces/trinidad/trinidad-impl/1.2.5/trinidad-impl-1.2.5.jar!/META-INF/faces-config.xml
Apr 28, 2008 10:54:00 PM org.apache.myfaces.config.FacesConfigurator feedWebAppConfig
INFO: Reading config /WEB-INF/faces-config.xml
Apr 28, 2008 10:54:00 PM org.apache.myfaces.config.FacesConfigurator logMetaInf
INFO: Starting up MyFaces-package : myfaces-api in version : 1.2.2 from path :
file:/C:/.m2/org/apache/myfaces/core/myfaces-api/1.2.2/myfaces-api-1.2.2.jar
Apr 28, 2008 10:54:00 PM org.apache.myfaces.config.FacesConfigurator logMetaInf
INFO: Starting up MyFaces-package : myfaces-impl in version : 1.2.2 from path :
file:/C:/.m2/org/apache/myfaces/core/myfaces-impl/1.2.2/myfaces-impl-1.2.2.jar
Apr 28, 2008 10:54:00 PM org.apache.myfaces.config.FacesConfigurator logMetaInf
INFO: MyFaces-package : tomahawk-sandbox not found.
Apr 28, 2008 10:54:00 PM org.apache.myfaces.config.FacesConfigurator logMetaInf
INFO: MyFaces-package : tomahawk not found.
Apr 28, 2008 10:54:00 PM org.apache.myfaces.shared_impl.util.LocaleUtils toLocale
WARNING: Locale name in faces-config.xml null or empty, setting locale to default locale : en_PL
Apr 28, 2008 10:54:01 PM org.apache.myfaces.config.FacesConfigurator handleSerialFactory
INFO: Serialization provider : class org.apache.myfaces.shared_impl.util.serial.DefaultSerialFactory
Apr 28, 2008 10:54:01 PM org.apache.myfaces.webapp.AbstractFacesInitializer initFaces
INFO: ServletContext 'C:\projs\sandbox\myfaces-witajswiecie\src\main\webapp\' initialized.
2008-04-28 22:54:01.739::INFO: Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
[INFO] Starting scanner at interval of 10 seconds.
Otwieram przeglądarkę na stronie http://localhost:8080/myfaces-witajswiecie/faces/index.jspx i mam podstawową aplikację JSF z wykorzystaniem MyFaces Trinidad.


Po wpisaniu Jacek Laskowski i wciśnięciu press me pojawiła się kolejna strona z okienkiem dialogowym.


Pozostaje jeszcze uaktualnić wersję Trinidada i ponownie uruchomić. Dla uproszczenia aktualizacji wersji autorzy archetypu umieścili numery wersji w sekcji properties w pliku pom.xml, tak że nie jest konieczna ich aktualizacja w kilku miejscach (można również uruchomić aplikację z poziomu mvn z podaniem wersji z linii poleceń, co ma tę zaletę, że nie modyfikujemy konfiguracji w pom.xml, a możemy bawić się własnymi wersjami). Uaktualniam trinidad.version do 1.2.7
 <trinidad.version>1.2.7</trinidad.version>
i ponownie uruchamiam aplikację. Też działa! Aplikacja jest gotowa do rozbudowania. Dalej to już czyste programowanie w JSF z MyFaces Trinidad, tym razem jednak już zapewne z pomocą jakiegoś IDE. Zawsze krążyłem koło kontrolek Trinidad, aby rozpoznać co oferują, więc teraz nadarza się okazja i to zadanie wykreślić z listy oczekujących.

Pytanie konkursowe: Jak nazywa się archetyp do automatycznego tworzenia aplikacji JSF opartej o MyFaces i Trinidad? oraz znacznie trudniejsze: Dlaczego do uruchomienia aplikacji musiałem podać adres http://localhost:8080/myfaces-witajswiecie/faces/index.jspx zamiast http://localhost:8080/myfaces-witajswiecie? Jedno z pytań, które możnaby dodatkowo zadać w kontekście tego wpisu to W jaki sposób uruchomić stworzoną aplikację z wersją Trinidad 1.2.7 bez modyfikacji pom.xml? Nagród nie ma.

28 kwietnia 2008

Tworzenie projektów Scali z pomocą mavenowego scala-archetype-simple

1 komentarzy
Poprzednio w Zestawienie środowiska do programowania w Scali z Apache Maven 2 i Eclipse IDE opisałem moje wrażenia z tworzeniem projektu opartego o Scalę z pomocą archetypu maven-archetype-quickstart. Tym razem sprawdzę możliwości specjalizowanego archetypu scala-archetype-simple, który niweluje konieczność wykonywania kilku z kroków, a którego jedyną dokumentacją, jaką udało mi się znaleźć w Sieci to wpis na blogu Scala Blog - maven for scala.
 jlaskowski@work /cygdrive/c/projs/sandbox
$ mvn org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create \
-DarchetypeGroupId=org.scala-tools.archetypes \
-DarchetypeArtifactId=scala-archetype-simple \
-DarchetypeVersion=1.2 \
-DremoteRepositories=http://scala-tools.org/repo-releases \
-DgroupId=pl.jaceklaskowski.scala -DartifactId=scala-witaj-swiecie -Dversion=1.0.0
...
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating Archetype: scala-archetype-simple:1.2
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: pl.jaceklaskowski.scala
[INFO] Parameter: packageName, Value: pl.jaceklaskowski.scala
[INFO] Parameter: basedir, Value: c:\projs\sandbox
[INFO] Parameter: package, Value: pl.jaceklaskowski.scala
[INFO] Parameter: version, Value: 1.0.0
[INFO] Parameter: artifactId, Value: scala-witaj-swiecie
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] Archetype created in dir: c:\projs\sandbox\scala-witaj-swiecie
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Poprawne uruchomienie wtyczki scala-archetype-simple jest możliwe jedynie z wersją maven-archetype-plugin 1.0-alpha-7 - najnowsza wersja nie odnajduje poprawnie archetypu scala-archetype-simple, który nie jest dostępny w repozytorium głównym m2 i kończy się błędem:
 jlaskowski@work /cygdrive/c/projs/sandbox
$ mvn archetype:create \
-DarchetypeGroupId=org.scala-tools.archetypes \
-DarchetypeArtifactId=scala-archetype-simple \
-DarchetypeVersion=1.2 \
-DremoteRepositories=http://scala-tools.org/repo-releases \
-DgroupId=pl.jaceklaskowski.scala -DartifactId=scala-witaj-swiecie -Dversion=1.0.0
...
[INFO] [archetype:create]
[INFO] Defaulting package to group ID: pl.jaceklaskowski.scala
[INFO] We are using command line specified remote repositories: http://scala-tools.org/repo-releases
Downloading: http://repo1.maven.org/maven2/org/scala-tools/archetypes/scala-archetype-simple/1.2/scala-archetype-simple-1.2.jar
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to resolve artifact.

GroupId: org.scala-tools.archetypes
ArtifactId: scala-archetype-simple
Version: 1.2

Reason: Unable to locate resource in repository

Try downloading the file manually from the project website.

Then, install it using the command:
mvn install:install-file -DgroupId=org.scala-tools.archetypes -DartifactId=scala-archetype-simple
-Dversion=1.2 -Dpackaging=jar -Dfile=/path/to/file

Alternatively, if you host your own repository you can deploy the file there:
mvn deploy:deploy-file -DgroupId=org.scala-tools.archetypes -DartifactId=scala-archetype-simple
-Dversion=1.2 -Dpackaging=jar -Dfile=/path/to/file -Durl=[url] -DrepositoryId=[id]

org.scala-tools.archetypes:scala-archetype-simple:jar:1.2

from the specified remote repositories:
id0 (http://scala-tools.org/repo-releases)

Przykładowa aplikacja w Scali, która jest tworzona przy zakładaniu projektu z archetypem scala-archetype-simple prezentuje się następująco:
 jlaskowski@work /cygdrive/c/projs/sandbox/scala-witaj-swiecie
$ cat src/main/scala/pl/jaceklaskowski/scala/App.scala
package pl.jaceklaskowski.scala

/**
* Hello world!
*
*/
object App extends Application {
println( "Hello World!" )
}
Różnica między powyższą wersją a poprzednią wersją:
 package pl.jaceklaskowski.scala

object WitajSwiecie {
def main(args: Array[String]) {
println("Witaj Świecie!")
}
}
jest taka, że w Scala użycie extends Application powoduje, że wszystkie instrukcje w klasie są traktowane jakby były umieszczone w metodzie def main(args: Array[String]). Szybko skorzystałem z tego uproszczenia w dzisiejszej, nowej wersji klasy pl.jaceklaskowski.scala.WitajSwiecie:
 object WitajSwiecie extends Application {
println("Witaj Świecie!")
}
Sprawdzę jej gotowość poleceniem mvn scala:run.
 jlaskowski@work /cygdrive/c/projs/sandbox/scala-witaj-swiecie
$ mvn scala:run
[INFO] Scanning for projects...
...
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] One or more required plugin parameters are invalid/missing for 'scala:run'

[0] Inside the definition for plugin 'maven-scala-plugin' specify the following:

<configuration>
...
<mainClass>VALUE</mainClass>
</configuration>

-OR-

on the command line, specify: '-DmainClass=VALUE'
Niestety. Mam dwa rozwiązania tego problemu - określenie mainClass w konfiguracji wtyczki maven-scala-plugin w sekcji configuration jak opisałem w poprzednim wpisie Zestawienie środowiska do programowania w Scali z Maven 2 i Eclipse IDE, albo skorzystanie z możliwości zdefiniowania jej z linii poleceń. Dla czysto demonstracyjnych pobudek skorzystam z drugiej opcji (normalnie skorzystałbym z możliwości określenia klasy głównej w pom.xml).
 jlaskowski@work /cygdrive/c/projs/sandbox/scala-witaj-swiecie
$ mvn scala:run -DmainClass=pl.jaceklaskowski.scala.App
...
[INFO] [scala:run]
[INFO] Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
I to już działa!

Uwaga na pom.xml, który deklaruje zmienną scala.version o wartości 2.7.0, a mamy już wersję 2.7.1-rc2. Dla zmniejszenia ilości miejsca zajmowanego przez lokalne repozytorium m2 zmieniam wersję Scala w pom.xml do 2.7.1-rc2 (należałoby również skasować zawartość już pobranych katalogów związanych z wersją 2.7.0, ale to pozostawiam jako zadanie domowe dla czytelników - może ktoś zechciałby napisać to w Scali?).

Pytanie konkursowe: Jaka konstrukcja w Scali pozwala na stworzenie klasy głównej bez jawnego deklarowania metody def main(args: Array[String])? (Byłoby wspaniale, gdybym mógł nagrodzić uczestników, ale na razie sponsorów brak, więc i nagród - zdjąłem reklamy Google AdSense, aby ich trochę zmotywować ;-)).

30. spotkanie Warszawskiej Grupy Użytkowników Technologii Java (Warszawa JUG)

7 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na 30. spotkanie, które odbędzie się 29.04.2008 o godzinie 18:00 w sali 5440 Wydziału MIMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Apache Struts2
Prowadzący: Łukasz Lenart

Apache Struts2 to kolejne wcielenie jednego z lepiej rozpoznawanych szkieletów webowych w Javie - Apache Struts. Jednak obecna wersja w dużej mierze powstała na bazie innego szkieletu, WebWork. Struts2 jest czasem określany, jako WebWork 2.3. Struts2 to połączenie szerokiej społeczności Struts1 oraz prostoty Xwork. Ucieleśnienie programowania opartego o kontrakty (interface'y) oraz proste klasy (POJO). Zainteresowany? Zapraszam do udziału w spotkaniu!

Łukasz Lenart - programista z zamiłowania, tworzenie programów od zawsze było jego hobby, aż przerodziło się w zajęcie komercyjne. Uważa, że dobry programista nie powinien być uzależniony od języka, w którym tworzy, a raczej patrzeć w przyszłość i próbować różnych języków i technologii w zależności od wymagań - dzisiaj jest to Java, a co będzie za 10 lat? Równie dobrze może pisać w PHP, C#, Borland Delphi, etc. Ważne, aby przynosiło mu to przyjemność.
Prywatnie mąż i ojciec, domator, który lubi czytać i ceni sobie święty spokój! Z przekonań socjalista, z działania kapitalista ;-)

Planowany czas prezentacji to 1,5 godziny, po której planuje się 15-30-minutową dyskusję.

Wstęp wolny!

Zapraszam w imieniu Łukasza i grupy Warszawa JUG!

27 kwietnia 2008

Zestawienie środowiska do programowania w Scali z Maven 2 i Eclipse IDE

2 komentarzy
Skoro można pisać programy javowe w Scali, tj. piszę je w Scali, a wynik uruchamiam jakby były napisane w Javie za pomocą interpretera javy, to do pełni szczęścia pozostaje odnaleźć wtyczki wspierające tworzenie aplikacji w Scali dla Eclipse bądź NetBeans oraz możliwość automatycznej kompilacji i uruchamiania aplikacji wspartej o Apache Maven 2. Pamiętałem, że gdzieś kiedyś czytałem o wtyczce Maven 2 do kompilacji aplikacji napisanych w Scala, więc chwila z Google i mam - maven-scala-plugin. Więcej informacji o samym repozytorium m2 dla narzędzi Scali w wątku [ANN] scala-tools.org maven2 repository. Wymaganiem do uruchomienia aplikacji scala z użyciem wtyczki m2 jest jedynie sam Maven oraz Java 5. Jak napisano w Requirements - No need to install scala! Dodatkowo jako zachęta do użycia wtyczki maven-scala-plugin napisano, że The plugin is used by /Lift/, który jest szkieletem webowym napisanym w Scali. Jakby tego było mało już na stronie Lifta - Lift borrows from the best of existing frameworks, providing: Wicket's designer-friendly templating style (see Lift View First). I już mi się podoba!

Zacznę spokojniej - od utworzenia środowiska opartego o maven, gdzie skorzystam z wtyczki maven-scala-plugin do kompilacji i uruchomienia aplikacji. Zacznę od utworzenia projektu mavenowego z użyciem Eclipse (mógłbym i z ręki, ale po co?! Mógłbym również w NetBeans, ale trochę czasu minęło od ostatniego spotkania z Eclipse, więc będzie tym razem o nim).

Po zainstalowaniu wtyczki m2eclipse - Maven Integration for Eclipse - Eclipse umożliwia stworzenie projektu mavenowego.

Najpierw przedstawienie wersji Eclipse.


Ctrl+N i wpisuję maven i wybieram Maven project.


Wciskam przycisk Next dwukrotnie i w kolejnym panelu wybieram maven-archetype-quickstart.


Next i podaję dane projektu:
  • Group Id: pl.jaceklaskowski.scala
  • Artifact Id: scala-witaj-swiecie
  • Version: 1.0.0
  • Package: pl.jaceklaskowski.scala

Finish i projekt scala-witaj-swiecie utworzony.


Pora na zmiany w związku z wykorzystaniem Scali jako języka programowania w projekcie. W pliku pom.xml dodaję zmiany zgodnie z wytycznymi na Using the plugin (common).

Zawsze cieszy mnie, kiedy poznając jedną rzecz dowiaduję się innych, równie istotnych informacji, i tym razem nie obyło się inaczej. W dokumentacji maven-scala-plugin wspomniano o kolejnej cesze mavena, gdzie brak określenia wersji wtyczki przy jej braku w lokalnym repozytorium jest możliwe wyłącznie, jeśli uruchomiono mvn z opcją -U. Pamiętam, że kiedyś miałem z tym problem i właśnie byłem zmuszony określić wersję wtyczki korzystając z http://mvnrepository.com/. Teraz mam i na to rozwiązanie.

I kolejna rzecz poboczna, o której wiedziałem, ale nigdy nie stosowałem. W Eclipse można edytować pliki xmlowe w edytorze XML, który zazwyczaj włączony jest w trybie Source, gdzie do dyspozycji mamy Ctrl+Spacja upraszczająca dodanie nowych elementów, bądź rozszerzenie istniejących. Tym razem postanowiłem przyjrzeć się trybowi (zakładce) Design (zakładki dostępne są w lewym dolnym rogu edytora). Jeśli teraz wybiorę element project i wcisnę prawy klawisz myszy pojawi się menu, za pomocą którego mogę dodać nowy element do pliku (a mam ich kilka do dodania związanych z wtyczką maven-scala-plugin). Przełączając się między trybami Design i Source można połączyć wyznaczenie odpowiedniego miejsca dla nowego elementu z pomocą trybu Design, a uzupełnić go techniką Copy-Paste mając gwarancję, że plik jest zgodny z DTD czy XML Schema (po kilku chwilach okazało się, że nowy element zawsze dodawany jest na końcu pliku, więc moja wiara o poprawne ułożenie elementów zgodnie z xsd zdaje się być nieuprawniona).


Ostatecznie plik pom.xml projektu prezentuje się następująco:
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.jaceklaskowski.scala</groupId>
<artifactId>scala-witaj-swiecie</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>scala-witaj-swiecie</name>
<url>http://www.jaceklaskowski.pl</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Kolejnym krokiem opisanym w Using the plugin (common) jest Displaying scala help and version. Tutaj wydaje mi się, że szybciej uruchomiłbym polecenie z poziomu linii poleceń, ale dla dobra sprawy przemogłem się i zdefiniowałem wykonanie polecenia w Eclipse w External Tools Configurations (Run > External Tools). Definiuję nowe polecenie


z moją własną instalacją Apache Maven 2.0.9 w c:/apps/apache-maven.


Wciśnięcie Run i...


czyli pracuję z Scala compiler version 2.6.0-final -- (c) 2002-2007 LAMP/EPFL. Dla zwrócenia uwagi ostatnia dostępna wersja to Scala 2.7.1.RC2 (z 21. kwietnia 2008), a w międzyczasie pojawiła się i Scala 2.7.0-final (z 6. marca 2008).

Tak mi się przypomniało, kiedy przeglądałem ustawienia Eclipse, że podczas ostatniego szkolenia IBM WebSphere Process Servera i IBM WebSphere Integration Developera, o którym wspominałem w Doświadczenia szkoleniowe i trochę o jpcap oraz Ubuntu Beryl poznałem kolejną cechę Eclipse, gdzie przełączanie się widoków w trakcie uruchamiania serwera można wyłączyć w sekcji Console (Window > Preferences > Run/Debug).


Jeśli wyłączyłbym Show when program writes to standard out oraz Show when program writes to standard error to ciągłe przełączanie się okien zostałoby raz na zawsze wyłączone. Tym razem nie ma to wielkiej wartości, bo nie będę uruchamiał żadnego serwera, ale w przyszłości na pewno i wrócę do tego ustawienia.

A wracając do Scali, jest jeszcze krok Displaying the command line used


co nie zakończyło się czymś imponującym


oraz Changing the scala version, gdzie korzysta się ze zmiennej ${scala.version}, ale nic poza tym, gdzie i jak się ją ustawia (za moment o tym).

W Usage zaprezentowano również sposób konfiguracji wtyczki do tworzenia raportów, ale na razie jakie raporty są dostępne jest poza moją wiedzą i zainteresowaniem. Mimo wszystko dodaję sekcję reporting do pom.xml.
 <reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
</plugin>
</plugins>
</reporting>
Okazało się, że więcej informacji jest na stronie Scaladoc. Jak dla mnie zaczyna mi to wyglądać na lekki bałagan w dokumentacji wtyczki.

Podczas kopiowania zawartości reporting ze strony okazało się, że brakuje w niej elementów końcowych dla groupId, artifactId oraz version. Już miałem zgłosić błąd w bazie błędów, ale strona Project Information > Issue Tracking kończy się No issue management system is defined. Please check back at a later date. Niech będzie zatem, zgłoszę się później.

Zgodnie z Compiling źródła umieszczam w src/main/scala, a źródła testów w src/test/scala (sekcja sourceDirectory oraz testSourceDirectory w build w pom.xml). Do uruchomienia aplikacji korzystam z Running, gdzie w konfiguracji maven-scala-plugin określa się klasę główną za pomocą parametru konfiguracyjnego mainClass.
 <mainClass>pl.jaceklaskowski.scala.WitajSwiecie</mainClass>
Definiuję skrót mvn scala-run w Eclipse


i mam już gotowe środowisko do pracy. Na razie wykonanie mvn scala:run kończy się BUILD FAILURE


ale i tym się zaraz zajmę.

Definiuję nowy katalog ze źródłami - src/main/scala, gdzie tworzę klasę pl.jaceklaskowski.scala.WitajSwiecie. Bez specjalizowanej wtyczki dla Eclipse dla projektów Scala na razie obchodzę tę niedogodność tworząc katalog, a w nim zwykły plik o rozszerzeniu scala. Jako wsparcie merytoryczne skorzystam z Getting Started with Scala oraz Scala By Example.
 package pl.jaceklaskowski.scala

object WitajSwiecie {
def main(args: Array[String]) {
println("Witaj Świecie!")
}
}
W międzyczasie zdefiniowałem zmienną scala.version z wartością 2.7.1-rc2.
 <properties>
<scala.version>2.7.1-rc2</scala.version>
</properties>
Jestem gotów do uruchomienia pierwszej aplikacji WitajSwiecie z pakietu pl.jaceklaskowski.scala (dodanie pakietu było konieczne, aby umieścić klasę w katalogu pl/jaceklaskowski/scala, podobnie jak miałoby to miejsce w Javie).

Wybieram mvn scala-run z External Tools


i po pobraniu 6-ciomegowego scala-compiler-2.7.1-rc2.jar oraz 3-megowego scala-library-2.7.1-rc2.jar uruchomiłem pierwszą aplikację Scala z poziomu Eclipse.

Nie tak prędko jednak przyszło mi zobaczyć poprawnie uruchomioną aplikację, bo świadomie użyłem polskiej litery w Witaj Świecie i kompilacja zakończyła się błędem:
 [INFO] IO error while decoding 
C:\.eclipse\scala-witaj-swiecie\src\main\scala\pl\jaceklaskowski\scala\WitajSwiecie.scala with UTF-8
[INFO] Please try specifying another one using the -encoding option
I tutaj zaskoczenie, że mimo, że kompilacja nie zakończyła się poprawnie, to komunikat jest na poziomie INFO i nie kończy wykonania wtyczki (!) Zmieniam ustawienia Eclipse, tak aby domyślnym kodowaniem znaków był UTF-8 (Window > Preferences... > General > Workspace - parametr Text file encoding).


i ponownie uruchomienie mvn scala-run.

Znowu BUILD FAILURE (!)
 [INFO] [scala:run]
[WARNING] Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject
[WARNING] at java.lang.ClassLoader.defineClass1(Native Method)
[WARNING] at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
[WARNING] at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
[WARNING] at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
[WARNING] at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
[WARNING] at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
[WARNING] at java.security.AccessController.doPrivileged(Native Method)
[WARNING] at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
[WARNING] at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
[WARNING] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
[WARNING] at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
[WARNING] at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
[WARNING] at pl.jaceklaskowski.scala.WitajSwiecie.main(WitajSwiecie.scala)
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
To już jednak wygląda znajomo po wczorajszym spotkaniu ze Scalą - Pierwsze spotkanie ze Scalą - lektura Scala Tutorial for Java programmers - brakuje scala-library.jar, ale skoro jestem w środowisku m2, to wystarczy jedynie zadeklarować zależność i voila...powinno wystartować poprawnie. Moment, przecież do uruchomienia Scali korzystam z narzędzia scala, które powinno mi zadbać o tego typu zależności automatycznie (poprzednio błąd pojawiał się jedynie w momencie uruchomienia za pomocą polecenia java).

Uruchomienie polecenia mvn scala-run z ustawionym parametrem <displayCmd>true</displayCmd> w pom.xml:
 <plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- http://scala-tools.org/mvnsites/maven-scala-plugin/run-mojo.html -->
<displayCmd>true</displayCmd>
<mainClass>pl.jaceklaskowski.scala.WitajSwiecie</mainClass>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
</plugin>
</plugins>
ukazuje jednak całą prawdę i tylko prawdę:
 [INFO] [scala:run]
[INFO] cmd: c:\apps\java5\jre\bin\java
-classpath C:\.eclipse\scala-witaj-swiecie\target\classes
pl.jaceklaskowski.scala.WitajSwiecie
[WARNING] Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject
[WARNING] at java.lang.ClassLoader.defineClass1(Native Method)
[WARNING] at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
[WARNING] at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
[WARNING] at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
[WARNING] at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
[WARNING] at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
[WARNING] at java.security.AccessController.doPrivileged(Native Method)
[WARNING] at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
[WARNING] at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
[WARNING] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
[WARNING] at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
[WARNING] at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
[WARNING] at pl.jaceklaskowski.scala.WitajSwiecie.main(WitajSwiecie.scala)
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
czyli uruchomienie aplikacji WitajSwiecie odbywa się z interpreterem java bez pliku scala-library.jar. Dodaję zależność org.scala-lang:scala-library:2.7.1-rc2 oraz przy okazji podnoszę wersję junit do 4.4.
 <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.7.1-rc2</version>
</dependency>
</dependencies>
Tym razem uruchomienie mvn scala-run zakończyło się pomyślnie!
 [INFO] [scala:run]
[INFO] cmd: c:\apps\java5\jre\bin\java
-classpath C:\.eclipse\scala-witaj-swiecie\target\classes;C:\.m2\org\scala-lang\scala-library\2.7.1-rc2\scala-library-2.7.1-rc2.jar
pl.jaceklaskowski.scala.WitajSwiecie
[INFO] Witaj Świecie!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Pora spróbować zbudować ziarno EJB z wykorzystaniem Scali - to już nie powinno stanowić problemu skoro wynikiem kompilacji jest postać binarna zrozumiała dla interpretera javy. Zostawiam jako zadanie domowe. Aby podnieść stawkę pierwszemu kto wyśle mi gotowy projekt m2 z ziarnem EJB wystawię podziękowania w moim Notatniku i opublikuję rozwiązanie (jak są inne propozycje nagród chętnie ich wysłucham).

Kompletny plik pom.xml wygląda następująco:
 <project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.jaceklaskowski.scala</groupId>
<artifactId>scala-witaj-swiecie</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>scala-witaj-swiecie</name>
<url>http://www.jaceklaskowski.pl</url>
<properties>
<scala.version>2.7.1-rc2</scala.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.7.1-rc2</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- http://scala-tools.org/mvnsites/maven-scala-plugin/run-mojo.html -->
<displayCmd>true</displayCmd>
<mainClass>pl.jaceklaskowski.scala.WitajSwiecie</mainClass>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</pluginRepository>
</pluginRepositories>
<reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</project>
Z lektury Scala By Example dzisiaj nici, ale jeszcze do niej wrócę. Pora zabrać się za trochę książek, których właśnie stałem się szczęśliwym posiadaczem, a w nich ta wymarzona Wicket in Action autorstwa Martijn Dashorst oraz Eelco Hillenius z Manning.

I na koniec pytanie, tym razem opatrzone przykładem napisanym w Scali: Dlaczego poniższa aplikacja zakończyła się komunikatem HelloWorld.main is not static?
 jlaskowski@work /cygdrive/c/scala-sandbox
$ c:/apps/scala/bin/scalac HelloWorld.scala

jlaskowski@work /cygdrive/c/scala-sandbox
$ c:/apps/scala/bin/scala HelloWorld
java.lang.NoSuchMethodException: HelloWorld.main is not static
Odpowiedź można umieścić w komentarzu bądź wysłać bezpośrednio do mnie. Nagród nie ma, ale możemy wspólnie o nich pomyśleć (ciekawie byłoby zaangażować jakąś firmę, która byłaby sponsorem niektórych z pytań, w zamian oferując jakąś ciekawą nagrodę dla aktywnych. Ech, się rozmarzyłem).

Gotowy projekt eclipsowy scala-witaj-swiecie jest do pobrania jako scala-witaj-swiecie.zip.

26 kwietnia 2008

Pierwsze spotkanie ze Scalą - lektura Scala Tutorial for Java programmers

3 komentarzy
Moje pierwsze spotkanie z językiem programowania Scala rozpocząłem od lektury Scala Tutorial for Java programmers wersja 1.2 z dnia 19 grudnia 2007. Nie miałem jakiegokolwiek pojęcia na temat tego języka poza faktem, że istnieje. Kiedyś coś pisał o nim Wiktor Gworek w Poznajemy nowe języki: Scala, czyli jak wypisać elementy z listy oraz Garść linków do debaty o językach dynamicznych i Scali i myślałem, że w ten sposób poznam Scalę, ale niestety na tym się skończyło. Skoro tyle się o nim mówiło ostatnio (teraz jakby trochę przycichło), to i mnie coś pokusiło, aby się z nim popróbować. Nie żebym narzekał na nadmiar czasu, ale może jest tak, że właśnie go tracę, ponieważ nie korzystam ze Scali (paradoks braku czasu?). Kto wie do czego może przydać mi się ten język. Nie spotkałem nikogo, kto zechciałby mi to wyjaśnić (poza wspomnianymi wpisami Wiktora), więc pokusiłem się o własne spojrzenie na język oczyma kompletnego laika. Może w ten sposób sprowokuję kogoś do wyrażenia swojej opinii o języku Scala.

Scala Tutorial for Java programmers to dokument składający się z 15 stron, w którym zaprezentowano przykłady obrazujące składnię Scali. Dokument czyta się płynnie i daje solidne podstawy do rozpoczęcia programowania w Scali.

Dokument zaczyna się przedstawieniem przykładu typu HelloWorld, w którym poznajemy podobieństwa i różnicę między Scalą a Javą. Cały dokument pisany jest poprzez pryzmat programisty w Javie, więc wiele z przykładów pojawiło się w nim właśnie ze względu na różnice między tymi językami. W HelloWorld poznajemy pierwsze słowa kluczowe Scali - object oraz def. Słowo object określa klasę singleton występującą wyłącznie w jednym egzemplarzu, w której definiujemy metody statyczne, które z kolei nie występują wcale w Scali poza object.

W Scali wymaganymi słowami kluczowymi dla definicji klasy są class lub object.
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ ../bin/scalac HelloWorld.scala
HelloWorld.scala:1: error: expected class or object definition
HelloWorld {
^
one error found
Po poprawnej kompilacji możemy przystąpić do uruchomienia aplikacji i podobnie jak w Javie interpreter Scali domyślnie dołącza katalog bieżący do ścieżki klas.
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ ../bin/scala HelloWorld
Hello, world!
Wbrew przykładowi w dokumencie, nie ma potrzeby dodatkowo definiować parametru -classpath . (na końcu jest kropka określająca katalog bieżący).

Nie mogłem doczekać się informacji o możliwości uruchamiania programów napisanych w Scali z interpreterem Java.
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ java HelloWorld
Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
at HelloWorld.main(HelloWorld.scala)
Pierwsza próba niezbyt udana, ale wyjątek kieruje mnie na plk lib/scala-library.jar (bo gdzie indziej mógłbym znaleźć scala.ScalaObject?)
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ java -cp "../lib/scala-library.jar;." HelloWorld
Hello, world!
Działa!

Ciekawostką jest użycie * (gwiazdka) jako poprawnego identyfikatora, co wyklucza jej użycie w deklaracji importu wszystkich klas/interfejsów w pakiecie. Zamiast import java.text.* piszemy import java.text._. To z pewnością będzie sprawiało wiele problemów programistom javy. Dodatkowo za pomocą _ (podkreślenie) importujemy elementy klasy podobnie jak za pomocą import static java.lang.Math.* w Javie.

Podręcznik wspomina o "an infix syntax", gdzie df.format(now) sprowadza się w Scali do df format now. Mniej pisania, ale czy potrzebne, kiedy w użyciu mamy IDE? Pewnie nie, ale już przy pisaniu krótkich programów-skryptów może być pomocne.

Scala jest językiem w pełni obiektowym, gdzie wszystko jest obiektem, włączając w to liczby i funkcje. W Javie typy podstawowe (prymitywy) mają swoje odpowiedniki obiektowe (typy opakowujące), np. int ma Integer, ale o obiektach reprezentujących funkcje mogliśmy jedynie pomyśleć w kontekście użycia mechanizmu prześwietlania (refleksji) z użyciem typu java.lang.reflect.Method.

Jako przykład obiektowości Scali podano niewinnie wyglądający przykład z użyciem operacji arytmetycznych 1 + 2 * 3 / x, co jest równoważne w Scali następującemu ciągowi wywołań funkcji na obiektach reprezentujących liczby 1.+(2.*(3./(x))). Na dokładkę napisano, że operacje arytmetyczne są poprawnymi identyfikatorami w Scali (!) To będzie kolejna pułapka dla programistów Javy, którzy z pewnością będą musieli przejść niezły odwyk, aby przyzwyczaić się do tej konstrukcji. Mnie ścięło z nóg, kiedy to zobaczyłem, ale po chwili nie mogłem oprzeć się wrażeniu, że takie podejście ma coś w sobie interesującego. Mi się podoba (chociaż, gdybym miał uzasadnić taką reakcję, po prostu nie potrafiłbym).

Coś czego nie doświadczymy w programowaniu w Javie to potraktowanie metod jako bytów obiektowych, którymi można manipulować podobnie jak ma to miejsce w przypadku innych klas/interfejsów. Ktokolwiek unikał zajęć programowania funkcjonalnego tym razem się nie wywinie, bo Scala jest cała o obiektach, więc i funkcje przybrały taką postać, co może przypominać elementy programowania funkcyjnego. Można, więc przekazywać funkcje jako parametry wejściowe metod, przypisywać do zmiennych, czy zwrócić z wywołania funkcji (skoro są obiektami to spodziewałbym się, że można na nich wywoływać ich metody - owe ich nie jest mi jeszcze znane). Deklaracja callback: () => unit w Scali oznacza, że mamy do czynienia z funkcją void callback(), gdzie unit w Scali odpowiada void w Javie.

Przykład prezentujący wykorzystanie obiektowe metody korzysta z konstrukcji Thread sleep 1000, która odpowiada Thread.sleep(1000) w Javie. Jak dla mnie notacja Scali jest wciąż novum, do którego nie mogę się przyzwyczaić i śmiem twierdzić, że wprowadza więcej zamieszania niż pożytku. Wszystko jednak przede mną, więc nie ma co narzekać, a raczej próbować zrozumieć, co spowodowało taką decyzję, bo nie sądzę, że jedynym kryterium było skrócenie czasu pisząc program (w tym przypadku, zyskujemy jedynie czas, który poświęcilibyśmy na zamykający nawias).

Jednakże konstrukcja println "Hello, world!" kończy się błędem?!
 jlaskowski@work /cygdrive/c/apps/scala/testy
$ ../bin/scalac HelloWorld.scala
HelloWorld.scala:3: error: ';' expected but string literal found.
println "Hello, world!"
^
one error found
I kolejna niespodzianka - klasy w Scali mogą...przyjmować parametry (!) Pachnie mi tutaj podobieństwem do szablonów w Javie, gdzie można zadeklarować klasę korzystającą z typu T, w postaci public class MojaSparametryzowanaKlasa<T> {}.

Dostęp do zmiennych w postaci metod get oraz set następuje poprzez def re() = real, a to mi przypomina C# (albo po prostu chciałem, aby tak mi przypominało, bo akurat owe metody były ciekawie zaimplementowane w C#). Typ zwracanego obiektu z metody get jest wyliczany dynamicznie i będzie odpowiadał typowi zmiennej, którego dotyczy.

Istnieje pojęcie metod bez parametrów, które wywołanie c.im() sprowadzają do wywołania c.re, kiedy ich definicja w klasie wygląda def re = real.

W Scali typem nadrzędnym wszystkich typów jest Any, jednakże klasa, która nie określa swojego rodzica domyślnie rozszerza typ scala.ScalaObject. I tutaj uwaga do czytelników Scala Tutorial, gdzie napisano o typie scala.Object (5.2 Inheritance and overriding strona 7: When no super-class is specified, as in the Complex example of previous section, scala.Object is implicitly used.). Przesłaniając metodę w klasie pochodnej należy jawnie użyć słowa kluczowego override, np. override def toString() (co tym razem wydłuża czas pisania programu o kilka dodatkowych uderzeń w klawiaturę).

Przez moment chciałem zreferować konstrukcję klasy warunkowej opartej o słowo kluczowe case w Scali, ale tym razem proponuję zajrzeć do tego dokumentu, bo pojęcie tego rodzaju klasy i próba jego relacji przez programistę javy jest nie lada wyzwaniem, którego się dzisiaj nie podejmuję. Są chętni? Przykłady mile widziane.

W dokumencie przedstawiono pojęcie mixin, które określa się za pomocą słowa kluczowego trait podobnie do javowego interface, chociaż mixin może dostarczać implementację.

Słowo kluczowe instanceOf oraz rzutowanie odpowiadają odpowiednio metodom isInstanceOf[] oraz asInstanceOf[].

Na zakończenie dokument przedstawia realizację typów parametryzowanych, które pojawiły się w Javie 5.

Dokument kończy się wskazaniem na kolejny dokument Scala By Example, który zaplanowałem przeczytać...niebawem. Na dzisiaj starczy wrażeń i spróbuję utrzymać naukę Scali poprzez pisanie równolegle aplikacji w niej, dla tych, które będę pisał docelowo w Javie. Będzie początkowo bolało, ale nie widzę innego sposobu.

Pytanie konkursowe: Czy aplikacja napisana w Scali może być uruchomiona przez interpreter Javy?

25 kwietnia 2008

Doświadczenia szkoleniowe i trochę o jpcap oraz Ubuntu Beryl

5 komentarzy
Skończyłem wyjątkowo pracowity tydzień przejmując pałeczkę trenera w szkoleniu WB111 Integration Using IBM WebSphere Integration Developer and Process Server. Dopiero po tych 5-ciu dniach doceniłem pracę szkoleniowców, których praca (zakładając, że podchodzą do tematu rzeczowo, z czuciem tego, co prezentują) bezsprzecznie kosztuje niemało wysiłku. Szczególnie cenię sobie w prowadzeniu szkoleń możliwość przeszkolenia osób, które w ten sposób stają się moimi potencjalnymi, przyszłymi rozmówcami i dopiero wtedy zaczyna się prawdziwa zabawa. Dyskusje, w jakich miałem możliwość uczestniczyć podczas prowadzenia szkolenia niejednokrotnie doprowadziły do sytuacji, w której po prostu musiałem zakończyć stwierdzeniem "Nie wiem", "Nie potrafię sobie tego wyobrazić" czy podobnie. Nie ma w tym nic złego, pod warunkiem, że są one jedynie podsumowaniem rozmowy, a nie jej "twardym" zakończeniem, bez chociażby próby podjęcia wysiłku umysłowego zrozumienia pojmowania świata przez rozmówcę. Często inne spojrzenie na temat, który wydaje się być całkowicie zrozumiałym, może postawić go na głowie. Pytania "Dlaczego?", "Jak to działa?", "Jak możnaby to zrealizować?" czy "Chwileczkę, ale czy nie powinno to działać tak?" pozwalają poszerzyć postrzeganie tematu po obu stronach - pytającego i odpowiadającego. Nie ma nic zdrożnego w tych pytaniach, jak i w samej odpowiedzi "Nie wiem". Co mnie jednak najbardziej uradowało w prowadzeniu szkolenia to fakt usystematyzowania znajomości WPSa i WIDa. Nie są to produkty nowe dla mnie, ale tematyka Service Component Architecture (SCA), Service Data Object (SDO) czy ich pochodne (przynajmniej w wykonaniu wspomnianych produktów) jak BPEL i ich realizacja (procesy biznesowe czy maszyny stanów) oraz ostatecznie modelowania rozwiązania z abstrakcyjnych modułów (komponentów) SCA, których implementacją może być kolejny abstrakcyjny byt może zakończyć się niezłym bólem głowy. Dodając do tego mnóstwo specyfikacji pomocniczych i technologii, i mamy niezłą jazdę. Tak czy owak, prowadzenie szkolenia to niesamowity zastrzyk uporządkowanej wiedzy, który z pewnością wspaniale przygotowuje do certyfikacji produktowej. Jeszcze tylko lektura kilku dokumentów i jestem gotów na podjęcie wyzwania w postaci podejścia do egzaminu IBM Certified Solution Developer - WebSphere Integration Developer V6.0.1. Jak wyjdzie, okaże się w praniu, ale postanowienie już złożone. Jeśli masz okazję poprowadzić szkolenie, nie wahaj się ani chwili - na pewno nie stracisz! Ja tam się piszę na kolejne.

W międzyczasie miałem możliwość stworzenia aplikacji sieciowej w Javie z użyciem biblioteki jpcap. Jpcap jest biblioteką napisaną w Javie do przechwytywania i wysyłania pakietów sieciowych. Wymaganiem aplikacji było przechwytywanie pakietów sieciowych i zapisywanie pakietów spełniających treść spełniającą podane wyrażenie regularne. Dodając do tego Commons CLI do obsługi parametrów wejściowych i miałem i ten temat z głowy. Owe monitorowanie pakietów spełnione zostało w niecały dzień (!) Sprawa początkowo trudna okazała się trywialnie prosta - nawet jak na moje środowisko na Windows XP. Do dyspozycji mamy kilka wspierających projektów dla jpcap, które sprowadzają temat do znanej wszystkim techniki Copy-Paste z bezsprzecznie doskonałej dokumentacji Jpcap Tutorial. Wersja Jpcap ver.0.7 została wydana 9 czerwca 2007, ale z mojego niewielkiego doświadczenia uważam, że należałoby nazwać ją wersją 1.0, bo spełniła moje wymagania w 100%.
Programowanie z jpcap rozpoczynamy od zestawienia środowiska, które wymaga Java 6 oraz WinPcap. Następnie instalujemy jpcap i voila możemy kodować. Więcej informacji o potrzebnych projektach na stronie Jpcap - How to Install. Oczywiście porty dla Linux, Mac OS X i innych systemów są również dostępne. Korzystając ze wspomnianego Jpcap Tutorial miałem zrąb aplikacji w kilka minut, a później pozostało jedynie jej wygładzanie. Temat stał się banalnie prosty i w zasadzie nie ma co wyjaśniać po lekturze Jpcap Tutorial. Tam jest po prostu wszystko wyłożone w postaci przykładów. Jest to idealny przykład projektu, który dostarcza wymaganą funkcjonalność i...strawną i aktualną dokumentację. Projekt Jpcap stał się dla mnie wzorem do naśladowania i będę dążył, aby projekty, w których uczestniczę właśnie tak prezentowały się światu - krótka, 5-10-minutowa dokumentacja opatrzona przykładami. Najbardziej znaczący wycinek aplikacji z użyciem jpcap prezentuje się następująco:
 public void run() {
NetworkInterface device = Infiltrator.getDeviceByNumber(getNumerUrzadzenia());
System.out.println("Przechwytuję komunikaty z urzadzenia " + device.description + " - "
+ device.addresses[0].address);
JpcapCaptor captor = null;
try {
captor = JpcapCaptor.openDevice(device, 65535, false, 20);
captor.loopPacket(10, new PacketReceiver() {
int liczbaPakietow;

public void receivePacket(Packet packet) {
if (debug) {
System.out.println(++liczbaPakietow + ". " + packet);
}
System.out.println("Dane: \n" + new String(packet.data));
}
});
captor.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Na zakończenie nie mógłbym wspomnieć o pewnym ciekawym spotkaniu z rozwiązaniem ze świata Linuksa. Było to jakiś czas tematu, kiedy bawiłem się nim i pamiętam, że ostatnim jądrem (=kernelem), z którym pracowałem, była wersja 2.4. Od czasu do czasu mam nieodpartą pokusę przenieść się na inny system niż Windows i póki co nie mam co liczyć na Maca, więc pozostaje mi Linux (co nie jest niczym złym). Nie chciałbym tracić czasu na konfigurację systemu i najlepiej potraktowałbym siebie jako początkującego użytkownika, dla którego wszystko powinno zostać skonfigurowane, tak aby system został podany na tacy. Za moich starych czasów linuksowych takim systemem był RedHat Linux, ale teraz słyszę, że króluje Ubuntu. I właśnie o nim przyszło mi rozmawiać w tym tygodniu podczas przerwy w szkoleniu i kiedy wspomniałem o moich zachwytach zmianami wyglądu Visty i podobieństwa do zarządcy okien Maca pokazano mi krótki film z YouTube - WINDOWS VISTA AERO VS LINUX UBUNTU BERYL. Normalnie zdębiałem. To, co mogłem zobaczyć w wykonaniu Beryla po prostu mnie zmroziło (pozytywnie). To jest na prawdę coś, co chciałbym mieć u siebie. Lubię tego typu gadżety, a jak mnie zapewniano, nie obciążają one systemu bardziej niż Vista, więc wchodzę w to. Od razu nowinę przyniosłem do domu, że zabieram się za formatowanie domowego PCta i ku mojemu zdziwieniu nie było ani przerażenia, ani euforii w reakcji rodzinki. Wydaje się, że faktycznie migrujemy do rozwiązań systemów w Sieci, gdzie PeCet jest jedynie oknem do aplikacji, które uruchamiane są w przeglądarce. Jest kilka aplikacji uruchamianych przez moją rodzinę lokalnie, ale jest ich tak niewiele, że mógłbym je albo zastąpić odpowiednikami linuksowymi, albo po prostu potraktować VmWare. Nie uśmiecha mi się instalacja Ubuntu (jakkolwiek łatwa i przyjemna), ale pomysł jest i realizacja kiedyś też się pojawi. Na razie podekscytowany jestem możliwościami Beryla i tylko czekam, kiedy kolejny laptop uruchomię właśnie z nim. Skoro daleko mu do Javy, albo technologii javopodobnych, więc odkładam na stos zadań do zrobienia pewnego razu. Może ktoś zechciałby podzielić się swoimi przemyśleniami linuksowymi w połączeniu z Ubuntu i Berylem? Chętnie posłuchałbym kilku rad, aby uprościć proces instalacji, migracji danych i rozpoczęcia pracy do niezbędnego minimum.

23 kwietnia 2008

JUnit4, OpenEJB3, Maven2, Cobertura i refactoring w Eclipse - wdrożenie wiedzy praktycznie

2 komentarzy
Dzisiejszy dzień to próba zastosowania narzędzi, które znałem od wieków, ale ze stosowaniem było (delikatnie ujmując) kiepsko - refactoring (ma to swoją nazwę w j. polskim?) oraz tworzenie testów jednostkowych w JUnit 4 z użyciem adnotacji, statycznymi importami oraz uruchomieniem ich poprzez Apache Maven 2. Wszystko za sprawą nachodzącej specyfikacji EJB 3.1, która za jakiś czas stanie się oficjalną, a ja nie zamierzam czekać na implementację od Suna, tylko dostarczyć ją w ramach projektu Apache OpenEJB. Niedawno udało nam się wypuścić wersję OpenEJB 3.0 z pełnym wsparciem dla EJB 3.0 i możliwością uruchamiania ziaren EJB bez specjalnego i skomplikowanego zestawiania środowiska, a jedynie zmianie CLASSPATH i odpowiednim skonstruowaniu kontekstu JNDI w aplikacji. Więcej najprawdopodobniej niebawem, kiedy uporządkuję kilka bieżących zadań wokół Wicket, OSGi, NetBeans i kilku innych projektów, a niecierpliwych kieruję na strony projektu - Apache OpenEJB. W związku z moim powrotem do korzeni i zwiększenia mojego udziału w rozwoju OpenEJB - rozpocząłem wgłębianie się w jego kod źródłowy właśnie za pomocą testów jednostkowych pisanych w JUnit 4. Niby teoretycznie proste, ale w praktyce nierzadko powoduje niemałą zagadkę logiczną i stworzenie środowiska odpowiedniego dla wykonania testu może przyprawić o ból głowy. Wtedy właśnie wychodzi styl programowania i zasada Najpierw test (strona kliencka), a później sama klasa testowana (sama aplikacja). Pod pojęciem strony klienckiej mam na myśli wymagany interfejs programistyczny, którego użycie planuje się w aplikacji.

Rozpocząłem moją przygodę od uruchomienia mvn clean cobertura:cobertura w ramach projektu mavenowego (wspomniałem, że był to moduł OpenEJB - openejb-ejbd, ale tak na prawdę nie ma to większego znaczenia).
 jlaskowski@work /cygdrive/c/oss/openejb3/server/openejb-ejbd
$ mvn clean cobertura:cobertura
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'cobertura'.
[INFO] ------------------------------------------------------------------------
[INFO] Building OpenEJB :: Server :: EJBd
[INFO] task-segment: [clean, cobertura:cobertura]
[INFO] ------------------------------------------------------------------------
...
[INFO] [cobertura:cobertura]
[INFO] Cobertura 1.9 - GNU GPL License (NO WARRANTY) - See COPYRIGHT file
Cobertura: Loaded information on 13 classes.
Report time: 594ms

[INFO] Cobertura Report generation was successful.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Uruchomienie polecenia wykonuje testy oraz za pomocą wtyczki cobertura-maven-plugin następuje badanie pokrycia kodu źródłowego testami. W wyniku, w katalogu target/site/cobertura, znajduje się dokumentacja HTML z prezentacją pokrycia testami.
 jlaskowski@work /cygdrive/c/oss/openejb3/server/openejb-ejbd
$ ls -l target/site/cobertura/
cobertura/ org.apache.openejb.server.ejbd.AuthRequestHandler.html
css/ org.apache.openejb.server.ejbd.BasicClusterableRequestHandler.html
frame-packages.html org.apache.openejb.server.ejbd.CallContext.html
frame-sourcefiles-org.apache.openejb.server.ejbd.html org.apache.openejb.server.ejbd.ClientObjectFactory.html
frame-sourcefiles.html org.apache.openejb.server.ejbd.ClusterableRequestHandler.html
frame-summary-org.apache.openejb.server.ejbd.html org.apache.openejb.server.ejbd.DeploymentIndex.html
frame-summary.html org.apache.openejb.server.ejbd.EjbDaemon.html
help.html org.apache.openejb.server.ejbd.EjbRequestHandler.html
images/ org.apache.openejb.server.ejbd.EjbServer.html
index.html org.apache.openejb.server.ejbd.JndiRequestHandler.html
js/ org.apache.openejb.server.ejbd.ServerSideResolver.html
Otworzenie strony index.html prezentuje następujący raport:


Widać na nim, że jedynie klasa BasicClusterableRequestHandler jest pokryta w 100% przez testy, a pozostałe prezentują się marnie. Pokrycie w 100% nie powinno być celem samym w sobie, ale dla mnie najważniejsze było, aby zacząć, więc akurat wybrałem moduł openejb-ejbd, w którym wybrałem klasę DeploymentIndex (obecnie ma 59% pokrycia, ale jeszcze wczoraj było 0!). Sama prezentacja pokrycia w postaci zielonych i czerwonych miejsc wskazuje, które są objęte testami, a które wymagają troski.


Po rozmowie z innym programistą projektu OpenEJB i jego liderem - Davidem Blevinsem - okazało się, że:
 <jlaskowski> write...I'm all ears (trying to polish a junit test for DeploymentIndex)
<dblevins> we don't use the DeploymentIndex anymore
<jlaskowski> what?!
<dblevins> kind of an unfortunate place to start, but yea, it's cruft at the moment
...czyli klasa nie jest w ogóle w użyciu. No cóż pierwsze podejście nie musi być idealne ;-) Mimo wszystko dobrze było rozpocząć rozpoznawanie kodu źródłowego OpenEJB wybierając dowolną klasę, której pokrycie jest bliskie zeru, a że nie jest w użyciu jeszcze - nic straconego. Od razu Dave przyszedł mi jednak z pomocą i napisał:
 <dblevins> I suggest the Assembler
<jlaskowski> org.apache.openejb.assembler.classic.Assembler?
<dblevins> right
<jlaskowski> k
<dblevins> and the ConfigurationFactory
<dblevins> the flow of the system startup is this...
i Dave rozwinął jak to tam w środku się dzieje. Mam się czym zająć w wolnych chwilach i mam pewność, że dana klasa jest w użyciu. Jestem ocalony!

Co to jednak ma wspólnego z refactoringiem?! Podczas pracy z klasą testową - DeploymentIndexTest - rozpocząłem niewinnie z pojedyńczą metodą, która później rozwinęła się w dwie, a później dodałem metodę setUp(), która ustawia środowisko (=zmienne) i jest wykonywana każdorazowo przed każdym testem - metoda oznaczona adnotacją @Before. Okazało się, że wbrew temu jak do tej pory podchodziłem do sprawy modyfikacji klas metodą Ctrl-C/Ctrl-V, tym razem postanowiłem spróbować przyzwyczaić się do kolejnego elementu środowisk IDE (korzystałem z Eclipse IDE 3.4M6) i podczas zmiany rangi zmiennej lokalnej do poziomu zmiennej instancji skorzystałem z menu Refactor > Convert Local Variable to Field...


Po chwili miałem temat z głowy. Wystarczy umiejscowić kursor na zmiennej lokalnej i wybrać menu, aby przenieść ją na poziom zmiennej instancji. Przydała się również funkcja Refactor > Rename (Alt+Shift+R), która jednak była już kilkakrotnie przeze mnie wykorzystywana. Piszę o tym, gdyż mając w nawyku pracę z różnej maści edytorami byłem przyzwyczajony do ciągłego stosowania techniki Copy-Paste, która była uniwersalna, i mimo wiedzy na temat funkcjonalności Refactoring w Eclipse (również NetBeans czy IntelliJ IDEA) nie miałem nawyku jej stosowania tracąc czas na rzeczy, które nie były ani ciekawe, ani miłe. Zdecydowanie polecam tego typu podejście do zmiany struktury kody, niż owe Copy-Paste.

W trakcie poznawania zmian w JUnit 4 natrafiłem na sposób deklarowania oczekiwanego wyjątku w teście. Do tej pory stosowałem technikę:
 try {
...kod, który zgłaszał wyjątek...
fail("Powinien zostać zgłoszony wyjątek NazwaWyjątkuException");
} catch (NazwaWyjątkuException expected) {
// ignored
}
W JUnit 4 zalecanym sposobem jest użycie atrybutu expected adnotacji @Test, np. @Test(expected= IndexOutOfBoundsException.class), czyli to, co zostało zaprezentowane wyżej teraz powinno wyglądać następująco:
 @Test(expected=NazwaWyjątkuException.class)
public void mojTest() {
...kod, który zgłaszał wyjątek...
}
Więcej informacji o JUnit w JUnit Cookbook. Warto się z nim zapoznać, bo zawiera kwintesencję JUnit i jego przeczytanie nie powinno zabrać więcej niż 5-10 minut (pewnie przeczytanie tego wpisu zajęło więcej ;-)). Wracam do testów jednostkowych...

Pytanie konkursowe: W jaki sposób deklarujemy oczekiwany wyjątek w JUnit 4?

19 kwietnia 2008

Nowości NetBeans 6.1 - Hibernate Framework Support w cięciu kosztów dostępu do danych relacyjnych

3 komentarzy
Otrzymałem prośbę o sprawdzenie artykułu, który napisałem jeszcze w 2006 roku o Hibernate - Hibernate - tniemy koszty dostępu do danych relacyjnych. Hibernate i jego wsparcie w NetBeans 6.1 było jedną z rzeczy, które niedawno postanowiłem sprawdzić, więc w czasach pieczenia wielu pieczeni na jednym ogniu tym razem nie będzie inaczej - sprawdzę aktualność artykułu i wsparcie NetBeans dla Hibernate.

Krótka lektura dokumentu NetBeans 6.1 Milestones New and Noteworthy i można zabrać się do pracy.

Instaluję wtyczkę NetBeans dla Hibernate - Tools > Plugins, zaznaczam Hibernate Support oraz Hibernate 3.2.5 Library.


i Install.

Ctrl+Shift+N i z kategorii Java wybieram Java Application. Next >. Definiuję szczegóły aplikacji


Tworzę klasę encji User. Ctrl+N, Java > Java Class. W edytorze klasy User wciskam Alt+Insert i wybieram Add Property...


i definiuję atrybuty id, a następnie imie i nazwisko.

Klasa prezentuje się następująco:
 package pl.jaceklaskowski.hibernate;

public class User {

private int id;
private String imie;
private String nazwisko;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getImie() {
return imie;
}

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

public String getNazwisko() {
return nazwisko;
}

public void setNazwisko(String nazwisko) {
this.nazwisko = nazwisko;
}
}
Dodaję bibliotekę Hibernate 3.2.5 do projektu. Wybieram Properties projektu i przechodzę do Libraries, gdzie korzystam z Add Library...


zatwierdzam wybór przyciskiem Add Library


i OK.

Przechodzę do klasy HibernateExample i kopiuję zawartość klasy z artykułu. Ctrl+Shift+I, aby uzupełnić pakiety klas Hibernate (uwaga na klasę Configuration, która powinna być org.hibernate.cfg.Configuration oraz Transaction z pakietu org.hibernate).

Ostatecznie klasa wygląda następująco:
 package pl.jaceklaskowski.hibernate;

import java.util.Iterator;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class HibernateExample {

private final static SessionFactory factory;

static {
// 1. Inicjalizacja Hibernate
Configuration cfg = new Configuration().configure();

// 2. Utworzenie fabryki sesji Hibernate
factory = cfg.buildSessionFactory();
}

public static void main(String[] args) {
HibernateExample m = new HibernateExample();
m.createUsers();
m.displayUsers();
}

public void createUsers() {
// 3. Otwarcie sesji Hibernate
Session session = factory.openSession();

// 4. Rozpoczęcie transakcji
Transaction tx = session.beginTransaction();

// 5. Utworzenie użytkownika
User u = new User();
u.setImie("Jacek");
u.setNazwisko("Laskowski");

// 6. Zapisanie użytkownika w bazie danych
session.save(u);

// 7. Zatwierdzenie transakcji
tx.commit();

// 8. Zamknięcie sesji Hibernate
session.close();
}

public void displayUsers() {
// 3. Otwarcie sesji Hibernate
Session session = factory.openSession();

// 4. Rozpoczęcie transakcji
Transaction tx = session.beginTransaction();

// 5. Utworzenie zapytania SQL do bazy o listę użytkowników
Criteria criteria = session.createCriteria(User.class);

// 6. Wykonanie zapytania SQL
List users = criteria.list();

// 7. Iterowanie po wyniku zapytania SQL
for (Iterator it = users.iterator(); it.hasNext();) {
User user = (User) it.next();
System.out.println(user);
}

// 8. Zatwierdzenie transakcji
tx.commit();

// 9. Zamknięcie sesji Hibernate
session.close();
}
}
Tworzę plik hibernate.cfg.xml - Ctrl+N, XML > XML Document. Nadaję mu nazwę hibernate.cfg (rozszerzenie xml dodawane jest automatycznie) i umieszczam w katalogu src.


Next i Finish.

Plik konfiguracyjny aplikacji opartej o Hibernate - hibernate.cfg.xml - przedstawia się następująco (zmieniłem nazwę pakietu, a w zasadzie katalogu, gdzie Hibernate będzie poszukiwał pliku mapowania dla encji User):
 <?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.hsqldb.jdbcDriver
</property>
<property name="connection.url">jdbc:hsqldb:.
</property>
<property name="connection.username">sa
</property>
<property name="connection.password">
</property>
<property name="connection.pool_size">1
</property>
<property name="dialect">org.hibernate.dialect.HSQLDialect
</property>
<property name="current_session_context_class">thread
</property>
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider
</property>
<property name="show_sql">true
</property>
<property name="hbm2ddl.auto">create
</property>
<mapping resource="pl/jaceklaskowski/hibernate/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
(Ech to formatowanie w NetBeans - dlaczego znaczniki zamykające są w nowej linii?!)

Tworzę plik mapowania dla encji User w katalogu src/pl/jaceklaskowski/hibernate/User.hbm.xml - Ctrl+N, XML > XML Document i z wcześniej podświetlonym pakietem pl.jaceklaskowski.hibernate w oknie Projects mam już uzupełnione pole Folder w asystencie tworzenia pliku xml. Ostatecznie plik User.hbm.xml prezentuje się następująco:
 <?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="pl.jaceklaskowski.hibernate">
<class name="User" table="User">
<id name="id">
<generator class="native"/>
</id>
<property name="imie" column="T_IMIE" length="10" not-null="true"/>
<property name="nazwisko" column="T_NAZWISKO" length="25" not-null="true"/>
</class>
</hibernate-mapping>
Podczas edycji pojawił się błąd w NetBeans, który zgłosiłem jako 133253: AE: Token length=0 <= 0, który zanim opublikowałem wpis został oznaczony jako duplikat błędu poprawionego w najnowszej wersji rozwojowej NetBeans 6.1 - kolejny powód porzucenia RC1. Po wystąpieniu tego błędu, praca z edytorem XML należała do niezwykle nieprzyjemnych (delikatnie mówiąc).

Pora na dodanie kilku bibliotek pomocniczych dla Hibernate (i tutaj kolejny raz pojawia się zaleta stosowania narzędzia, które samodzielnie zadbałoby o zarządzanie zależnościami, np. Apache Maven 2). Ale moment, przecież dodanie biblioteki Hibernate w NetBeans to jest dokładnie ten krok - wszystkie konieczne zależności zostały również dodane (!) Do czegoś się jednak ten NetBeans 6.1 przydał ;-)

Pierwsze uruchomienie aplikacji za pomocą Run (skrót klawiszowy F6) kończy się błędem:
 java.lang.ClassNotFoundException: org.hsqldb.jdbcDriver
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:276)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at org.hibernate.util.ReflectHelper.classForName(ReflectHelper.java:100)
at org.hibernate.connection.DriverManagerConnectionProvider.configure(DriverManagerConnectionProvider.java:61)
at org.hibernate.connection.ConnectionProviderFactory.newConnectionProvider(ConnectionProviderFactory.java:124)
at org.hibernate.connection.ConnectionProviderFactory.newConnectionProvider(ConnectionProviderFactory.java:56)
at org.hibernate.cfg.SettingsFactory.createConnectionProvider(SettingsFactory.java:414)
at org.hibernate.cfg.SettingsFactory.buildSettings(SettingsFactory.java:62)
at org.hibernate.cfg.Configuration.buildSettings(Configuration.java:2009)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1292)
at pl.jaceklaskowski.hibernate.HibernateExample.(HibernateExample.java:21)
java.lang.ExceptionInInitializerError
Caused by: org.hibernate.HibernateException: JDBC Driver class not found: org.hsqldb.jdbcDriver
at org.hibernate.connection.DriverManagerConnectionProvider.configure(DriverManagerConnectionProvider.java:66)
at org.hibernate.connection.ConnectionProviderFactory.newConnectionProvider(ConnectionProviderFactory.java:124)
at org.hibernate.connection.ConnectionProviderFactory.newConnectionProvider(ConnectionProviderFactory.java:56)
at org.hibernate.cfg.SettingsFactory.createConnectionProvider(SettingsFactory.java:414)
at org.hibernate.cfg.SettingsFactory.buildSettings(SettingsFactory.java:62)
at org.hibernate.cfg.Configuration.buildSettings(Configuration.java:2009)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1292)
at pl.jaceklaskowski.hibernate.HibernateExample.(HibernateExample.java:21)
Caused by: java.lang.ClassNotFoundException: org.hsqldb.jdbcDriver
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:276)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at org.hibernate.util.ReflectHelper.classForName(ReflectHelper.java:100)
at org.hibernate.connection.DriverManagerConnectionProvider.configure(DriverManagerConnectionProvider.java:61)
... 7 more
Jak najbardziej zrozumiałe biorąc pod uwagę, że korzystam z HSQLDB, a nigdzie nie zadeklarowałem tej potrzeby poza plikiem konfiguracyjnym Hibernate'a - hibernate.cfg.xml (linia <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>). Dodaję bibliotekę HSQLDB do projektu - Properties > Libraries > Add Library... i okazuje się, że nie ma wsparcia dla niego wsparcia w NetBeans 6.1. Jest jednak wsparcie (gotowa do użycia biblioteka) dla Java DB (Apache Derby) oraz MySQL. Zmieniam konfigurację Hibernate, tak aby w użyciu była baza danych Java DB (jestem zbyt leniwy, aby pobierać HSQLDB z sieci, skoro mogę skorzystać z czegoś co mam pod ręką).

Dodaję bibliotekę Java DB Driver do projektu.


Muszę również zmienić plik hibernate.cfg.xml (nie muszę oczywiście zmieniać innych części aplikacji, które są niezależne od wykorzystywanej bazy danych). Tutaj przyadłoby się wsparcie NetBeans dla pracy z Hibernate, w którym zdefiniowałbym źródło danych, a plik konfiguracyjny zostałby zmodyfikowany w odpowiedni sposób, ukrywając przede mną tajniki konfiguracji Hibernate i Java DB. Na razie muszę to zrobić ręcznie (podeprę się moim artykułem o OpenJPA z Derby - Java Persistence API z OpenJPA i Derby oraz TestNG z Eclipse IDE w tle oraz Hibernate jako dostawca JPA w samodzielnej aplikacji). Zmieniony plik hibernate.cfg.xml prezentuje sie następująco:
 <?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.apache.derby.jdbc.EmbeddedDriver
</property>
<property name="connection.url">jdbc:derby:derbyDB;create=true
</property>
<property name="connection.username">app
</property>
<property name="connection.password">app
</property>
<property name="connection.pool_size">1
</property>
<property name="dialect">org.hibernate.dialect.DerbyDialect
</property>
<property name="current_session_context_class">thread
</property>
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider
</property>
<property name="show_sql">true
</property>
<property name="hbm2ddl.auto">create
</property>
<mapping resource="pl/jaceklaskowski/hibernate/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Ponownie uruchamiam aplikację - F6 i...aplikacja kończy się błędem o braku poprawnej składni SQL.
 INFO: exporting generated schema to database
Apr 19, 2008 12:37:38 PM org.hibernate.tool.hbm2ddl.SchemaExport create
SEVERE: Unsuccessful: create table User (id integer not null, T_IMIE varchar(10) not null, T_NAZWISKO varchar(25) not null, primary key (id))
Apr 19, 2008 12:37:38 PM org.hibernate.tool.hbm2ddl.SchemaExport create
SEVERE: Syntax error: Encountered "User" at line 1, column 14.
Apr 19, 2008 12:37:38 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: schema export complete
Hibernate: insert into User (T_IMIE, T_NAZWISKO, id) values (?, ?, ?)
Apr 19, 2008 12:37:38 PM org.hibernate.util.JDBCExceptionReporter logExceptions
WARNING: SQL Error: -1, SQLState: 42X01
Apr 19, 2008 12:37:38 PM org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: Syntax error: Encountered "User" at line 1, column 13.
Apr 19, 2008 12:37:38 PM org.hibernate.event.def.AbstractFlushingEventListener performExecutions
SEVERE: Could not synchronize database state with session
org.hibernate.exception.SQLGrammarException: could not insert: [pl.jaceklaskowski.hibernate.User]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:67)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2267)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2660)
at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:56)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:141)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at pl.jaceklaskowski.hibernate.HibernateExample.createUsers(HibernateExample.java:46)
at pl.jaceklaskowski.hibernate.HibernateExample.main(HibernateExample.java:26)
Caused by: java.sql.SQLSyntaxErrorException: Syntax error: Encountered "User" at line 1, column 13.
at org.apache.derby.client.am.SQLExceptionFactory40.getSQLException(Unknown Source)
at org.apache.derby.client.am.SqlException.getSQLException(Unknown Source)
at org.apache.derby.client.am.Connection.prepareStatement(Unknown Source)
at org.hibernate.jdbc.AbstractBatcher.getPreparedStatement(AbstractBatcher.java:505)
at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:94)
at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:87)
at org.hibernate.jdbc.AbstractBatcher.prepareBatchStatement(AbstractBatcher.java:222)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2224)
... 12 more
Caused by: org.apache.derby.client.am.SqlException: Syntax error: Encountered "User" at line 1, column 13.
at org.apache.derby.client.am.Statement.completeSqlca(Unknown Source)
at org.apache.derby.client.net.NetStatementReply.parsePrepareError(Unknown Source)
at org.apache.derby.client.net.NetStatementReply.parsePRPSQLSTTreply(Unknown Source)
at org.apache.derby.client.net.NetStatementReply.readPrepareDescribeOutput(Unknown Source)
at org.apache.derby.client.net.StatementReply.readPrepareDescribeOutput(Unknown Source)
at org.apache.derby.client.net.NetStatement.readPrepareDescribeOutput_(Unknown Source)
at org.apache.derby.client.am.Statement.readPrepareDescribeOutput(Unknown Source)
at org.apache.derby.client.am.PreparedStatement.readPrepareDescribeInputOutput(Unknown Source)
at org.apache.derby.client.am.PreparedStatement.flowPrepareDescribeInputOutput(Unknown Source)
at org.apache.derby.client.am.PreparedStatement.prepare(Unknown Source)
at org.apache.derby.client.am.Connection.prepareStatementX(Unknown Source)
... 18 more
Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not insert: [pl.jaceklaskowski.hibernate.User]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:67)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2267)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2660)
at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:56)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:141)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at pl.jaceklaskowski.hibernate.HibernateExample.createUsers(HibernateExample.java:46)
at pl.jaceklaskowski.hibernate.HibernateExample.main(HibernateExample.java:26)
Caused by: java.sql.SQLSyntaxErrorException: Syntax error: Encountered "User" at line 1, column 13.
at org.apache.derby.client.am.SQLExceptionFactory40.getSQLException(Unknown Source)
at org.apache.derby.client.am.SqlException.getSQLException(Unknown Source)
at org.apache.derby.client.am.Connection.prepareStatement(Unknown Source)
at org.hibernate.jdbc.AbstractBatcher.getPreparedStatement(AbstractBatcher.java:505)
at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:94)
at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:87)
at org.hibernate.jdbc.AbstractBatcher.prepareBatchStatement(AbstractBatcher.java:222)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2224)
... 12 more
Caused by: org.apache.derby.client.am.SqlException: Syntax error: Encountered "User" at line 1, column 13.
at org.apache.derby.client.am.Statement.completeSqlca(Unknown Source)
at org.apache.derby.client.net.NetStatementReply.parsePrepareError(Unknown Source)
at org.apache.derby.client.net.NetStatementReply.parsePRPSQLSTTreply(Unknown Source)
at org.apache.derby.client.net.NetStatementReply.readPrepareDescribeOutput(Unknown Source)
at org.apache.derby.client.net.StatementReply.readPrepareDescribeOutput(Unknown Source)
at org.apache.derby.client.net.NetStatement.readPrepareDescribeOutput_(Unknown Source)
at org.apache.derby.client.am.Statement.readPrepareDescribeOutput(Unknown Source)
at org.apache.derby.client.am.PreparedStatement.readPrepareDescribeInputOutput(Unknown Source)
at org.apache.derby.client.am.PreparedStatement.flowPrepareDescribeInputOutput(Unknown Source)
at org.apache.derby.client.am.PreparedStatement.prepare(Unknown Source)
at org.apache.derby.client.am.Connection.prepareStatementX(Unknown Source)
... 18 more
Patrząc na komentarze użytkowników do mojego wpisu Hibernate - tniemy koszty dostępu do danych relacyjnych wielu prosiło o możliwość podejrzenia struktury bazodanowej po uruchomieniu aplikacji, więc skorzystam z bazy dostarczanej przez NetBeans - sample. W międzyczasie musiałem zrestartować NetBeans i dopiero po tym zauważam, że NetBeans 6.1 rozpoznaje pliki hibernate i upraszcza ich edycję - działa podpowiadanie elementów pliku konfiguracyjnego Hibernate (!) Ctrl+Spacja działa i w dodatku podmienia starą konfigurację (elementy bez prefiksu hibernate na te, z prefiksem).


czy


Kolejną ciekawostką wsparcia NetBeans 6.1 dla projektów korzystających z Hibernate jest oznaczenie dedykowaną ikoną Hibernate plików konfiguracyjnych hibernate.cfg.xml oraz User.hbm.xml (wydaje mi się, że NetBeans początkowo ich nie rozpoznawał, ale dopiero po restarcie mam wrażenie, że pliki zostały rozpoznane jako hibernetowe i oznaczone odpowiednią ikoną).


oraz udostępnienie specjalizowanego edytora:


Dodam do tego, że w Hibernate - tniemy koszty dostępu do danych relacyjnych miałem w pliku hibernate.cfg.xml zdefiniowane <property name="hbm2ddl.auto">create</property>, a w Hibernate jako dostawca JPA w samodzielnej aplikacji <property name="hibernate.hbm2ddl.auto" value="update"/> i jako że nie pamiętałem, jaką wartość powinien mieć ten parametr, aby baza była każdorazowo tworzona przy startcie aplikacji skorzystałem z pomocy NetBeans 6.1 (zamiast, jak to w takich momentach robię, Google. Zdaje się, że rośnie nam ciekawa konkurencja dla Google w postaci NetBeans 6.1 ;-)).


Po tych zachwytach i pomocy ze strony NetBeans plik hibernate.cfg.xml wygląda następująco:
 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">org.apache.derby.jdbc.ClientDriver
</property>
<property name="hibernate.connection.url">jdbc:derby://localhost:1527/sample
</property>
<property name="hibernate.connection.username">app
</property>
<property name="hibernate.connection.password">app
</property>
<property name="hibernate.connection.pool_size">1
</property>
<property name="hibernate.current_session_context_class">thread
</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider
</property>
<property name="hibernate.dialect">org.hibernate.dialect.DerbyDialect
</property>
<property name="hibernate.show_sql">true
</property>
<property name="hibernate.format_sql">true
</property>
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<mapping resource="pl/jaceklaskowski/hibernate/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Uruchamiam Java DB - w oknie Services, kategoria Databases i na Java DB wybieram Start Server (niestety poza nieaktywnym menu Start Server nie ma wizualnego oznaczenia, np. w postaci zielonej strzałki jak na serwerach, że baza już jest uruchomiona).

Ponowne uruchomienie i ponowny błąd. Co jest?! Po kilku próbach okazuje się, że Java DB nie akceptuje nazwy tabeli USER (nawet edytor SQL nie podświetla USER na zielono, co już wskazuje na jakiś błąd).


Sprawdzam w dokumentacji Apache Derby 10.2 CREATE TABLE statement i sama struktura polecenia jest dobra, a w dodatku nic nie widzę o zakazie stosowania USER jako nazwy bazy danych. Hmmm, coś tu jest nie tak. Ale co?! Nie zastanawiając się długo zmieniam nazwę tabeli, na którą jest mapowana klasa User na UZYTKOWNIK w pliku User.hbm.xml.
 <?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="pl.jaceklaskowski.hibernate">
<class name="User" table="UZYTKOWNIK">
<id name="id" >
<generator class="native"/>
</id>
<property name="imie" column="T_IMIE" length="10" not-null="true"/>
<property name="nazwisko" column="T_NAZWISKO" length="25" not-null="true"/>
</class>
</hibernate-mapping>
Ponownie F6 i już działa jak należy. W końcu! Teraz mógłbym wrócić do konfiguracji z wbudowaną bazą danych, ale to pozostawiam jako zadanie domowe dla zainteresowanych.

Pytanie konkursowe: Jaka jest domyślna nazwa pliku konfiguracyjnego Hibernate? (nagród proszę nie oczekiwać).

Pełna aplikacja HibernateExample dostępna jest do pobrania jako HibernateExample.zip. Miłej hibernacji!