03 kwietnia 2008

Praktycznie o OSGi - Bundle-ActivationPolicy: lazy w akcji

Dzisiaj postanowiłem wykonać trochę robótek ręcznych i podejść do OSGi teoretycznie (nowe pytania w egzaminie OSGi Basic na javaBlackBelt) i praktycznie. Ostatnio opisywałem wycinek specyfikacji dotyczący warstwy rozwojowej i na dzisiaj zaplanowałem jej praktyczne sprawdzenie, jak to działa. Nie obyło się bez niespodzianek.

Zacznijmy od niewielkiej dawki teorii.

Pakunek jest uruchomiony przez wywołanie jednej z metod Bundle.start(...) lub bezpośrednie uruchomienie przez sam Szkielet OSGi, jeśli pakunek jest gotowy do uruchomienia lub konfiguracja ustawienia automatycznego startowania wskazuje, że musi on zostać uruchomiony.

Pakunek jest gotowy do uruchomienia, jeśli:
  • Pakunek jest rozwiązany, tj. wszystkie jego zależności zadeklarowane w manifeście zostały odszukane i są dostępne
  • Jeśli użyto opcjonalnej usługi Poziomu Uruchomienia (ang. Start Level service) i poziom uruchomienia pakunku jest spełniony
Po uruchomieniu pakunku, musi on zostać aktywowany, aby umożliwić pakunkowi własną inicjalizację. Pakunek jest aktywowany przez wywołanie (opcjonalnego) aktywatora pakunku (wskazanego przez Bundle-Activator w manifeście). Aktywacja pakunku odbywa się natychmiast po uruchomieniu (aktywacja natychmiastowa/gorliwa) lub podczas wczytania klasy z pakunku (aktywacja opóźniona/leniwa).

Pora na popróbowanie się z tą wiedzą praktycznie.

Rozpoczynam od stworzenia projektu dla pakunku, w którym zdefiniuję Bundle-ActivationPolicy: lazy.
  mvn archetype:create \
-DarchetypeGroupId=org.springframework.osgi \
-DarchetypeArtifactId=spring-osgi-bundle-archetype \
-DarchetypeVersion=1.0 \
-DgroupId=pl.jaceklaskowski.osgi \
-DartifactId=spring-osgi-activationpolicy \
-Dversion=1.0
Następnie importuję projekt do Eclipse, poprawiam Java Build Path i rozpoczynam edycję pliku pom.xml, aby nadać właściwą nazwę pakunkowi (projektowi)
 <name>Pakunek z Bundle-ActivationPolicy: lazy</name>
, zdefiniować aktywator pakunku wraz z konfiguracją aktywacji opóźnionej
  <Bundle-Activator>${groupId}.AktywatorPakunku</Bundle-Activator>
<Bundle-ActivationPolicy>lazy</Bundle-ActivationPolicy>
oraz dodać kolejną zależność projektową.
  <dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
<version>4.0</version>
</dependency>
Po zmianie pom.xml uruchamiam polecenie
 mvn eclipse:eclipse
aby odświeżyć projekt w Eclipse i dodać do niego nowododaną zależność (wydaje się, że mógłbym ją dodać poprzez menu Maven w Eclipse i w ten sposób zniwelowałbym tą wycieczkę na linię poleceń). Po tym F5 (odświeżenie projektu) w Eclipse i voila - projekt gotowy.

Dodaję aktywatora pakunku - pl.jaceklaskowski.osgi.AktywatorPakunku.
 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 {
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");
}

}
Następnie wykonuje polecenie budujące projekt, a tym samym i mój pakunek.
  mvn clean package
Pakunek z Bundle-ActivationPolicy: lazy jest gotowy.

Pora na kolejny pakunek instalujący poprzedni. Rozpoczynam podobnie, najpierw utworzenie projektu o nazwie spring-osgi-install, zmiany w pom.xml oraz dodanie aktywatora:
 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 {
Bundle bundle = bundleContext.installBundle(
"file:c:/projs/osgi/spring-osgi-activationpolicy/target/spring-osgi-activationpolicy-1.0.jar");
long bundleId = bundle.getBundleId();
String bundleLocation = bundle.getLocation();
String bundleSymbolicName = bundle.getSymbolicName();
System.out.println("------------------------------------");
System.out.println("Charakterystyka zainstalowanego pakunku:");
System.out.println(" Identyfikator: " + bundleId);
System.out.println(" Identyfikator położenia: " + bundleLocation);
System.out.println(" Nazwa symboliczna: " + bundleSymbolicName);
System.out.println("------------------------------------");
System.out.println("Startuję pakunek " + bundleSymbolicName);
bundle.start();
}

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

}
. Zbudowanie projektu, tj. pakunku poleceniem
  mvn clean package
i próba generalna.
 jlaskowski@work /cygdrive/c/apps/felix
$ java -Dfelix.cache.profile=install -jar bin/felix.jar

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

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

-> install file:c:/projs/osgi/spring-osgi-install/target/spring-osgi-install-1.0.jar
Bundle ID: 4
-> 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-04-03 19:49:20 pl.jaceklaskowski.osgi.AktywatorPakunku start
------------------------------------
Charakterystyka zainstalowanego pakunku:
Identyfikator: 5
Identyfikator położenia: file:c:/projs/osgi/spring-osgi-activationpolicy/target/spring-osgi-activationpolicy-1.0.jar
Nazwa symboliczna: pl.jaceklaskowski.osgi.spring-osgi-activationpolicy
------------------------------------
Startuję pakunek pl.jaceklaskowski.osgi.spring-osgi-activationpolicy
DEBUG: WIRE: 5.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 5.0 -> pl.jaceklaskowski.osgi.impl -> 4.0
DEBUG: WIRE: 5.0 -> pl.jaceklaskowski.osgi -> 4.0
2008-04-03 19:49:20 pl.jaceklaskowski.osgi.AktywatorPakunku start
------------------------------------
Charakterystyka zainstalowanego pakunku:
Identyfikator: 5
Identyfikator położenia: file:c:/projs/osgi/spring-osgi-activationpolicy/target/spring-osgi-activationpolicy-1.0.jar
Nazwa symboliczna: pl.jaceklaskowski.osgi.spring-osgi-activationpolicy
------------------------------------
Startuję pakunek pl.jaceklaskowski.osgi.spring-osgi-activationpolicy
org.osgi.framework.BundleException: Starting a bundle that is starting or stopping is currently not supported.
-> 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] Pakunek instalujacy (1.0)
[ 5] [Resolved ] [ 1] Pakunek z Bundle-ActivationPolicy lazy (1.0)
-> shutdown
I tu pierwsza niespodzianka - przykład nie działa (!) Ów wyjątek BundleException psuje całe moje rozumienie tematu. Hmmm, przechodzę, więc na inne środowisko OSGi - Eclipse Equinox. Equinox jest wzorcową (referencyjną) implementacją, więc wierzę, że będzie inaczej (czy lepiej to się właśnie okaże). Ponownie trochę lektury Equinox QuickStart Guide i uzbrojony w wiedzę uruchamiam moje pakunki.
 jlaskowski@work /cygdrive/c/projs/osgi/spring-osgi-install
$ java -jar c:/apps/eclipse/plugins/org.eclipse.osgi_3.4.0.v20080205.jar -console

osgi> ss

Framework is launched.

id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.0.v20080205

osgi> install file:c:/projs/osgi/spring-osgi-install/target/spring-osgi-install-1.0.jar
Bundle id is 1

osgi> start 1
2008-04-03 22:26:06 pl.jaceklaskowski.osgi.AktywatorPakunku start
------------------------------------
Charakterystyka zainstalowanego pakunku:
Identyfikator: 2
Identyfikator położenia: file:c:/projs/osgi/spring-osgi-activationpolicy/target/spring-osgi-activationpolicy-1.0.jar
Nazwa symboliczna: pl.jaceklaskowski.osgi.spring-osgi-activationpolicy
------------------------------------
Startuję pakunek pl.jaceklaskowski.osgi.spring-osgi-activationpolicy
2008-04-03 22:26:07 pl.jaceklaskowski.osgi.AktywatorPakunku start
------------------------------------
Charakterystyka zainstalowanego pakunku:
Identyfikator: 2
Identyfikator położenia: file:c:/projs/osgi/spring-osgi-activationpolicy/target/spring-osgi-activationpolicy-1.0.jar
Nazwa symboliczna: pl.jaceklaskowski.osgi.spring-osgi-activationpolicy
------------------------------------
Startuję pakunek pl.jaceklaskowski.osgi.spring-osgi-activationpolicy
org.osgi.framework.BundleException: Exception in
pl.jaceklaskowski.osgi.AktywatorPakunku.start() of bundle
pl.jaceklaskowski.osgi.spring-osgi-install.
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:1018)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(BundleContextImpl.java:974)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:346)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:265)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:257)
at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._start(FrameworkCommandProvider.java:257)
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:585)
at
org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.execute(FrameworkCommandInterpreter.java:150)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(FrameworkConsole.java:298)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(FrameworkConsole.java:283)
at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(FrameworkConsole.java:219)
at java.lang.Thread.run(Thread.java:595)
Caused by: org.osgi.framework.BundleException: Exception in pl.jaceklaskowski.osgi.AktywatorPakunku.start()
of bundle pl.jaceklaskowski.osgi.spring-osgi-activationpolicy.
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:1018)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(BundleContextImpl.java:974)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:346)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:265)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:257)
at pl.jaceklaskowski.osgi.AktywatorPakunku.start(AktywatorPakunku.java:26)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl$2.run(BundleContextImpl.java:999)
at java.security.AccessController.doPrivileged(Native Method)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:993)
... 14 more
Caused by: org.osgi.framework.BundleException:
State change in progress for bundle
"file:c:/projs/osgi/spring-osgi-activationpolicy/target/spring-osgi-activationpolicy-1.0.jar"
by thread "OSGi Console".
at org.eclipse.osgi.framework.internal.core.AbstractBundle.beginStateChange(AbstractBundle.java:1143)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:263)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:257)
at pl.jaceklaskowski.osgi.AktywatorPakunku.start(AktywatorPakunku.java:26)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl$2.run(BundleContextImpl.java:999)
at java.security.AccessController.doPrivileged(Native Method)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:993)
... 22 more
Caused by: org.eclipse.osgi.framework.internal.core.AbstractBundle$BundleStatusException
... 29 more
...
osgi> ss

Framework is launched.

id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.0.v20080205
1 RESOLVED pl.jaceklaskowski.osgi.spring-osgi-install_1.0.0
2 RESOLVED pl.jaceklaskowski.osgi.spring-osgi-activationpolicy_1.0.0

osgi> exit
I tu również wyjątek. Zanim skonstruowałem tak wyrafinowany przykład badałem działanie pakunku z Bundle-ActivationPolicy: lazy bezpośrednio z konsoli Felixa i każdorazowe polecenie start powodowało aktywację (uruchomienie aktywatora). Napisałem na grupę użytkowników Felixa, a tam kolejna niespodzianka. Na moje pytanie, dlaczego tak się dzieje otrzymałem odpowiedź (Bundle-ActivationPolicy: lazy and start command):

Very simple answer: Felix does not yet implement that R4.1 feature...

więc mając tą odpowiedź i doświadczywszy problemów z uruchomieniem opóźnionym pakunku postanowiłem odłożyć temat na później.

Ważną informacją w kontekście uruchomienia pakunków z Equinoxem jest zapisywanie przez niego stanu (pakunków) w katalogu configuration, który znajduje się w tym samym katalogu, co jego plik jar (org.eclipse.osgi_3.4.0.v20080205.jar), czyli w podkatalogu plugins Eclipse'a. Warto go usuwać dla zapewnienia "czystości" środowiska uruchomieniowego, gdyż zgodnie ze specyfikacją stan pakunków jest trwały, pomimo możliwych przestojów środowiska OSGi.

Pytanie na zakończenie: Jak nazywa się nagłówek manifestu pozwalający na określenie opóźnionej aktywacji pakunku?. Nagród nie ma.