30 marca 2008

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

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

Temat prezentacji: Java w zastosowaniach bazodanowych
Prowadzący: Piotr Pietrzak

Celem spotkania jest przedstawienie Javy jako języka procedur składowanych. Wszyscy, którzy nie mieli jeszcze okazji zetknąć się z tą technologią dowiedzą się, w jaki sposób można wykorzystać swoje umiejętności programowania w Javie do tworzenia procedur składowanych. Zostaną przedstawione podobieństwa i różnice istniejących implementacji w wykonaniu Oracle i Postgresql, oraz porównać procedury składowane do EJB.

Przewiduje się dyskusję na temat Javy jako języka procedur składowanych - wad i zalet tego podejścia.

Piotr Pietrzak jest absolwentem Politechniki Warszawskiej, doktorantem Szkoły Głównej Handlowej i pracownikiem Działu Aplikacji Komputerowych (DAK) Uniwersytetu Warszawskiego, prowadzi wykłady z zakresu Matematyki i Informatyki, oraz tworzy, rozwija i wdraża oprogramowanie w DAK. Zainteresowany głównie Javą, bazami danych, EJB, statystyką, ekonomią, ekonometrią i gokartami.

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

Wstęp wolny!

Zapraszam w imieniu Piotra i grupy Warszawa JUG!

29 marca 2008

Trochę lektury o JRuby on Rails z GlassFish i powrót do OSGi - rozdział 4.3 Obiekt Bundle

0 komentarzy
Przeczytałem wczoraj artykuł dotyczący JRuby on Rails z GlassFishem - Rails Powered by the GlassFish Application Server i tylko niepotrzebnie się rozgrzałem. Artykuł wyśmienicie przedstawił zalety zastosowania serwera aplikacji Java EE - GlassFish, aby udostępnić funkcjonalność niedostępną w typowych rozwiązaniach RoR bez wyrafinowanych "sztuczek" administracyjnych - klastrowanie, współdzielenie zasobów, prostsza administracja, w tym aktualizacja aplikacji, możliwość uruchomienia wielu aplikacji RoR na jednej instancji serwera Java EE. Co niestety mnie zmartwiło, to, że kiedy tylko już skończyłem "wstęp" i już nie mogłem doczekać się przykładu z wykorzystaniem usługi oferowanej przez GlassFisha, okazało się, że to, co postrzegałem jako "wstęp" faktycznie było całym artykułem! I po co mi to było?! Tylko się człowiek niepotrzebnie napalił. Nie zamierzam jeszcze rozpoznawać tematu samodzielnie, ale ciekawym czy ktoś z czytających ten wpis ma doświadczenie w tego typu instalacjach, gdzie korzysta się z serwera aplikacji Java EE (zapewne GlassFish jako gem) i korzysta z jego usług w aplikacji RoR. Może jest tak, że osoby z kręgu RoR nie widzą potrzeby zastosowania serwera aplikacji Java EE. Dlaczego? Może chociaż ktoś rozważa taką integrację? Kiedyś pewnie i samemu spróbuję, ale na razie zawieszam temat. Oczywiście artykuł warty uwagi, chociaż należy podejść ze swoimi oczekiwaniami bardzo skromnie. Uaktualniłem w międzyczasie JRuby i popróbowałem się z kilkoma poleceniami, ale zdecydowanie za mało dla wymagających. Na zakończenie artykułu pozwoliłem sobie na skomentowanie go i wysłanie uwag do autorów. Nieoczekiwanie już dzisiaj dostałem od jednego z autorów - Ricka Palkovica - odpowiedź:
 Thanks for your comments on our article "Rails Powered by the GlassFish Application Server."

As you noted, the article really focuses on the use of GlassFish for development and deployment
of JRuby applications, not on how to use JRuby. For information about JRuby on Rails and using
it with a Java code, you might want to read:

"JRuby and the Java Platform" by Monica Powlan
(http://java.sun.com/developer/technicalArticles/scripting/jruby/index.html)

or the JRuby tutorials at http://jruby.codehaus.org/Tutorials

If you're a NetBeans user, you also might want to look at
"How to Work with JRuby in the NetBeans IDE"
(http://wiki.netbeans.org/NetbeansedJRuby)

Also, check out Arun's blog, where he often gives examples with JRuby and NetBeans.
(http://blogs.sun.com/arungupta)
Also, his webcasts, for example "First JRuby App in GlassFish:
(http://download.java.net/javaee5/screencasts/jruby-in-glassfish/)

Hope this helps,
-- Rick
Pewnie, że "helps", ale nie chodziło zupełnie o samo użycie JRuby, ale raczej o jego integrację z Java EE 5, czyli stworzenie aplikacji w ten sposób, że jej część będzie bazowała na RoR, a część na Korporacyjnej Javie. Odkładam temat na (przysłowiowe) później.

Jakby tego było mało lektura Java is losing the battle for the modern Web. Can the JVM save the vendors? dała na zakończenie dużo do myślenia w kontekście przyszłego rozpoznawania tematu JRuby, Ruby on Rails i GlassFish. Nie zamierzam streszczać artykułu tutaj i zachęcam do jego lektury. Wydaje się, że komuś ta Java już się przejadła. Ciekawe czasy.

Po krótkiej przerwie, wracam do OSGi i jego atrakcji. Pod młotek pójdzie rozdział 4.3 o obiekcie Bundle.

Każdy pakunek reprezentowany jest w środowisku uruchomieniowym OSGi przez swojego "partnera" w postaci obiektu org.osgi.framework.Bundle, który posiada unikatowy identyfikator typu long przypisany przez Platformę OSGi. Identyfikator pakunku jest niezmienny przez cały cykl rozwojowy pakunku (wliczając w to wszelkie operacje związane z jego stanem, w tym i jego aktualizację oraz ponowne uruchomienie środowiska OSGi). Odinstalowanie pakunku i jego ponowna instalacja skutkuje utworzeniem nowego identyfikatora. Identyfikatory pakunków są przydzielane w rosnącym porządku. Zarządzanie pakunkiem odbywa się właśnie poprzez obiekt Bundle zazwyczaj przez dedykowany przez Platformę OSGi pakunek zarządczy (Management Agent).

Poza identyfikatorem pakunku wyróżnia się unikatowy identyfikator położenia pakunku w Systemie, który jest przypisywany podczas instalacji i zazwyczaj interpretowany jest jako URL do pliku jar zawierającego pakunek (podkreśla się słowo zazwyczaj, gdyż nie jest to wymagane). Podobnie jak z identyfikatorem pakunku identyfikator położenia nie zmienia się w trakcie cyklu rozwojowego pakunku.

Symboliczna nazwa pakunku (ang. bundle symbolic name) jest nazwą określaną przez dostawcę pakunku poprzez nagłówek Bundle-SymbolicName. Para {wersja pakunku, nazwa symboliczna pakunku} jest unikatowa w ramach Systemu OSGi.

Metoda Bundle.getBundleId() zwraca identyfikator pakunku.

Metoda Bundle.getLocation() zwraca identyfikator położenia pakunku w Systemie.

Metoda Bundle.getSymbolicName() zwraca symboliczną nazwę pakunku, jaką przypisano przez jego dostawcę.

Pakunek może znajdować się w 6 stanach:
  • INSTALLED - pakunek poprawnie zainstalowany w Systemie
  • RESOLVED - wszystkie klasy Java wymagane przez pakunek są dostępne; pakunek jest gotowy do uruchomienia lub właśnie został zatrzymany
  • STARTING - pakunek jest uruchamiany i wywołanie metody start() aktywatora (BundleActivator.start()) jeszcze nie zakończyło się; wykorzystanie polityki uruchomienia może spowodować wstrzymanie pakunku w tym stanie (więcej o tym niebawem)
  • ACTIVE - pakunek został uruchomiony; wykonanie metody start() aktywatora zakończyło się poprawnie
  • STOPPING - pakunek jest zatrzymywany i wywołanie metody stop() aktywatora (BundleActivator.stop()) nie zakończyło się jeszcze
  • UNINSTALLED - pakunek usunięto z Systemu, niemożliwa zmiana stanu
Pakunek powinien wykonywać swoją pracę wyłącznie w stanie STARTING, ACTIVE lub STOPPING. Pakunek w stanie UNINSTALLED nie może zmienić swojego stanu (wymagana jest ponowna instalacja, co w kontekście nowego identyfikatora powoduje, że dla systemu jest to całkowicie inny pakunek). Zmiana stanu jest utrwalana przez Szkielet OSGi aż do odinstalowania (usunięcia) pakunku.

Metoda Bundle.getState() zwraca aktualny stan pakunku.

Jedynie Szkielet OSGi może powoływać obiekty Bundle do życia, które istnieją wyłącznie w ramach Platformy OSGi, który je powołał.

I na koniec krótki przykład dla utrwalenia wiedzy. Różnica między tym przykładem, a poprzednimi leży w dodaniu kilku System.out.println.
 package pl.jaceklaskowski.osgi;

import java.util.logging.Logger;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class AktywatorPakunku implements BundleActivator {

Logger log = Logger.getLogger(AktywatorPakunku.class.getName());

public void start(BundleContext bundleContext) throws Exception {
log.info("start() wykonano - rozpoczynam pracę");
Bundle bundle = bundleContext.getBundle();
long bundleId = bundle.getBundleId();
String bundleLocation = bundle.getLocation();
String bundleSymbolicName = bundle.getSymbolicName();
System.out.println("------------------------------------");
System.out.println("Charakterystyka aktywowanego pakunku:");
System.out.println(" Identyfikator: " + bundleId);
System.out.println(" Identyfikator położenia: " + bundleLocation);
System.out.println(" Nazwa symboliczna: " + bundleSymbolicName);
System.out.println("------------------------------------");
}

public void stop(BundleContext bundleContext) throws Exception {
log.info("stop() wykonano - czyszczę po sobie");
}

}
Polecenia są identyczne, za wyjątkiem wykonania aktualizacji po tym jak zmieniłem wcięcie w System.out.println.
 jlaskowski@dev /cygdrive/c/apps/felix
$ java -jar bin/felix.jar

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

Enter profile name: notatnik

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
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)
-> install file:c:/projs/osgi/spring-osgi-lifecycle/target/spring-osgi-lifecycle-1.0.jar
Bundle ID: 4
-> 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)
[ 4] [Installed ] [ 1] Spring OSGi Bundle (1.0)
-> start 4
DEBUG: WIRE: 4.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 4.0 -> pl.jaceklaskowski.osgi.impl -> 4.0
DEBUG: WIRE: 4.0 -> pl.jaceklaskowski.osgi -> 4.0
2008-03-27 13:21:03 pl.jaceklaskowski.osgi.AktywatorPakunku start
INFO: start() wykonano - rozpoczynam pracę
------------------------------------
Charakterystyka aktywowanego pakunku:
Identyfikator: 4
Identyfikator położenia: file:c:/projs/osgi/spring-osgi-lifecycle/target/spring-osgi-lifecycle-1.0.jar
Nazwa symboliczna: pl.jaceklaskowski.osgi.spring-osgi-lifecycle
------------------------------------
-> 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)
[ 4] [Active ] [ 1] Spring OSGi Bundle (1.0)
-> stop 4
2008-03-27 13:21:51 pl.jaceklaskowski.osgi.AktywatorPakunku stop
INFO: stop() wykonano - czyszczŕ po sobie
-> start 4
2008-03-27 13:22:03 pl.jaceklaskowski.osgi.AktywatorPakunku start
INFO: start() wykonano - rozpoczynam pracŕ
------------------------------------
Charakterystyka aktywowanego pakunku:
Identyfikator: 4
Identyfikator po-o¬enia: file:c:/projs/osgi/spring-osgi-lifecycle/target/spring-osgi-lifecycle-1.0.jar
Nazwa symboliczna: pl.jaceklaskowski.osgi.spring-osgi-lifecycle
------------------------------------
Tutaj następuje aktualizacja pakunku i sprawdzenie, czy wszystkie identyfikatory są jak poprzednio.
 -> update 4 file:c:/projs/osgi/spring-osgi-lifecycle/target/spring-osgi-lifecycle-1.0.jar
2008-03-27 13:22:50 pl.jaceklaskowski.osgi.AktywatorPakunku stop
INFO: stop() wykonano - czyszczę po sobie
DEBUG: WIRE: 4.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 4.0 -> pl.jaceklaskowski.osgi.impl -> 4.0
DEBUG: WIRE: 4.0 -> pl.jaceklaskowski.osgi -> 4.0
2008-03-27 13:22:50 pl.jaceklaskowski.osgi.AktywatorPakunku start
INFO: start() wykonano - rozpoczynam pracŕ
------------------------------------
Charakterystyka aktywowanego pakunku:
Identyfikator: 4
Identyfikator położenia: file:c:/projs/osgi/spring-osgi-lifecycle/target/spring-osgi-lifecycle-1.0.jar
Nazwa symboliczna: pl.jaceklaskowski.osgi.spring-osgi-lifecycle
------------------------------------
-> shutdown
-> 2008-03-27 13:23:05 pl.jaceklaskowski.osgi.AktywatorPakunku stop
INFO: stop() wykonano - czyszczę po sobie
Pytanie dla wytrwałych: Ile stanów jest w cyklu rozwojowym pakunku? oraz trochę trudniejsze - Jakie stany wyróżnia specyfikacja OSGi w cyklu rozwojowym pakunku? Nagród nie przewiduje się.

28 marca 2008

AJDT - AspectJ w Eclipse za Łukaszem

0 komentarzy
Łukasz Lipka przybrał szaty blogera serią artykułów o AspectJ z pomocą wtyczki do Eclipse - AJDT: AspectJ Development Tools - ApectJ wprowadzenie oraz wcześniej AOP wstęp.

Sprawdziłem własnoręcznie jak AJDT będzie sprawował się z moją wersją Eclipse 3.4M5 (a przyznam, że nie spowdziewałem się, że będzie lekko i wręcz oczekiwałem niespodzianek). Kiedyś już instalowałem wtyczkę AJDT, ale było to tak dawno, że nawet wyłączyłem ją w wersji 3.4M5.
Zacząłem od aktualizacji wtyczki. Poszła gładko, ale uruchomienie już nie. Utworzenie projektu AspectJ zakończyło się komunikatem błędu z NCDFE (!)

, gdzie w .log znalazłem następujący wyjątek NCDFE:
 !ENTRY org.eclipse.ui 4 0 2008-03-27 15:29:50.656
!MESSAGE Unhandled event loop exception
!STACK 0
java.lang.NoClassDefFoundError: org/eclipse/jdt/internal/ui/wizards/JavaProjectWizardFirstPage
at org.eclipse.ajdt.internal.ui.wizards.AspectJProjectWizard.addPages(AspectJProjectWizard.java:66)
at org.eclipse.jface.wizard.WizardSelectionPage.getNextPage(WizardSelectionPage.java:113)
at org.eclipse.ui.internal.dialogs.WorkbenchWizardSelectionPage.getNextPage(WorkbenchWizardSelectionPage.java:100)
at org.eclipse.jface.wizard.WizardDialog.nextPressed(WizardDialog.java:813)
at org.eclipse.jface.wizard.WizardDialog.buttonPressed(WizardDialog.java:369)
at org.eclipse.jface.dialogs.Dialog$2.widgetSelected(Dialog.java:623)
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:227)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:982)
at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:3760)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3371)
at org.eclipse.jface.window.Window.runEventLoop(Window.java:825)
at org.eclipse.jface.window.Window.open(Window.java:801)
at org.eclipse.ui.internal.handlers.WizardHandler$New.executeHandler(WizardHandler.java:260)
at org.eclipse.ui.internal.handlers.WizardHandler.execute(WizardHandler.java:280)
at org.eclipse.ui.internal.handlers.HandlerProxy.execute(HandlerProxy.java:252)
at org.eclipse.core.commands.Command.executeWithChecks(Command.java:475)
at org.eclipse.core.commands.ParameterizedCommand.executeWithChecks(ParameterizedCommand.java:470)
at org.eclipse.ui.internal.handlers.HandlerService.executeCommand(HandlerService.java:165)
at org.eclipse.ui.internal.keys.WorkbenchKeyboard.executeCommand(WorkbenchKeyboard.java:470)
at org.eclipse.ui.internal.keys.WorkbenchKeyboard.press(WorkbenchKeyboard.java:821)
at org.eclipse.ui.internal.keys.WorkbenchKeyboard.processKeyEvent(WorkbenchKeyboard.java:879)
at org.eclipse.ui.internal.keys.WorkbenchKeyboard.filterKeySequenceBindings(WorkbenchKeyboard.java:568)
at org.eclipse.ui.internal.keys.WorkbenchKeyboard.access$3(WorkbenchKeyboard.java:510)
at org.eclipse.ui.internal.keys.WorkbenchKeyboard$KeyDownFilter.handleEvent(WorkbenchKeyboard.java:126)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
at org.eclipse.swt.widgets.Display.filterEvent(Display.java:1160)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:981)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1006)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:991)
at org.eclipse.swt.widgets.Widget.sendKeyEvent(Widget.java:1019)
at org.eclipse.swt.widgets.Widget.sendKeyEvent(Widget.java:1015)
at org.eclipse.swt.widgets.Widget.wmChar(Widget.java:1331)
at org.eclipse.swt.widgets.Control.WM_CHAR(Control.java:3883)
at org.eclipse.swt.widgets.Tree.WM_CHAR(Tree.java:5637)
at org.eclipse.swt.widgets.Control.windowProc(Control.java:3776)
at org.eclipse.swt.widgets.Tree.windowProc(Tree.java:5633)
at org.eclipse.swt.widgets.Display.windowProc(Display.java:4473)
at org.eclipse.swt.internal.win32.OS.DispatchMessageW(Native Method)
at org.eclipse.swt.internal.win32.OS.DispatchMessage(OS.java:2333)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3369)
at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2392)
at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2356)
at org.eclipse.ui.internal.Workbench.access$4(Workbench.java:2222)
at org.eclipse.ui.internal.Workbench$4.run(Workbench.java:474)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:288)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:469)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149)
at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:106)
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:193)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:106)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:76)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:362)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:175)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:564)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:504)
at org.eclipse.equinox.launcher.Main.run(Main.java:1251)
at org.eclipse.equinox.launcher.Main.main(Main.java:1227)
Zmieniłem stronę z aktualizacjami wtyczki na zalecaną na stronie AJDT - http://download.eclipse.org/tools/ajdt/33/dev/update, co wcale nie rozwiązało problemu.

Wtyczka nie może zostać pobrana, a co dopiero mówić o jej uruchomieniu.

Pobrałem więc wersję rozwojową ajdt_1.5.2.200803241209_archive.zip ze strony Development builds i znowu NCDFE. Czyżby koniec mojego poznawania AspectJ z Eclipse 3.4M5?

Niezmęczony początkowymi trudnościami pozostało pogooglować (ajdt eclipse 3.4m5) i mam - [ajdt-dev] AJDT available for Eclipse 3.4, gdzie znalazłem informację, której potrzebowałem. Wystarczy zmienić adres z aktualizacjami dla wersji Eclipse 3.4M5 na http://download.eclipse.org/tools/ajdt/34/dev/update. Kiedy porównuję adresy - w pierwszym, gdzie miałem błąd jest 33/dev/update, podczas gdy w tym nowym jest już 34/dev/update.

Po instalacji, restarcie Eclipse, Ctrl+N i wybieram nowy projekt typu AspectJ Project. Wstrzymanie oddechu i...

Jest! Mogę kontynuować naukę AJDT jak opisał to Łukasz. Wszystko działa teraz bez zarzutu. Zapisałem się nawet na grupę AJDT, tak mnie te trudności zaintrygowały.

Czekam z niecierpliwością na kolejne wpisy Łukasza o AspectJ i AJDT.

26 marca 2008

Pakunki OSGi z pomocą Spring Dynamic Modules for OSGi

2 komentarzy
Ostatnia wpis Specyfikacja OSGi - rozdział 4. Warstwa rozwojowa - wprowadzenie dotyczył warstwy rozwojowej OSGi i utworzeniu pakunku, którego projektem zarządzał Apache Maven 2. Teraz dodatkowo wesprę się Spring Dynamic Modules for OSGi(tm) Service Platforms (dalej Spring DM) do uproszczenia samego oprogramowania pakunku. Szkieletu aplikacyjnego Spring Framework nie trzeba z pewnością przedstawiać nikomu, więc nie mam wątpliwości, że i w zakresie OSGi kilka konstrukcji upraszczających tworzenie pakunku w Springu się znajdzie.

Stworzę dokładnie taki sam pakunek jak poprzednio z tym, że teraz dorzucę Springa DM. Po co? Sam jeszcze nie wiem, ale właśnie w ten sposób mam zamiar się dowiedzieć. Spodziewam się, że cała maszyneria DI będzie dostępna za darmo, a to już sprawia, że jest warto.

Na stronach Spring DM zawarty jest przepis krok-po-kroku na pakunek OSGi - How to create a Spring bundle project in maven (and eclipse...), którym się wesprę.
 jlaskowski@dev /cygdrive/c/projs/osgi
$ mvn archetype:create \
-DarchetypeGroupId=org.springframework.osgi \
-DarchetypeArtifactId=spring-osgi-bundle-archetype \
-DarchetypeVersion=1.0 \
-DgroupId=pl.jaceklaskowski.osgi \
-DartifactId=spring-osgi-lifecycle \
-Dversion=1.0
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO] task-segment: [archetype:create] (aggregator-style)
[INFO] ------------------------------------------------------------------------
...
[INFO] OldArchetype created in dir: c:\projs\osgi\spring-osgi-lifecycle
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Jak przedstawiono we wspomnianym dokumencie, zalety wykorzystania Spring DM to m.in. automatyczne utworzenie testów integracyjnych i ich możliwość wykonania poza kontenerem OSGi.
 jlaskowski@dev /cygdrive/c/projs/osgi/spring-osgi-lifecycle
$ mvn package
[INFO] Scanning for projects...
Downloading: http://repo1.maven.org/maven2//org/apache/felix/maven-bundle-plugin/1.0.0/maven-bundle-plugin-1.0.0.pom
2K downloaded
Downloading: http://repo1.maven.org/maven2//org/apache/felix/maven-bundle-plugin/1.0.0/maven-bundle-plugin-1.0.0.jar
125K downloaded
[INFO] ------------------------------------------------------------------------
[INFO] Building Spring OSGi Bundle
[INFO] task-segment: [package]
[INFO] ------------------------------------------------------------------------
...
[INFO] [surefire:test]
[INFO] Surefire report directory: c:\projs\osgi\spring-osgi-lifecycle\target\surefire-reports

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running pl.jaceklaskowski.osgi.impl.BeanIntegrationTest
log4j:WARN No appenders could be found for logger (pl.jaceklaskowski.osgi.impl.BeanIntegrationTest).
log4j:WARN Please initialize the log4j system properly.
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.218 sec
Running pl.jaceklaskowski.osgi.impl.BeanImplTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
...
[INFO] [bundle:bundle]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Trochę trwało zanim wszystkie zależności zostały pobrane (zapewne nie wszystkie są konieczne), ale fakt faktem, że jakiś test się wykonał i nie mam pojęcia, czy wymagało to zestawienia środowiska OSGi, czy nie. BUILD SUCCESSFUL intryguje, aby iść dalej.

Pora zaimportować projekt do jakiegoś IDE. Kiedy zaimportowałem projekt do Eclipse 3.4M5 przy pomocy wtyczki m2eclipse, dostałem ostrzeżenie
 Cannot nest 'spring-osgi-lifecycle/src/main/resources/META-INF' inside 'spring-osgi-lifecycle/src/main/resources'. 
To enable the nesting exclude 'META-INF/' from 'spring-osgi-lifecycle/src/main/resources'
Usunąłem spring-osgi-lifecycle/src/main/resources/META-INF z Java Build Path, co "zniosło" komunikat.


Dodatkowo pojawiło się ostrzeżenie odnośnie zmiennej JUNIT_HOME, który rozwiązałem przez usunięcie JUNIT_HOME/junit.jar z zakładki Libraries. Dodałem w zamian bibliotekę JUnit 4 (Add Library > JUnit > JUnit 4).


Utworzenie projektu przy pomocy spring-osgi-bundle-archetype to dodatkowo utworzenie dwóch testów: jednostkowego dla wykonania ziarna (które Spring "ubiera" w szaty modułu OSGi podczas uruchomienia) i integracyjnego do zasymulowania pracy pakunku w środowisku OSGi.

Interfejs biznesowy utworzonego pakunku to pl.jaceklaskowski.osgi.Bean:
 package pl.jaceklaskowski.osgi;

public interface Bean {

boolean isABean();

}
a jego implementacja pl.jaceklaskowski.osgi.impl.BeanImpl jest następująca:
 package pl.jaceklaskowski.osgi.impl;

import pl.jaceklaskowski.osgi.Bean;

public class BeanImpl implements Bean {

public boolean isABean() {
return true;
}

}
W końcu, co nie było do tej pory przeze mnie podkreślane, to właśnie jest pakunek OSGi, czyli moduł udostępniający pewną usługę - interfejs i jego implementacja.

Konfiguracja Springa znajduje się w pliku src/main/resources/META-INF/spring/bundle-context.xml.
 <?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.xsd">

<!-- regular spring configuration file defining the beans for this
bundle. We've kept the osgi definitions in a separate
configuration file so that this file can easily be used
for integration testing outside of an OSGi environment -->

<bean name="myBean" class="pl.jaceklaskowski.osgi.impl.BeanImpl" />

</beans>
Nic szczególnego ponownie, bo i OSGi w swojej prostocie i łatwości wykorzystania jest właśnie takie. Można by powiedzieć nic szczególnego. To, co nadaje sens zastosowaniu OSGi w projektach to jego możliwości, które uaktywniają się podczas uruchomienia aplikacji opartej o pakunki OSGi, gdzie m.in. dostęp do zasobów jest dodatkowo zabezpieczony przez warstwę bezpieczeństwa OSGi. W przypadku "czystego" JVM dostęp do klas i interfejsów jest dozwolony, dla każdego, jeśli tylko klasa/interfejs znajduje się na ścieżce klas.

Dodaję do projektu pakunku jego aktywatora - klasę pl.jaceklaskowski.osgi.AktywatorPakunku, który prezentowałem w poprzedniej notatce o OSGi. Z jakiś nieznanych mi powodów domyślna konfiguracja projektu po wczytaniu do Eclipse nie dawała mi dostępu do klas OSGi i jedynym rozwiązaniem było wykonanie mvn eclipse:eclipse w katalogu projektu i F5 (Refresh) projektu w Eclipse. W ten sposób zostały dodane do projektu Eclipse biblioteki zależne jak poszukiwany (org.osgi.)org.osgi.core-4.0.jar.

Dodanie aktywatora wymusiło utworzenie konfiguracji pakunku w pom.xml w konfiguracji wtyczki maven-bundle-plugin o dodanie nagłówka Bundle-Activator. Warto zapamiętać.

Pora na sprawdzenie poprawności konstrukcji pakunku przez jego uruchomienie.

Uruchamiam środowisko OSGi, którym będzie Apache Felix.
 jlaskowski@dev /cygdrive/c/apps/felix
$ java -jar bin/felix.jar

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

Enter profile name: notatnik

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
Następnie sprawdzam dostępne pakunki w systemie.
 -> 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)
Instaluję właśniezbudowany pakunek z użyciem Springa...
 -> install file:c:/projs/osgi/spring-osgi-lifecycle/target/spring-osgi-lifecycle-1.0.jar
Bundle ID: 4
i uruchamiam go.
 -> start 4
DEBUG: WIRE: 4.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 4.0 -> pl.jaceklaskowski.osgi.impl -> 4.0
DEBUG: WIRE: 4.0 -> pl.jaceklaskowski.osgi -> 4.0
2008-03-26 13:40:48 pl.jaceklaskowski.osgi.AktywatorPakunku start
INFO: start() wykonano - rozpoczynam pracŕ
Teraz mogę zatrzymać pakunek poleceniem stop.
 -> stop 4
2008-03-26 13:40:53 pl.jaceklaskowski.osgi.AktywatorPakunku stop
INFO: stop() wykonano - czyszczŕ po sobie
Kolejne sprawdzenie, co mamy w systemie dostępnego (jakie pakunki są znane systemowi).
 -> 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)
[ 4] [Resolved ] [ 1] Spring OSGi Bundle (1.0)
Odinstalowanie pakunku o identyfikatorze 4, który jest moim pakunkiem stworzonym ze Springiem.
 -> uninstall 4
Weryfikacja, czy wszystko poszło jak należy (a raczej, czy wszystko wciąż rozumiem).
 -> 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)
I na koniec zamknięcie systemu - środowiska OSGi, którym jest Felix.
 -> shutdown
Wszystko poszło zgodnie z planem i użycie Springa DM było całkowicie bezinwazyjne. Oczywiście zakładam, że poznanie tego, co faktycznie integracja między Spring a OSGi w wykonaniu Spring DM dostarcza jest wciąż przede mną, ale o tym w następnych odsłonach (bo w końcu możnaby zadać pytanie Po co w ogóle zajmować się OSGi?, nieprawdaż?).

Do pełnego obrazu, pozostaje zaprezentować pom.xml:
 <?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>pl.jaceklaskowski.osgi</groupId>
<artifactId>spring-osgi-lifecycle</artifactId>
<packaging>bundle</packaging>
<name>Spring OSGi Bundle</name>
<version>1.0</version>
<url>http://www.springframework.org/osgi</url>
<properties>
<slf4j.version>1.4.3</slf4j.version>
<spring.maven.artifact.version>2.5.1</spring.maven.artifact.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.osgi</groupId>
<artifactId>junit.osgi</artifactId>
<version>3.8.2-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.maven.artifact.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.maven.artifact.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.maven.artifact.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.maven.artifact.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl104-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>4.0</version>
</dependency>
</dependencies>

<!-- for packaging as an OSGi bundle, we use the maven-bundle-plugin -->

<!-- see http://felix.apache.org/site/maven-bundle-plugin-bnd.html for more info -->
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<version>1.0.0</version>
<configuration>
<instructions>
<manifestLocation>META-INF</manifestLocation>
<Export-Package>pl.jaceklaskowski.osgi*</Export-Package>
<Import-Package>*</Import-Package>
<Bundle-Activator>${groupId}.AktywatorPakunku</Bundle-Activator>
</instructions>
</configuration>
</plugin>
</plugins>
</build>

<!-- ================================================ -->
<!-- Repository Configuration -->
<!-- ================================================ -->
<repositories>
<repository>
<id>apache.m2.incubator</id>
<name>Apache M2 Incubator Repository</name>
<url>http://people.apache.org/repo/m2-incubating-repository/</url>
</repository>
<repository>
<id>eclipse-repository</id>
<name>Eclipse Repository</name>
<url>http://repo1.maven.org/eclipse/</url>
</repository>
<repository>
<id>safehaus-repository</id>
<name>Safehaus Repository</name>
<url>http://m2.safehaus.org</url>
</repository>
<repository>
<id>spring-ext</id>
<name>Spring External Dependencies Repository</name>
<url> https://springframework.svn.sourceforge.net/svnroot/springframework/repos/repo-ext/ </url>
</repository>
<repository>
<id>i21-s3-osgi-repo</id>
<name>i21 osgi artifacts repo</name>
<snapshots>
<enabled>true</enabled>
</snapshots>
<url>http://s3.amazonaws.com/maven.springframework.org/osgi</url>
</repository>
<repository>
<id>i21-s3-maven-repo</id>
<name>i21 S3 milestone repo</name>
<url>http://s3.amazonaws.com/maven.springframework.org/milestone</url>
</repository>

<!--

Spring-DM snapshot repository - disabled by default

<repository>
<snapshots><enabled>true</enabled></snapshots>
<id>springframework.org</id>
<name>Springframework Maven SNAPSHOT Repository</name>
<url>http://s3.amazonaws.com/maven.springframework.org/snapshot</url>
</repository>
-->
</repositories>
<pluginRepositories>
<pluginRepository>
<id>maven-repo</id>
<name>maven repo</name>
<url>http://repo1.maven.org/maven2/</url>
</pluginRepository>
</pluginRepositories>
</project>
Na koniec, pytanie (nie)konkursowe dla wytrwałych: Jaki nagłówek MANIFEST.MF definiuje aktywator pakunku? I pytanie uzupełniające: W jaki sposób zdefiniować nagłówek aktywatora pakunku w projekcie zarządzanym przez Maven2? Nagród nie należy oczekiwać.

25 marca 2008

Specyfikacja OSGi - rozdział 4. Warstwa rozwojowa - wprowadzenie

5 komentarzy
W poprzednich odsłonach relacji z lektury specyfikacji OSGi Service Platform Core Specification Release 4, Version 4.1 (w skrócie specyfikacja OSGi) - Pora na łyk OSGi - rozdział 1. Wprowadzenie oraz Kolejny łyk OSGi - rozdział 3. Warstwa modułowa wspomniałem o podziale platformy OSGi na warstwy. Jedną z już przedstawionych była warstwa modułowa, gdzie wprowadzono pojęcie pakunku (ang. bundle) - zwartego acz rozbudowywalnego modułu usługowego (podstawowego bytu OSGi), który dostarcza/udostępnia pewną usługę (innymi słowy - jest to aplikacja realizująca pewien kontrakt. Kontrakt zabrzmiał zbyt wyniośle? Zatem jeszcze innymi słowy: aplikacja spełniająca pewne wymagania wymagająca zasobów zewnętrznych jak dostęp do bazy danych). Wprowadzenie pakunku w OSGi ma podobne zadanie jak para pakiet + zarządca klas (ang. classloader) w Javie. Po prostu OSGi poszedł dalej, po tym kiedy zauważono, że pewne elementy języka są niepełne. Jak to ujęto w specyfikacji OSGi - strona 12:

The Security Layer is based on Java 2 security but adds a number of constraints and fills in some of the blanks that standard Java leaves open.

czy

The Module Layer defines a modularization model for Java. It addresses some of the shortcomings of Java’s deployment model

Co jest interesującego w OSGi to fakt, że związany jest nierozerwalnie z wirtualną maszyną Javy (JVM) dodając do niej dodatkowe elementy-warstwy usprawniające, czyli co w zamyśle autorów OSGi wymagało usprawnienia, jak dodanie mechanizmu zabiezpieczania dostępu do wybranych elementów (pakietów) aplikacji czy możliwości ich włączania/wyłączania dynamicznie w trakcie pracy. Właśnie możliwości włączania/wyłączania opisane są w rozdziale 4. Warstwa rozwojowa (Life Cycle Layer), któremu się przyjrzę pokrótce.

Warstwa rozwojowa (ang. Life Cycle Layer) dostarcza interfejs programistyczny API do kontrolowania bezpieczeństwa i cyklu rozwojowego pakunków. Do poprawnego działania wymaga współpracy z warstwami modułową i bezpieczeństwa. O pierwszej można było przeczytać w mojej notatce - Kolejny łyk OSGi - rozdział 3. Warstwa modułowa.

Integralne cechy warstwy rozwojowej:
  • Warstwa rozwojowa musi dostarczać implementację API do zarządzania etapami rozwojowymi pakunków.
  • Udostępnia mechanizm prześwietlenia (sprawdzania) stanu platformy i jej pakunków.
  • Udostępnia API do szczegółowego określania uprawnień (warstwa bezpieczeństwa jest opcjonalna).
  • Zarządzanie zdalne
Podstawowe pojęcia platformy OSGi w kontekście warstwy rozwojowej:
  • Pakunek (ang. bundle) - podstawowy moduł usługowy
  • Kontekst (wykonawczy) pakunku (ang. bundle context) w ramach platformy OSGi, który jest przekazywany do aktywatora pakunku (ang. bundle activator) podczas uruchamiania/zatrzymywania pakunku.
  • Aktywator pakunku (ang. bundle activator) - interface implementowany przez klasę w pakunku, która służy do jego uruchomienia bądź zatrzymania.
  • Zdarzenie pakunkowe (ang. bundle event) - zdarzenie, które sygnalizuje zmianę etapu rozwojowego w cyklu rozwojowym pakunku. Zdarzenie obsługiwane jest przez (synchronicznego) słuchacza pakunku (ang. bundle listener).
  • Zdarzenie OSGi (szkieletowe) (ang. framework event) - zdarzenie, które syngalizuje błąd lub zmianę stanu szkieletu OSGi. Zdarzenie obsługiwane jest przez (synchronicznego) słuchacza Szkieletu OSGi (ang. framework listener).
  • Słuchacz pakunku (ang. bundle listener) - obiekt nasłuchujący zmian rozwojowych pakunku, obsługujący zdarzenia pakunkowe
  • Słuchacz szkieletowy (ang. Framework listener) - obiekt nasłuchujący błędów lub zmian stanu Szkieletu OSGi, obsługujący zdarzeń platformy OSGi.
  • Wyjątek pakunkowy (ang. bundle exception) - wyjątek zgłoszony jako wynik wystąpienia błędu podczas wykonania operacji OSGi
  • Pakunek systemowy (ang. system bundle) - pakunek reprezentujący Szkielet OSGi
Pakunek reprezentuje plik JAR, który jest uruchamiany w ramach Szkieletu OSGi. Sposób zarządzania ładowaniem zasobów pakunku jest przedstawiony w rozdziale dotyczącym warstwy modułowej (jego relację przedstawiłem w Kolejny łyk OSGi - rozdział 3. Warstwa modułowa). Warstwa dostarcza mechanizmów instalacji, aktualizacji oraz odinstalowania pakunku.

Instalacja pakunku może być wykonana wyłącznie przez inny pakunek lub za pomocą mechanizmów specyficznych dla implementacji platformy OSGi (polecenie install w Felix oraz Equinox).
 jlaskowski@dev /cygdrive/c/apps/felix
$ java -jar bin/felix.jar

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

Enter profile name: sandbox

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

-> help
bundlelevel <level> <id> ... | <id> - set or get bundle start level.
cd [<base-URL>] - change or display base URL.
headers [<id> ...] - display bundle header properties.
help - display impl commands.
install <URL> [<URL> ...] - install bundle(s).
obr help - OSGi bundle repository.
packages [<id> ...] - list exported packages.
ps [-l | -s | -u] - list installed bundles.
refresh [<id> ...] - refresh packages.
resolve [<id> ...] - attempt to resolve the specified bundles.
services [-u] [-a] [<id> ...] - list registered or used services.
shutdown - shutdown framework.
start <id> [<id> <URL> ...] - start bundle(s).
startlevel [<level>] - get or set framework start level.
stop <id> [<id> ...] - stop bundle(s).
uninstall <id> [<id> ...] - uninstall bundle(s).
update <id> [<URL>] - update bundle.
version - display version of framework.
lub
 jlaskowski@dev /cygdrive/c/apps/eclipse
$ java -jar plugins/org.eclipse.osgi_3.4.0.v20080205.jar -console

osgi> help
---Controlling the OSGi framework---
launch - start the OSGi Framework
shutdown - shutdown the OSGi Framework
close - shutdown and exit
exit - exit immediately (System.exit)
init - uninstall all bundles
setprop <key>=<value> - set the OSGi property
---Controlling Bundles---
install - install and optionally start bundle from the given URL
uninstall - uninstall the specified bundle(s)
start - start the specified bundle(s)
stop - stop the specified bundle(s)
refresh - refresh the packages of the specified bundles
update - update the specified bundle(s)
---Displaying Status---
status [-s [<comma separated list of bundle states>] [<segment of bsn>]] - display installed bundles and registered services
ss [-s [<comma separated list of bundle states>] [<segment of bsn>]] - display installed bundles (short status)
services [filter] - display registered service details
packages [<pkgname>|<id>|<location>] - display imported/exported package details
bundles [-s [<comma separated list of bundle states>] [<segment of bsn>]] - display details for all installed bundles
bundle (<id>|<location>) - display details for the specified bundle(s)
headers (<id>|<location>) - print bundle headers
log (<id>|<location>) - display log entries
---Extras---
exec <command> - execute a command in a separate process and wait
fork <command> - execute a command in a separate process
gc - perform a garbage collection
getprop [ name ] - displays the system properties with the given name, or all of them.
---Controlling Start Level---
sl [<id>|<location>] - display the start level for the specified bundle, or for the framework if no bundle specified
setfwsl <start level> - set the framework start level
setbsl <start level> (<id>|<location>) - set the start level for the bundle(s)
setibsl <start level> - set the initial bundle start level
---Controlling the Profiling---
profilelog - Display & flush the profile log messages
---Eclipse Runtime commands---
diag - Displays unsatisfied constraints for the specified bundle(s).
enableBundle - enable the specified bundle(s)
disableBundle - disable the specified bundle(s)
disabledBundles - list disabled bundles in the system
---Controlling the Console---
more - More prompt for console output

osgi> exit
Pakunek uruchamiany jest przez swojego aktywatora, który zdefiniowany jest w manifeście za pomocą nagłówka Bundle-Activator. Aktywator pakunku implementuje interfejs org.osgi.framework.BundleActivator. Jedynie pojedyńczy aktywator może zostać zdefiniowany dla pakunku (ale nie dla pakunku częściowego - ang. fragment bundles, który nie może mieć ich wcale).

Interfejs BundleActivator dostarcza metod start() oraz stop() odpowiednio do uruchomienia i zatrzymania pakunku. W ten sposób dostawca pakunku może wpłynąć (zmodyfikować) domyślny proces uruchamiania/zatrzymywania pakunku. Szkielet OSGi gwarantuje, że poprawne wykonanie metody start() implikuje wykonanie metody stop() na tym samym egzamplarzu pakunku. Zabrania się, aby Szkielet OSGi wykonywał metody aktywatora wielowątkowo. Podczas uruchomienia/zatrzymania pakunku przkazywany jest kontekst wykonania w postaci obiektu org.osgi.framework.BundleContext, który dostarcza informacje o stanie środowiska wykonawczego OSGi, możliwość zainstalowania innych pakunków oraz dostęp do rejestru usług (pakunków).

Przyjrzyjmy się przykładowemu pakunkowi ze zdefiniowanym aktywatorem. Wesprę się Mavenem 2 do zarządzania projektem i wtyczką maven-bundle-plugin. Działanie wtyczki opisałem w artykule Pakunki OSGi w projekcie wielomodułowym Apache Maven 2 z maven-bundle-plugin.
 jlaskowski@dev /cygdrive/c/projs/osgi
$ mvn archetype:create -DgroupId=pl.jaceklaskowski.osgi -DartifactId=osgi-lifecycle -Dversion=1.0
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO] task-segment: [archetype:create] (aggregator-style)
[INFO] ------------------------------------------------------------------------
...
[INFO] OldArchetype created in dir: c:\projs\osgi\osgi-lifecycle
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Import do Eclipse 3.4M5 z pomocą wtyczki m2eclipse (tym razem do Eclipse) i modyfikacja pom.xml o wpisy dotyczące wtyczki maven-bundle-plugin (ta wspomniana od Maven2). Ostatecznie plik pom.xml wygląda następująco:
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.jaceklaskowski.osgi</groupId>
<artifactId>osgi-lifecycle</artifactId>
<packaging>bundle</packaging>
<version>1.0</version>
<name>osgi-lifecycle</name>
<url>http://www.jaceklaskowski.pl</url>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Activator>${groupId}.AktywatorPakunku</Bundle-Activator>
<!-- Bez Private-Package pakunek (projekt) nie budował się -->
<Private-Package>${pom.groupId}.*</Private-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.osgi.core</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
Tworzę klasę aktywatora pakunku, który nie robi nic poza wypisaniem komunikatu o stanie prac:
 package pl.jaceklaskowski.osgi;

import java.util.logging.Logger;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class AktywatorPakunku implements BundleActivator {

Logger log = Logger.getLogger(AktywatorPakunku.class.getName());

public void start(BundleContext bundleContext) throws Exception {
log.info("start() wykonano - rozpoczynam pracę");
}

public void stop(BundleContext bundleContext) throws Exception {
log.info("stop() wykonano - czyszczę po sobie");
}

}
Zbudowanie projektu, a tym samym i pakunku (skasowałem domyślnie tworzoną klasę App oraz jej test i zależność junit z pom i stąd tak skromnie przy uruchomieniu).
 jlaskowski@dev /cygdrive/c/projs/osgi/osgi-lifecycle
$ mvn clean package
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building osgi-lifecycle
[INFO] task-segment: [clean, package]
[INFO] ------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

jlaskowski@dev /cygdrive/c/projs/osgi/osgi-lifecycle
$ ls -l target/osgi-lifecycle-1.0.jar
-rwxr-xr-x+ 1 jlaskowski None 4009 Mar 25 22:37 target/osgi-lifecycle-1.0.jar
I pora na uruchomienie w środowisku Apache Felix:
 jlaskowski@dev /cygdrive/c/apps/felix
$ java -jar bin/felix.jar

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

Enter profile name: osgi

DEBUG: WIRE: 7.0 -> org.osgi.service.packageadmin -> 0
DEBUG: WIRE: 7.0 -> org.osgi.service.startlevel -> 0
DEBUG: WIRE: 7.0 -> org.ungoverned.osgi.service.shell -> 7.0
DEBUG: WIRE: 7.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 7.0 -> org.apache.felix.shell -> 7.0
DEBUG: WIRE: 8.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 8.0 -> org.apache.felix.shell -> 7.0
DEBUG: WIRE: 9.0 -> org.osgi.service.obr -> 9.0
DEBUG: WIRE: 9.0 -> org.osgi.framework -> 0
-> DEBUG: WIRE: 9.0 -> org.apache.felix.shell -> 7.0

-> install file:c:/projs/osgi/osgi-lifecycle/target/osgi-lifecycle-1.0.jar
-> start 10
DEBUG: WIRE: 10.0 -> org.osgi.framework -> 0
2008-03-25 22:38:47 pl.jaceklaskowski.osgi.AktywatorPakunku start
INFO: start() wykonano - rozpoczynam pracŕ
-> stop 10
2008-03-25 22:38:52 pl.jaceklaskowski.osgi.AktywatorPakunku stop
INFO: stop() wykonano - czyszczŕ po sobie

Pytanie dla wytrwałych (całkowicie niezwiązane z OSGi acz zostało wykorzystane w tej notatce): Jaka jest różnica między wykonaniem polecenia mvn package a mvn install? Ponownie, nagrody nie ma.

23 marca 2008

Nowości NetBeans IDE 6.1 - JSF CRUD Generator

1 komentarzy
6 marca 2008 wyszła wersja NetBeans IDE 6.1 BETA - NetBeans IDE 6.1 Beta Now Available!. Jedyną z nowości, która zwróciła moją uwagę było JSF CRUD Generator (back by popular demand). Moją ciekawość dodatkowo spotęgował konkurs NetBeans IDE 6.1 Beta Blogging Contest, gdzie jak zrozumiałem wystarczy jedynie przedstawić NetBeans IDE 6.1 i tyle. Na uwagę zasługuje fakt, że Blogs will be accepted in the following languages a tam Polish! A skoro even if you don’t win, you might get a bunch of traffic! to nawet bez nagrody będę miał ten zysk, że potencjalnie mogę przyciągnąć uwagę kolejnych krytyków mojego spojrzenia na sprawy związane z Javą, jej korporacyjną wersją i projektami otwartymi. Sami się prosili ;-)

Rozpocząłem od lektury NetBeans IDE 6.1 Beta Information. Niestety niewiele można było tam znaleźć o JSF CRUD Generator. Szczęśliwie był tam zrzut ekranu z funkcjonalnością, która mnie zainteresowała, więc wiedziałem, że powinienem zacząć od asystenta JSF Pages From Entity Class. Zajrzałem również na stronę JsfCrudGenerator i znowu niewiele. Trochę informacji znalazłem również na stronie Milestones New and Noteworthy. W sumie głucho jak na funkcjonalność, którą wprowadzono do NetBeans back by popular demand. Chyba wszyscy wszystko wiedzą i stwierdzono, że popularność tej funkcjonalności nie zasługuje na szersze przedstawienie, bo co tutaj opisywać skoro wiadomo w co się gra. I tutaj wchodzę ja ;-)

Rozpoczynam od przygotowania bazy danych, którą w NetBeans (NB) jest Java DB. W zakładce Services w Databases znajduje się pozycja Java DB.


Za pomocą menu kontekstowego Start Server uruchamiam bazę Java DB.


Po chwili, po uruchomieniu bazy danych, uaktywni się menu Create Database....


Wypełniam danymi:
  • Database Name: piodb
  • User Name: jacek
  • Password: jacek


i zatwierdzam OK. Tym samym baza jest gotowa. Pojawi się kolejna pozycja w Databases reprezentująca połączenie z nowoutworzoną bazą piodb.


Pora trochę poprogramować. Pora na utworzenie dwóch encji Pytanie i Odpowiedz w relacji jeden-do-wielu. Zanim jednak do tego przejdziemy najpierw należy utworzyć ich projekt.

Wybieram zakładkę Projects, gdzie za pomocą kombinacji klawiszy Ctrl+Shift+N uruchamiam asystenta Java Class Library (kategoria Java - powinna być domyślnie wybrana). Jakkolwiek encje mogłyby być częścią aplikacji webowej, postanowiłem jednak wydzielić je jako oddzielny projekt do ponownego wykorzystania w innych projektach, niekoniecznie webowych (jeszcze nieokreślonych i trochę na wyrost, ale mam wrażenie, że ostatecznie takie podejście się opłaci).

W kolejnym kroku - Name and Location - wpisuję dane biblioteki.
  • Project Name: PioJPA


Zatwierdzam przyciskiem Finish.

Ctrl+N i wybieram kategorię Persistence, a w niej Entity Class.


Mógłbym wybrać asystenta Entity Classes from Database, ale skoro tworzę aplikację w podejściu top-down, gdzie najpierw aplikacja a później baza danych, gdzie baza danych jest "czysta", nie mam innego wyboru.

W kolejnym kroku podaję dane klasy encji Pytanie.
  • Class Name: Pytanie
  • Package: pl.jaceklaskowski.pio.encja
Ignoruję podpowiedź asystenta "The project does not have a persistence unit. You need a persistence unit to persist entity classes", gdyż "dostawcą" konfiguracji będzie aplikacja webowa, którą dopiero stworzymy, a projekt encji PioJPA poza użyciem adnotacji JPA nie będzie zależny od JPA. Nigdy nie wiadomo, gdzie ostatecznie zostanie użyty nasz projekt, a jeśli można go uniezależnić od użytego obecnie szkieletu programistycznego czy technologii tym lepiej dla późniejszego ponownego użycia.

Zatwierdzamy konfigurację przyciskiem Finish.

I tu pierwszy błąd NB 6.1 BETA - bez utworzenia PU zgodnie z podpowiedzią asystenta powoduje brak dodania biblioteki JPA i powoduje błąd składni klas (niedostępność użytych interfejsów JPA). Zgłoszone jako 130858: Using Entity Class wizard without creating PU leads to missing JPA interface errors.

Mamy kila możliwości obejścia tego błędu - skasować projekt i utworzyć go od nowa z jednoczesnym utworzeniem PU, ręcznie dodać bibliotekę JPA bądź ostatecznie stworzyć PU podczas tworzenia kolejnej encji, którą i tak mieliśmy utworzyć. Wybieramy jednak podejście numer 2, gdzie dodamy bibliotekę JPA i nie będziemy infekować projektu plikami jak persistence.xml, których i tak nie mieliśmy zamiaru używać. Czym mniej bałaganu tym lepiej.

Wybieramy Properties z menu kontekstowego projektu PioJPA.


Przechodzimy do Libraries, wciskamy przycisk Add Library... i po wybraniu biblioteki TopLink Essentials wciskamy przycisk Add Library.


Wciskamy przycisk OK i problem "odpływa".

Tworzę nową encję Odpowiedz. Ctrl+N, Persistence > Entity Class, gdzie podaję
  • Class Name: Odpowiedz
  • Package: pl.jaceklaskowski.pio.encja
Wracam do klasy encji Pytanie, gdzie umieszczam kolekcję Odpowiedzi (typ java.util.List). Uzupełniam import za pomocą Ctrl+SPACE bądź alternatywnie Ctrl+Shift+I do uzupełnienia importu java.util.List.


Stawiam kursor w ciele klasy Pytanie, Alt+Insert i wybieram menu Getter and Setter... do utworzenia metod get (odczytującej) i set (modyfikującej) dla atrybutu odpowiedzi (można dodatkowo skorzystać ze spacji do oznaczenia atrybutu, dla którego będą tworzone metody, co zniesie konieczność korzystania z pomocy myszki).

Wybieram atrybut odpowiedzi

i wciskam przycisk Generate.

Zgodnie z komunikatem NB przy metodzie Pytanie.getOdpowiedzi() (The multi-valued entity relation is not defined) dodaję adnotację @OneToMany do metody get.

Dodaję kolejny atrybut tresc (typu String) do klasy encji Pytanie i Odpowiedz. Tworzę odpowiadające mu metody get i set w obu klasach za pomocą Alt+Insert.

Praca nad projektem PioJPA zakończona (mimo komunikatu ostrzegawczego, że mamy encje, ale nie mamy zdefiniowanej jednostki trwałej - The project does not contain a persistence unit). Żegnam projekt PioJPA i przechodzę do utworzenia kolejnego - PioWeb.

Ctrl+Shirt+N i wybieram asystenta Web > Web Application.


W kolejnym kroku podaję:
  • Project Name: PioWeb
i rejestruję serwer aplikacyjny za pomocą przycisku Add... przy polu Server. Pozostawiam szczegóły tego kroku jako zadanie domowe.

Ustawiam Context Path na /pio.


Zatwierdzam przyciskiem Next >. I jeszcze raz Next >, aż pojawi się etap Frameworks, w którym wybieram Visual Web JavaServer Faces. Nigdy nie korzystałem z tej opcji, a naczytałem się, że jest to jedna z tych integracji, którą zespół NetBeans szczyci się szczególnie, więc pora jej skosztować.


Zatwierdzam Finish.

Po dłużej chwili pojawia się nowy projekt PioWeb w widoku Projects. Zauważalnie dłużej trwa inicjowanie biblioteki Woodstock, która jak rozumiem jest fundamentem dla Visual Web JavaServer Faces w NetBeans.

Związuję projekt PioWeb z PioJPA. PioJPA staje się biblioteką encji w projekcie PioWeb - menu kontekstowe Properties projektu PioWeb, a później wybieram Libraries i Add Project..., gdzie wybieram projekt PioJPA.


Teraz w końcu nadeszła pora na skorzystanie z dobrodziejstw funkcji JSF Pages from Entity Class. Ctrl+N, a następnie Persistence i JSF Pages from Entity Class.


Dodaję wszystkie dostępne encje - Odpowiedz i Pytanie za pomocą przycisku Add All >>.


Dopiero teraz utworzę jednostkę trwałą (PU - persistence unit) dla mojej aplikacji za pomocą przycisku Create Persistence Unit... Tworzę nowe źródło danych na serwerze aplikacyjnym (w tym przypadku jest to GlassFish).


Podaję JNDI Name: jdbc/piodb oraz wybieram Database Connection z listy rozwijalnej, która wskazuje na bazę danych piodb.

Ciekawostką tego Create Persistence Unit w porównaniu z tym, które napotkałbym w Java Class Library jest sposób pobrania źródeł danych dla PU - w pierwszym (obecnie wykorzystywanym) przypadku będzie to lista źródeł z serwera aplikacyjnego, podczas, gdy w drugim przypadku będzie to lista zdefiniowana w NetBeans IDE w zakładce Services. Zawsze mi tego brakowało i nie jestem pewien, kiedy pojawiło się to rozróżnienie w NetBeans.

Dodatkowo wybieram opcję Drop and Create.


Definicję jednostki trwałej zatwierdzam przyciskiem Create.

Przyciskiem Next > przechodzę do kolejnego etapu asystenta JSF Pages from Entity Class. Przyciskiem Browse... wybieram katalog docelowy tworzonych stron JSF (pole JSF Pages Folder) i jako Package podaję pl.jaceklaskowski.pio.faces.


Zatwierdzam przyciskiem Finish.

I tu niespodzianka. Mimo, że wybrałem pakiet pl.jaceklaskowski.pio.faces jako pakiet klas JSF to i tak ostatecznie asystent umieścił niektóre z nich w pakiecie pioweb i dodatkowo utworzył puste pakiety pioweb.odpowiedz oraz pioweb.pytanie. Zgłosiłem jako 130861: JSF Pages from Entity Class generates classes in package as project name oraz 130862: JSF Pages from Entity Class wizard creates empty packages. I na koniec jeszcze jeden błąd, gdzie klasy tworzone są w pakiecie "krótszym" od podanego, tj. pl.jaceklaskowski.pio, który okazał się być pojedyńczym katalogiem o nazwie pl.jaceklaskowski.pio.faces - 130863: JSF Pages from Entity Class generates classes in a "shorter" package that's a single directory.

Rozwiązaniem tych niespodzianek jest zaniechanie podania własnego katalogu dla tworzonych klas bądź utworzenie pakietu własnoręcznie i przeniesienie do niego klas. Wybieram podejście drugie i przeniosę klasy do pakietu pioweb (refaktoryzacja Move Classes). I tu kolejna niespodzianka - 130864: NPE upon Move Classes.

Nie pozostaje nic innego jak skasować projekt PioWeb i stworzyć go od nowa, bez specyfikowania pakietu.

UWAGA: Czasami skasowanie projektu nie kasuje jego katalogu mimo zaznaczenia opcji Also Delete Sources Under...
I tutaj jeszcze kolejna niespodzianka - podczas tworzenia projektu o tej samej nazwie otrzymałem zbiór wjątków NPE oraz IAE - 130865: NPE and IAE upon creating a visual jsf web project after it's been deleted. Mimo wszystko projekt się utworzył. Zamknąłem jednak NetBeans IDE 6.1 i otworzyłem go ponownie wcześniej kasując projekt PioWeb z poziomu Exploratora Windows. Tym razem obyło się bez niespodzianek.

Pora na uruchomienie aplikacji webowej PioWeb. Wybieram menu kontekstowe Run.


Projekt otworzy domyślną stronę startową aplikacji, więc nie ma zaskoczenia, kiedy pojawi się pusta strona w przglądarce. Przechodzimy do strony, gdzie utworzymy kilka nowych odpowiedzi - http://localhost:8080/pio/faces/odpowiedz/New.jsp.


Wybranie akcji Create kończy się jednakże błędem, który okazuje się być związany z niepełną konfiguracją JPA - brakiem wskazania klas encji w PU.


Wracam do NetBeans, gdzie do pliku persistence.xml w projekcie PioWeb dodaję klasy encji. Ten błąd wynika z faktu, że encje są w innym projekcie, więc niekoniecznie klasyfikuję to jako błąd w NetBeans.


Po zmianie wybieram menu kontekstowe Undeploy and Deploy i ponownie tworzę odpowiedź.


Teraz pora na nowe pytania, czyli przechodzę do strony http://localhost:8080/pio/faces/pytanie/New.jsp.

Myliłby się ten, kto uważałby, że tutaj pójdzie gładko ;-) Na konsoli GF pojawi się następujący błąd:
Caused by: javax.el.PropertyNotFoundException: The class 'pioweb.PytanieController' does not have the property 'odpowiedziOfPytanie'.
at javax.el.BeanELResolver.getBeanProperty(BeanELResolver.java:547)
at javax.el.BeanELResolver.getValue(BeanELResolver.java:249)
at javax.el.CompositeELResolver.getValue(CompositeELResolver.java:143)
at com.sun.faces.el.FacesCompositeELResolver.getValue(FacesCompositeELResolver.java:64)
at com.sun.el.parser.AstValue.getValue(AstValue.java:138)
at com.sun.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:206)
at javax.faces.component.UIOutput.getValue(UIOutput.java:173)
... 57 more
Zgłaszam kolejny błąd do bazy zgłoszeń NB - 130867: PNFE : The class 'pioweb.PytanieController' does not have the property 'odpowiedziOfPytanie'.

Rozwiązanie to dodanie atrybutu odpowiedziOfPytanie do klasy ziarna pioweb.PytanieController (pole private List<Odpowiedz> odpowiedziOfPytanie z metodami set i get). Ponownie Undeploy and Deploy, utworzenie odpowiedzi i podejście do utworzenia pytania.


Wstrzymanie oddechu, wciśnięcie Create i...

...tym razem udało się - pytanie utworzone! Nie było łatwo, ale wierzę, że przed produkcyjną wersją NetBeans IDE 6.1 wszystkie zgłoszone problemy zostaną rozwiązane. Fajnie jest mieć możliwość szybkiego utworzenia w pełni działającej aplikacji opartej o JPA i JSF, więc już nie mogę doczekać się pozamykania zgłoszeń i ogłoszeniu, że to, co znamy z Ruby on Rails czy JBoss Seam mamy i w NetBeans IDE 6.1 (nie pamiętam, czy przypadkiem podobnej funkcjonalności nie ma już w Eclipse czy jego rozbudowanym krewnym IBM Rational Application Developer 7.5 BETA).

Pytanie na zakończenie dla wytrwałych czytelników, którzy mają przyjemność czytać to: Dlaczego w polu Odpowiedzi pojawiło się pl.jaceklaskowski.pio.encja.Odpowiedz[id=1] zamiast treści odpowiedzi? Na szczęśliwych zwycięzców nie czekają nagrody.

Aplikacja Pytania i Odpowiedzi (PiO) w postaci projektów NetBeans dostępna jest jako pio-netbeansprojects.zip.