OSGi je špecifikácia modulárneho java frameworku. Úvod k tejto téme nájdete v jednom z mojich predchádzajúcich článkov. Okrem iného som v ňom napísal, že celá OSGi aplikácia je tvorená modulmi, ktoré sa nazývajú bundle, a že tieto kusy kódu majú určitý životný cyklus. Teraz sa pozrieme presnejšie ako taký bundle vyzerá a akým životom si v OSGi žije.
Bundle samo o sebe nie je v java svete nič mimoriadne. Je to štandardný JAR súbor s .java súbormi vo vnútri (v správnej adresárovej štruktúre). To, čo ho odlišuje, sú metainformácie zapísané v manifestovom súbore. Tie poskytujú OSGi kontajneru (t.j. implementácii OSGi špecifikácie) informácie potrebné na to, aby vedel manipulovať s týmto balíkom kódu. OSGi relevantné údaje v manifeste sa dajú rozdeliť do troch kategórií:
- ľudsky čitateľné informácie (nepovinné) – napríklad meno bundlu,
- identifikátory bundlu – často sú to verzia a symbolické meno (vysvetlenie viď. nižšie),
- viditeľnosť kódu – informácie popisujúce poskytované a požadované balíky pre daný bundle.
Štruktúra každého takéhoto údaju je:
Property-name: clause, clause, clause, …
Pričom štruktúra clause je nasledovná:
target1;paremeter1=value1;parameter2=value2;...
Veľmi zjednodušene sa dá povedať, že každý zápis má názov a sériu hodnôt oddelených čiarkami (clause). Každá takáto hodnota môže mať ešte sériu ľubovoľného počtu parametrov oddelených bodkočiarkami (pričom parameter je opäť dvojica názov – hodnota). To je rozdelenie a štruktúra. Aké konkrétne údaje je tam možné nájsť?
Je to napríklad meno bundlu. To má dokonca až 2 zápisy: Name a Bundle-SymbolicName . To prvé existuje od prvej verzie špecifikácie a patrí do skupiny ľudsky čitateľných informácií. Je nepovinné a nemusí byť jedinečné. Dôvod, prečo je to tak, som už spomenul v predchádzajúcom článku. Väzby medzi bundlami sú v OSGi kontajnery na úrovni balíkov nie bundlov. To znamená, že nepopisujem závislosť pomocou identifikátora bundlu, ale názvu balíka. Aj preto bundle nemusí mať meno alebo nemusí byť jedinečné. Od verzie 4 sa v špecifikácii objavuje aj závislosť na bundly (je možné ju zadefinovať, aj keď nie je odporúčaná), a preto prichádza vlastnosť Bundle-SymbolicName, ktorá už musí byť v rámci jedného OSGi kontajnera jedinečná (a patrí do druhej skupiny, teda je to identifikátor bundlu).
Ďalší dôležitý identifikátor bundlu je verzia. Tá sa zapisuje v tvare:
Version: major.minor.micro.qualifier
pričom major, minor a micro sú povinné a mali by predstavovať číslice a qualifier je nepovinný a môže obsahovať ľubovoľné znaky (napríklad dátum zostavenia).
Najpodstatnejšia je ale tretia skupina, ktorá popisuje viditeľnosť kódu. Bundle môže definovať balíky, ktorých prítomnosť vyžaduje v OSGi kontajnery, aby mohol fungovať a tiež balíky, ktoré poskytuje pre ostatné bundle. To prvé sa zapisuje pomocou vlastnosti s názvom Import-Package a predstavuje to zoznam názvov balíkov. Pre každý z nich je tiež môžné mať definovaný zoznam parametrov, ktoré sú vyžadované (napríklad môj bundle požaduje špecifickú verziu balíka – toto sa práve deje pomocou parametrov clause v zápise jednej vlastnosti). Podobne aj balíky, ktoré bundle poskytuje, predstavuje zoznam mien zapísaných vo vlastnosti Export-Package . Opäť môže mať každý takýto balík ešte radu parametrov, medzi ktoré patrí napríklad verzia.
Importované a exportované balíky sú asi najdôležitejším OSGi údajom v manifeste. Na základe nich potom OSGi vyhodnocuje, či je možné daný balík nasadiť v kontajnery (vyhodnocuje, či sú v ňom prítomné všetky požadované balíky). A tiež čo sa udeje, ak sa rozhodnete nejaký bundle odobrať alebo aktualizovať – OSGi vyvolá sériu vypnutí (a prípadne opätovných zapnutí) bundlov, ktoré na odoberanom bundle závisia. OSGi tiež umožňuje nasadiť niekoľko bundlov, ktoré poskytujú ten istý balík rôznej alebo dokonca rovnakej verzie. Špecifikácia pritom popisuje pomerne zložitý algoritmus ako nájsť ten správny balík, pričom OSGi sa snaží udržať čo najkonzistentnejšie prostredie (čo najviac bundlov používa ten istý balík). Popis situácií, ktoré môžu nastať a ako sa s nimi OSGi vysporiada, je ale téma na samostatný článok.
Do tretej kategórie ešte spadá posledná dôležitá vlastnosť s názvom Bundle-ClassPath. Tá popisuje cesty hľadania závislostí pre kód v danom bundle. Popisom toho, čo je class path som sa zaoberal v inom článku. Pre OSGi je dôležité povedať, že každý bundle má svoj vlastný class loader. A ten (zhruba povedané) nazerá do troch množín kódu: do knižníc JVM, do balíkov, ktorý daný bundle importuje a do kódu v rámci daného bundlu. Práve tú tretiu množinu popisuje Bundle-ClassPath. Sú to interné názvy balíkov v rámci daného bundlu. Ak ju do manifestu nezadáte, tak je nastavená na „ . “ čo znamená, že class loader má k dispozícii všetok kód, ktorý je v bundle. V špecifických prípadoch sa ale môže rozhodnúť, že napríklad nejaký balík chcete exportovať, ale nechcete, aby bol zároveň používaný kódom v danom bundle a túto cestu podľa toho nastaviť.
Pochopenie manifestu je dôležité, lebo takto ste schopní zostaviť bundle – základný stavebný prvok OSGi. Čo sa ale deje s bundlom po tom, čo sa ho pokúsite dostať do OSGi kontajnera, vypovedá časť špecifikácie, ktorá sa zaoberá životným cyklom. Ten sa dá najlepšie popísať diagramom:
Všetko začína stavom Installed. V tomto stave sa bundle dostáva do OSGi kontajnera. Framework si vezme bundle a nakopíruje do jeho vnútornej štruktúry (tiež nazývaj Bundle cache). Ďalším stavom je Resolved, čo je stav, kedy OSGi vyhodnotí, že požiadavky daného bundlu sú splnené. V podstate to znamená, že všetky požadované balíky tohto bundlu sú už k dispozícii vo forme balíkov exportovaných inými bundlami. Bundle je ešte stále v stave, že sa nepoužíva. Zatiaľ je len nainštalovaný a pripravený na použitie. Nasleduje príkaz start, ktorý ho prepne najprv do dočasného stavu Starting a potom do stavu Active. Stav Active je v podstate cieľový stav, v ktorom by sa bundle mal ocitnúť, aby správne fungoval. Práve v ňom sú exportované balíky iným k dispozícii a kód môže byť používaný. Späť do stavu Resolved sa dá vrátiť cez príkaz stop a medzistav Stopping. Príkaz na odinštalovanie bundlu spôsobí, že sa najprv stopne (ak bol aktívny), potom sa prepne do Resovled, následne do Installed a na záver do Uninstalled.
Okrem týchto spomenutých prechodov sú v grafe ešte dva, ktoré sa dejú pri príkaze refresh/update. Update v podstate znamená aktualizáciu bundlu – nahratie jeho novšej verzie. Aktívny bundle sa musí dostať do stavu Installed, vymení sa jeho binárne data (teda kód) a znova musí prejsť všetkými krokmi až do stavu Active. Nová verzia totiž môže mať nové požiadavky, ktoré nemusia byť splniteľné. Čo sa ale deje, keď takto aktualizujem nejaký bundlu, na ktorom sú závislé iné? Bude nad nimi vykonaný príkaz Refresh. Ten spôsobí, že sa z Active stavu prepnú až do Installed a odtiaľ opäť postupne do stavu Active. Opäť sa totiž môže stať, že nový bundle neposkytuje niektorý balík, ktorý jeho stará verzia poskytovala a to treba ošetriť. Takto sa OSGi snaží udržať v závislostiach integritu. Za žiadnych okolností by sa nemalo stať, že balík, ktorý je v stave Active, nebude mať prístupné všetky svoje závislosti.
Pochopenie metadát bundlu a jeho životného cyklu sú len ďalšie dva kroky k pochopeniu OSGi ako celku. Špecifikácia bola navrhnutá dosť defenzívne, a tak pokiaľ je dodržaná v konkrétnej implementácii, nemalo by dochádzať k známym ClassNotFoundException, kedy interpreter Javy nemá dostupné všetky triedy. Dôležité je len správne zadefinovať požiadavky bundlu a pochopiť, čo sa s ním deje po tom, ako sa raz dostane do OSGi kontajnéra.