Modelování existujících OSGi komponent Lukáš Valenta, Přemysl Brada Katedra informatiky a výpočetní techniky, FAV, ZČU – Západočeská univerzita, Univerzitní 22, 30100, Plzeň
[email protected],
[email protected]
Abstrakt. Pohlížíme-li na komponenty jako na zapouzdřené objekty, pracujeme pouze s jejich rozhraním. Toto rozhraní je možno formálně popsat nějakým modelem a pracovat tak s abstraktní reprezentací rozhraní komponenty. Při skládání komponentové aplikace lze tedy s výhodou používat tuto reprezentaci. Často se však setkáváme se situací, kdy je potřeba do modelu zahrnout komponentu získanou (typicky zakoupenou) od třetí strany – většinou pak nemáme k dispozici zdrojový kód ani dostačující technickou dokumentaci. Modelovou reprezentaci takové komponenty je pak potřeba získat přímo z její distribuční formy. Tento článek shrnuje naše zkušenosti a problémy s tímto postupem pro komponentový model OSGi. Klíčová slova: OSGi, komponenta, rozhraní, model, Objekty
1
Úvod
Existuje několik průmyslově používaných platforem pro komponentové programování – Enterprise JavaBeans (EJB) [14], CORBA Component Model [9], OSGi [11], Robocop [10] a například také portlety [8]. Všechny používají pojem „komponentaÿ ve smyslu části aplikace, kterou je možné samostatně nasadit, aktualizovat a (v run-time) referencovat. Obvyklý obsah pojmu [13] je ale poněkud širší, neboť zahrnuje také vlastnost, že komponenta je černá skříňka s explicitně definovanými rozhraními a závislostmi na okolí. V tomto článku chceme diskutovat, jak se s tímto chápáním komponenty vypořádává OSGi framework, a ukázat, s jakými obtížemi se lze setkat v případě, kdy z existující komponenty chceme získat kompletní informace o jejím rozhraní.
2
Reprezentace rozhraní komponent
Cílem komponentového programování je, aby většina využití komponent byla ve formě čistého re-use bez znalosti vnitřní implementace. Z tohoto důvodu potřebujeme velmi často pracovat s popisem jejich vnějšího rozhraní – nebo také „povrchuÿ – pro účely zakomponování do existující nebo vyvíjené aplikace (tj. nikoli za účelem jejího vlastního vývoje). Typickou ukázkou je modelování aplikací
v diagramu komponent či tříd v UML2, kde je abstrahováno od vnitřní struktury komponent a jsou naopak zdůrazněny vazby mezi nimi, případně mezi komponentami a implementačními třídami aplikace, které je využívají. Poměrně často
cd: Sample Application
<< component >>
<< component , UI >> StudentAdministration
Student
Student
Persistence
Student Persistence << component , infrastructure >>
<< component >>
Persistence
Database JDBC JDBC
Obr. 1. Ukázkový UML2 diagram komponent
se můžeme dostat do situace, kdy potřebujeme do modelu zahrnout komponentu získanou (typicky zakoupenou) od třetí strany; v takovém případě je nepravděpodobné, že bychom měli přístup ke zdrojovému kódu, a technická dokumentace nemusí být dostačující. Potřebovali bychom tedy provést jakousi reverzní analýzu distribuční podoby komponenty pro získání popisu jejího rozhraní. Tento popis typicky zahrnuje dvě množiny prvků (rozhraní, služeb, atributů, událostí, atd., dle možností použitého komponentového modelu): jednak ty, které jsou komponentou poskytovány okolí – tzv. provides část, a za druhé ty, které ona sama vyžaduje od okolí pro svoji funkčnost – tzv. requires část. 2.1
Praktické aspekty
Problém nastává pokud model rozhraní, který máme k dispozici, neobsahuje všechny prvky komponentou skutečně poskytované či vyžadované. V takovém případě jej buďto musíme doplnit ručně, nebo pracovat s neúplným modelem za cenu nepřesností a možných komplikací, které se objeví až za běhu výsledné aplikace. Dalo by se očekávat, že model získaný automatickou analýzou distribuční podoby komponenty bude kompletní, tak jako je nutně kompletní sama distribuovaná komponenta. Moderní komponentové platformy obvykle používají nějakou formu deklarativního popisu komponenty (deployment descriptor u EJB, soubor manifestu u OSGi) a zároveň takové implementační jazyky, které obsahují mechanismus introspekce (Java, C#). To dává šanci zjistit o komponentě vše potřebné až na úroveň jednotlivých tříd, metod a datových typů.
Naše zkušenosti bohužel potvrzují spíše opak. V následující části článku chceme ukázat na příkladu OSGi, jaké prvky rozhraní je potřeba mít k dispozici pro kompletní modelování komponenty a jakým způsobem lze které z nich získat. Zejména se pak budeme zabývat technicky pokročilými technikami a obtížemi, se kterými je spojeno získávání informací o některých z nich.
3
Komponentová platforma OSGi
OSGi [11] je otevřená platforma umožňující nasazení a správu služeb. Původně byla jejím hlavním cílem oblast mobilních a zabudovaných (embedded ) zařízení, v současné době ale tato komponentová architektura slouží i jako základ celých enterprise informačních systémů či například vývojových prostředí [6, 7]. Jádro platformy OSGi tvoří tzv. OSGi Framework (v jiných komponentových modelech se obvykle nazývá kontejner ), který vytváří běhové prostředí umožňující nasazení a provoz aplikací (komponent) nazývaných bundle. V tomto článku se budeme zabývat modelováním právě těchto komponent – OSGi bundles.
3.1
OSGi Bundle
OSGi bundle je softwarová komponenta obsahující Javovské třídy tvořící vlastní implementaci a další pomocné zdroje, které společně poskytují funkcionalitu pro své klienty. Komponenty mohou sdílet Javovské balíky – mezi exportéry a importéry mohou vznikat přesně definované vazby. Tento mechanismus umožňuje komponentě přistupovat k třídám poskytnutým komponentou jinou. Nejdůležitějším aspektem OSGi jsou však služby, přístupné přes Java rozhraní. Myšlenka celé architektury je založena na množině nezávislých komponent, které si mohou v centrálním registru běhového prostředí registrovat služby, které poskytují a naopak hledat služby, jež od svého okolí vyžadují. Bundle připravený k nasazení (distribuční forma) je ve formě JAR archivu. Tento soubor obsahuje: – veškeré zdroje nutné k běhu obsaženého OSGi bundle, tedy přeložené Javovské třídy, HTML soubory, nápovědu, ikony atd. – „manifestÿ – soubor popisující obsah archivu („deskriptorÿ). Hlavičky tohoto souboru specifikují informace, které OSGi Framework potřebuje pro úspěšné nasazení a spuštění komponenty. V okamžiku nasazení vyřeší Framework všechny vnější závislosti specifikované v manifestu a propojí importéry se správnými exportéry. Po spuštění komponenty jsou jí poskytované služby zpřístupněny i ostatním komponentám v daném prostředí.
4
Zjišťování prvků rozhraní OSGi komponent
Mluvíme-li o modelování rozhraní komponent nějakého konkrétního komponentového modelu, musíme nejdříve prostudovat jeho specifikaci za účelem zjištění všech funkcí a vlastností, které překračují pomyslnou bariéru zapouzdření komponenty1 . Námi zkoumaný model OSGi ve verzi 4 specifikuje možné druhy prvků rozhraní komponent uvedené v tabulce 1. Oproti jiným komponentovým modelům (např. EJB) je jejich počet poměrně malý.
Název
Role
export types
Poskytovaný Java typy exportované touto komponentou
Popis
import types
Vyžadovaný Java typy touto komponentou vyžadované
export services
Poskytovaný Služby, které komponenta poskytuje ostatním
import services
Vyžadovaný Služby, které komponenta vyžaduje od okolí
native code
Vyžadovaný Vyžadované systémové knihovny
require bundles
Vyžadovaný Komponenta požaduje jiný celý bundle
required exec env Vyžadovaný Podporované běhové prostředí (jedno či více) Tabulka 1. Druhy prvků rozhraní komponent OSGi
Při vytváření abstraktní reprezentace rozhraní konkrétní komponenty je třeba získat z její distribuční formy (tedy ze zmíněného JAR archivu) informace o těch prvcích z výše uvedených, které komponenta opravdu poskytuje/vyžaduje. Následující kapitoly ukazují, že u části z nich je to obtížné až zcela nemožné. 4.1
Bezproblémové části rozhraní
Nejprve uvedeme části rozhraní komponent OSGi, jejichž získání je bezproblémové. Jejich formální popis existuje v manifestu a obsahuje všechny potřebné informace. Jedná se o seznam vyžadovaných systémových knihoven (native code), běhových prostředí (required execution environment) a vyžadovaných bundle (require bundles), jak je ilustrováno na následujícím fragmentu manifest souboru: Bundle-RequiredExecutionEnvironment: OSGi/Minimum-1.1 Bundle-NativeCode: lib/http.dll ; lib/zlib.dll ; osname = Windows98 ; osname = WindowsNT ; processor = x86 ; lib/linux/libhttp.so ; osname = Linux ; 1
Jak bylo zmíněno v úvodu – na komponentu pohlížíme jako na černou skříňku, známe pouze její rozhraní.
processor = mips Require-Bundle: cz.zcu.example;resolution:=optional U následujících částí rozhraní již není zjištění všech podstatných informací takto triviální. 4.2
Exportované typy
V manifestu JAR souboru slouží k popisu exportovaných typů hlavička ExportPackage. Specifikovány jsou tedy celé Java balíky, pro potřeby modelování je však potřeba přejít o jednu úroveň „nížÿ – ke konkrétním Java třídám a rozhraním2 . Bundle-Name: A Export-Package: cz.zcu.logging; version=1.3.0; exclude=cz.zcu.logging.Invisible Bundle uvedený na příkladu exportuje celý Java balík cz.zcu.logging – tedy všechny třídy a rozhraní definované jako public kromě třídy Invisible. Získání seznamu exportovaných tříd je poměrně jednoduché – vzhledem k tomu, že celý balík musí být v JAR archivu přítomen. Je třeba projít celý adresář a každou veřejnou javovskou třídu, která vyhovuje podmínkám include/exclude, do seznamu zařadit. Zda je třída veřejná lze zjistit instrospekčním mechanismem – použitím Java Reflection API [15]. V tomto případě obsahuje distribuční forma komponenty tedy kompletní informaci o tomto poskytovaném prvku rozhraní – introspekčním mechanismem je možno získat kompletní typovou informaci (jednotlivé metody, parametry, . . . ), je-li potřeba3 . Jak uvidíme dále, zdaleka ne vždy lze takto kompletní informace získat. 4.3
Importované typy
Deklarace importovaných balíků u OSGi komponent slouží podobnému účelu, jako systémová proměnná CLASS PATH u běžných Javovských aplikací. Pouze určuje závislosti komponenty na „knihovníchÿ (vyžadovaných) balících. Výhodou oproti zmíněnému CLASS PATH je u OSGi samozřejmě nezávislost na umístění, komponenta se nemusí zajímat o to, odkud se potřebné balíky „vezmouÿ, toto zajistí OSGi Framework automaticky. Navíc dokáže řešit složitější podmínky a nároky kladené na exportovaná rozhraní, například požadovanou verzi, shodnost parametrů atd. Zjištění importovaných typů je na první pohled velice podobné typům exportovaným: 2
3
Už jen proto, že specifikace OSGi umožňuje ke specifikaci exportovaného balíku přidat seznam tříd z exportu vyloučených – proto je lepší hovořit o seznamu tříd, než o „neúplnémÿ balíku. Například při určování podtypové relace mezi dvěma komponentami – při ověřování jejich nahraditelnosti.
Bundle-Name: B Import-Package: cz.zcu.logging; version=[1.1.5, 2) Oproti nim je zde však jeden podstatný rozdíl: nikde není explicitně specifikována přesná „podobaÿ vyžadovaných tříd a rozhraní. V manifestu je specifikován pouze název Java balíku, kompletní informace o obsažených typech k dispozici není. Podobu vyžadovaných typů (metod a jejich hlaviček . . . ) tak, jak je předpokládá a s nimi pracuje komponenta B, bychom zjistili z exportující komponenty, tu ale obecně nemáme k dispozici. Musíme tedy použít informace z naší komponenty samotné: představu lze získat analýzou jejího přeloženého programového kódu. Je potřeba zjistit, jaké používá třídy, k čemuž je potřeba nejen introspekčního mechanismu, ale i přímé analýzy Java .class souborů. Výsledný postup lze slovy popsat jako rekurzivní procházení všech interních tříd zkoumané komponenty a hledání referencí na třídy, které náleží do balíků specifikovaných v Import-Package hlavičce: 1. Nechť List je množina názvů tříd, které ještě nebyly analyzovány. Nechť P arsed je množina názvů tříd, které již byly analyzovány. 2. List = { Název třídy Bundle-Activator } (tj. „hlavníÿ třída OSGi bundle) P arsed = ∅. 3. Nechť Class ∈ List (výběr jedné třídy), List = List \ {Class}, P arsed = P arsed ∪ {Class}. (a) Pokud třída Class náleží do tohoto OSGi bundle, analyzuj její byte-kód. Do List přidej všechny nalezené reference na jiné třídy nebo rozhraní (pouze pokud již nejsou v P arsed). Jdi na krok 4. (b) Patří-li třída do některého z balíků specifikovaných v hlavičce Import-Package, ulož její jméno do vytvářené abstraktní reprezentace této komponenty (jako import type). Jdi na krok 4. (c) Neplatí-li možnost (a) ani (b), třídu ignoruj (může se jednat například o knihovní třídu Javy). 4. Je-li List = ∅, ukonči proces. V opačném případě jdi na krok 3. Uvedený postup není ze své podstaty zcela spolehlivý, ale ve většině případů dává správné výsledky. Jeho výhodou je fakt, že nalezneme pouze ty třídy, které komponenta skutečně používá a nikoli všechny, které jsou jinou komponentou exportovány. Hledání referencí na používané třídy není vzhledem ke struktuře .class4 souborů tak obtížné, jak by se mohlo zdát. Soubory obsahují tabulky používaných konstant, řetězců, názvů metod, tříd a dalších důležitých údajů a stačí tedy procházet tyto tabulky bez nutnosti analýzy například jednotlivých instrukcí vlastního kódu. V naší prototypové implementaci tento algoritmus používá knihovnu The Byte Code Engineering Library (BCEL) [1]. 4
Souborů obsahujících přeložený kód komponenty – Java byte-code
4.4
Služby
Důležitou součástí rozhraní OSGi komponent jsou služby (Services). Bohužel, právě tato důležitá vlastnost není žádným způsobem formálně specifikována. Narozdíl od Export-Package či Import-Package diskutovaných výše, o službách, které OSGi komponenta požaduje či poskytuje, není například v manifest souboru taková informace žádná. Veškeré operace se službami, včetně jejich publikování a vyhledávání, probíhají pouze ve vlastním kódu komponenty, tedy uvnitř pomyslné černé skříňky, za kterou komponentu považujeme. Způsob práce se službami je ilustrován v následujících ukázkách kódu. Registrace poskytované služby: BundleContext bc = ...; /* muj BundleContext */ /* ziskani objektu implementujiciho rozhrani sluzby */ serviceImpl = new MyServiceImplementation(); /* registrovani sluzby - nazvem sluzby je nazev jejiho rozhrani */ bc.registerService( MyServiceInterface.getName(), serviceImpl, new Hashtable() ); Vyhledání a získání reference na vyžadovanou službu: /* nalezeni reference na sluzbu */ ServiceReference sr = bc.getServiceReference( MyServiceInterface.getName() ); /* ziskani reference na sluzbu */ MyServiceInterface = (MyServiceInterface) bc.getService(sr); /* volani metody poskytovane sluzbou */ sr.serviceMethod(...); /* uvolneni sluzby */ bc.ungetService(sr); Postup použitý pro hledání referencí na používané třídy (viz sekce 4.3) nelze v tomto případě použít. Lehce modifikovaným postupem lze pouze odhalit, že třída volá nějakou konkrétní metodu (například BundleContext. registerService()), což svědčí pouze o tom, že bundle pravděpodobně nějakou službu registruje. Další potřebné údaje (název služby, její rozhraní) již není možné zjistit, protože parametry této metody jsou již zcela „skrytyÿ ve vlastním kódu komponenty a obecně je nelze určit. Stejný problém nastává i při určování služeb, které komponenta ke svému běhu potřebuje.
4.5
Shrnutí
Komponentový model OSGi trpí, stejně jako i další průmyslově používané modely (např. EJB [14]), nedostatečným formálním popisem rozhraní komponent. Největší problém vidíme v absenci popisu komponentou vyžadovaných a poskytovaných služeb, protože se jedná o základ a princip celé architektury. Abstraktní reprezentace komponenty OSGi, získaná výše uvedenými automatickými postupy, proto bohužel postrádá de-facto nejdůležitější součást a jak jsme v předchozích kapitolách ukázali, informace nelze z distribuční formy získat žádným způsobem. Ale ani ostatní části rozhraní, kromě triviálních prvků (viz sekce 4.1), nejsou z tohoto pohledu bezchybné. Informace, které by měly být deklarativně přímo přístupné například v manifest souboru, se musejí složitě a ne vždy zcela spolehlivě hledat introspekcí či ještě hlubší analýzou „hotovéÿ formy komponenty. V naší práci využíváme pro abstraktní reprezentaci rozhraní softwarové komponenty ENT metamodel [4], jehož prototypová implementace nám umožňuje provádět nad reprezentacemi konkrétních komponent další operace (pro ilustraci viz obrázek 2). Především se zaměřujeme na ověřování nahraditelnosti softwarových komponent [5], konkrétní aplikací pro OSGi je nástroj pro automatické generování identifikátorů verzí nových revizí komponenty na základě analýzy změn v jejím rozhraní [16, 17]. Každé takové využití reprezentace rozhraní ovšem trpí neúplností informací, které je možné o komponentě získat.
Obr. 2. Vytvoření modelu rozhraní a jeho použití
5
Závěr
Na problém s nedostatečnými nebo chybějícími (formálními) popisy softwarových komponent je ukazováno v práci [2]. Autoři představují model a metriky
kvality komponent a v rozsáhlé případové studii analyzují množství komponent ve formě, v jaké je možno je zakoupit na trzích komponent. V závěru práce upozorňují na fakt, že mnoho důležitých informací o komponentě není zákazníkovi k dispozici (a to ani nemluvíme o jejím formálním popisu, ale o libovolném zdroji informací – návody, tutoriály . . . ). Druhým významným závěrem je srovnání s množinou komponent, ke kterým měli autoři „plnýÿ přístup (zdrojové kódy . . . ). Tato množina dopadla o poznání lépe – vidíme, že informace k dispozici jsou, ale nejsou poté k dispozici v nějaké podobě (nejlépe formalizované) při distribuci komponent. Na směr, kterým se bude zřejmě komponentový výzkum dále ubírat, tedy na pokročilejší formální popisy jak rozhraní komponent, tak i jejich protokolů, chování, výkonových charakteristik atd., ukazují i další současné práce prezentované na konferencích věnovaných komponentovým systémům [3, 12]. Ukazuje se, že požadavky na komponentové programování kladené a především jeho očekávané přínosy (re-use, možnost update/upgrade komponenty za běhu aplikace a související přísné kontroly kompatibility, striktní black-box pohled na jednotlivé komponenty, . . . ) jsou stále spíše v rovině teoretické. Současné průmyslově používané komponentové modely se soustřeďují spíše na implementační aspekty a spektrum podporovaných služeb (transakce, persistence, transparentní distribuovatelnost . . . ). Námi prezentovaný pohled na OSGi ukazuje, že se jedná spíše o dobře navrženou běhovou architekturu či přesně specifikované běhové prostředí pro provoz nezávislých aplikací v jedné JVM, než o komponentový model s dobře definovanými (tj. formálně popsatelnými) rozhraními komponent, nemluvě o dalších zmíněných aspektech – protokoly, chováním atd. v případě, že by další verze specifikace již nutily vývojáře s komponentou distribuovat i tyto modely, bylo by vyřešeno mnoho problémů, se kterými se nyní uživatelé OSGi potýkají.
Reference 1. The Byte Code Engineering Library, http://jakarta.apache.org/bcel/. 2. Alexandre Alvaro, Eduardo Santana de Almeida, Silvio Lemos Meira, a Software Component Quality Model: a Preliminary Evaluation, In proceedings of 32nd EuroMicro conference, 0-7695-2594-6, 2006. 3. Egor Bondarev, Michel R.V. Chaudron, Compositional Performance Analysis of Component-Based Systems on Heterogeneous Multiprocessor Platforms, In proceedings of 32nd EuroMicro conference, 0-7695-2594-6, 2006. 4. P. Brada. The ENT model: a general model for software interface structuring. Technical Report DCSE/TR-2002-10, Department of Computer Science and Engineering, University of West Bohemia, Pilsen, Czech Republic, 2002. 5. P. Brada. Specification-Based Component Substitutability and Revision Identification, PhD thesis, Department of Computer Science, University of Western Bohemia, Pilsen, Czech Republic, 2003, http://www.kiv.zcu.cz/˜brada/research/thesis/. 6. Eclipse, an open development platform, http://www.eclipse.org/. 7. IBM, Rational Application Developer, http://www-128.ibm.com/developerworks/rational/products/rad/.
8. Java Community Process, Java Portlets Specification, http://jcp.org/aboutJava/communityprocess/final/jsr168/. 9. Object Management Group, CORBA Component Model, V3.0, http://www.omg.org/technology/documents/formal/components.htm. 10. Ronan Mac Laverty, ROBOCOP – Revised specification of framework and models, 2003, http://www.hitech-projects.com/euprojects/robocop/deliverables.htm 11. The OSGi Alliance. OSGi Service Platform Core Specification, Release 4, August 2005, available at http://www.osgi.org/. 12. Gernot Schmoelzer, Egon Teiniker, Christian Kreiner, Michael Thonhauser, Modeltyped Component Interfaces, In proceedings of 32nd EuroMicro conference, 0-76952594-6, 2006. 13. Clemens Szyperski. Component Software. ACM Press, AddisonWesley, 1998. 14. Sun Microsystems. Enterprise JavaBeansT M Specification, Version 2.1, 2003, available at http://java.sun.com/products/ejb/. 15. Sun Microsystems, Java Reflection API, http://java.sun.com/j2se/1.5.0/docs/guide/reflection/index.html. 16. L. Valenta, P. Brada. Automated generating of OSGi component versions. In proceedings of ECI 2006 conference, 80-8073-598-0, 2006. 17. L. Valenta, P. Brada. OSGi Component Substitutability Using Enhanced ENT Metamodel Implementation. Technical Report DCSE/TR-2006-05, Department of Computer Science and Engineering, University of West Bohemia, Pilsen, Czech Republic, 2006.