28 lutego 2008

Tworzenie dokumentacji z DocBook i Maven - wtyczka Docbkx Maven Plugin

3 komentarzy
Już nie pierwszy raz zastanawiałem się w czym tworzyć dokumentację. Jakkolwiek TeX jest dobry i korzystając z niego można tworzyć profesjonalną dokumentację, to jednak znacznie prostszy i przez to z pewnością popularniejszy wydaje się DocBook. Nie znam DocBooka, ale gdziekolwiek nie spojrzę na darmową dokumentację formatu innego niż Confluence Wiki, czy generowaną przez to narzędzie, wygląd książek tworzonych z pomocą DocBook przykuł nie raz moją uwagę. I nie raz obiecywałem sobie rozpoznać w jaki sposób możnaby tworzyć dokumenty PDF i HTML z jego pomocą.

Dzisiaj podczas uaktualniania źródeł wtyczki Geronimo Eclipse Plugin zobaczyłem, wiele z nierozpoznanych przez svn plików. Powiedzmy, że było ich około 30-40 plików wszystkie oznaczone jako nieznane - uruchomienie svn status pokazuje je z ?. Postanowiłem je usunąć. Cygwin i shell są w takiej sytuacji nieocenione. Skończyło się ostatecznie na poleceniu:
  svn status | cut -d ' ' -f7 | xargs rm -rf
Wszystkie nieznane svn pliki wycięte. Zanim jednak je skonstruowałem potrzebowałem poznać format wyniku svn status. Rzut oka na książkę Version Control with Subversion i miałem odpowiedź. Coś mnie tknęło, aby zajrzeć do źródeł książki, a tam DocBook (!) A co mi tam - pomyślałem - zobaczę jak się tworzy dokumentację PDF z pomocą DocBooka. Nie miałem zamiaru tracić czasu, więc od razu zacząłem poszukiwania wtyczki DocBook dla Maven. Znalazłem docbook-maven-plugin, czyli coś czego w ogóle nie powinienem dotykać. Szkoda czasu. Nawet nie zamierzam wskazywać na jej stronę domową. Stworzyłem projekt mavenowy, a w nim zgodnie z wymaganiami wtyczki katalog src/docbook i umieściłem w nim dwa pliki - ksiazka.xml (zgodnie z dokumentacją Creating DocBook Documents i źródłami książki o Subversion):
 <?xml version='1.0'?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"
[
<!ENTITY ch1 SYSTEM "ch1.xml">
]>

<book id="ksiazka">
<title>Moja pierwsza ksiazka w DocBook</title>

<bookinfo>

<subtitle>Podtytul</subtitle>

<edition>First</edition>
<isbn>?-?????-???-?</isbn>
<authorgroup>

<author>
<firstname>Jacek</firstname>
<surname>Laskowski</surname>
</author>

</authorgroup>

<pagenums>350 pages (est.)</pagenums>
<pubdate>(TBA)</pubdate>

<copyright>
<year>2008</year>
<holder>Jacek Laskowski</holder>
</copyright>

<legalnotice><para>Legalnotice</para></legalnotice>

</bookinfo>

&ch1;

</book>
oraz ch1.xml:
 <chapter id="ksiazka.rozdzial1">
<title>Wprowadzenie</title>

<para>Teraz dopiero sobie popiszemy</para>

<sect1 id="ksiazka.rozdzial1.pomocy">
<title>Gdzie szukac pomocy?</title>

<para>Chcialbym pomoc, ale sam wiesz - zarobiony jestem!</para>

<screen>
jlaskowski@dev ~
$ uname -a
CYGWIN_NT-5.1 dev 1.5.25(0.156/4/2) 2007-12-14 19:21 i686 Cygwin
</screen>
</sect1>
</chapter>
Uruchomienie wtyczki bez modyfikacji pom.xml projektu to trochę sztuczek mavenowych.
 jlaskowski@dev /cygdrive/c/projs/sandbox/docbook-ksiazka
$ mvn org.apache.maven.plugins:maven-help-plugin:2.0.2:describe -Dmedium=true \
-Dplugin=org.codehaus.mojo:docbook-maven-plugin:1.0.0-alpha-1
[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------------------
[INFO] Building docbook-ksiazka Maven Webapp
[INFO] task-segment: [org.apache.maven.plugins:maven-help-plugin:2.0.2:describe]
(aggregator-style)
[INFO] ----------------------------------------------------------------------------
[INFO] [help:describe]
[INFO] Plugin: 'org.codehaus.mojo:docbook-maven-plugin:1.0.0-alpha-1'
-----------------------------------------------
Group Id: org.codehaus.mojo
Artifact Id: docbook-maven-plugin
Version: 1.0.0-alpha-1
Goal Prefix: docbook
Description:

This plugin adds support for Docbook transformations to Maven.

Mojos:

Goal: 'transform'
Description:
Transforms a set of Docbook files into XHTML output. Currently there is only support for
XHTML output, though is planned to add all kind of outputs available in the standard
stylesheets.

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Wed Feb 27 21:22:27 PST 2008
[INFO] Final Memory: 3M/254M
[INFO] ------------------------------------------------------------------------
Długie nazwy przy wtyczkach to po prostu sposób na ich uruchomienie bez konieczności ich instalacji w lokalnym repozytorium czy wspomnianej modyfikacji pliku projektu pom.xml.

Okazuje się, że ja albo wtyczka nie mogliśmy poradzić sobie z najprostszym przykładem, który wykańczał wtyczkę docbook-maven-plugin z komunikatem FNFE:
 jlaskowski@dev /cygdrive/c/projs/sandbox/docbook-ksiazka
$ mvn clean docbook:transform
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'docbook'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building docbook-ksiazka Maven Webapp
[INFO] task-segment: [clean, docbook:transform]
[INFO] ----------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting directory c:\projs\sandbox\docbook-ksiazka\target
[INFO] [docbook:transform]
[INFO] Loading olink database generation stylesheet
[INFO] Creating olink database for 2 Docbook stale file(s)
...
[INFO] Creating master olink database file
[INFO] Transforming 2 Docbook stale file(s)
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] java.io.FileNotFoundException:
c:\projs\sandbox\docbook-ksiazka\target\site\docbook\ch1.html
(The system cannot find the path specified)
[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 minutes 47 seconds
[INFO] Finished at: Wed Feb 27 22:08:29 PST 2008
[INFO] Final Memory: 35M/254M
[INFO] ------------------------------------------------------------------------
Z jakiś nieznanych mi powodów podczas uruchomienia w trybie śledzenia (opcja -X polecenia mvn) wtyczka nie znajdowała pliku ch1.xml. Już wiedziałem, że za długo się z nią zmagam.

Nie było łatwo znaleźć wtyczki DocBook do Mavena i już nawet zdecydowałem się na uruchomienie zadania Ant z poziomu Maven, gdy przypadkiem natrafiłem na wpis Docbkx Maven Plugin 2.0.8 released. Świeża wersja sprzed kilku dni i jej wysoki numer dawały nadzieję, że nie wszystko stracone.
 jlaskowski@dev /cygdrive/c/projs/sandbox/docbook-ksiazka
$ mvn org.apache.maven.plugins:maven-help-plugin:2.0.2:describe -Dmedium=true \
-Dplugin=com.agilejava.docbkx:docbkx-maven-plugin:2.0.8
[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------------------
[INFO] Building docbook-ksiazka Maven Webapp
[INFO] task-segment: [org.apache.maven.plugins:maven-help-plugin:2.0.2:describe]
(aggregator-style)
[INFO] ----------------------------------------------------------------------------
[INFO] [help:describe]
...
[INFO] Plugin: 'com.agilejava.docbkx:docbkx-maven-plugin:2.0.8'
-----------------------------------------------
Group Id: com.agilejava.docbkx
Artifact Id: docbkx-maven-plugin
Version: 2.0.8
Goal Prefix: docbkx
Description:

A Maven plugin for generating HTML from DocBook.

Mojos:

Goal: 'generate-pdf'
Description:
A Maven plugin for generating fo output from DocBook documents, using version
1.73.2 of the DocBook XSL stylesheets.

Goal: 'generate-eclipse'
Description:
A Maven plugin for generating eclipse output from DocBook documents, using version
1.73.2 of the DocBook XSL stylesheets.

Goal: 'generate-manpages'
Description:
A Maven plugin for generating manpages output from DocBook documents, using version
1.73.2 of the DocBook XSL stylesheets.

Goal: 'generate-html'
Description:
A Maven plugin for generating html output from DocBook documents, using version
1.73.2 of the DocBook XSL stylesheets.

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Zapowiadało się wspaniale!
 jlaskowski@dev /cygdrive/c/projs/sandbox/docbook-ksiazka
$ mvn clean docbkx:generate-pdf
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'docbkx'.
[INFO] artifact org.apache.maven.plugins:maven-docbkx-plugin:
checking for updates from central
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] The plugin 'org.apache.maven.plugins:maven-docbkx-plugin'
does not exist or no valid version could be found
[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Wed Feb 27 22:14:48 PST 2008
[INFO] Final Memory: 1M/254M
[INFO] ------------------------------------------------------------------------
No tak! Przecież nie mam jej w repozytorium lokalnym, więc wywołuję ją w formacie rozszerzonym.
 jlaskowski@dev /cygdrive/c/projs/sandbox/docbook-ksiazka
$ mvn clean com.agilejava.docbkx:docbkx-maven-plugin:2.0.8:generate-pdf
[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------------------
[INFO] Building docbook-ksiazka Maven Webapp
[INFO] task-segment: [clean, com.agilejava.docbkx:docbkx-maven-plugin:2.0.8:generate-pdf]
[INFO] ----------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting directory c:\projs\sandbox\docbook-ksiazka\target
...
[INFO] [docbkx:generate-pdf]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Jeju! Działa i to za pierwszym strzałem. Pora sprawdzić dokumentację. Żadnego katalogu target? Pora zajrzeć do dokumentacji, bo widzę, że bez niej ani rusz. Kilka chwil na User Guide i już wiadomo, że katalog z moją książką powinien być src/docbkx. Przenoszę pliki i ponownie wykonuję wtyczkę docbkx-maven-plugin.
 jlaskowski@dev /cygdrive/c/projs/sandbox/docbook-ksiazka
$ mvn clean com.agilejava.docbkx:docbkx-maven-plugin:2.0.8:generate-pdf
[INFO] Scanning for projects...
[INFO] ----------------------------------------------------------------------------
[INFO] Building docbook-ksiazka Maven Webapp
[INFO] task-segment: [clean, com.agilejava.docbkx:docbkx-maven-plugin:2.0.8:generate-pdf]
[INFO] ----------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting directory c:\projs\sandbox\docbook-ksiazka\target
[INFO] [docbkx:generate-pdf]
[WARNING] Failed to find catalog files.
[INFO] Processing ksiazka.xml
Feb 27, 2008 10:22:30 PM org.apache.fop.hyphenation.Hyphenator getHyphenationTree
SEVERE: Couldn't find hyphenation pattern en
[INFO] Processing ch1.xml
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 27 seconds
[INFO] Finished at: Wed Feb 27 22:22:31 PST 2008
[INFO] Final Memory: 21M/254M
[INFO] ------------------------------------------------------------------------
Jest! BUILD SUCCESSFUL i widać, że coś tam wtyczka przetwarzała.
 jlaskowski@dev /cygdrive/c/projs/sandbox/docbook-ksiazka
$ ls -l target/docbkx/pdf
total 120
-rwx------+ 1 jlaskowski None 40792 Feb 27 22:22 ch1.fo
-rwx------+ 1 jlaskowski None 6567 Feb 27 22:22 ch1.pdf
-rwx------+ 1 jlaskowski None 58902 Feb 27 22:22 ksiazka.fo
-rwx------+ 1 jlaskowski None 9745 Feb 27 22:22 ksiazka.pdf
Jest i książka w formacie PDF! Tego mi było trzeba. Wystarczy teraz zgłębić tajniki DocBook i można publikować PDFy.

27 lutego 2008

The Feel Of Java Revisited by James Gosling

7 komentarzy
Wczoraj przeglądając listę członków grup JUG na świecie zobaczyłem zaproszenie na spotkanie twórcy języka Java - James Goslinga w Sun Santa Clara Campus Auditorium w dniu 28.02.2008. Moment - pomyślałem - Przecież to jutro i przejeżdżam koło tego miejsca bodaj kilkakrotnie w ciągu tygodnia, nie wspomniając, że jeżdżę na lunch niedaleko (nie dalej niż 1km od tego miejsca). Rzut oka na godzinę - 5:45 pm PT - 8:30 pm PT i myśl, że przecież mogę się tam pojawić! Spotkanie darmowe, tylko należy się zarejestrować. Niestety, wszystkie 150 miejsc zajęte. Nie odpuszczając, wierząc, że na pewno ktoś się wypisał, czy nie będzie kompletu, piszę do organizatorów, że jestem zbłąkanym chętnym na spotkanie, że dopiero co się dowiedziałem i że jest to unikatowa dla mnie okazja do spotkania wielu ludzi z Bay Area i w ogóle z Jamesem, bo jestem nie dalej niż 5 minut od nich! Dzisiaj zaglądam do poczty, a tam wiadomość od Van Ripera i Aarona Houstona:

We have had a few cancellations so far, but, I have no reopened registration. So, I think we can fit the two of you in. I need first/last names and email addresses to add people to the registration system manually myself. I have yours, but, I need this information for the person you will be bringing with you. Please send that information and we will see you tomorrow night.

Cheers, Van

Odpisuję, a po kilku minutach kolejna wiadomość - potwierdzenie mojego uczestnictwa w spotkaniu z James Goslingiem!

Message to attendees of The Feel Of Java Revisited by James Gosling

Event: The Feel Of Java Revisited by James Gosling

Date: February 28, 2008
Time: 5:45 pm PT - 8:30 pm PT
Location:
Sun Santa Clara Campus Auditorium
4030 George Sellon Circle
Santa Clara, CA 95054

For more information: http://feel-of-java.eventbrite.com

Hello Attendees,

First off, I want to thank all of you for registering for this very special event with James. We haven't done anything like this in the Bay Area for awhile, and so, we're hoping we can do more of these types of events in Santa Clara in the future.

Special Thanks, to all of Bay Area User Group leaders that are making this event possible.

Special Thanks to some of the Bay Area Java Champions who will be attending this event in person... So Welcome Josh, Neal, Bob, Dick, Bill, Calvin, Cay, Chris...and also Daniel who is town from Brazil. Thanks Guys, for your support!! More about the Champions here:
https://java-champions.dev.java.net/

A little bit of information about tomorrow evening's ***agenda:

6pm **Doors Officially Open** to the Sun Santa Clara auditorium. We will have pizza and sodas available. (There will be staff working beforehand inside the auditorium to get everything set up as early as 5pm)

CHECK-IN -- There will be a check-in station as you walk-into the lobby of the auditorium. We will have name tag stickers if you wish to write your name down for everyone.

Because the auditorium is an historic building, we had to limit the registration to 150 attendees. There will be some "freebie" items across from the check-in station. We also hope to have copies of the original "Feel of Java" paper at the check-in station.

***Hello WiFi*** -- There is free WiFi in the auditorium so everyone should stay connected to the web.

6:30-6:45pm --- We will be starting the program with a brief introduction of the local Bay Area User Group leaders. There will also be a presentation of a appreciation gift by James to the leaders of the Silicon Valley Web Users Group and to San Jose State University. We will also be joined by folks on a conference call line; they will be listening-in to James' presentation and asking questions during the Q&A.

The "EmCee" for the event will be Sun's Java Technology Evangelism Manager: Reggie Hutcherson.

Approx 6:45 to 7:45 -- James will begin his talk. We're giving James plenty of room to talk, but there will be plenty of time for interaction during the Q & A session after James finishes. (Standing microphones will be placed in the aisle ways, in addition to some wireless mics that will be available for folks to ask questions with)

8:30 -- We hope to wrap things up.

***Please help US keep the Agnew's Auditorium clean and pick up any cans or trash as you leave.

Looking forward to meeting you-all and we're really excited to have this opportunity with the Bay Area Java developer community.

Przepuściłem spotkanie grupy Maven bodajże 2 tygodnie temu w piątek w Bay Area (również około 10 minut od mojego apartamentu), ale tego wydarzenia nie mogłem przepuścić! Już nie mogę się doczekać spotkania z Jamesem!

Pora na łyk OSGi - rozdział 1. Wprowadzenie

1 komentarzy
Nie mogłem się oprzeć, aby nie zajrzeć do specyfikacji OSGi. Co jakiś czas to tu, to tam pojawiają się jakieś ciekawostki z nim związane, a na koniec jeszcze Daniel ze swoim komentarzem dotyczącym Wicketa, Springa i właśnie OSGi. Zaczęło się całkiem niewinnie:

Jeżeli chodzi o samego Wicketa, to korzystam z niego od prawie roku (w połączeniu m.in. ze Springiem i OSGi) i mogę tylko powiedzieć, że nie żałuję decyzji o jego wyborze. Polecam ;)

Okazuje się, że integracja nie jest czymś nadzwyczajnym w/g Daniela, a korzyści płynące z jej zastosowania są ogromne (tak, brzmi jakby za chwile miało trzęsienie ziemi nastąpić, albo podobnie, ale "ogromne" najbardziej mi w tym kontekście leżało). Dopiero, co wdrażam się w Wicketa, od kilku dni mam go uruchomionego ze Springiem, więc pozostaje jedynie włączyć OSGi. Hmmm, łatwo powiedzieć, trudno zrobić. Najlepiej, więc zacząć od wprowadzenia Daniela w jego kolejnym komentarzu. Ten komentarz sam w sobie zasługuje na pojawienie się jako wpis w moim Notatniku, więc nie zawaham się go użyć:

Jak widać po Twoich wpisach OSGi nie jest Ci obce, dlatego zdziwiło mnie zdanie: "to nie wiem jaki miałby zysk wdrożenie OSGi mając na pokładzie Spring + Wicket." :) Przecież korzyści jakie płyną z wykorzystania OSGi są w miarę jasne, chociażby podstawowa: dobre wsparcie dla modularyzacji aplikacji.

Wicket z OSGi integruje się dosyć łatwo. Można to zrobić publikując servlet wicketowy jako serwis OSGi, który następnie zostanie zlokalizowany przez odpowiedni OSGi HTTP service (np. standardowo dostępny Jetty, jeżeli korzystamy z Equinoxa: http://www.eclipse.org/equinox/server/) i zarejestrowany. Oczywiście możemy włączać/wyłączać bundla z naszą aplikacją webową w trakcie działania aplikacji, możemy też w jednym środowisku uruchomić wiele takich aplikacji jednocześnie. Cała integracja sprowadza się w praktyce do zaimplementowania kilku (w moim przypadku pięciu) klas: "osgi-owych" wersji WicketServlet i WicketFilter, OsgiWicketServiceRegistration - zawierającą info o rejestrowanym serwisie, no i jeszcze proste application factory i application factory bean (aplikację konfigurujemy za pomocą Springa, jako bean będący właśnie taką faktorką), który w praktyce rejestruje i wyrejestrowywuje serwis z naszą aplikacją. Dalej już korzystamy ze standardowych mechanizmów Wicketa. Jest jeszcze projekt pod egidą OPS4J - Pax Wicket, gdzie też jest zrobiona taka integracja.

Kilka dni temu ukazała się oficjalna wersja Spring Dynamic Modules (wcześniej Spring-OSGi), która ułatwia pewne rzeczy tj. definiowanie i konsumowanie serwisów OSGi (są tu pewne analogie do declarative services). Jeżeli używamy Springa, to pod OSGi wykorzystanie tych mechanizmów wydaje się naturalne. Serwisy definiujemy jako normalne beany w Springu i publikujemy za pomocą elementu [osgi:service] (oczywiście wszedzie zamiast [ ] wstaw < >), np.:
[osgi:service interface="com.naszafirma.najlepiejJakisInterface" ref="beanZeSpringa" /]
a tam gdzie korzystamy z tego serwisu, uzyskujemy do niego dostęp za pomocą:
[osgi:reference interface="com.naszafirma.najlepiejJakisInterface" id="nazaBeanaPodJakaBedzieOnWidoczny" /]
Proste prawda ;). Ciekawe porównanie sposobów obsługi serwisów jest tu: http://www.eclipsezone.com/articles/extensions-vs-services/

Na koniec integracja Wicketa ze Springiem. Najwygodniej jest chyba korzystać z adnotacji @SpringBean. Jeżeli mamy zdefiniowanego beana o [bean id="contactStore" class="com.naszafirma.OracleDataStore" /], gdzie com.naszafirma.OracleDataStore implementuje interfejs DataStore, wtedy wstrzyknięcie beana do dowolnego komponentu (np. strony, czy panelu) wygląda tak:

class MyPanel extends Panel {
@SpringBean(name = "contactStore")
private DataStore contactStore;

public MyPanel(String id) {
super(id);
contactStore.someMethod();
}
}

czyli już w konstruktorze mamy dostęp do tego beana. Oczywiście możemy go wstrzykiwać jako interfejs albo klasę. Wicket utworzy nam dla niego odpowiednie proxy, które będzie serializowalne w komponencie, natomiast nie będzie serializowalny bezpośrednio wstrzykiwany bean. Jeżeli chodzi o użycie tego w OSGi to nie ma problemu dopóki nie podzielimy aplikacji na więcej modułów i gdy pojawia się wiecej niż jeden application context (w praktyce każdy bundle ma swój własny). Ale to już inna historia, o której może innym razem ;).

To brzmi jak jakieś zaklęcie (!) Dorzuć szczyptę skrzydła muchy, kropelkę jadu, trochę pajęczyny, itp. Pewnie, że proste, ale dla niektórych to nawet zrobienie jajecznicy zakrawa na wyczyn i nagrodę Nobla. Nie, Daniel, nie chcę Cię urazić, ale wybacz, połowy nie zrozumiałem. Jakkolwiek z "lotu ptaka" jestem w stanie sobie to jakoś wyobrazić, to żeby to zaprezentować w działaniu. Co to, to nie...jeszcze.

Jakby tego było mało pojawił się nowy test OSGI basic na javaBLACKbelt, o którym już wspominałem i już po pierwszym pytaniu...WPADKA (!) Błędna odpowiedź, więc wypad, idź się podszkolić.

Na koniec moich wynurzeń odnośnie wzrastającej świadomości programistów Java architekturą OSGi jako metodą na zwiększenie modularności aplikacji natrafiłem na zapowiedź serii artykułów w JavaWorld. Swego czasu JavaWorld było kopalnią wiedzy, jednakże podupadło i dopiero od niedawna ponownie zaczynają się pojawiać ciekawe artykuły. I tu proszę:

Hello, OSGi
First article in a three-part series introducing the Open Source Gateway Initiative. - UPCOMING ON JAVAWORLD's Enterprise Java Newsletter.


Czyż nie zabawnie?

Wczoraj więc, na dobranoc, postanowiłem zajrzeć do specyfikacji OSGi. Całość to jedynie 288 stron, więc możnaby to w jeden dobry wieczór "obrobić". Mnie starczyło sił na początkowy rozdział wprowadzający 1 Introduction. Dobrze jest go przeczytać przez wszystkich parających się Javą w jakikolwiek sposób, aby rozeznać się naprędce, co to cudeńko potrafi. Kilka słów o zaletach, zero wad, więc w zasadzie nie ma złudzeń - trzeba zagłębić się w temat. Z rzeczy, które wydały mi się wartościowe to przede wszystkim:
  • OSGi Service Platform (w skrócie OSGi) to otwarta specyfikacja architektury dostarczania zarządzanych usług poprzez sieć
  • Ostatnia wersja specyfikacji to 4.1
  • Zakłada się, że usługi będą funkcjonowały w różnych środowiskach - od urządzeń domowych, w samochodzie, telefonów komórkowych, komputerów stacjonarnych, itp.
  • Głównym graczem w architekturze OSGi jest szkielet aplikacyjny w Javie rozszerzający jej mechanizm bezpieczeństwa i modularyzacji
  • Podstawową jednostką w OSGi jest pakunek (ang. bundle) - zwarty acz rozbudowywalny moduł usługowy
  • Urządzenia OSGi mają możliwość pobrania i zainstalowania pakunków OSGi oraz ich usunięcia na żądanie
  • Platforma OSGi umożliwia dynamiczne uaktualnianie pakunków podczas działania systemu
  • OSGi udostępnia mechanizm zależności między pakunkami i dostarczanymi przez nie usługami
  • Wyróżnia się następujące warstwy w platformie OSGi:
    • Security Layer - Warstwa Bezpieczeństwa
      • Rozszerza mechanizm bezpieczeństwa w Javie 2 m.in. przez udostępnienie mechanizmów bezpiecznego opakowania ("gwarancja pierwszego otwarcia")
    • Module Layer - Warstwa Zarządzania Modułami / Warstwa Modułów
      • Udostępnia model modularyzacji w Javie i kontroli dostępności pakunków
      • Udostępniane są jedynie zadeklarowane pakiety klas (a tym samym ukrywane są te, które bezpośrednio nie są publiczne - mowa o pakietach Java a nie klasach czy interfejsach)
      • Nie wymaga Warstwy Rozwojowej i Warstwy Kontroli Usług
    • Life Cycle Layer - Warstwa udostępniająca mechanizm Kontroli Rozwoju pakunków / Warstwa Rozwojowa
      • Udostępnia interfejs programistyczny API do zarządzania pakunkami w warstwie modułów
      • Udostępnia mechanizm uruchamiania/zatrzymywania oraz instalacji/aktualizacji/odinstalowania
      • Udostępnia model zdarzeń i jego interfejs programistyczny API
      • Wymaga Warstwy Modułów, podczas gdy Warstwa Bezpieczeństwa jest opcjonalna
    • Service Layer - Warstwa Komunikacji/Kontroli Usług (pakunków) / Warstwa Usługowa
      • Udostępnia mechanizm komunikacji między pakunkami
      • Udostępnia interfejs programistyczny API, który pozwala na komunikację z jej interfejsem ukrywając implementację
      • Udostępnia możliwość dostarczania implementacji podczas działania pakunku
      • Udostępnia model programistyczny, który umożliwia dostosowywanie implementacji pakunków do dostępnych zasobów środowiska uruchomieniowego (urządzenia z dostępnymi zasobami, w którym pakunki są uruchomione)
      • Udostępnia mechanizm wykrywania implementacji pakunków przez rejestr usług OSGi
      • Pakunki rejestrują się, otrzymują informacje zwrotne od środowiska o stanie innych pakunków z możliwością dynamicznej rekonfiguracji podczas działania systemu
    • Actual Services - Warstwa Pakunków
  • Wymaga się, aby osoby rozpoczynające swoją przygodę z OSGi jako programiści pakunków mieli większe niż przeciętne doświadczenie programistyczne w Javie, ze szczególnym naciskiem na mechanizm ładowania klas, odśmiecania, bezpieczeństwa oraz ładowania bibliotek natywnych (nie ma lekko, jak widać).
Ciekaw jestem ilu z Was para się OSGi i do czego tę architekturę wykorzystujecie? Zawsze miałem wrażenie, że OSGi to jakaś bardzo wyrafinowana i specjalistyczna architektura, a tu proszę Daniel wykorzystuje ją do udoskonalenia aplikacji opartej o Wicket wspartej przez Spring Framework. Ech, w jakiej to człowiek niewiedzy żył przez lata.

26 lutego 2008

Wicket 1.3.1 i Spring Framework 2.5.1 jednak współpracują

3 komentarzy
Tak niewiele było trzeba, aby przekonać Apache Wicket 1.3.1 do współpracy ze Spring Framework 2.5.1. Wczorajszy wpis "Globalizacja" obiektów Wicketa ze Spring Framework wzbudził zainteresowanie Waldiego, który swoim komentarzem z kolei sprowokował mnie do głębszego zastanowienia nad sposobem uaktualnienia trochę już zakurzonej zależności Wicket 1.3.1 od Spring Framework 2.0. Czy to nie-mavenowe rozwiązanie Waldka czy po prostu jego wizyta w moim Notatniku, ale to wystarczyło, abym wpadł na to właściwe mavenowe rozwiązanie. Coraz częściej daje się słyszeć utyskiwania na zarządzanie zależnościami przez Apache Maven 2, ale w tej sytuacji spisał się zgodnie z oczekiwaniami. Uruchomienie Spring Framework 2.5.1 oraz Wicket 1.3.1 sprowadza się do następującej zmiany w pom.xml w projekcie mojej demonstracyjnej aplikacji:
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring</artifactId>
<version>${wicket.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.1</version>
</dependency>
Najistotniejszą zmianą jest wykluczenie zależności org.springframework.spring z zależności org.apache.wicket.wicket-spring za pomocą elementu exclusions i samodzielne dopisanie zależności org.springframework.spring.2.5.1 do projektu. Tyle! Dziękuję Waldek za inspirację! Czasami trzeba zwykłego pytania/sugestii/po prostu pogadać z kimś kto się tym również para/interesuje, aby rozwiązać zadanie w parę sekund.

Dla upiększenia przykładu dodałem wyświetlenie wersji Springa w etykiecie na stronie do uwierzytelnienia użytkownika. Zmiana w kodzie klasy-strony pl.jaceklaskowski.wicket.PrzedstawSie:
// wyświetl wykorzystywaną wersję Spring Framework
loginForm.add(new Label("springVersion", SpringVersion.getVersion()));
Zmiana wymagała dodania odpowiadającemu etykiecie elementowi span na stronie HTML - PrzedstawSie.html:
<tr>
<td colspan="3">
Wersja Spring Framework: <b><span wicket:id="springVersion">Wersja Spring Framework</span></b>
</td>
</tr>
Uruchomienie aplikacji z tą zmianą wyświetliło poprawną wersję Springa.


Takiej inspiracji mi trzeba było! Z pozdrowieniami dla Waldka!

25 lutego 2008

"Globalizacja" obiektów Wicketa ze Spring Framework

5 komentarzy
Mimo tak szumnego tytułu zacznę lekko, od wtyczki Wicket Bench. Podczas mojej pracy z Eclipse i projektem wicketowym całkowicie o niej zapomniałem. Podczas tworzenia klasy-strony PrzedstawSie w mojej demonstracyjnej aplikacji nieoczekiwanie pojawiła się podpowiedź odnośnie możliwości utworzenia odpowiadającej jej strony html.


Nie ukrywam, że mile mnie zaskoczyła nadgorliwość wtyczki do skracania czasu potrzebnego do tworzenia kolejnej strony, a że miałem zabrać się za HTML, więc rychło skorzystałem z pomocy. Jakież było moje zaskoczenie, kiedy moim oczom ukazała się strona następującej treści:
<html xmlns:wicket>
<wicket:panel>

</wicket:panel>
</html>
Pomyślałem, że to z powodu, że nic nie było w samej klasie. Szybko uzupełniłem (oprogramowałem) klasę o potrzebne mi komponenty i po skasowaniu strony HTML, wykonałem generacje strony ponownie. Niestety, ale efekt końcowy nie zmienił się, ani na jotę. Spodziewałem się więcej. Na razie odpuszczam ją sobie, chociaż wciąż w tle próbuje mi pomóc.

Wciąż część mojego czasu poświęcam na lekturę książki Pro Wicket z Manning i wciąż ten sam rozdział 3. Developing a Simple Application. Ten rozdział zacznie mi się niedługo śnić. Po ostatnim moim wpisie odnośnie sesji - Sesja i przekierowanie żądania w Wicket - przyszła pora na wzmiankę o zasięgu aplikacyjnego - przestrzeni obiektów globalnych aplikacji. Krótka wizyta na stronie dokumentacji klasy org.apache.wicket.markup.repeater.data.IDataProvider, a tam oto taki przykład pomocniczy:
class UsersProvider implements IDataProvider {

public Iterator iterator(int first, int count) {
((MyApplication)Application.get()).getUserDao().iterator(first, count);
}

public int size() {
((MyApplication)Application.get()).getUserDao().getCount();
}

public IModel model(Object object) {
return new DetachableUserModel((User)object);
}
}
Pomijając wartość płynącą z IDataProvider przyjrzyjmy się konstrukcji
((MyApplication)Application.get()).getUserDao()
, która zwraca pewne DAO (w tym przypadku UserDao)...globalne swoim zasięgiem. Właśnie w ten sposób realizuje się zasięg application znany z JSF czy JSP/Servlets w wykonaniu Wicket.

Przypominając jak działała sesja w Wicket można zauważyć podobieństwo między nimi w sposobie ich pozyskiwania. I sesję, i aplikację pobieramy za pomocą konstrukcji PewnienBytWicketa.get() z rzutowaniem na właściwy typ. Tak było z sesją - Session.get() i tak jest z aplikacją - Application.get(). Jeśli zatem przyjdzie nam umieścić pewnien byt w przestrzeni obiektów o zasięgu aplikacyjnym (application) wystarczy dostarczyć odpowiednie metody do naszej klasy aplikacyjnej (u mnie będzie to pl.jaceklaskowski.wicket.WicketDemoApplication) i na tym sprawa się kończy. Podobnie jak miało to miejsce przy obiektach sesyjnych, mamy dostarczone silne typowanie za darmo, tzn. w czasie proporcjonalnym do naszego nakładu pracy przy utworzeniu atrybutu zadanego typu.
public class WicketDemoApplication extends WebApplication {

private Logger logger = Logger.getLogger(WicketDemoApplication.class.getName());

private SlowoDao slowoDao;

...

public SlowoDao getSlowoDao() {
return slowoDao;
}

public void setSlowoDao(SlowoDao slowoDao) {
this.slowoDao = slowoDao;
}
}
Nie wiedzieć czemu, kiedykolwiek widzę DAO na myśl przychodzi mi Spring Framework. Skoro już przy nim jestem spojrzenie jak to mógłby on nas wesprzeć w tworzeniu aplikacji z Wicket (a wiem, że może, więc pewnie oczekuję tej integracji tak pewnie). Skok do rozdziału 5. Integration with Other Frameworks w Pro Wicket, gdzie odszukałem niezbędne informacje o integracji Wicket-Spring.

Krok 1. Zmieniamy konfigurację aplikacji webowej celem zarejestrowania obiektu nasłuchującego realizującego integrację między Wicketem a Springiem.
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
oraz dodaję parametr konfiguracyjny dla wicketowego servletu lub (jak w mojej demonstracyjnej aplikacji) filtra.
<init-param>
<param-name>applicationFactoryClassName</param-name>
<param-value>wicket.spring.SpringWebApplicationFactory</param-value>
</init-param>
Dodatkowo usuwam parametr
<init-param>
<param-name>applicationClassName</param-name>
<param-value>pl.jaceklaskowski.wicket.WicketDemoApplication</param-value>
</init-param>
który będzie wskazany przez konfigurację Springa w jego domyślnie poszukiwanym pliku konfiguracyjnym /WEB-INF/applicationContext.xml lub dowolnym innym pliku konfiguracyjnym wskazanym przez parametr konfiguracyjny wicketowego servletu/filtra przez parametr contextConfigLocation.

Ostatecznie kończę zmiany z następującym plikiem /WEB-INF/web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<display-name>wicket-demo</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>wicket.wicket-demo</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationFactoryClassName</param-name>
<param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>wicket.wicket-demo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Krok 2: Utworzenie konfiguracji dla Spring Framework z Wicketem

Krótka wizyta na stronie dokumentacji Spring Framework - Chapter 3. The IoC container i mamy gotowy plik /WEB-INF/applicationContext.xml o następującej treści:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="slowoDao" class="pl.jaceklaskowski.wicket.model.SlowoDao" />
<bean id="wicketApplication" class="pl.jaceklaskowski.wicket.WicketDemoApplication">
<property name="slowoDao" ref="slowoDao" />
</bean>
</beans>
I tutaj olśnienie - spostrzegłem, że do tej pory wszystkie moje encje były zawarte w pakiecie pl.jaceklaskowski.wicket.entities, podczas gdy trafniejszym byłby pl.jaceklaskowski.wicket.model, który nie wskazywałby na technologię, ale na rolę klas w nim zawartych. Zmieniłem i wdrażam do codziennego użycia.

Krok 3. (opcjonalny) Aktualizacja pom.xml dla projektu mavenowego

Kolejny raz dostrzegam zalety stosowania Maven do zarządzania moim projektem, tak że dodanie wymaganych bibliotek Spring Framework sprowadza się do dodania następującej sekcji dependency do pom.xml:
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring</artifactId>
<version>${wicket.version}</version>
</dependency>
Wystarczy, więc dodanie zależności org.apache.wicket.wicket-spring i potrzebne zależności, w tym i sam Spring Framework, zostaną pobrane dzięki uprzejmości Mavena.

Parametr ${wicket.version} to już zadanie dla Maven, który rozwiązuje mi ją do wersji 1.3.1 (co, jak i dlaczego w tym temacie było na samym początku moich zmagań z Wicket - Pierwsze kroki z Apache Wicket 1.3).

Podczas uruchomienia tak zmodyfikowanej aplikacji webowej obiekt nasłuchujący uruchomienia aplikacji zarejestrowany w jej deskryptorze - org.springframework.web.context.ContextLoaderListener zainicjuje infrastrukturę Spring Framework. Korzystamy wyłącznie z funkcjonalności IoC Springa, więc ziarna (wskazane przez elementy beans w pliku applicationContext.xml) zostaną zainicjowane i wstrzelone, gdzie wskazano, np. WicketDemoApplication zostanie "zasilone" egzemplarzem SlowoDao poprzez publiczną metodę zapisu setSlowoDao(SlowoDao slowoDao). Następnie podczas inicjowania Wicket (w naszej konfiguracji podczas uruchomienia filtra) nastąpi uruchomienie fabryki klasy aplikacji wskazanej przez parametr applicationFactoryClassName, tj. org.apache.wicket.spring.SpringWebApplicationFactory. Fabryka SpringWebApplicationFactory już wie, czego należy szukać w "przestrzeni" springowej, aby uruchomić aplikację. Już wiadomo, że potrzebna jest klasa rozszerzająca (wprost bądź niewprost) klasę org.apache.wicket.protocol.http.WebApplication, co w tym przypadku jest jednoznacznym wskazaniem na pl.jaceklaskowski.wicket.WicketDemoApplication.

W dokumentacji klasy org.apache.wicket.spring.SpringWebApplicationFactory napisano, że w przypadku wielu aplikacji wicketowych zdefiniowanych w "przestrzeni" Springa można wskazać tę jedną, wybraną przez parametr beanName. Trudno mi teraz to wytłumaczyć, ale natchnęło mnie na przejrzenie źródeł i...strzał w plecy - dokumentacja klasy jest niepoprawna (!) Okazuje się, że należy skorzystać z parametru applicationBean (kolejny raz, kiedy potwierdza się stara dobra maksyma, że warto zaglądać do kodu źródłowego, jeśli się go ma i ma się czas na jego lekturę):
<init-param>
<param-name>applicationBean</param-name>
<param-value>wicketApplication</param-value>
</init-param>
To może skłaniać do pytania, czy nazwa aplikacji wicketowej w applicationContext (poprzez atrybut id) ma znaczenie. Otóż nie. Poszukiwanie tej jednej wybranej aplikacji wicketowej bez określenia jej przez beanName jest realizowane przez odszukanie ziaren (springowych) rozszerzających org.apache.wicket.protocol.http.WebApplication.

I tutaj uwaga. Jeśli ktokolwiek pomyślałby o zadeklarowaniu zależności Spring Framework 2.5.1 w aplikacji opartej o Wicket 1.3.1 może na starcie spodziewać się poniższego komunikatu błędu:
2008-02-24 20:40:23.447::INFO:  jetty-6.1.7
2008-02-24 20:40:23.588::INFO: No Transaction manager found - if your webapp requires one,
please configure one.
2008-02-24 20:40:24.947::WARN:
failed org.mortbay.jetty.plugin.Jetty6PluginWebAppContext@1083717
{/wicket-demo,C:\projs\sandbox\wicket-demo\src\main\webapp}
java.lang.NoSuchMethodError:
org.springframework.core.CollectionFactory.createConcurrentMapIfPossible(I)Ljava/util/Map;
at org.springframework.web.context.ContextLoader.(ContextLoader.java:153)
at org.springframework.web.context.ContextLoaderListener.createContextLoader
(ContextLoaderListener.java:53)
at org.springframework.web.context.ContextLoaderListener.contextInitialized
(ContextLoaderListener.java:44)
at org.mortbay.jetty.handler.ContextHandler.startContext(ContextHandler.java:540)
at org.mortbay.jetty.servlet.Context.startContext(Context.java:135)
at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1220)
at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:510)
at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:448)
at org.mortbay.jetty.plugin.Jetty6PluginWebAppContext.doStart
(Jetty6PluginWebAppContext.java:110)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
at org.mortbay.jetty.handler.HandlerCollection.doStart(HandlerCollection.java:152)
Wicketowy org.apache.wicket.wicket-spring.1.3.1 deklaruje już zależność od Spring Framework, więc należy usunąć własną, bo...za nowa. Wersja Spring Framework zadeklarowana jako zależność dla modułu wicket-spring (org.apache.wicket.wicket-parent.1.3.1) to 2.0 i jak widać jest pewna rozbieżność między nimi w publicznym interfejsie Springa, z którego korzysta Wicket.

Pora uruchomić aplikację.
2008-02-24 20:44:18.036::INFO:  jetty-6.1.7
2008-02-24 20:44:18.176::INFO: No Transaction manager found - if your webapp requires one,
please configure one.
INFO - ContextLoader - Root WebApplicationContext: initialization started
2008-02-24 20:44:19.520:/wicket-demo:INFO: Loading Spring root WebApplicationContext
INFO - CollectionFactory - JDK 1.4+ collections available
INFO - XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource
[/WEB-INF/applicationContext.xml]
INFO - XmlWebApplicationContext - Bean factory for application context [Root WebApplicationContext]:
org.springframework.beans.factory.support.DefaultListableBeanFactory defining
beans [slowoDao,wicketApplication]; root of BeanFactory hierarchy
INFO - XmlWebApplicationContext - 2 beans defined in application context [Root WebApplicationContext]
INFO - XmlWebApplicationContext - Unable to locate MessageSource with name 'messageSource':
using default [org.springframework.context.support.DelegatingMessageSource@5ead9d]
INFO - XmlWebApplicationContext - Unable to locate ApplicationEventMulticaster with name
'applicationEventMulticaster': using default
[org.springframework.context.event.SimpleApplicationEventMulticaster@1a93f38]
INFO - UiApplicationContextUtils - Unable to locate ThemeSource with name 'themeSource':
using default [org.springframework.ui.context.support.ResourceBundleThemeSource@29d75]
INFO - DefaultListableBeanFactory - Pre-instantiating singletons in factory
[org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans
[slowoDao,wicketApplication]; root of BeanFactory hierarchy]
INFO - ContextLoader - Using context class
[org.springframework.web.context.support.XmlWebApplicationContext] for root WebApplicationContext
INFO - ContextLoader - Root WebApplicationContext: initialization completed in 1391 ms
INFO - Application - [WicketDemoApplication] init: Wicket core library initializer
...
INFO - WebApplication - [WicketDemoApplication] Started Wicket version 1.3.1 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. ***
********************************************************************
2008-02-24 20:44:21.254::INFO: Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
Aplikacja wystartowała poprawnie! Sprawdzenie w przeglądarce i po chwili już wiem, że wszystko w porządku (tutaj kolejny raz przydałoby się wsparcie Selenium czy podobnie do wykonania testów za mnie - książka An Introduction to Testing Web Applications with twill and Selenium z OReilly już czeka na mnie, jak i Tomasz Kaczanowski z prezentacją na marcowym spotkaniu grupy Warszawa JUG, na którym mnie jeszcze nie będzie).

Na zakończenie kilka ciekawostek, na które dzisiaj natrafiłem w tak zwanym międzyczasie. W trakcie przeglądania mojego zbioru blogów zacząłem od naszych, lokalnych i najciekawszym wydał mi się wpis Jak nie należy programować w polskim The Daily WTF, w którym kluczowym był fragment:

Tydzień z grubsza zajęło mi doprowadzenie do używalności kodu, który otrzymałem w spadku po pewnym urlopowiczu. Dobrze, że nie mam włosów, bo z pewnością bym je dawno wyrwał, choć z — drugiej strony — na programowaniu cierpi moja broda. Kolega wypoczywa w Iranie, a my próbujemy dojść, co też podmiot liryczny miał na myśli. Kiedy czytanie źródeł dłuży się ponad lekturę Nad Niemnem, znak to, że czas na notkę.

Szczególnie ten moment z "podmiot liryczny" jest na 5+. Dobrze, że nic nie piłem, bo parsknąłem, kiedy na niego trafiłem. Ładnie wyglądałby mój laptop. Gratuluję poczucia humoru nawet w tak trudnych chwilach jak analiza czyjegoś kodu źródłowego! Czy ja dzisiaj nie miałem podobnej eskapady?! Szczęśliwie moja lektura obyła się bez wyrywania włosów (wciąż je mam gotowe do przycinki) i wyrywania brody (tego nie mam i nie miałem).

Przy okazji analizy blogów natrafiłem na wpis JSF vs Wicket, Job Opportunities. Znowu pod temat dzisiejszego mojego wpisu o Wicket (czy oni się przypadkiem nie skrzyknęli? ;-)). Na razie Wicket daleko w tyle, ale czuję, że podobnie jak w moim przypadku, wszystko za sprawą owego ciała standaryzującego Korporacyjną Javę, gdzie JSF jest jednym z kluczowych graczy i w ogóle całego nagłaśniania ważności technologii Java EE (również i przeze mnie). Wielu z nas, nie ma czasu, chęci, wpisz dowolny powód na brak własnego rozwoju technologicznego i kiedy pozna jedną technologię przykuwa się do niej grubym łańcuchem, aby albo ona wyniosła nas na wyżyny intelektualne, albo my ją. Tak, czy owak nie spotykam się często z przeniesieniem swoich uczuć na inne technologie w ramach samej dżawki (i nie piszę tu o migracji do całkowicie innej platformy jak np. .Net). To wydaje mi się głównym powodem dla popularności JSF. Wierzę, że w Polsce już tą kwestię mamy za sobą ;-)

24 lutego 2008

EJB 3.0 - Rozdział 17: Mechanizm bezpieczeństwa

1 komentarzy
Trochę minęło od mojego ostatniego relacjonowania lektury specyfikacji EJB3, jednakże wciąż się z nią zmagam. Przyszła w końcu pora na dwa, zwykle traktowane po macoszemu, tematy - bezpieczeństwo i transakcje. Zacznę od bezpieczeństwa, który ma swój rozdział 17: Mechanizm bezpieczeństwa w specyfikacji EJB3. Wiele ciekawego i w zasadzie streszczonego już w nim. Czasami trudno zdecydować się, co wyciąć, co nadmiarowe, bo już było wcześniej, itp. Przelewam moje tłumaczenie rozdziału z przekonaniem, że coś mi się jednak udało skrócić, wyrzucić i w ogóle uatrakcyjnić, co ostatecznie przyczyni się do lepszego zrozumienia tematu lub w ogóle do jego poznania. Nie ma przykładów, więc spragnieni ich powinni jeszcze uzbroić się w cierpliwość lub po prostu sami popróbować się z tematem. Miłej lektury!

Zacznijmy od uwspólnienia słownictwa (często doskwiera mi pewna rozbieżność podczas rozmów na ten i inne tematy, więc dobrze od tego zaczać):
  • method permissions - prawo wykonania metody
  • bean provider - Dostawca (ziarna)
  • application assembler - Osoba Składająca/Składający aplikację (do rozważenia: Monter, Montażysta)
  • deployer - Osoba Wdrażająca/Wdrażający
  • identity - tożsamość (wyłącznie jako logiczny byt)
  • principal - osobowość (wyłącznie w środowisku uruchomieniowym)
  • principal realm - przestrzeń (królestwo) osobowości
Specyfikacja EJB3 zaleca, aby metody biznesowe nie zawierały żadnej logiki związanej z bezpieczeństwem. W ten sposób Wdrażający aplikację ma możliwość zdefiniowania polityki bezpieczeństwa w najbardziej korzystny dla środowiska uruchomieniowego sposób.

Bezpieczeństwo w EJB3 oparte jest o pojęcie ról bezpieczeństwa (ang. security roles), które są logicznym zbiorem uprawnień, które muszą posiadać użytkownicy korzystający z aplikacji. Dostawca ziarna deklaratywnie określa prawo wykonania metody dla każdej z ról korzystajac z adnotacji bądź deskryptora wdrożenia. Składający aplikację ma możliwość zmiany konfiguracji wyłącznie za pomocą deskryptora wdrożenia. Prawo wykonania metody (ang. method permission) jest prawem do wykonania określonej metody bądź grupy metod ziarna zdefiniowanych w interfejsie biznesowym, domowym, komponentu i/lub usługi sieciowej. Role upraszczają spojrzenie na bezpieczeństwo aplikacji korzystajacej z EJB3 dla osoby wdrażającej aplikacje w serwerze aplikacyjnym do niezbędnego minimum bez konieczności poznawania ich przez pryzmat kodu źródłowego czy dodatkowych dokumentacji.

Osobowością (ang. security principal), który wykonuje dana metode jest wywołujący ziarno. Za pomocą tożsamości określanej przez run-as można to zmienić. Sprawdzenie aktualnej tożsamości wykonującego metodę ziarna możliwe jest za pomocą metody javax.ejb.EJBContext.getCallerPrincipal().

Dostawca ziarna ma możliwość określenia tożsamości wykonującego za pomocą adnotacji lub deskryptora wdrożenia.
  • Domyślnie tożsamość wykonującego metodę ziarna odpowiada tożsamości wywołującego. Za pomocą adnotacji @RunAs lub elementu run-as dostawca ziarna określa jego (tymczasowy) zamiennik.
  • Skorzystanie z deskryptora wdrożenia umożliwia Dostawcy lub Składającemu określenie tożsamości wykonującego za pomocą elementu security-identity. Brak elementu security-identity lub adnotacji @RunAs/elementu run-as implikuje security-identity jako use-caller-identity, tożsamość wykonującego metodę ziarna jest tożsamościa wywołującego. Użycie elementu run-as definiuje bieżąca tożsamość wykonującego ziarno. Składający ma prawo zmienić tożsamość wykonującego ustanowioną przez Dostawcę.
  • Wdrażający odpowiada za przypisanie osobowości i ich grup, które są zdefiniowane w środowisku uruchomieniowym do ról zdefiniowanych przez Dostawcę lub Składającego. Wdrażający jest odpowiedzialny za przypisanie osobowości do ról okreslonych przez run-as. Dodatkowo w jego obowiązkach leży konfiguracja innych aspektów bezpieczeństwa związanego z ziarnami, jak odwzorowanie (mapowanie) osobowości w komunikacji miedzy ziarnami oraz dostępu do zasobów.
Podczas uruchomienia aplikacji, klient ma prawo wykonać metodę ziarna, jeśli osobowość związana z wywołaniem ziarna została przypisana przez Wdrażającego do przynajmniej jednej roli uprawnionej do wykonania metody lub jeśli Dostawca lub Składający określiły, że sprawdzenie bezpieczeństwa nie jest wykonywane dla tej metody, tj. wszystkie role mają prawo wykonania metody.

Dostawca kontenera (producent) odpowiada za zagwarantowanie, że polityka bezpieczeństwa jest przestrzegana udostepniając narzędzia do zarządzania bezpieczeństwem podczas pracy kontenera i udostępniając narzędzia wspomagające pracę Wdrażającego do zarządzania bezpieczeństwem podczas wdrożenia.

Ze względu na fakt, że nie wszystkie polityki bezpieczeństwa da sie wyrazić deklaratywnie (za pomocą adnotacji lub deskryptora wdrożenia), specyfikacja EJB udostępnia interfejs programistyczny, który dostawca ziarna może wykorzystać do dostępu do kontekstu bezpieczeństwa z poziomu metody biznesowej.

I w zasadzie to wystarczyłoby do spojrzenia na możliwości związane z bezpieczeństwem w EJB3. Dalsze strony specyfikacji dotyczą już rozwinięciu tematu - przekazywaniu tożsamości wykonującego, mapowanie ról i referencji, adnotacje i deskryptor wdrożenia, itp.

Interfejs programistyczny javax.ejb.EJBContext


Specyfikacja EJB nie udostepnia interfejsu programistycznego, który wspierałby kontrolę tożsamości wywołującego, która jest przekazywana do wywoływanego ziarna przez ich interfejs biznesowy, domowy lub komponentu.

Zarządzanie osobowościami wywołującego przekazywanymi w komunikacji między ziarnami (delegacja osobowości) jest konfigurowana przez Wdrażającego lub Administratora serwera w specyficzny dla kontenera EJB (serwera aplikacji) sposób. Dostawca i/lub Składający powinni opisać wszystkie wymagania związane z tego typu wywołaniami jako część opisu ziarna (opcjonalne pole description).

Specyfikacja EJB nie definiuje osobowości w systemie operacyjnym, z którym wywoływana jest metoda. Z tego powodu, Dostawca nie może polegać na specyficznej osobowości, z którą będzie następował dostęp do zasobów systemu operacyjnego, np. plików.

Dostawca korzysta z adnotacji lub deskryptora wdrożenia celem określenia wymagań bezpieczeństwa aplikacji. W ten sposób Wdrażający ma możliwość zdefiniowania poprawnej polityki bezpieczeństwa.

Zarządzanie bezpieczeństwem powinno być w gestii kontenera EJB i powinno być przeźroczyste dla metod biznesowych ziarna. Udostępniony interfejs programistyczny powinien być stosowany wyłącznie w wyjątkowych sytuacjach.

Interfejs javax.ejb.EJBContext udostępnia dwie metody (oraz dwie niezalecane do użycia metody z EJB 1.0), które pozwalają Dostawcy na dostęp do kontekstu bezpieczeństwa i pozyskanie informacji o tożsamości wywołującego ziarno.

Dostawca wykonuje getCallerPrincipal() oraz isCallerInRole(String roleName) jedynie w metodach biznesowych (warto zapoznać się z tabelą uprawnień w specyfikacji EJB3 w tabeli 10 Operations Allowed in the Methods of an Entity Bean (strona 269). Każdorazowe wykonanie ich poza określonymi miejscami powoduje wystąpienie wyjątku java.lang.IllegalStateException.

Metody getCallerIdentity() oraz isCallerInRole(Identity role) są pozostałością (reliktami) czasów EJB 1.0 i nie zaleca sie ich wykorzystania. Dostawca musi używać wspomniane wcześniej metody getCallerPrincipal() oraz isCallerInRole(String roleName). Kontener ma prawo dostarczyć implementacje metod z EJB 1.0, które zawsze zgłaszają wyjątek java.lang.RuntimeException.

Celem metody getCallerPrincipal() jest pozyskanie informacji o bieżącej tożsamości wykonującego metodę biznesową. Na ich podstawie metody mogą przykładowo pozyskiwać dodatkowe informacje z bazy danych traktując tożsamość jako klucz do danych.

Metoda getCallerPrincipal() zwraca tożsamość wykonującego metodę jako egzemplarz java.security.Principal. Jeśli nie przypisano praw wykonania metody, metoda zwróci reprezentację kontenera odpowiadającą domyślnej tożsamości nieuwierzytelnionej.

Metoda getCallerPrincipal() zwraca tożsamość, która reprezentuje wywołującego ziarno, a nie tożsamość określoną przez run-as (jeśli zdefiniowano).

Głównym celem isCallerInRole(String roleName) jest udostępnienie dostawcy ziarna możliwości przeprowadzenia kontroli wykonania, która nie może być wyrażona deklaratywnie w deskryptorze wdrożenia za pomocą prawa wykonania metody.

Metoda biznesowa ziarna może użyć isCallerInRole(String roleName) do sprawdzenia, czy bieżąca tożsamość wywołującego metodę ziarna posiada daną rolę bezpieczeństwa. Role bezpieczeństwa są zdefiniowane przez Dostawcę lub Składającego i są przypisywane do właściwych osobowości i ich grup w środowisku uruchomieniowym przez Wdrażającego.

isCallerInRole(String roleName) sprawdza osobowość reprezentującego faktycznie wywołującego ziarno, a nie osobowość, która odpowiada tożsamości z run-as dla ziarna (jeśli określono).

Dostawca jest zobowiązany zadeklarować wykorzystywane nazwy ról w kodzie ziarna za pomocą adnotacji @DeclareRoles lub security-role-ref w deskryptorze wdrożenia. Adnotacja @DeclareRoles jest umieszczana na poziomie klasy ziarna i określa nazwy ról, które są wykorzystywane w ziarnie jako parametry wejściowe do metody isCallerInRole(String roleName). Deklaracja nazw ról pozwala Dostawcy, Składającemu lub Wdrażającemu związać je z rolami zdefiniowanymi w aplikacji. W przypadku braku deklaracji za pomocą adnotacji lub deskryptora zakłada się, że wykorzystywane nazwy ról odpowiadają rolom w aplikacji.

Dostawca deklaruje role wykorzystywane w kodzie ziarna korzystając z adnotacji @DeclareRoles. Nazwa roli musi odpowiadać tej wykorzystywanej jako parametr wejściowy dla isCallerInRole(String roleName). Dostawca ziarna może opisać rolę poprzez opcjonalny element description adnotacji @DeclareRoles.

Jeśli @DeclareRoles nie jest w użyciu, dostawca ziarna musi użyc security-role-ref w deskryptorze, aby zadeklarować role wykorzystywane w kodzie ziarna.

Elementy security-role-ref:
  • role-name - nazwa roli jaką podano w parametrze metody isCallerInRole(String roleName)
  • description - opcjonalny element do opisu roli
security-role-ref, włączajac nazwę zdefiniowana przez referencję, jest ograniczone do ziarna, które zawiera adnotację @DeclareRoles lub którego deskryptor wdrożenia zawiera element security-role-ref.

Dostawca lub Składający mogą również użyć security-role-ref dla tych referencji ról, które zostały zadeklarowane w adnotacjach, a które dostawca ziarna chciałby związać z security-role, której nazwa różni się od zaproponowanej. Jeśli referencja roli nie jest powiązana do roli w ten sposób, kontener EJB musi związać nazwę referencji do roli o tej samej nazwie.

17.3 Obowiązki Dostawcy oraz Składającego (w kontekście bezpieczeństwa)


Dostawca lub Składający mogą zdefiniować opcjonalny "widok bezpieczeństwa" ziaren w ramach deskryptora wdrożenia.

Głównym powodem utworzenia "widoku bezpieczeństwa" jest uproszczenie zadania Wdrażającemu. W przeciwnym przypadku musiałby istnieć inny sposób deklarowania/określania wymagań przez Dostawcę i Składającego dla Wdrażającego. Za pomocą "widoku" Wdrażający może wykonać swoje zadanie zabezpieczenia aplikacji bez konieczności poznawania innych mechanizmów czy zapoznawania sie z innymi materiałami związanymi z aplikacją, a jedynie poznać skonsolidowany widok wszystkich wymagań bezpieczeństwa aplikacji w deskryptorze wdrożenia.

"Widok bezpieczeństwa" składa się z ról bezpieczeństwa. Rola jest zbiorem uprawnień, które nadawane sa użytkownikowi, aby mógł on poprawnie korzystać z aplikacji.

Dostawca lub Składający określa uprawnienia metod dla każdej z roli. Uprawnienie metody jest uprawnieniem, aby wykonać określoną grupę metod ziarna.

"Widok bezpieczeństwa" jest jedynie logicznym spojrzeniem na wymagania bezpieczeństwa aplikacji i nie powinno być mylone z ich odpowiednikami w środowisku uruchomieniowym jakim jest serwer aplikacyjny - użytkownik, grupa użytkowników, osobowości.

Dostawca lub Składający (opcjonalnie) definiuje jedną lub więcej ról bezpieczeństwa za pomocą adnotacji lub deskryptora. Dostawca lub Składający przypisują metody (biznesowe, domowe, komponentu i usługi sieciowej) do ról, aby zdefiniować "widok bezpieczeństwa". Brak "widoku bezpieczeństwa" jest określeniem braku specyficznych wymagań związanych z bezpieczeństwem aplikacji dla Wdrażającego.

Ze względu na (możliwą) nieznajomość środowiska uruchomieniowego (serwera aplikacyjnego), role bezpieczeństwa są jedynie logicznymi bytami (aktorami), każdy reprezentujący typ użytkownika, który powinien mieć określone uprawnienia.

Wdrażający przypisuje (mapuje) grupy i pojedyńczych użytkowników ze środowiska uruchomieniowego do ról bezpieczeństwa zdefiniowanych przez Dostawcę lub Składającego.

W przypadku stosowania adnotacji, dostawca ziarna używa @DeclareRoles i @RolesAllowed w celu zdefiniowania ról bezpieczeństwa. Zbiór ról bezpieczeństwa używanych przez aplikację jest zbiorem ról bezpieczeństwa zdefiniowanych przez adnotacje @DeclareRoles i @RolesAllowed. Dostawca ziarna moze zmodyfikować zbiór ról bezpieczeństwa przez element deskryptora wdrożenia - security-role.

W przypadku zastosowania deskryptora, Dostawca lub Składający używa elementu security-role w następujący sposób:
  • Definiuje rolę bezpieczeństwa za pomocą elementu security-role
  • Opcjonalnie, opisuje rolę w elemencie description
  • Wykorzystuje role-name, aby zdefiniować nazwę roli
Wszystko w ramach elementu assembly-descriptor w deskryptorze.

Poza zdefiniowaniem ról bezpieczeństwa przez Dostawcę lub Składającego, moga oni również określić metody interfejsów biznesowego, domowego i komponentu oraz usług sieciowych, które mogą zostać wykonane przez daną rolę. W tym celu stosują adnotacje lub deskryptor wdrożenia.

Prawo wykonania metod może zostać określone za pomocą adnotacji na poziomie klasy, metody biznesowej lub obu. W tym celu korzysta się z adnotacji @RolesAllowed, @PermitAll oraz @DenyAll.

Wartością @RolesAllowed jest lista (logicznych) nazw ról bezpieczeństwa uprawnionych do wykonania danej metody. Adnotacja @PermitAll określa, że wszystkie role są uprawnione do wykonania metody, podczas gdy @DenyAll jest jej przeciwieństwem (żadna z ról bezpieczeństwa nie ma prawa wykonania wybranej metody).

Określenie @RolesAllowed oraz @PermitAll na poziomie klasy dotyczy wszystkich metod biznesowych ziarna.

Prawo wykonania metody określone na poziomie metody nadpisuje prawa określone na poziomie klasy.

Jesli klasa ziarna ma nadklasy, następujące reguły zachodza:
  • Prawo wykonania określone dla nadklasy S dotyczy metod biznesowych klasy S
  • Prawo wykonania może zostać określone dla metody biznesowej M zdefiniowanej przez klasę S, co przesłoni uprawnienia zdefiniowane dla tej metody wprost bądź niewprost przez klasę S.
  • Jeśli metoda M klasy S przesłania metodę biznesowa zdefiniowaną przez nadklasę S, prawa wykonania dla M są wyznaczone przez reguły opisane powyżej jakby dotyczyły klasy S.
Dostawca może użyć deskryptora wdrożenia jako alternatywę dla adnotacji lub jako sposób uzupełnienia/nadpisania praw z adnotacji. Składający ma prawo nadpisania praw za pomocą deskryptora.

Prawa określone w deskryptorze nadpisują odpowiadajace im prawa z adnotacji. Brak określenia praw w deskryptorze, oznacza uprawomocnienie praw z adnotacji. Ziarnistość reguł nadpisywania jest do poziomu metod.

Dostawca lub Składający definiują prawa metod w deskryptorze za pomocą elementu method-permission.
  • Każdy element method-permission składa się z listy ról bezpieczeństwa i uprawnionych do wykonania metod. Wymienione role mają prawo wykonać wszystkie z wymienionych metod. Każda z ról określona jest przez element role-name, a każda metoda (zbiór metod) jest identyfikowana przez element method. Opcjonalnie możemy opisać method-permission poprzez element description.
  • Relacja praw wykonania metod jest zdefiniowana jako złożenie wszystkich uprawnień wykonania zdefiniowanych w poszczególnych elementach method-permission.
  • Wybrana rola bezpieczeństwa lub metoda mogą wystąpić w wielu elementach method-permission.
Dostawca lub Składający mogą wskazać, że wszystkie role są uprawnione do wykonania pojedyńczej lub wielu określonych metod, tj. dostęp do metod nie powinien wiązać się z uwierzytelnieniem przed wykonaniem przez kontener. Element unchecked podany zamiast nazwy roli w elemencie method-permission wskazuje, że wszystkie role są dozwolone.

Jeśli prawo wykonania dla danej metody określono jako unchecked oraz jednocześnie związano z rolą, wszystkie role są uprawnione do wykonania metody.

Element exclude-list może być użyty, aby określić listę metod, które nie powinny być wywołane. Wdrażający powinien tak skonfigurować bezpieczeństwo ziarna, aby żaden dostęp nie był dozwolony do metod zawartych na liście exclude-list.

Jeśli podana metoda znajduje się w exclude-list oraz występuje w innym opisie prawa wykonania, Wdrażający powinien tak skonfigurować bezpieczeństwo ziarna, że żadne wykonanie metody nie będzie dozwolone.

Element method zawiera elementy ejb-name, method-name oraz method-params w celu opisania metod ziarna. Istnieją trzy sposoby opisu metody przez element method:
  • ejb-name określony z method-name=* - dotyczy wszystkich metod ziarna
  • ejb-name określony z method-name=<nazwa metody> - dotyczy wszystkich metod ziarna o podanej nazwie bez względu na liczbę parametrów
  • ejb-name określony, method-name=<nazwa metody> z method-params - określona metoda o zadanej nazwie i liczbie parametrów
Opcjonalny element method-intf wskazuje na metodę z zadaną sygnaturą, jednakże określoną przez wybrany interfejs biznesowy, domowy, komponentu i usługi sieciowej.

Może się zdarzyć, że role bezpieczeństwa nie zostały przypisane do wybranej metody ani nie określono dla niej @DenyAll czy nie zawarto jej w liście exclude-list. W takim przypadku, Wdrażający powinien przypisać wszystkie nieprzypisane metody do pewnej roli lub po prostu oznaczając je jako unchecked. Brak przypisania uprawnienia do wybranej metody powoduje, że ich wykonanie jest unchecked.

Referencje ról bezpieczeństwa używane w komponentach aplikacji są wiązane z rolami bezpieczeństwa zdefiniowanych dla całej aplikacji. Przy braku jakiegokolwiek jawnego wiązania, referencje ról bezpieczeństwa będą związane z rolami bezpieczeństwa o tej samej nazwie.

Składający może jawnie określić wiązanie wszystkich referencji ról bezpieczeństwa zadeklarowanych przez @DeclareRoles lub element security-role-ref dla komponentu do ról bezpieczeństwa zdefiniowanych przez adnotacje i/lub w elementach security-role.

Składający wiąże każdą referencję roli bezpieczeństwa do roli bezpieczeństwa korzystając z elementu role-link. Wartością role-link musi być nazwa jednej z ról bezpieczeństwa zdefiniowanej w elemencie security-role lub za pomocą adnotacji @DeclareRoles lub @RolesAllowed, jednakże nie potrzebuje być określona, kiedy role-name używane w kodzie jest takie samo jak security-role (które ma zostać związane).

Dostawca lub Składający zazwyczaj określają czy tożsamość wywołującego powinna być użyta jako bieżąca podczas wywoływania metod ziarna lub czy w zamian nie powinna być podmieniana na określoną tożsamość zdefiniowaną przez run-as.

Domyślnie, tożsamość wywołującego jest przekazywana do ziarna. Dostawca może użyć adnotacji @RunAs, aby określić tożsamość run-as dla wykonania metod ziarna. Zdefiniowanie tożsamości wykonującego przez element security-identity w deskryptorze wdrożenia nadpisuje tożsamość wykonującego bądź tą określoną przez adnotację @RunAs. Wartością elementu security-identity jest use-caller-identity lub run-as.

Dostawca może użyć adnotacji @RunAs lub dostawca ziarna oraz Składający aplikację mogą użyć elementu run-as w deskryptorze wdrożenia celem przypisania tożsamości run-as dla wywoływania metod ziarna. Tożsamość run-as dotyczy ziarna EJB w całości, tj. wszystkie metody interfejsu biznesowego, domowego, komponentu oraz usługi sieciowej oraz metoda obsługi komunikatów ziarna MDB, metoda zwrotna timeout i wszystkich wewnętrznych metod ziarna, które mogą z kolei zostać wywołane przez nie.

Określenie tożsamości run-as nie zmienia tożsamości wywołującego, które są sprawdzane celem zweryfikowania prawa wykonania, a jedynie przesłania ją (ustanawia jako tymczasową tożsamość bieżącą), która będzie następnie bieżącą podczas pracy ziarna.

Nazwa określona przez dostawcę i składającego jako tożsamość run-as jest logiczną nazwą roli bezpieczeństwa i odpowiada roli bezpieczeństwa zdefiniowaną przez dostawcę i składającego w adnotacjach bezpieczeństwa lub deskryptorze wdrożenia.

Wdrażający przypisuje osobowość bezpieczeństwa określoną w środowisku wykonawczym, która będzie wykorzystywana jako odpowiednik logicznej tożsamości run-as. Osobowość przypisana przez wdrażającego będzie odpowiadała roli bezpieczeństwa określoną przez adnotację @RunAs lub przez element role-name w elemencie run-as w deskryptorze.

Dostawca i składający odpowiedzialni są za następujące zadania podczas określania tożsamości run-as:
  • Użycie adnotacji @RunAs lub elementu role-name w deskryptorze, aby określić nazwę roli bezpieczeństwa
  • Opcjonalnie, w elemencie description, opisać wymaganie stawiane roli, która powinna być przypisana do tożsamości run-as ze względu na jej uprawnienia

17.4 Obowiązki Wdrażającego


Wdrażający odpowiada za zapewnienie, że aplikacja działa w środowisku gwarantującym jej bezpieczne użytkowanie po wdrożeniu w docelowym środowisku uruchomieniowym.

Wdrażający korzysta z narzędzi dostarczanych przez środowisko uruchomieniowe (serwer aplikacyjny) do odczytania "widoku bezpieczeństwa" aplikacji dostarczonej przez Dostawcę i Składającego. "Widok" oparty jest o informacje zapisane w adnotacjach i deskryptorze. Zadaniem Wdrażającego jest odzwierciedlenie "widoku bezpieczeństwa" na odpowiednie mechanizmy i polityki bezpieczeństwa używane przez docelowe środowisko uruchomieniowe. Wynikiem prac Wdrażającego jest deskryptor z polityką bezpieczeństwa aplikacji, która jest specyficzna dla środowiska uruchomieniowego. Format i zawartość deskryptora jest specyficzny dla środowiska uruchomieniowego.

Wdrażający przypisuje osobowości i/lub grup osobowości (pojedyńczy użytkownicy i grupy) określone w środowisku wykonawczym do ról bezpieczeństwa zdefiniowanych za pomocą adnotacji @DeclaredRoles i @RolesAllowed oraz elementów security-role w deskryptorze wdrożenia.

Wdrażający nie przypisuje osobowości i/lub grup osobowości do referencji ról bezpieczeństwa - osobowości i ich grupy przypisane do ról bezpieczeństwa dotyczą również wszystkich przypisanych referencji ról bezpieczeństwa.

Proces przypisywania logicznych nazw ról bezpieczeństwa zdefiniowanych w deskryptorze lub adnotacjach jest specyficzny dla środowiska uruchomieniowego. Zazwyczaj wdrażanie aplikacji polega na przypisaniu ról bezpieczeństwa do grup użytkowników (osobowości) dostępnych w środowisku uruchomieniowym. Przypisanie jest wykonywne per aplikacja. Te same nazwy ról bezpieczeństwa w różnych aplikacjach korporacyjnych mogą być przypisane inaczej. Jeśli logiczna nazwa roli nie zostanie przypisana do odpowiadających bytów w środowisku uruchomieniowym, zakłada się, że logiczna nazwa roli odpowiada osobowości lub grupie osobowości o tej samej nazwie w środowisku uruchomieniowym.

Wdrażający zobowiązany jest do konfiguracji delegacji uprawnień osobowości wykorzystywanej do komunikacji między elementami aplikacji. Wdrażający musi przestrzegać zasad opisanych przez Dostawcę lub Składającego w opcjonalnym elemencie description adnotacji @RunAs lub elemencie run-as w deskryptorze.

Jeśli tożsamość bezpieczeństwa jest domyślna, lub jest wymieniona expicite, że tożsamość wywołującego będzie w użyciu, np. poprzez użycie use-caller-identity w deskryptorze, osobowość wywołującego jest przekazana z jednego elementu aplikacji do wywoływanego. Podobnie jest z określeniem tożsamości run-as, która będzie przekazywana podczas wywołań i musi skonfigurować jej osobowość.

17.5 Wymagania stawiane klientowi EJB


Klient transakcyjny nie może zmienić swojej tożsamości w ramach transakcji. Dzięki temu zapewniono, że wszystkie wywołania od klienta w ramach pojedyńczej transakcji są wykonywane pod auspicjami tego samego kontekstu bezpieczeństwa.

Klient ziarna sesyjnego nie może zmienić swojej tożsamości podczas trwania komunikacji z obiektem sesyjnym. Dzięki temu zapewnia się, że serwer może przypisać tożsamość z instancją ziarna podczas jego tworzenia i nie zmieniać jej przez cały czas jej istnienia.

Jeśli żądania transakcyjne nadejdą od wielu klientów w ramach pojedyńczej transakcji, wszystkie żądania muszą być wykonane pod auspicjami tej samej tożsamości.

17.6.5. Metody dotyczące bezpieczeństwa w javax.ejb.EJBContext


Kontener EJB musi udostępniać do informacji o kontekście bezpieczeństwa wywołującego z egzemplarzy ziaren przez metody getCallerPrincipal() oraz isCallerInRole(String rolename). Kontener musi zapewnić, że wywołanie metod biznesowych, domowych, komponentu, usługi sieciowej, metody obsługi komunikatów lub metod TimedObject są związane z pewną osobowością. Jeśli nie określono tożsamości wywołującego, kontener zobowiązany jest przypisać wywołanie do własnej reprezentacji tożsamości nieuwierzytelnionej. Kontener nie może w żadnym momencie zwrócić null z wywołania metody getCallerPrincipal().

Kontener EJB umożliwia wykonanie metody wtw jeśli metoda określona jest jako @PermitAll lub tożsamość wywołującego przypisana jest do co najmniej jednej z roli, która związana jest z prawem wykonania dla tej metody. Wystarczy, że tożsamość wywołującego przypisana jest do przynajmniej jednej roli. Jeśli kontener zabroni wykonania metody zostanie zgłoszony wyjątek javax.ejb.EJBAccessException. W przypadku użycia interfejsu EJB 2.1, kontener zgłosi java.rmi.RemoteException lub jego podklasę java.rmi.AccessException dla klientów zdalnych, a javax.ejb.EJBException lub podklasę javax.ejb.AccessLocalException dla klientów lokalnych.

20 lutego 2008

Sesja i przekierowanie żądania w Wicket

4 komentarzy
Czytanie książek informatycznych zaczyna ponownie sprawiać mi przyjemność. Kilkakrotne czytanie tego samego rozdziału i wertowanie kartek w tą i z powrotem w poszukiwaniu odpowiedzi tylko mnie w tym utrwala. Zauważyłem, że lektura Pro Wicket wydawnictwa Manning, jakkolwiek dotyczy wcześniejszych wersji Apache Wicket, to mimo wszystko napisana jest bardzo przyzwoicie. Gdzie tam przyzwoicie?! Czytam rozdział 3 Developing a Simple Application już bodajże z 10 raz i zawsze coś ciekawego odnajduję. Autor książki - Karthik Gurumurthy - wykonał świetną robotę.

Wciąż nie mogę przestać porównywać Wicketa z JSF. Z jednej strony podoba mi się zwięzłość kontrolek JSF a z drugiej przyciąga prostota Wicketa, która objawia się możliwością tworzenia stron w Javie. Tak, nie jest to najprzyjemniejsze, biorąc pod uwagę, co możnaby jeszcze uprościć w tej kwestii patrząc na GWT, ale to, co oferuje Wicket, zaczyna mnie przyciągać ku niemu coraz silniej. JSF to standard, część Korporacyjnej Javy, więc nie sposób go nie znać, ale Wicket rulez.

Od kilku dni "trawię" obsługę sesji w Wicket. Niby nic nadzwyczajnego, a jednak żadne ze znanych mi szkieletów aplikacyjnych do tej kwestii nie podeszło w ten sposób (przypominam, że nie znam Tapestry, a o nim się pisze jako o najbliższym krewnym). Na czym polega fenomen obsługi sesji w Wicket? Na silnym typowaniu obiektów przechowywanych w sesji bez bezpośredniego dostępu do obiektu javax.servlet.http.HttpSession znanego każdemu tworzącemu aplikacje webowe oparte o JSP i serwlety. To tak, jakby dodać typy generyczne do HttpSession.
package pl.jaceklaskowski.wicket;

import org.apache.wicket.Request;
import org.apache.wicket.protocol.http.WebSession;

import pl.jaceklaskowski.wicket.entities.Osoba;

public class WicketDemoSession extends WebSession {

private static final long serialVersionUID = 1L;

private Osoba osoba;

public WicketDemoSession(Request request) {
super(request);
}

public Osoba getOsoba() {
return osoba;
}

public void setOsoba(Osoba osoba) {
this.osoba = osoba;
}

}
pl.jaceklaskowski.wicket.WicketDemoSession to typ reprezentujący sesję w mojej aplikacji opartej o Wicket. Klasa koniecznie musi rozszerzać typ org.apache.wicket.protocol.http.WebSession. Zalecane jest udostępnienie konstruktora z pojedyńczym parametrem reprezentującym bieżące żądanie (typ org.apache.wicket.Request) a pozostałe "rzeczy" są już nasze. Skoro chciałem przechowywać egzemplarz typu Osoba w sesji udostępniłem metodę zapisu i odczytu dla tego typu. I tyle! Chcemy przechowywać więcej w sesji, dodajemy kolejne atrybuty do własnej klasy reprezentującej sesję.

Aktywacja naszej sesji polega na nadpisaniu metody public Session newSession(Request request, Response response) w klasie aplikacji.
package pl.jaceklaskowski.wicket;

...

public class WicketDemoApplication extends WebApplication {

...

@Override
public Session newSession(Request request, Response response) {
return new WicketDemoSession(request);
}
}
I tyle. Użycie sesji to skorzystanie z pomocy org.apache.wicket.Component.getSession(), która sprowadza się do wywołania org.apache.wicket.Session.get(), która z kolei pobiera aktualną sesję z bieżącego wątku (robiąc jeszcze poboczne sprawy). Możliwości dostania się do sesji jest więc kilka, a zwracany obiekt jest "naszego" typu.
package pl.jaceklaskowski.wicket;

import org.apache.log4j.Logger;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RequiredTextField;
import org.apache.wicket.model.CompoundPropertyModel;

import pl.jaceklaskowski.wicket.entities.Osoba;

public class PrzedstawSie extends WebPage {

private static final long serialVersionUID = 1L;

private transient Logger logger = Logger.getLogger(PrzedstawSie.class.getName());

public PrzedstawSie(final PageParameters params) {
CompoundPropertyModel model = new CompoundPropertyModel(new Osoba());
Form loginForm = new Form("dane", model) {
private static final long serialVersionUID = 1L;

protected void onSubmit() {
Osoba osoba = (Osoba) getModel().getObject();
logger.info(osoba);
((WicketDemoSession) getSession()).setOsoba(osoba);
if (!continueToOriginalDestination()) {
setResponsePage(new Powitanie(getModelObjectAsString()));
}
}
};
RequiredTextField imie = new RequiredTextField("login");
loginForm.add(imie);
add(loginForm);
}
}
Na uwagę zasługuje linia ((WicketDemoSession) getSession()).setOsoba(osoba);, gdzie pobieram sesję i ustawiam w niej egzemplarz typu Osoba. Przy okazji tworzenia tej nowej strony w aplikacji musiałem użyć słowa kluczowego transient, które było moim pierwszym jego użyciem. To też zaliczam jako plus dla Wicketa.

Baczne oko zauważy użycie metody public boolean org.apache.wicket.Component.continueToOriginalDestination(), która zwróci true, jeśli przerwaliśmy wcześniej obsługę żądania (przepływ) za pomocą org.apache.wicket.RestartResponseAtInterceptPageException. Jego zgłoszenie powoduje przerwanie obsługi bieżącego żądania i przekierowanie do wskazanej w konstruktorze wyjątku strony. Najbardziej naturalne użycie tego rodzaju przerwania to obsługa uwierzytelnienia użytkownika.
package pl.jaceklaskowski.wicket;

import java.util.Arrays;

import org.apache.log4j.Logger;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RequiredTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;

import pl.jaceklaskowski.wicket.entities.Osoba;

public class DaneOsobowe extends WebPage {

private static final long serialVersionUID = 1L;

private transient Logger logger = Logger.getLogger(DaneOsobowe.class.getName());

public DaneOsobowe(PageParameters params) {
// pobranie danych z sesji
WicketDemoSession session = (WicketDemoSession) getSession();
Osoba osoba = session.getOsoba();
if (osoba == null) {
throw new RestartResponseAtInterceptPageException(PrzedstawSie.class);
}
logger.info("Dane osoby: " + osoba);
CompoundPropertyModel model = new CompoundPropertyModel(osoba);
Form loginForm = new Form("daneOsobowe", model) {
private static final long serialVersionUID = 1L;

protected void onSubmit()
{
// TODO: miejsce dla JPA
Osoba osoba = (Osoba) getModel().getObject();
logger.info(osoba);
((WicketDemoSession) getSession()).setOsoba(osoba);
setResponsePage(new Powitanie(getModelObjectAsString()));
}
};
TextField imie = new TextField("imie");
// określenie pola obowiązkowego
imie.setRequired(true);
loginForm.add(imie);
// pomocnicza klasa wykonująca setRequired(true)
TextField nazwisko = new RequiredTextField("nazwisko");
loginForm.add(nazwisko);
TextField login = new RequiredTextField("login");
login.setRequired(true);
loginForm.add(login);
// lista rozwijalna z danymi z Osoba.getMiejscowosci (model formularza to Osoba)
DropDownChoice choice = new DropDownChoice("miejscowosc",
Arrays.asList(new String[] { "Warszawa", "Krak\u00f3w",
"Wroc\u0142aw", "Pozna\u0144", "Szczecin", "Gda\u0144sk" })) {

private static final long serialVersionUID = 1L;

// wyślij wybór z listy po zmianie wyboru do serwera
@Override
protected boolean wantOnSelectionChangedNotifications() {
return true;
}

protected void onSelectionChanged(final Object newSelection)
{
System.out.println("Miejscowosc: " + newSelection);
}
};
choice.setRequired(true);
loginForm.add(choice);
add(loginForm);
// panel komunikatów
add(new FeedbackPanel("komunikaty"));
}
}
Jeśli wywołanie strony DaneOsobowe nie będzie związane z egzemplarzem Osoba w sesji nastąpi przerwanie do klasy-strony PrzedstawSie, w której po pomyślnym zatwierdzeniu formularza umieszczam wymagany obiekt i wykonanie wspomnianej metody continueToOriginalDestination() spowoduje powrót do wykonania poprzednio przerwanej strony - powraca do przerwanego wątku. Jeśli wywołana zostanie strona PrzedstawSie bezpośrednio, metoda continueToOriginalDestination() zwróci false i nastąpi przekierowanie do strony Powitanie. Proste, nieprawdaż?