25 marca 2008

Specyfikacja OSGi - rozdział 4. Warstwa rozwojowa - wprowadzenie

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.