03 marca 2008

Wicket Demo na OSGi z Felix i Pax Web

Wciąż rozpoznaję możliwe ścieżki wykorzystania OSGi w mojej aplikacji opartej o Apache Wicket. Pamiętam wskazówki Daniela, który wskazywał na Spring Dynamic Modules for OSGi Service Platforms, jednakże mimo, że już wprowadziłem Springa do aplikacji wciąż rozglądam się za alternatywnymi rozwiązaniami. Ot, tak na przekór, aby nie było na skróty.

Wczytując się w pRaSSówkę (wspaniałe określenie dla zbioru RSS za Polskim The Daily WTF) i posiłkując się Google natrafiłem na ciekawe dokonania w dziedzinie OSGi i tworzenia aplikacji webowych z Wicket. Trafiłem na Open Participation Software for Java, gdzie mnóstwo ciekawego oprogramowania związanego z OSGi, m.in. Pax Wicket (o czym nie będę jeszcze pisał, ale nie mogłem się oprzeć, aby już nie wspomnieć).

Projektem, który przyciągnął moją uwagę był Pax Web, czyli OSGi Http Service ze wsparciem dla wielu elementów specyfikacji Servlets, w tym i filtrów. Dlaczego wymieniłem filtry jako istotne? Przypominam, że Wicket pracuje oparty o filtr lub servlet, a w mojej przykładowej aplikacji skorzystałem z filtra. Stąd padło, że za dzisiejszy cel postanowiłem obrać uruchomienie demonstracyjnej aplikacji Wicketa z Pax Web.

Po zapoznaniu się z dokumentacją Pax Web (wyjątkowo niewiele acz treściwie) przyszło mi zdecydować o środowisku uruchomieniowym OSGi. Wybrałem Apache Felix. Poza nim miałem do wyboru Knoplerfisha bądź Eclipse Equinox. Z bardziej osobistych niż technicznych pobódek wybrałem Felix. Pobrałem Felix 1.0.3.

Rozpocząłem od uruchomienia Pax Web na Felixie. Uruchomienie Felixa i instalacja opisane są w Apache Felix Usage Documentation.
 jlaskowski@dev /cygdrive/c/apps/felix
$ java -jar bin/felix.jar

Welcome to Felix.
=================

Enter profile name: wicket

DEBUG: WIRE: 1.0 -> org.osgi.service.packageadmin -> 0
DEBUG: WIRE: 1.0 -> org.osgi.service.startlevel -> 0
DEBUG: WIRE: 1.0 -> org.ungoverned.osgi.service.shell -> 1.0
DEBUG: WIRE: 1.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 1.0 -> org.apache.felix.shell -> 1.0
DEBUG: WIRE: 2.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 2.0 -> org.apache.felix.shell -> 1.0
DEBUG: WIRE: 3.0 -> org.osgi.service.obr -> 3.0
DEBUG: WIRE: 3.0 -> org.osgi.framework -> 0
-> DEBUG: WIRE: 3.0 -> org.apache.felix.shell -> 1.0

-> version
1.0.3
-> ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (1.0.3)
[ 1] [Active ] [ 1] Apache Felix Shell Service (1.0.0)
[ 2] [Active ] [ 1] Apache Felix Shell TUI (1.0.0)
[ 3] [Active ] [ 1] Apache Felix Bundle Repository (1.0.2)
Podczas uruchomienia Felixa, podawana jest nazwa profilu, która jest nazwą zbioru zainstalowanych pakunków tak, że kolejne uruchomienie Felixa z tą nazwą zainstaluje i uruchomi dokładnie te pakunki zawarte w zadanym profilu. W praktyce ów profil jest po prostu katalogiem pakunków w katalogu domowym użytkownika.
 jlaskowski@dev /cygdrive/c/apps/felix
$ ls -l c\:/Documents\ and\ Settings/jlaskowski/.felix/wicket/
total 0
d---------+ 2 jlaskowski None 0 Mar 2 23:02 bundle0
d---------+ 3 jlaskowski None 0 Mar 2 23:02 bundle1
d---------+ 3 jlaskowski None 0 Mar 2 23:02 bundle2
d---------+ 3 jlaskowski None 0 Mar 2 23:02 bundle3
Uruchomienie Pax Web to wykonanie poleceń install oraz start z pobranym pax-web-service (pax-web-service-0.3.1.jar)
 -> install file:/C:/apps/pax-web/pax-web-service-0.3.1.jar
Bundle ID: 4
-> start 4
org.osgi.framework.BundleException: Unresolved package in bundle 4:
package; (&(package=org.osgi.service.http)(version>=1.0.0)(!(version>=2.0.0)))
I tutaj pierwsza niespodzianka. Niespełniona zależność od pakietu org.osgi.service.http. Dzięki Creating a jetty based OSGi HttpService for apache felix z dnia 29.02.2008 (!) dowiedziałem się, że potrzebuję pobrać i zainstalować również pakunek OSGi compendium API's Feliksa - org.osgi.compendium-1.0.0.jar.
 -> install file:/C:/apps/felix/bundle/org.osgi.compendium-1.0.0.jar
Bundle ID: 5
-> start 5
DEBUG: WIRE: 5.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 5.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 5.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 5.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 4.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 4.0 -> org.xml.sax -> 0
DEBUG: WIRE: 4.0 -> javax.servlet.resources -> 4.0
DEBUG: WIRE: 4.0 -> org.osgi.service.http -> 5.0
DEBUG: WIRE: 4.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 4.0 -> org.osgi.service.cm -> 5.0
DEBUG: WIRE: 4.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 4.0 -> org.xml.sax.helpers -> 0
DEBUG: WIRE: 4.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 4.0 -> org.osgi.util.tracker -> 0
DEBUG: WIRE: 4.0 -> javax.servlet.jsp.resources -> 4.0
DEBUG: WIRE: 4.0 -> javax.net.ssl -> 0
DEBUG: WIRE: 4.0 -> org.ops4j.pax.web.service -> 4.0
-> start 4
2008-03-02 23:42:54.096::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
2008-03-02 23:42:54.142::INFO: jetty-6.1.x
2008-03-02 23:42:54.174::INFO: Started SocketConnectorWrapper@0.0.0.0:8080
Tym razem Pax Web rozpoczął pracę bez zgłaszania braku zależności i Jetty 6.1 ruszył na porcie 8080.

Pytanie jakie sobie zadawałem, to: Dobrze mam uruchomionego Jetty, ale co z moją aplikacją? Jak mogę ją zainstalować? Każdy kto "dotknie" OSGi wie, że cokolwiek nie pojawi się w tym środowisku musi być wprost lub niewprost pakunkiem. Oznaczało to, że aplikacja webowa musiałaby w jakiś sposób stać się pakunkiem. Nie jest to wyczyn sam w sobie, bo opisywałem to już w Pakunki OSGi w projekcie wielomodułowym Apache Maven 2 z maven-bundle-plugin czy Tworzenie pakietów OSGi z Apache Maven 2, ale jak sprawić, aby pakunek aplikacji webowej był właśnie aplikacją webową i to jeszcze rozpoznaną przez właśnie uruchomionego Jetty. Na pewno musiałaby pojawić się jakaś zależność między nimi, ale jak ją określić?!

Z pomocą przychodzi Pax Web Extender - War, czyli kolejny pakunek z OPS4J, który sprawia, że uruchomienie aplikacji webowych staje się trywialne. Wystarczy pobrać pax-web-ex-war-0.3.0.jar i uruchomić.
 -> install file:/C:/apps/pax-web/pax-web-ex-war-0.3.0.jar
Bundle ID: 6
-> start 6
DEBUG: WIRE: 6.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 6.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 6.0 -> org.xml.sax -> 0
DEBUG: WIRE: 6.0 -> org.osgi.service.http -> 5.0
DEBUG: WIRE: 6.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 6.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 6.0 -> org.w3c.dom -> 0
DEBUG: WIRE: 6.0 -> org.osgi.util.tracker -> 0
DEBUG: WIRE: 6.0 -> org.ops4j.pax.web.service -> 4.0
Pakunek Pax Web Extender uruchomiony. A teraz co? Znowu dokumentacja, a tam instrukcja jak bez żadnych modyfikacji do aplikacji webowej uruchomić ją na OSGi, czyli Pax URL - war. Kolejny pakunek do uruchomienia.
 -> install file:/C:/apps/pax-web/pax-url-war-0.2.1.jar
Bundle ID: 7
-> start 7
DEBUG: WIRE: 7.0 -> org.osgi.service.url -> 0
DEBUG: WIRE: 7.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 7.0 -> org.ops4j.pax.url.war -> 7.0
DEBUG: WIRE: 7.0 -> javax.xml.transform.stream -> 0
DEBUG: WIRE: 7.0 -> org.osgi.service.cm -> 5.0
DEBUG: WIRE: 7.0 -> javax.xml.transform -> 0
DEBUG: WIRE: 7.0 -> javax.net.ssl -> 0
Teraz wystarczy po prostu uruchomić aplikację webową, np. http://repo1.maven.org/maven2/org/apache/wicket/wicket-examples/1.3.1/wicket-examples-1.3.1.war.
 -> install 
war:http://repo1.maven.org/maven2/org/apache/wicket/wicket-examples/1.3.1/wicket-examples-1.3.1.war
Bundle ID: 8
-> start 8
DEBUG: WIRE: 8.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 8.0 -> javax.sql.rowset -> 0
DEBUG: WIRE: 8.0 -> javax.xml.namespace -> 0
DEBUG: WIRE: 8.0 -> javax.crypto -> 0
DEBUG: WIRE: 8.0 -> javax.xml.transform -> 0
DEBUG: WIRE: 8.0 -> javax.naming -> 0
DEBUG: WIRE: 8.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 8.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 8.0 -> javax.management.modelmbean -> 0
DEBUG: WIRE: 8.0 -> javax.xml.transform.stream -> 0
DEBUG: WIRE: 8.0 -> javax.swing -> 0
DEBUG: WIRE: 8.0 -> javax.management -> 0
DEBUG: WIRE: 8.0 -> javax.swing.border -> 0
DEBUG: WIRE: 8.0 -> javax.management.remote -> 0
DEBUG: WIRE: 8.0 -> javax.swing.event -> 0
DEBUG: WIRE: 8.0 -> org.xml.sax.helpers -> 0
DEBUG: WIRE: 8.0 -> javax.swing.tree -> 0
DEBUG: WIRE: 8.0 -> javax.swing.table -> 0
DEBUG: WIRE: 8.0 -> javax.sql -> 0
DEBUG: WIRE: 8.0 -> javax.crypto.spec -> 0
DEBUG: WIRE: 8.0 -> javax.rmi.CORBA -> 0
DEBUG: WIRE: 8.0 -> javax.transaction -> 0
DEBUG: WIRE: 8.0 -> javax.swing.text -> 0
DEBUG: WIRE: 8.0 -> org.xml.sax -> 0
DEBUG: WIRE: 8.0 -> javax.imageio -> 0
DEBUG: WIRE: 8.0 -> org.w3c.dom -> 0
DEBUG: WIRE: 8.0 -> javax.xml.transform.dom -> 0
DEBUG: WIRE: 8.0 -> javax.rmi -> 0
...
INFO - WebApplication - [TemplateApplication] Started Wicket in development mode
********************************************************************
*** WARNING: Wicket is running in DEVELOPMENT mode. ***
*** ^^^^^^^^^^^ ***
*** Do NOT deploy to your live server(s) without changing this. ***
*** See Application#getConfigurationType() for more information. ***
********************************************************************
...
->
Aplikacja wydaje się uruchomiona. Pozostaje sprawdzić ją w działaniu. Ale moment, a jaki adres aplikacji? Chciałoby się, aby było to podobnie jak to ma miejsce w typowym środowisku kontenera servletów, gdzie nazwa pliku wskazuje na nazwę kontekstu. W tym przypadku tak nie jest i nazwa aplikacji obliczana jest na podstawie parametru przekazanego po war, czyli w tym przypadku będzie to:
 http://localhost:8080/http___repo1.maven.org_maven2_org_apache_wicket_wicket-examples_1.3.1_wicket-examples-1.3.1.war
Pytanie tylko skąd o tym się dowiedzieć, bo przecież mimo dokumentacji Instructions file syntax nie wyliczyłem jej zamieniając odpowiednie znaki.

Nazwa kontekstu kryje się w Bundle-SymbolicName pakunku. Sprawdzam jaką konfigurację ma pakunek o identyfikatorze 8, który jest zainstalowaną aplikacją.
 -> headers 8

Bundle 8
--------
Generated-By-Ops4j-Pax-From =
http://repo1.maven.org/maven2/org/apache/wicket/wicket-examples/1.3.1/wicket-examples-1.3.1.war
Bundle-ClassPath = .,WEB-INF/classes,WEB-INF/lib/...
Tool = Bnd-unknown version
Created-By = 1.5.0_14 (Sun Microsystems Inc.)
Bnd-LastModified = 1204529294312
WAR-URL =
http://repo1.maven.org/maven2/org/apache/wicket/wicket-examples/1.3.1/wicket-examples-1.3.1.war
Built-By = fb
Originally-Created-By = Apache Maven
Bundle-Version = 0
Build-Jdk = 1.5.0_13
Manifest-Version = 1.0
Bundle-ManifestVersion = 2
Archiver-Version = Plexus Archiver
Import-Package = javax.activation;resolution:=...
Bundle-SymbolicName =
http___repo1.maven.org_maven2_org_apache_wicket_wicket-examples_1.3.1_wicket-examples-1.3.1.war

Interesująca nas właściwość to Bundle-SymbolicName.


Pozostaje sprawdzić instalację mojej demonstracyjnej aplikacji webowej - wicket-demo.
 -> install war:file:/c:/projs/sandbox/wicket-demo/target/wicket-demo-1.0-SNAPSHOT.war
Bundle ID: 10
-> start 10
DEBUG: WIRE: 10.0 -> javax.servlet.http -> 4.0
DEBUG: WIRE: 10.0 -> javax.sql.rowset -> 0
DEBUG: WIRE: 10.0 -> javax.xml.namespace -> 0
DEBUG: WIRE: 10.0 -> javax.crypto -> 0
DEBUG: WIRE: 10.0 -> javax.xml.transform -> 0
DEBUG: WIRE: 10.0 -> javax.naming -> 0
DEBUG: WIRE: 10.0 -> javax.servlet -> 4.0
DEBUG: WIRE: 10.0 -> javax.xml.parsers -> 0
DEBUG: WIRE: 10.0 -> javax.xml.transform.stream -> 0
DEBUG: WIRE: 10.0 -> javax.management.modelmbean -> 0
DEBUG: WIRE: 10.0 -> javax.swing -> 0
DEBUG: WIRE: 10.0 -> javax.management -> 0
DEBUG: WIRE: 10.0 -> javax.swing.border -> 0
DEBUG: WIRE: 10.0 -> javax.transaction.xa -> 0
DEBUG: WIRE: 10.0 -> javax.management.remote -> 0
DEBUG: WIRE: 10.0 -> javax.swing.event -> 0
DEBUG: WIRE: 10.0 -> org.xml.sax.helpers -> 0
DEBUG: WIRE: 10.0 -> javax.swing.tree -> 0
DEBUG: WIRE: 10.0 -> javax.swing.table -> 0
DEBUG: WIRE: 10.0 -> javax.management.openmbean -> 0
DEBUG: WIRE: 10.0 -> javax.sql -> 0
DEBUG: WIRE: 10.0 -> javax.crypto.spec -> 0
DEBUG: WIRE: 10.0 -> javax.rmi.CORBA -> 0
DEBUG: WIRE: 10.0 -> javax.transaction -> 0
DEBUG: WIRE: 10.0 -> javax.swing.text -> 0
DEBUG: WIRE: 10.0 -> org.xml.sax -> 0
DEBUG: WIRE: 10.0 -> javax.imageio -> 0
DEBUG: WIRE: 10.0 -> org.w3c.dom -> 0
DEBUG: WIRE: 10.0 -> javax.rmi -> 0
-> headers 10

Bundle 10
---------
Generated-By-Ops4j-Pax-From =
file:/c:/projs/sandbox/wicket-demo/target/wicket-demo-1.0-SNAPSHOT.war
Bundle-ClassPath = .,WEB-INF/classes,WEB-INF/lib/...
Tool = Bnd-unknown version
Created-By = 1.5.0_14 (Sun Microsystems Inc.)
Bnd-LastModified = 1204531073078
WAR-URL =
file:/c:/projs/sandbox/wicket-demo/target/wicket-demo-1.0-SNAPSHOT.war
Built-By = jlaskowski
Originally-Created-By = Apache Maven
Bundle-Version = 0
Build-Jdk = 1.5.0_14
Manifest-Version = 1.0
Bundle-ManifestVersion = 2
Archiver-Version = Plexus Archiver
Import-Package = javax.activation;resolution:=...
Bundle-SymbolicName =
file__c__projs_sandbox_wicket-demo_target_wicket-demo-1.0-SNAPSHOT.war
Niestety z jakiś nieznanych mi powodów moja aplikacja nie pozwoliła się uruchomić. Zostawiam to na później.
 -> shutdown
-> INFO - Application - [EchoApplication] destroy: Wicket JMX initializer
INFO - Application - [GuestBookApplication] destroy: Wicket JMX initializer
INFO - Application - [FormInputApplication] destroy: Wicket JMX initializer
INFO - Application - [UnicodeConverterApplication] destroy: Wicket JMX initializer
INFO - Application - [SignInApplication] destroy: Wicket JMX initializer
INFO - Application - [PrototypeApplication] destroy: Wicket JMX initializer
INFO - Application - [ImagesApplication] destroy: Wicket JMX initializer
INFO - Application - [AjaxApplication] destroy: Wicket JMX initializer
INFO - Application - [HangmanApplication] destroy: Wicket JMX initializer
INFO - Application - [StatelessApplication] destroy: Wicket JMX initializer
INFO - Application - [BreadCrumbApplication] destroy: Wicket JMX initializer
INFO - Application - [NiceUrlApplication] destroy: Wicket JMX initializer
INFO - Application - [MyAuthenticatedWebApplication] destroy: Wicket JMX initializer
INFO - Application - [ExampleApplication] destroy: Wicket JMX initializer
INFO - Application - [GuiceApplication] destroy: Wicket JMX initializer
INFO - Application - [CaptchaApplication] destroy: Wicket JMX initializer
INFO - Application - [RolesApplication] destroy: Wicket JMX initializer
INFO - Application - [LibraryApplication] destroy: Wicket JMX initializer
INFO - Application - [HelloBrowserApplication] destroy: Wicket JMX initializer
INFO - Application - [VelocityTemplateApplication] destroy: Wicket JMX initializer
INFO - Application - [WizardApplication] destroy: Wicket JMX initializer
INFO - Application - [WicketExamplesMenuApplication] destroy: Wicket JMX initializer
INFO - Application - [ComponentReferenceApplication] destroy: Wicket JMX initializer
INFO - Application - [NavomaticApplication] destroy: Wicket JMX initializer
INFO - Application - [DatesApplication] destroy: Wicket JMX initializer
INFO - Application - [Application] destroy: Wicket JMX initializer
INFO - Application - [StockQuoteApplication] destroy: Wicket JMX initializer
INFO - Application - [NestedApplication] destroy: Wicket JMX initializer
INFO - Application - [PubApplication] destroy: Wicket JMX initializer
INFO - Application - [RepeaterApplication] destroy: Wicket JMX initializer
INFO - Application - [UploadApplication] destroy: Wicket JMX initializer
INFO - Application - [FramesApplication] destroy: Wicket JMX initializer
INFO - Application - [CustomResourceLoadingApplication] destroy: Wicket JMX initializer
INFO - Application - [LinkomaticApplication] destroy: Wicket JMX initializer
INFO - Application - [HelloWorldApplication] destroy: Wicket JMX initializer
INFO - Application - [SignIn2Application] destroy: Wicket JMX initializer
INFO - Application - [TemplateApplication] destroy: Wicket JMX initializer
INFO - Application - [EncodingsApplication] destroy: Wicket JMX initializer
INFO - Application - [PubApplication] destroy: Wicket JMX initializer
2008-03-03 00:01:28.468:/http___repo1.maven.org_maven2_org_apache_wicket_wicket-examples_1.3.1_wicket-example
INFO - XmlWebApplicationContext - Closing application context [Root WebApplicationContext]
INFO - DefaultListableBeanFactory - Destroying singletons in {org.springframework.beans.factory.support.Defa
f BeanFactory hierarchy}
W dowód wdzięczności zgłosiłem błąd - (PAXWEB-84) NPE after a web app (bundle) is stopped, który został już naprawiony (!) To nazywa się współpraca.