26 marca 2008

Pakunki OSGi z pomocą Spring Dynamic Modules for OSGi

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ć.