Ověřování nahraditelnosti EJB komponent Pavel Stuna 17. května 2005
Abstract Verification of EJB components substitutability The aim of this diploma thesis is to compare two Enterprise JavaBeans components (version 2.1) from the client-side view. It is assumed, that one of the components is to be replaced with the second one, so two versions of one component are compared. EJB component is thought to be some kind of software package, which provides some functionality and also has some requirements. This view of components is defined in ENT component meta-model. There is given a big emphasis on comparing Java classes, which are the main part of EJB components. Theory of subtyping is mentioned here, because it is connected with substitutability. The created program loads the components from JAR-files, compares them and generates a value, from which can be decided the possibility of substitutability. A XML document with detailed descriptions of differences is also generated as a secondary output.
Obsah 1 Úvod
1
2 Enterprise JavaBeans 2.1 Softwarové komponenty . . . . . . . . 2.2 J2EE . . . . . . . . . . . . . . . . . . . 2.3 Distribuované vícevrstvé aplikace . . . 2.4 J2EE komponenty . . . . . . . . . . . 2.5 JavaBeans × Enterprise JavaBeans . . 2.6 Kontejnery v J2EE . . . . . . . . . . . 2.7 Architektura EJB (verze 2.1) . . . . . 2.7.1 EJB kontejner . . . . . . . . . 2.7.2 Druhy EJB komponent . . . . 2.8 Struktura EJB komponenty . . . . . . 2.8.1 Deployment Descriptor . . . . 2.9 Session beans . . . . . . . . . . . . . . 2.10 Entity beans . . . . . . . . . . . . . . 2.10.1 Container Managed Persistence 2.10.2 Bean Managed Persistence . . 2.11 Message-driven beans . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
2 2 2 3 4 4 4 5 7 7 8 8 8 9 9 9 9
3 ENT meta-model 3.1 Podobnosti v současných komponentních modelech 3.2 Klasifikační systém . . . . . . . . . . . . . . . . . . 3.3 Traits (charakteristické rysy) . . . . . . . . . . . . 3.4 Kategorie rysů . . . . . . . . . . . . . . . . . . . . 3.5 ENT meta model EJB komponent . . . . . . . . . 3.5.1 Typy komponent . . . . . . . . . . . . . . . 3.5.2 Tags (značky) . . . . . . . . . . . . . . . . . 3.5.3 Traits (rysy) . . . . . . . . . . . . . . . . . 3.5.4 Příklad jednoduché EJB komponenty . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
10 10 10 11 11 12 12 13 13 15
4 Nahraditelnost komponent 4.1 Druhy rozdílů . . . . . . . . . 4.2 Striktní nahraditelnost . . . . 4.3 Podtypování . . . . . . . . . . 4.3.1 Rozšíření funkčnosti . 4.3.2 Podtypování funkcí . . 4.3.3 Podtypování atributů
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
17 17 18 18 19 19 19
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . . . . . . . . . . . .
. . . . . .
. . . . . .
5 Použité technologie 5.1 Programovací jazyk . . . . . . . . 5.2 Vývojové prostředí . . . . . . . . 5.3 Načtení komponenty . . . . . . . 5.3.1 Třída JarFile . . . . . . . 5.3.2 Document Object Model . 5.4 Introspekce . . . . . . . . . . . . 5.4.1 Třída ClassLoader . . . . 5.4.2 Třída Class . . . . . . . . 5.4.3 Třída Field . . . . . . . . 5.4.4 Třída Method . . . . . . . 5.4.5 Třída Constructor . . . . 5.5 J2EE server . . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
21 21 21 22 22 22 23 23 23 23 23 23 23
6 Implementace 6.1 Podtypové relace tříd v Javě . . . . . 6.1.1 Primitivní datové typy . . . . . 6.1.2 Klíčové slovo void . . . . . . . 6.1.3 Pole . . . . . . . . . . . . . . . 6.1.4 Třídy . . . . . . . . . . . . . . 6.2 Podtypové relace v ENT meta-modelu 6.2.1 Tagy . . . . . . . . . . . . . . . 6.2.2 Rysy (traits) . . . . . . . . . . 6.3 Důležité algoritmy . . . . . . . . . . . 6.3.1 Porovnání tříd . . . . . . . . . 6.3.2 Operace nad množinou rozdílů 6.3.3 Načtení EJB komponent . . . . 6.3.4 Porovnání EJB komponent . . 6.4 Výsledek porovnání EJB komponent . 6.4.1 Příklad výsledku porovnání . . 6.5 Technické nároky programu . . . . . . 6.6 Ověření funkčnosti . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
25 25 25 26 27 27 27 27 29 31 31 39 40 40 41 41 42 43
7 Struktura programu 7.1 cz.zcu.stuna.beancmp . . . . . 7.2 cz.zcu.stuna.beancmp.classcmp 7.3 cz.zcu.stuna.beancmp.entejb . . 7.4 cz.zcu.stuna.beancmp.utilities .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
44 44 45 45 45
. . . .
. . . . . . . . . . . .
. . . .
. . . . . . . . . . . .
. . . .
. . . .
8 Závěr
47
Literatura
III
Přílohy
IV
Prohlášení Prohlašuji, že jsem diplomovou práci vypracoval samostatně a výhradně s použitím citovaných pramenů. V Plzni dne 20.5.2005 Pavel Stuna,
1
Kapitola 1
Úvod Podnikovou softwarovou aplikaci můžeme rozložit do tří vrstev. Nejnižší vrstva zajišťuje úschovu dat (například do databáze nebo souboru). Nejvyšší vrstvou je prezentační logika, která slouží k zobrazování uživatelského rozhraní. Mezi těmito dvěma vrstvami se nachází aplikační logika, která představuje výkonný kód, provádějící převod dat z databázové vrstvy do prezentační logiky. Enterprise JavaBeans (EJB) je komponentní architektura, tvořící aplikační logiku. V této práci se budu zabývat EJB architekturou verze 2.1 (v současnosti je nejnovější verze 3.0). Softwarová komponenta je programový balík, který poskytuje sadu funkcí k řešení společného problému. Zapouzdřuje softwarovou funkcionalitu, umožňuje vícenásobné použití a lze ji použít s více komponentami, čímž se vytvoří softwarová aplikace. Jedna aplikace obsahuje zpravidla několik komponent, které mezi sebou komunikují přes daná rozhraní. Cílem práce je navrhnout a vytvořit program, který porovná dvě EJB komponenty. Z výsledků porovnání by mělo být – mimo jiné – jasné, zda jedna z komponent může nahradit druhou, aniž by se změnila funkcionalita celé aplikace, jejíž součástí je první z komponent. Nejčastěji se toto porovnání vyžaduje při pořízení novější verze komponenty. V úvodních kapitolách se budu snažit seznámit čtenáře s architekturou EJB. Poté se budu věnovat popisu ENT komponentního meta-modelu, který rozlišuje součásti komponenty podle toho, zda něco poskytují nebo vyžadují. V další kapitole se zmíním o teorii podtypových relací (podtypování, anglicky subtyping), která silně souvisí s nahraditelností. Dále popíši technologie použité na vývoj programu a poslední kapitoly budou patřit popisu implementace programu na porovnávání EJB komponent. V této práci se vyskytují různé anglické technické výrazy, které jsem se – pokud to nepůsobí nezvykle – rozhodl přeložit. U těchto slov ale vždy při prvním výskytu uvedu i jeho anglický tvar. Nicméně existují termíny, které jsem se rozhodl nepřekládat vůbec, protože jejich anglické názvy jsou v oboru vžité a jejich překlad do češtiny by mohl způsobit nedorozumění (v lepším případě pobavení čtenáře). Jsou to například termíny „Enterprise JavaBeansÿ nebo „deployment descriptorÿ.
2
Kapitola 2
Enterprise JavaBeans Enterprise JavaBeans je komponentní architektura, vyvíjená firmou Sun Microsystems.
2.1
Softwarové komponenty
Softwarová komponenta je programový balík, který poskytuje množinu služeb, přístupných přes jedno nebo více rozhraní a je navrhována, implementovaná a testovaná zvlášť před začleněním do softwarové aplikace. Softwarové komponenty musí umět mezi sebou komunikovat, aby mohly být použitelné v jedné aplikaci. Existuje několik standardů pro vývoj softwarových komponent. Jedním z nich je například CORBA (Common Object Request Broker Architecture), který vyvinulo sdružení OMG (Object Management Group). CORBA je otevřená, platformově nezávislá architektura a infrastruktura, kterou počítačové aplikace používají ke spolupráci v síťovém prostředí. Ke komunikaci se využívá protokol IIOP (Internet Inter-Orb Protocol). Více informací najdete v [OMG]. Další z komponentových architektur je COM 1 (Component Object Model). Přestože byla implementována na několika platformách, nejvíce se používá v operačních systémech Microsoft Windows. Další informace – viz [COM]. Třetí komponentovou technologií je EJB (Enterprise JavaBeans) firmy Sun. Touto technologií se budu dále zabývat více podrobněji.
2.2
J2EE
Protože Enterprise JavaBeans komponenty jsou součástí architektury J2EE, pokusím se zde krátce tuto architekturu představit. V dnešní době se vytváří mnoho distribuovaných podnikových aplikací, a proto je důležité zvýšit rychlost, bezpečnost a spolehlivost tzv. server-side technologií, na nichž jsou většinou tyto aplikace založeny. Samozřejmou snahou je vyvíjet je rychleji, jednodušeji a za méně peněz. Proto se používají technologie, založené na tří-vrstvém modelu – například Java 2 Platform, Enterprise Edition (J2EE). 1 Předchůdcem této technologie bylo OLE (Object Linking and Embedding) a COM bude pravděpodobně nahrazen technologií Microsoft .NET.
Distribuované vícevrstvé aplikace
3
Tato platforma rozšiřuje standardní Javu (J2SE – Java 2 Platform, Standard Edition) o nástroje pro práci s vícevrstevnými aplikacemi – třídy a rozhraní pro vývoj aplikace, aplikační server, webový server a databázový server pro běh těchto aplikací. J2EE poskytuje komponentový přístup k návrhu, vývoji a nasazení enterprise (podnikových) aplikací, což vede ke snížení nákladů na návrh a vývoj aplikace. J2EE nabízí vícevrstevný model distribuovaných aplikací, znovupoužitelných komponent, jednotný bezpečnostní model, flexibilní správu transakcí a podporu webových služeb. J2EE je navíc nezávislá na platformě (stejně jako Java samotná).
2.3
Distribuované vícevrstvé aplikace
Platforma J2EE používá model distribuovaných mnohovrstevnatých aplikací. Aplikační logika je rozdělena podle funkcí na komponenty, různé komponenty tvořící aplikaci jsou instalovány na různých strojích (počítačích) podle toho, do které vrstvy komponenta patří. Na obrázku 2.1 jsou znázorněny dvě aplikace rozdělené do tří nebo čtyř vrstev.
Obrázek 2.1: Vícevrstvé aplikace
• Klientská vrstva – komponenty této vrstvy běží na klientském počítači. • Webová vrstva – komponenty běží na J2EE serveru. • Business vrstva – komponenty běží na J2EE serveru. • Vrstva podnikového informačního systému (Enterprise IS - EIS) – běh na EIS serveru. Tento tří- nebo čtyř-vrstevný model rozšiřuje klasický model klient/server, když vkládá více-vláknový aplikační server mezi klientskou aplikaci a úložiště dat.
Kontejnery v J2EE
2.4
4
J2EE komponenty
J2EE aplikace je tvořena z komponent. Komponenta obsahuje potřebné třídy, rozhraní a další soubory; každá komponenta komunikuje s ostatními v dané aplikaci. Specifikace J2EE určuje 3 druhy komponent: • Klient aplikace nebo applet běžící na klientském počítači. • Java Servlet nebo JavaServer Pages (JSP) – tyto komponenty běží na webovém serveru. • Enterprise JavaBeans (EJB, enterprise beans) – business komponenty, běží na serveru. J2EE komponenty jsou naprogramovány v jazyku Java a jsou překládány stejným způsobem jako jakýkoliv jiný program v Javě. Rozdíl mezi komponentami a „normálnímiÿ třídami je ten, že J2EE komponenty jsou sestaveny do aplikace, je ověřena jejich správnost podle J2EE specifikace a nakonec jsou nasazeny do výroby, kde jsou spouštěny a řízeny J2EE serverem.
2.5
JavaBeans × Enterprise JavaBeans
Existuje ještě technologie JavaBeans [JB], která ale s Enterprise JavaBeans nemá mnoho společného. JavaBeans je sice také komponentový architektura, ale komponenty zde hrají roli pouze při vývoji desktopové aplikace, nejsou „instalovatelnéÿ do aplikačního serveru. Nejčastěji se JavaBeans používají při tvorbě grafického uživatelského rozhraní. JavaBeans komponenty jsou součástí prezentační vrstvy, zatímco EJB komponenty tvoří aplikační (business) vrstvu.
2.6
Kontejnery v J2EE
Kontejner (jako součást J2EE) je rozhraní mezi komponentou a samotnou implementací aplikačního serveru (která se výrobce od výrobce liší). Předtím než může být komponenta spuštěna, musí být vložena (instalována) do kontejneru. Před vložením do kontejneru se nastaví pro každou komponentu její specifické vlastnosti i vlastnosti celé aplikace. Do těchto vlastností patří například nastavení zabezpečení a správy transakcí, nastavení jmenných a adresářových služeb (Java Naming and Directory Interface – JNDI, viz [JNDI]) a nastavení vzdáleného připojení. Pro každý druh komponentové technologie (webová, EJB, klientská komponenta) existuje jeden kontejner. Na obrázku 2.2 je znázorněna architektura J2EE z pohledu kontejnerů. J2EE se skládá z těchto částí: • J2EE server – běhové prostředí J2EE. J2EE server se skládá z EJB a webového kontejneru. • EJB kontejner – má na starosti spouštění a běh EJB komponent J2EE aplikace. • Webový kontejner – řídí spouštění a běh JSP skriptů a servletů J2EE aplikace.
Architektura EJB (verze 2.1)
5
• Kontejner aplikačního klienta – řídí spouštění a běh komponent aplikačního klienta. Aplikační klienti a jejich kontejner běží na straně klientského počítače. • Appletový kontejner – Má na starosti spouštění a běh appletů. Skládá se z webového prohlížeče a z Java Plugin, obojí běží spolu na straně klientského počítače.
Obrázek 2.2: Kontejnery v J2EE Kontejnery mají na starost i další (neovlivnitelné) vlastnosti. Je to například životní cyklus komponenty, sdílení připojení k databázi, perzistenci (stálost) dat a přístup k aplikačnímu rozhraní J2EE.
2.7
Architektura EJB (verze 2.1)
EJB specifikace je jedno z aplikačních programových rozhraní (API) definovaných v J2EE. EJB komponenty poskytují funkce pro: • Vzdálenou komunikaci s CORBA • Perzistenci dat • Transakční zpracování • Souběžný přístup • Práce s událostmi • Jmenné a adresářové služby (JNDI) • Bezpečnost EJB komponenty lze charakterizovat: • EJB komponenta typicky obsahuje business (aplikační) logiku, která pracuje s nějakými (podnikovými – enterprise) daty.
Architektura EJB (verze 2.1)
6
• Instance EJB komponenty jsou vytvářeny a spravovány v době běhu (runtime) kontejnerem. • EJB komponenta může být upravena v době tzv. nasazení (instalace do kontejneru) editací atributů prostředí. • Atributy pro správu transakcí a bezpečnostní atributy jsou odděleny od zdrojového kódu třídy komponenty (ejb-class). To umožňuje tyto atributy upravovat ještě před nasazením komponenty do kontejneru. • Přístup klienta ke komponentě je zprostředkován kontejnerem, ve kterém je komponenta nasazena. • Mohou se vyskytovat specializované EJB kontejnery, poskytující nějaké nové služby navíc. • EJB komponenta může být vložena do aplikace, aniž by bylo potřeba jejího zdrojového kódu nebo rekompilace.
Obrázek 2.3: RMI - Remote Method Invocation Každá EJB komponenta obsahuje tzv. ejb-třídu (ejb-class), což je samotná implementace funkčnosti komponenty. Kontejner ji použije při vytvoření instance této komponenty. Dále EJB komponenty 2 obsahují dvě rozhraní – home a remote. Rozhraní home slouží k vytváření, rušení a vyhledávání komponenty. Rozhraní remote je používáno klientem (klientskou aplikací, servletem nebo jinou EJB komponentou) pro přístup k business metodám. Tato rozhraní slouží jako zástupce (spojka) skutečné implementace. Klient volá metody zástupce, který pošle parametry serveru a ten pošle výsledek volání metody zpět klientovi. Toto se odehrává v rozhraní RMI (Remote Method Invocation), které slouží ke vzdálenému volání metod [RMI]. Na obrázku 2.3 je v levém obdélníku znázorněn klient, v pravém je server (zde představovaný kontejnerem – viz. dále). Komunikace mezi nimi probíhá přes rozhraní RMI a JMS [JMS]. JMS (Java Message Service) je rozhraní asynchronní komunikace pomocí zpráv. 2 Kromě komponent typu message-driven beans. Ty jsou řízené zasíláním zpráv, proto tato rozhraní nepotřebují.
Architektura EJB (verze 2.1)
7
EJB komponenta může obsahovat tzv. lokální rozhraní. Ta jsou dvě a odpovídají rozhraním home a remote. Označují se local-home a local. Tyto rozhraní slouží k urychlení práce. Používají se od verze EJB 2.0 a jsou nepovinná (volitelná). Pokud klient (tím může být i jiná komponenta) běží ve stejném virtuálním stroji jako aplikační server, může se použít pro vytvoření instance komponenty rozhraní local-home. Poté se pro volání metod komponenty využívá rozhraní local. Při tomto volání se nevytváří spojky (stubs) a při volání metod se parametry předávají odkazem (při použití remote rozhraní se parametry předávají hodnotou – posíláním zpráv, viz. obrázek 2.3).
2.7.1
EJB kontejner
Do EJB kontejneru se vkládají EJB komponenty. Do jednoho kontejneru může být vloženo více komponent, tyto komponenty jsou v kontejneru spravovány spouštěny a řízeny. EJB kontejner může komponentu „pasivovatÿ (odložit do perzistentního úložiště) nebo naopak „aktivovatÿ. Kontejner může také spravovat perzistenci entitních komponent (viz dále) nebo řídit transakční zpracování. Kontejner je odpovědný za zpřístupnění home rozhraní skrz JNDI rozhraní. Díky tomu může klient vyhledat určitou komponentu pomocí jejího JNDI názvu. Na obrázku 2.4 je zobrazen EJB kontejner z pohledu klientské aplikace. „EJB objektyÿ jsou jednotlivé instance jedné komponenty.
Obrázek 2.4: Pohled klienta na EJB kontejner
2.7.2
Druhy EJB komponent
V EJB architektuře existují 3 druhy komponent. Session Beans (dočasné komponenty) mají relativně krátkou dobu života (proto dočasné) a představují nějakou akci. Entity Beans (entitní komponenty) poskytují objektový pohled na data (entity) z databáze. Message-Driven Beans (událostmi řízené komponenty) se spouští na základě přijetí zprávy od klienta. Stejně jako session beans představují nějakou akci.
Session beans
2.8
8
Struktura EJB komponenty
EJB komponenta je distribuována v Java archívu. Uvnitř archívu jsou třídy komponenty (rozhraní home, remote, ejb-class, . . . ) a adresář META-INF, který musí obsahovat manifest (soubor MANIFEST.MF) a soubor ejb-jar.xml, neboli deployment descriptor. Tento soubor obsahuje informace o komponentě a lze díky němu upravit specifické vlastnosti komponenty bez nutnosti rekompilace tříd komponenty. Takto uložená komponenta může být nasazena na jakýkoliv aplikační server, který podporuje technologii EJB. Informace nutné pro běh na konkrétním aplikačním serveru se uloží do adresáře META-INF. Například pro kontejner serveru JBoss je to jboss.xml, pro server firmy Sun je to sun-ejb-jar.xml. Tyto soubory většinou obsahují názvy komponent, které jsou uloženy v archívu a jejich JNDI názvy.
2.8.1
Deployment Descriptor
Deployment Descriptor je soubor, ve kterém jsou v XML formátu uloženy informace o EJB komponentě. Některé z těchto informací se mohou měnit a tím se částečně mění i funkčnost komponenty. Lze například změnit práva pro přístup ke komponentě nebo jejím metodám. Ostatní informace mají informativní charakter. Z deployment descriptoru lze zjistit typ komponenty, jaké další komponenty jsou vyžadovány nebo jakým způsobem je řešeno spojení s databází (u entitních komponent).
2.9
Session beans
Tyto komponenty slouží k zapouzdření aplikační (business) logiky. Třída této komponenty musí implementovat rozhraní javax.ejb.SessionBean. Pro správu instancí SB (session beans) kontejnerem je velmi důležité, zda si instance udržuje vlastní stav zpracování nebo ne. Tato informace je uložena v deployment descriptoru. V zásadě platí, že stavové komponenty jsou skutečně vytvářeny pro každého klienta zvlášť (vztah 1:1), zatímco bezstavové komponenty mohou být sdíleny více klienty (tzv. pooling), jedna bezstavová komponenta může tedy obsluhovat několik klientů. Stavové komponenty mohou být v určitém stavu uloženy do perzistentního úložiště, odkud mohou být opět načteny. Pokud je instance session bean komponenty využívána delší dobu, může být pasivována, tj. dočasně uložena do perzistentního úložiště. Tím může kontejner ušetřit paměť. Jestliže přijde do kontejneru požadavek na uloženou komponentu, kontjener ji aktivuje (obnoví). Před těmito činnostmi jsou volány metody komponenty ejbPassivate() a ejbActivate(). Do těchto metod lze napsat ošetření dat, která nemohou být uložena (například připojení k databázi). Obě metody jsou součástí i entitních komponent.
Message-driven beans
2.10
9
Entity beans
Entitní komponenta reprezentuje objektově orientovaný pohled na libovolnou entitu uloženou v perzistentním (trvalém) úložišti (v relační databázi) nebo na entitu implementovanou v existující podnikové aplikaci. Doba života těchto komponent je dána existencí odpovídajících záznamů (entit) v databázi. Po celou dobu své existence se tyto komponenty nachází v prostředí kontejneru, který pro ně zajišťuje bezpečnost, řízení souběžného přístupu, transakcí, perzistence a další služby. Třída entitní komponenty musí implementovat rozhraní javax.ejb.EntityBean. V této třídě musí být implementovány metody ejbCreate (vytvoří novou instanci), ejbPostCreate (zde se určí další vlastnosti instance, například primární a cizí klíč), ejbActivate, ejbPassivate, ejbRemove (odstranění dat z databáze), ejbLoad a ejbStore (synchronizace instance s její perzistentní podobou), ejbFind (vyhledání instance).
2.10.1
Container Managed Persistence
Perzistence dat entitních komponent může být zajištěna buď kontejnerem nebo vývojářem komponenty (ve zdrojovém kódu komponenty). Pokud je perzistence řízena kontejnerem, vývojář komponenty se nemusí starat o připojení k databázi, pouze se v deployment descriptoru určí namapování dat z databáze do entitní komponenty a o ostatní se postará EJB kontejner. Dále se u tohoto druhu komponent vytváří tzv. abstraktní schéma perzistence, které určuje vztahy s ostatními entitními komponentami. Abstraktní schéma je také uloženo v deployment descriptoru.
2.10.2
Bean Managed Persistence
Perzistence těchto komponent je zajištěna autorem komponenty ve zdrojovém kódu komponenty (ve třídě ejb-class). Vývojář je tedy plně odpovědný za připojení k databázi (například použitím JDBC) a přístup k datům, který implementuje v metodách ejbCreate, ejbRemove, ejbFind, ejbLoad a ejbStore a případně i v business metodách třídy komponenty.
2.11
Message-driven beans
Tyto komponenty jsou novinkou v EJB architektuře od verze 2.0. Jsou to komponenty řízené (aktivované) zprávami. Proto tyto beany nemají vzdálené ani home rozhraní, veškerá komunikace probíhá díky JMS systému pro zasílání zpráv. Message-driven komponenta je konzumentem zpráv, klient jí zprávy zasílá. Všechny instance jedné message-driven komponenty jsou ekvivalentní, a proto příchozí požadavek klienta může být zpracován libovolnou instancí. Třída messagedriven komponenty musí implementovat rozhraní javax.ejb.MessageDrivenBean.
10
Kapitola 3
ENT meta-model ENT meta-model je komponentní model, který definoval P.Brada v [BR03] ([BR04]). Komponentní model [BR04] definuje, mimo jiné, druhy struktur, které jsou viditelné na rozhraní komponenty a jejich konkrétní syntaxi. Komponentní modely jsou implementovány v komponentních rámcích (frameworks); příkladem takového rámce je třeba CORBA nebo EJB. Komponentní meta-model definuje slovník a struktury, ze kterých jsou odvozeny schopnosti konkrétního komponentního modelu. Některé meta-modely jsou tvořeny „na zelené louceÿ, jiné vznikly odvozením z běžných částí už existujících komponentních modelů.
3.1
Podobnosti v současných komponentních modelech
Jak už bylo zmíněno, existuje více komponentních modelů (CORBA, COM, EJB). A každý z těchto modelů používá jiná pojmenování pro víceméně stejné (nebo podobné) pojmy. Specifikace daného rozhraní komponenty se skládá z prvků (elementů), které definují schopnosti komponenty přístupné zvenku. Většina těchto prvků je jednoznačně pojmenována (v rozsahu rozhraní komponenty) a mohou být rozlišeny podle mnoha charakteristik (vlastností). Elementy můžeme rozlišovat například podle toho, zda reprezentují data nebo funkcionalitu, zda je element ve formě jedné položky nebo struktury nebo zda je element konstantou, instancí nebo typem. Elementy lze také rozlišit podle toho, v jaké části životního cyklu komponenty jsou používány.
3.2
Klasifikační systém
Nalezené charakteristiky můžeme formalizovat do klasifikačního systému. Objekt se klasifikuje tzv. ENT klasifikátorem, což je uspořádaná osmice (nature,
Kategorie rysů
11
kind, role, granularity, construct, presence, arity, lifecycle), kde každý prvek reprezentuje podmnožinu (většinou jedno-prvkovou) jemu odpovídající množiny. Tyto množiny jsou vytvořeny z nalezených charakteristik: • N ature = {syntax, semantics, nonf unctional}, • Kind = {operational, data}, • Role = {provided, required, neutral}, • Granularity = {item, structure, compound}, • Construct = {constant, instance, type}, • P resence = {mandatory, permanent, optional}, • Arity = {single, multiple}, • Lif ecycle = {development, assembly, deployment, setup, runtime} Z klasifikátoru lze tedy celkem jasně poznat význam a postavení klasifikovaného prvku v rozhraní komponenty. Tento klasifikační systém postačuje na většinu komponentních modelů, je však možné ho pro určitý model rozšířit nebo zúžit. Více v [BR03] a [BR04].
3.3
Traits (charakteristické rysy)
Aby mohl být komponentní model bližší lidskému vnímání, zavedeme tzv. charakteristické rysy komponenty (traits [BR04]). Rys je definován uspořádanou čtveřicí (název, metatyp, klasifikátor, množina hodnot), kde název je názvem charakteristického rysu (traitu), metatyp je „nadtypÿ všech prvků tohoto rysu (traitu), klasifikátor je uspořádaná n-tice, tvořená prvky množin z charakteristik definovaných v předcházející části. Množina hodnot je množina všech povolených hodnot rysu. Informace o meta-typu a o klasifikátoru jsou zjištěny z dřívější analýzy nebo z návrhu konkrétního komponentního modelu. Účelem je vytvořit kompletní, ale minimální množinu meta-typů a klasifikátorů, která může spolehlivě rozlišit požadované charakteristické rysy prvků.
3.4
Kategorie rysů
Různé rysy lze dále třídit do kategorií (category sets). Tímto tříděním lze ještě lépe zpřehlednit komponentní model. Můžeme například vytvořit dvě kategorie, kde do jedné kategorie se zařadí prvky s nějakou funkcionalitou a do druhé kategorie se zařadí prvky s datovým významem. ENT meta model je založen na roztřídění částí komponentního modelu do těchto tří kategorií podle charakteristiky Role. Komponenta obsahuje elementy, které komponenta: • Poskytuje (Exports, role ={provided}), • Vyžaduje (Needs, role ={required})
ENT meta model EJB komponent
12
• Vyžaduje i poskytuje (Ties, role ={provided, required}) A právě z počátečních písmen anglických názvů těchto kategorií je složen název ENT meta modelu (Exports-Needs-Ties). Analogii tohoto dělení lze vidět ve funkcích v programovacích jazycích – funkce poskytuje nějaký výstup (Exports), požaduje nějaké parametry (Needs) a některé parametry mohou být současně výstupními parametry (Ties, například ukládání výsledků do parametrů typu ukazatel). Díky tomuto rozdělení se tedy lze dívat na komponenty jako na funkce, což může být užitečné právě při porovnávání komponent.
3.5
ENT meta model EJB komponent
V této části bude specifikován komponentní model Enterprise JavaBeans na základě dělení do kategorií Exports, Needs a Ties (ENT). Podrobnější informace o tomto modelu viz [BR04]. Tagy (tags) jsou určeny názvem a množinou povolených hodnot, odkazy na zdroje odkazují na zdrojový kód komponenty (deployment descriptor nebo třídu), kde je trait nebo tag definován.
3.5.1
Typy komponent
Meta-model Enterprise JavaBeans definuje tři typy komponent: SessionBean, EntityBean a MessageDrivenBean. Pro stručnost jejich definicí se deklarují následující dvě množiny definic tagů a traitů, společných všem typům: tagsetcommon = security id traitsetcommon = Tprovided ∪ Trequired , kde Tprovided = { business interf aces, home interf aces, security roles} a Trequired = { business ref erences, msg destination ref erences, web service ref erences, home ref erences, env entries, resource managers, resource env ref erences, timer service} SessionBean tags: tagsetcommon ∪ {state, transaction} definice množiny rysů: traitsetcommon ∪ { web service endpoint} EntityBean tags: tagsetcommon ∪ {persistence, reentrancy, schema} definice množiny rysů: traitsetcommon ∪ {attributes} MessageDrivenBean tags: tagsetcommon ∪ {transaction, msg type} definice množiny rysů: traitsetcommon ∪ { message consumed, msg activation}
ENT meta model EJB komponent
3.5.2
13
Tags (značky)
msg type= Identif iers, standardně javax.jms.MessageListener Java rozhraní (interface) pro příjem zpráv. Zdroj: deployment descriptor (element message type). persistence = {container, bean}, standardně ² (pouze u entitních komponent) Zdroj: deployment descriptor (element persistence). reentrancy = {reentrant, non reentrant}, standardně reentrant Mód reentrance (znovu-vstoupení). Zdroj: deployment descriptor (element reentrant). schema = Identif iers, standardně ² Název abstraktního schématu. Užívá se pouze, když persistence = container. Zdroj: deployment descriptor (element abstract-schema-name). security id = {use caller, run as}, standardně use caller Jakou totožnost použít pro ověření. Zdroj: deployment descriptor (element security-identity). state = {statef ul, stateless}, standardně ² Způsob chování Session Bean komponenty (stavová nebo bezstavová). Zdroj: deployment descriptor (element session-type). transaction = {container, bean}, standardně ² Správce transakcí (kontejner nebo komponenta). Zdroj: deployment descriptor (element transaction-type).
3.5.3
Traits (rysy)
attributes – perzistentní data entitní komponenty. meta-typ = attribute klasifikátor = ({syntax}, {data}, {provided}, {item}, {instance}, {permanent}, {multiple}, Lifecycle) tags: status = {normal, primary key} Pozn.: Zdroj: deployment descriptor pro CMP entitní komponenty (elementy cmp field a primkey field). business interfaces – základní funkcionalita komponenty. meta-typ = interf ace klasifikátor = ({syntax}, {operational}, {provided}, {structure}, {type}, {mandatory}, {multiple}, {development, assembly, deployment, runtime}) tags: locality = {local, remote} Pozn.: Zdroj: deployment descriptor (elementy remote a local), zdrojový kód. business references – rozhraní použitých komponent v dané komponentě. meta-typ = interf ace klasifikátor = ({syntax}, {operational}, {required}, {structure}, {instance},
ENT meta model EJB komponent
14
{permanent}, {single}, Lifecycle) tags: locality = {local, remote} Pozn.: Zdroj: deployment descriptor (části elementů ejb-ref a ejb-local-ref). env entries – proměnné prostředí, které komponenta vyžaduje. meta-typ = structureenv entry type klasifikátor = ({syntax}, {data}, {required}, {item}, {instance}, {permanent}, {single}, Lifecycle) tags: ∅ Pozn.: Zdroj: deployment descriptor (element env-entry), typem proměnné musí být primitivní datový typ (například java.lang.Integer). home interfaces – správa životního cyklu komponenty. meta-typ = interf ace klasifikátor = ({syntax}, {operational}, {provided}, {structure}, {type}, {mandatory}, {multiple}, {development, assembly, deployment, setup}) tags: locality = {local, remote} Pozn.: Zdroj: deployment descriptor (elementy home a local-home). Přestože rozhraní typu home mohou být použity v době běhu programu (run-time), jejich nejdůležitější role je v době nastavení (set-up). home references – přístup ke komponentám, které komponenta používá. meta-typ = interf ace klasifikátor = ({syntax}, {operational}, {required}, {structure}, {instance}, {permanent}, {single}, {development, assembly, deployment, setup}) tags: locality = {local, remote} bean = Identifiers Pozn.: Zdroj: deployment descriptor (elementy ejb-ref a ejb-local-ref). msg activation – komunikační aktivační vlastnosti komponent typu messagedriven. meta-typ = map klasifikátor = ({syntax}, {data}, {provided}, {item}, {instance}, {permanent}, {multiple}, {development, deployment}) tags: ∅ Pozn.: Zdroj: deployment descriptor (element activation-config-property). msg consumed – názvy tříd zpráv, které komponenta přijímá. meta-typ = class klasifikátor = ({syntax}, {operational}, {provided}, {item}, {instance}, {permanent}, {multiple}, {development, assembly, runtime}) tags: ∅ Pozn.: Zdroj: Zdrojový kód (implementace metody onMessage()). msg destination references – cíle komunikace pomocí zpráv. meta-typ = structuremsg destination ref type klasifikátor = ({syntax}, {data}, {required}, {item}, {instance}, {permanent}, {single}, Lifecycle}) tags: ∅ Pozn.: Zdroj: deployment descriptor (element message-destination-ref).
ENT meta model EJB komponent
15
resource env references – odkazy na potřebné zdroje. meta-typ = map klasifikátor = ({syntax}, {data}, {required}, {item}, {instance}, {permanent}, {single}, {development, deployment, setup, runtime}) tags: ∅ Pozn.: Zdroj: deployment descriptor (element resource-env-ref). resource managers – správci zdrojových prostředků. meta-typ = map klasifikátor = ({syntax}, {operational}, {required}, {structure}, {instance}, {permanent}, {single}, {development, deployment, setup, runtime}) tags: sharing = {shareable, unshareable} authentication = {application, container} Pozn.: Zdroj: deployment descriptor (element env-entry), typem proměnné musí být primitivní datový typ (například java.lang.Integer). security roles – bezpečnostní role spojené s business rozhraními. meta-typ = Identif iers klasifikátor = ({nonfunctional}, {operational}, {provided}, {item}, {constant}, {permanent}, {na}, {assembly, deployment}) tags: ∅ Pozn.: Zdroj: deployment descriptor (element security-role-ref). timer service – reakce na časové události. meta-typ = interf ace klasifikátor = ({syntax}, {operational}, {required}, {structure}, {type}, {permanent}, {single}, {development, setup, runtime}) tags: ∅ Pozn.: Zdroj: Zdrojový kód. web service endpoint – přístup k funkcionalitě pomocí webových služeb. meta-typ = interf acesei klasifikátor = ({syntax}, {operational}, {provided}, {structure}, {type}, {permanent}, {multiple}, {development, assembly, deployment, runtime}) tags: ∅ Pozn.: Zdroj: deployment descriptor (element service-endpoint), pouze u bezstavových (stateless) komponent typu session bean. web service references – odkazy na používané webové služby. meta-typ = structureservice ref group klasifikátor = ({syntax}, {operational}, {required}, {structure}, {instance}, {permanent}, {multiple}, {development, deployment, setup, runtime}) tags: ∅ Pozn.: Zdroj: deployment descriptor (element service-ref).
3.5.4
Příklad jednoduché EJB komponenty
Následuje příklad jednoduché session komponenty. Zde je uveden deployment descriptor komponenty Fyzika2, která používá další komponentu Matematika2
ENT meta model EJB komponent
(viz element ejb-ref): <enterprise-beans> <session> <ejb-name>Fyzika2
fyzika2.interfaces.Fyzika2Home fyzika2.interfaces.Fyzika2 <ejb-class>fyzika2.ejb.Fyzika2Session <session-type>Stateless
Container <ejb-ref> <ejb-ref-name>Matematika2 <ejb-ref-type>Session
matematika2.interfaces.Matematika2Home matematika2.interfaces.Matematika2 <ejb-link>Matematika2 ... Značky (tags): state = stateless, transaction = container Rysy (traits): business interfaces = {(², f yzika2.interf aces.F yzika2, {(locality, remote)}, ∅;)} home interfaces = {(², f yzika2.interf aces.F yzika2Home, {(locality, remote)}, ∅;)} business references = {(M atematika2, matematika2.interf aces. M atematika2, {(locality, remote)}, ∅;)} home references = {(M atematika2, matematika2.interf aces. M atematika2Home, {(locality, remote)}, ∅;)}
16
17
Kapitola 4
Nahraditelnost komponent Objekt je nahraditelný jiným objektem, pokud po nahrazení nedojde ke změně funkčnosti systému, ve kterém docházi k nahrazení. Na nový objekt, který má nahradit původní, můžeme klást různě přísná omezení. Podle toho rozlišujeme několik druhů nahraditelnosti – striktní (podtypová), kontextová nebo částečná [BR03]. Definice nahraditelnosti, ve které jsou zahrnuty pouze komponenty, které se přímo účastní nahrazení [WZ88]: Nahrazující komponenta (která je podtypem původní) by měla být použitelná všude, kde je očekávaná původní komponenta (která je nadtypem nahrazující), bez toho, že si klient všimne změny. Úkolem této práce bylo porovnání pouze na základě znalostí obou komponent, bez jejich dalšího kontextu. K dispozici máme pouze obě komponenty a nic jiného. Budeme tedy vyžadovat pouze striktní (podtypovou) nahraditelnost, kde nás okolí (kontext) komponenty nezajímá.
4.1
Druhy rozdílů
Pro zjištění možnosti nahraditelnosti musíme komponenty porovnat. Výsledkem porovnání (nejen komponent) je nalezený rozdíl (pokud nějaký je). V [BR03] byla vytvořena množina druhů rozdílů Dif f erences. Do této množiny jsem pro větší odlišení přidal navíc tři druhy – Insertion, Deletion a U nknown. Differences = {None, Insertion, Specialization, Deletion, Generalization, Mutation, Unknown} Rozdílem dvou objektů je pak prvek množiny Dif f erencies, který je generován funkcí dif f : • dif f (σ1 , σ2 ) = none, pokud σ1 = σ2 . (žádný rozdíl) • dif f (σ1 , σ2 ) = insertion, pokud σ1 není definován a σ2 je. (vložení) • dif f (σ1 , σ2 ) = specialization, pokud σ2 rozšiřuje vlastnosti σ1 (σ2 je podtypem σ1 ). (specializace)
Podtypování
18
• dif f (σ1 , σ2 ) = deletion, pokud σ2 není definován a σ1 je (smazání). • dif f (σ1 , σ2 ) = generalization, pokud σ1 rozšiřuje vlastnosi σ2 (σ1 je podtypem σ2 ). (generalizace) • dif f (σ1 , σ2 ) = mutation, pokud porovnání obsahuje příznaky specializace (nebo vložení) i generalizace (nebo smazání). (mutace) • dif f (σ1 , σ2 ) = unknown, pokud nelze objekty porovnat (například u rekurzivních datových typů (viz. dále). (neznámý rozdíl).
4.2
Striktní nahraditelnost
Nahrazující komponenta (C r ) může nahradit stávající (původní) komponentu (C c ), pokud její rozhraní poskytuje stejnou nebou rozšířenou funkčnost a požaduje stejné nebo obecnější vstupy. Přesná definice striktní nahraditelnosti je v [BR03]. Striktní nahraditelnost můžeme vyjádřit pomocí ENT meta-modelu (podle [BR03], s přidáním nových druhů rozdílů): Nahrazující komponenta s reprezentací ENT C r = {E r , N r , T r } je striktní náhradou za současnou komponentu C c = {E c , N c , T c }, pokud dif f (E c , E r ) = {none, insertion, specialization}, dif f (N c , N r ) = {none, deletion, generalization} a dif f (T c /A, T r /A) = {none, insertion, specialization}.
4.3
Podtypování
Podtypování je nejčastěji definováno „zákonem nahraditelnosti Barbary Liskovovéÿ (Liskov Substitutability Principle) [LI88]: „To co je požadováno, je něco jako následující vlastnost nahrazení: jestliže pro každý objekt o1 typu S existuje objekt o2 typu T takový, že v každém programu P s výskytem typu T se chování P při nahrazení o2 objektem o1 nezmění, pak S je podtypem T .ÿ Relace podtypování se značí S <: T (S je podtypem T ). Tento vztah můžeme vyjádřit i pomocí funkce dif f : dif f (S, T ) = generalization . Toto je pouze jedna z mnoha definic podtypování. Většina však ztotožňuje podtypování s nahrazením, to znamená že pokud a je podtypem b, a může b také kdykoliv nahradit.
Podtypování
4.3.1
19
Rozšíření funkčnosti
Pokud máme dvě třídy (objektové typy) S a T, kdy S má všechny atributy i funkce stejné jako T a navíc obsahuje libovolný počet nových metod či atributů, pak S je podtypem T. Jinak řečeno – S obsahuje novou funkčnost a zároveň poskytuje stejnou funkčnost jako třída T. Pokud tedy bude třída S použita v programu, který původně očekával na jejím místě původní třídu T, program bude bez problémů přeložen a navenek nepůjde poznat, že se jedná o novější třídu. Třída S tedy může nahradit třídu T.
4.3.2
Podtypování funkcí
Mějme dvě funkce stejného jména (například foo). Původní (první) funkce měla předpis R1 foo(P1 ), nahrazující (druhá) funkce má předpis R2 foo(P2 ), kde Ri je návratový typ funkce a Pi je typ parametru funkce. Návratové typy Funkce foo je volána na nějakém místě v nějakém programu s parametry typu P1 a poskytuje výsledek návratového typu R1 . Výsledek funkce se v programu ukládá do proměnné typu R1 . Aby tedy druhá funkce mohla nahradit funkci první, musí být z návratového typu druhé funkce možné odvodit objekt třídy R1 . A to lze jen tehdy, pokud R2 <: R1 (v objektových jazycích může potomek nahradit svého rodiče, ale opačně to nejde, proto – R2 je potomkem R1 ). Kdyby například program očekával jako výsledek třídu se dvěma atributy (veřejnými, například souřadnice x, y) a nová funkce by poskytovala výsledek pouze s jedním atributem, který je stejný jako jeden ze dvou atributů původního návratového typu (například x), došlo by ke ztrátě informace (nemáme informace o očekávané souřadnici y), proto by nová funkce nemohla nahradit funkci původní. Pro návratové typy funkcí tedy platí, že nová funkce musí poskytovat buď výsledek stejného typu jako původní funkce nebo výsledek se stejnými informacemi a nějakými informacemi navíc (výsledek „s přidanou hodnotouÿ). Této vlastnosti se říká kovariance. Návratové typy funkcí jsou tedy kovariantní. Argumenty Opačné je to ale u argumentů funkcí. Nová funkce nesmí mít větší nároky na argumenty (vstupy) než původní funkce. V místě volání jsou totiž funkci předávány argumenty původního typu. Pokud by nová funkce požadovala argument typu „s přidanou hodnotou navícÿ, nešlo by nový typ změnit přetypováním. Této vlastnosti se říká kontravariance. Typy argumentů funkcí jsou kontravariantní.
4.3.3
Podtypování atributů
Všechny společné veřejně přístupné atributy obou tříd (původní i nové) musí být stejných typů. Veřejné atributy totiž mohou být čteny (podobnost s návratovým typem funkce), zároveň však do nich může být zapisováno (podobnost s argumenty funkcí). Jedině stejné typy jsou svými nadtypy i podtypy. Jestliže typy atributů budou různé, není možné rozhodnout, zda mezi nimi platí relace
Podtypování
20
podtypování nebo nadtypování. Při porovnání těchto dvou atributů by funkce dif f vrátila hodnotu mutation (mutace). Atributy jsou tedy invariantní. Více o podtypování v [SI02] a v [SI03].
21
Kapitola 5
Použité technologie V této kapitole se budu věnovat technologiím, které jsem se rozhodl využít při psaní programu. Objasním důvody pro výběr programovacího jazyka a vývojového prostředí.
5.1
Programovací jazyk
Protože komponenty se mají porovnávat přímo tak, jak jsou distribuovány (v tzv. Java archívu), nebudeme mít k dispozici zdrojový kód komponenty. K práci s přeloženým kódem Javy (tzv. byte-kód) existují v programovacím jazyku Java pomocné třídy, které tuto práci významně zjednodušují. Například pro načtení Java-archívu existuje třída JarFile, pro načtení informací o přeložené třídě existují třída ClassLoader a Class. Tyto třídy umožňují tzv. introspekci. Díky těmto okolnostem jsem pro implementaci porovnání EJB komponent zvolil programovací jazyk Java (J2SE verze 1.4.2 a platformu J2EE 1.4).
5.2
Vývojové prostředí
Program jsem vytvářel ve vývojovém prostředí Eclipse 3.0 [Ecl]. Toto prostředí jsem zvolil pro jeho výbornou správu zdrojových kódů (refactoring, doplňování kódu, atd.) a pro zabudovanou podporu nástroje pro kompilaci v Javě – programu Apache Ant [ANT]. Tento program je obdobou programu make, používaného pro kompilaci programů v jazyce C. Místo souboru Makefile se vytvoří soubor build.xml (standardně) a do něj se zapisují ve formátu XML úkoly, které má Ant provádět. Pomocí tohoto nástroje lze jednoduše vytvářet adresáře, kopírovat soubory, vytvářet JAR-soubory a samozřejmě kompilovat. Následuje příklad cíle programu Ant, který přeloží všechny zdrojové soubory z adresáře daném proměnnou src, uloží je do adresáře build a při kompilaci se použijí externí třídy uložené v adresáři j2eedir. Tento cíl compile závisí na jiném cíli init, který se automaticky provede před provedením compile. Soubory typu buildfile jsou díky XML dobře čitelné, narozdíl od souborů typu Makefile.
Načtení komponenty
22
<javac srcdir="${src}" destdir="${build}" classpath="${j2eedir}"/>
5.3
Načtení komponenty
V následujících odstavcích stručně popíšu třídy nebo rozhraní, která jsem použil pro načtení EJB komponenty.
5.3.1
Třída JarFile
EJB komponenty jsou distribuovány v jednom balíku, což je Java-archív, ve kterém jsou uloženy potřebné třídy, adresář META-INF a případně další potřebné soubory. K načtení Java-archívu (JAR) jsem využil třídu java.util.jar.JarFile.
5.3.2
Document Object Model
Pro načtení deployment descriptoru, který je XML dokumentem, jsem použil třídy a rozhraní implementující Document Object Model. DocumentBuilder Třída javax.xml.parsers.DocumentBuilder poskytuje metody pro rozpoznání elementů v XML dokumentu (tzv. parsování). Instance této třídy se vytváří ve třídě javax.xml.parsers.DocumentBuilderFactory. Document Rozhraní org.w3c.Document reprezentuje XML dokument, načtený (parsovaný) metodou parse() ve třídě DocumentBuilder. Document poskytuje metody pro přístup ke všem elementům XML dokumentu. Obsahuje metodu getDocumentElement(), která vrací kořenový element (rozhraní org.w3c.dom.Element). Rozhraní Document i Element jsou potomky rozhraní Node. Toto rozhraní představuje jeden uzel stromu dokumentu. Následuje ukázka načtení XML souboru. V příkladu je cesta k souboru uložena v proměnné path. Třída DocumentBuilderFactory slouží k vytvoření instance třídy DocumentBuilder a je ze stejného balíku. File f = new File(path); Document doc = null; DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); doc = builder.parse(f); } catch ( ...
J2EE server
5.4
23
Introspekce
Programovací jazyk Java umožňuje tzv. introspekci. To znamená, že objekt může díky introspekci získat informace o sobě, třídě, ze které je vytvořen nebo o své rodičovské třídě. Následuje stručný popis tříd, poskytujících funkce pro introspekci.
5.4.1
Třída ClassLoader
Objekt třídy java.lang.ClassLoader slouží k načítání tříd. Umožňuje načíst třídu ze souboru nebo z jakéhokoliv proudu bytů (zdrojem může být například jiná aplikace nebo proud bytů přijatý z Internetu).
5.4.2
Třída Class
Výsledkem načtení třídy je instance třídy java.lang.Class. Každý objekt v běžící Java aplikaci obsahuje odkaz na svou třídu (přesněji – na instanci třídy Class, obsahující informace o dané třídě). Primitivní datové typy (boolean, byte, int, . . . ) a klíčové slovo void jsou také reprezentovány jako instance třídy Class. Z instance třídy Class lze zjistit veškeré informace – všechny atributy (nejen globální (public)), metody, konstruktory, vnitřní třídy i instanci třídy ClassLoader, která danou třídu načetla.
5.4.3
Třída Field
Tato třída (java.lang.Field) poskytuje informace o atributu dané třídy – to znamená lze zjistit jméno atributu a jeho typ (třídu).
5.4.4
Třída Method
Třída java.lang.Method umožňuje získat informace o dané metodě. V instanci této třídy jsou uloženy informace o návratovém typu (třídě) metody, o všech argumentech (typech argumentů), o všech výjimkách, které metoda propaguje a samozřejmě také o názvu metody.
5.4.5
Třída Constructor
Tato třída (java.lang.Constructor) zapouzdřuje informace o daném konstruktoru. Z instance této třídy lze zjistit (podobně jako u třídy Method) typy argumentů a výjimky, které konstruktor propaguje. Třídy Field, Method a Constructor mají společného předka – třídu java.lang.AccessibleObject. Tato třída umožňuje nastavit přístupnost k danému objektu (atributu, metodě nebo konstruktoru).
5.5
J2EE server
Pro vyzkoušení porovnávaných komponent jsem zvolil dva aplikační servery – JBoss (verze 4.0) [JBOS] a server firmy Sun – Sun JavaTM System Application
J2EE server
24
Server Platform Edition 8. Oba dva používají J2EE verze 1.4. Servery jsem provozoval v prostředí operačního systému Windows XP. Tvorba komponent (vytvoření balíku s deployment descriptorem) pro server firmy Sun probíhá v grafickém uživatelském prostředí, čímž je na první pohled jednodušší. Komponenty pro server JBoss jsem vytvářel pomocí nástroje XDoclet [XDOC], který umožňuje automaticky vytvořit deployment descriptor (a další XML soubory) z informací (atributů), získaných ze zdrojového kódu třídy komponenty (rozhraní home a remote). Tyto informace se zapisují ve speciálním formátu do komentářů tříd (rozhraní), atributů a metod. Programátor se tedy může soustředit pouze na funkcionalitu komponenty. Navíc existuje plugin do prostředí Eclipse (JBoss Eclipse IDE), který umožňuje s pomocí XDoclet vytvářet komponenty pro aplikační server JBoss. Tento plugin navíc umožňuje vytvořit balík (JAR soubor), do kterého komponentu uloží ve správném formátu. J2EE servery jsou velmi náročné na operační paměť (ostatně jako většina Java aplikací). Můj počítač je bohužel vybaven „pouhýmiÿ 256 MB operační paměti, proto jsem se během spuštění aplikačního serveru setkal s častým odkládáním obsahu paměti na pevný disk (tzv. swapping). Jestliže jsem měl spuštěný navíc prostředí Eclipse, běh aplikací se zpomalil na neúnosnou míru.
25
Kapitola 6
Implementace Program, který jsem vytvořil, se nazývá BeanComparator. Může se používat buď z příkazového řádku nebo v jiném Javovském programu, pro který poskytuje potřebné aplikační rozhraní (výsledky porovnání se ukládají do instance třídy BeanDifferences). V následujících kapitolách popíšu jednotlivé kroky, vedoucí k dokončení implementace porovnání komponent. Nejdůležitějším z nich je nalezení podtypových relací mezi třídami v Javě a mezi rysy ENT meta-modelu. Dále jsem musel načíst informace o EJB komponentách do datových struktur (tříd) a na závěr jsem musel porovnat jednotlivé části (tagy a rysy) komponent.
6.1
Podtypové relace tříd v Javě
Protože třídy (rozhraní) jsou nejdůležitější součástí EJB komponenty (home, remote rozhraní, reference na jiné komponenty, atd. (viz. ENT meta-model)), musel jsem vytvořit porovnávač tříd a datových typů. Datové typy v Javě se dělí na dva druhy – primitivní datové typy a „standardníÿ Java třídy (potomci třídy Object).
6.1.1
Primitivní datové typy
V jazyku Java je definováno osm primitivních datových typů (tabulka 6.1). Ty se dělí na tři druhy – celočíselné, s plovoucí desetinnou čárkou a typ boolean. Typ boolean byte short int long char float double
Počet bitů ? 8 16 32 64 16 32 64
Znaménkový ? ano ano ano ano ne ano ano
Druh boolean celočíselný celočíselný celočíselný celočíselný celočíselný desetinný desetinný
Tabulka 6.1: Primitivní datové typy v Javě
Podtypové relace tříd v Javě
26
Typ boolean nabývá dvou hodnot – true a false. Proměnné typu boolean lze přiřadit explicitním přetypováním jakoukoliv číselnou hodnotu. Pak nulová hodnota znamená hodnotu false a libovolné nenulové číslo odpovídá hodnotě true. Pokud bychom přetypovali instanci třídy Object (nebo jakéhokoli potomky této třídy), hodnota null by odpovídala hodnotě false a jakákoliv jiná hodnota než null by odpovídala hodnotě true. Typ boolean by tedy mohl být jakýmsi nadtypem všech ostatních typů. Jenže v Javě je povoleno pouze implicitní přetypování typu boolean na sebe sama, proto je typ boolean mimo hierarchii podtypování. (boolean ≮: jiny typ) ∧ (jiny typ ≮: boolean) , kde jiny typ je jakýkoliv typ jiný než boolean. (Boolean není podtypem ani nadtypem žádného jiného typu. Nebo také dif f ( boolean, jiny typ) = mutation) Z celočíselných znaménkových typů (byte, short, int a long) už lze podtypovou hierarchii vytvořit. Pokud by program (klient) očekával na nějakém místě typ long, a my bychom chtěli tento typ nahradit jeho podtypem, klient nesmí poznat změnu. Nesmí dojít ke ztrátě informace (musí být povolená implicitní konverze), proto typ long může být nahrazen typem (podtypem) int (nebo short, byte). Tímto způsobem bychom mohli dále pokračovat pro hledání podtypu typu int a short. byte <: short <: int <: long Typ char je jediným neznaménkovým typem. Je šestnácti-bitový, tvoří tedy „horní polovinuÿ (čísla větší než 0) 32-bitového typu int. Konverze z typu int na typ char tedy proběhne bez ztráty informace (implicitní typová konverze je povolena). char <: int <: long Primitivní datové typy s plovoucí desetinnou čárkou (floating point) jsou v Javě dva – float a double. Typ float je podtypem typu double. f loat <: double Protože při konverzi celočíselného typu na typ desetinný (s plovoucí desetinnou čárkou) může dojít ke ztrátě přesnosti, neexistuje žádná další podtypová relace mezi těmito a ostatními typy.
6.1.2
Klíčové slovo void
Klíčové slovo void označuje prázdný nebo žádný datový typ. Používá se pouze pro označení metody, která nevrací žádnou hodnotu (obdoba tzv. „proceduryÿ v jazyce Pascal). Při introspekci metody, která vrací „typÿ void, příslušná metoda třídy Class vrátí instanci třídy Class, která má název void. Void lze tedy v těchto případech považovat za datový typ. Pro porovnání „typuÿ void jsem zavedl pravidla (druhy rozdílů): dif f (void, jiny typ) = mutation , kde jiny typ je jakýkoliv jiný typ než void.
Podtypové relace v ENT meta-modelu
27
Možná by se mohlo zdát, že pokud například původní metoda měla návratový typ void a nahrazující metoda má návratový typ int (jiný než void), jejich porovnáním by mohla být specializace (přidání funkčnosti). Jenže v opačném případě (původní metoda vracela int, nová metoda nevrací nic) by překladač hlásil chybu – výsledek původní funkce se ukládá v klientském programu do nějaké proměnné a nová funkce nevrací nic. V tomto případě musí být výsledkem porovnání mutace (klientský program s novou komponentou by nešel vůbec přeložit). Protože tato dvě porovnání by měla být symetrická, musí být dif f (jiny typ, void) = mutation. Pokud nová metoda (na rozdíl od původní) vrací nějakou hodnotu, je očekáváno, že se s ní bude dále pracovat (uloží se nebo se porovná). Klient ale žádnou hodnotu neočekává, proto nová funkčnost metody zůstává „nevyužitaÿ a výsledkem porovnání je mutace.
6.1.3
Pole
Jazyk Java neumožňuje omezení rozsahů pole (jako například jazyk ADA), proto pro porovnání dvou polí různých typů je podstatné pouze porovnání typů prvků obou polí. Nejprve se musí zjistit typ prvků pole (metoda getComponentType() ve třídě Class). Poté už lze oba typy porovnat klasickým způsobem. Pouze pole různých primitivních datových typů nelze nahradit vůbec – výsledkem jejich porovnání je tedy vždy mutation. Pokud se porovnává pole s „normálnímÿ typem nebo třídou (není polem), výsledkem je mutace.
6.1.4
Třídy
Standardní třídu (nebo rozhraní) lze díky introspekci rozložit na třídu, která obsahuje pouze primitivní datové typy (a void). Třída Class poskytuje metody pro zjištění typů atributů, návratových typů, typů argumentů funkcí a typů výjimek. Všechny tyto typy jsou reprezentovány instancemi třídy Class, a proto lze každý z těchto typů rozkládat (rekurzivně), dokud se nedostaneme na některý z primitivních datových typů. Pravidla pro porovnání primitivních datových typů už známe, zbývá tedy ještě určit pravidlo pro porovnání primitivního typu s datovým typem neprimitivním (se třídou nebo rozhraním). (prim typ ≮: trida) ∧ (trida ≮: prim typ)
6.2
Podtypové relace v ENT meta-modelu
V této kapitole popíši všechny nalezené podtypové relace mezi tagy a charakteristickými rysy (traits) z ENT meta-modelu a pokusím se odůvodnit, proč jsem se rozhodl některé části neporovnávat, případně proč mezi částmi neexistuje vztah na úrovni podtypování.
6.2.1
Tagy
msg type Tento tag určuje Java rozhraní pro příjem zpráv (viz. ENT meta-model). Tagy typu msg type se tedy porovnají jako normální Java třídy (rozhraní).
Podtypové relace v ENT meta-modelu
28
persistence Tento tag určuje, kdo se bude starat o perzistentní (stálá) data entitní komponenty. Může to být buď kontejner (container) nebo vývojář komponenty (bean), který musí ve třídě komponenty napsat kód pro připojení k úložišti dat a pro vytváření a rušení entit. Pokud se tyto tagy dvou komponent odlišují, výsledkem jejich porovnání je mutace. Perzistentní data jsou uložená v jiných databázích, proto nelze jedním typem nahradit druhý. dif f (container, bean) = mutation reentrancy Definuje, zda je komponenta znovu-spustitelná (reentrantní). Pokud je, umožňuje komponenta spouštět sebe samu přes jinou komponentu, kterou používá. Z toho lze tedy určit podtypovou relaci: reentrant <: non reentrant schema Abstraktní schéma definuje perzistentní atributy entitní komponenty a její vztahy s jinými komponentami (určení vazeb typu 1:N, M:N, atd.). Používá se u entitních komponent, řízených kontejnerem. Název abstraktního schématu je definován v deployment descriptoru a je používán v dotazech (jazyk EJB Query Language), používaných ve vyhledávajících metodách. Většinou odpovídá názvu samotné EJB komponenty, ale může být pojmenováno libovolně. Proto jsem se rozhodl tento tag neporovnávat. security id Tento tag určuje, zda bude mít uživatel při volání metod svojí identitu (use caller) nebo dostane přidělenu jinou (z definovaných v security roles). Tento tag nemá význam pro samotnou funkčnost komponenty (identitu může měnit deployer komponenty), proto nebude zahrnut do porovnání komponent. state Udává, zda je komponenta stavová (stateful) nebo bezstavová (stateless). Stavová komponenta má v každém okamžiku nějaký stav, rozšiřuje tedy vlastnosti bezstavové, proto mezi nimi platí vztah: statef ul <: stateless transaction Tento tag určuje, kdo bude zodpovědný za řízení transakcí. Tím může být buď kontejner nebo EJB komponenta. Mezi těmito dvěma druhy řízení transakce není žádný vztah, proto dif f (container, bean) = mutation
Podtypové relace v ENT meta-modelu
6.2.2
29
Rysy (traits)
attributes Určují perzistentní atributy entitní EJB komponenty. U kontejnerem řízené entitní komponenty jsou tyto atributy uloženy v deployment descriptoru. Protože se zde neudává datový typ atributu (pouze jeho název), porovnávají se atributy podle názvů – pokud jsou stejné názvy všech atributů, výsledek porovnání je none (bez rozdílu), jinak je to mutace. business interfaces Lokální (local) a vzdálená (remote) rozhraní obou komponent se porovnají jako standardní třídy nebo rozhraní. business references Odkazy na jiné EJB komponenty, které jsou v komponentě použity. Tyto reference jsou Java rozhraními (interface), proto se porovnávají stejně jako běžné třídy nebo rozhraní. U každé reference je uveden i název odkazované komponenty (v JNDI kontextu). Tento název musí zůstat u nové verze komponenty stejný, protože klient bude spouštěn ve stejném kontejneru, kde zůstává i stejná struktura JNDI názvů. Při porovnání se tedy musí nalézt reference se stejnými JNDI názvy a pak se porovnají odpovídající rozhraní (local nebo remote) odkazovaných komponent. Podle počtu nalezených dvojic stejných názvů komponent se poté určí, zda výsledkem porovnání je mutace, specializace nebo generalizace. env entries Proměnné prostředí se porovnávají stejně jako atributy třídy (jsou invariantní). Proměnné se stejným JNDI názvem musí být stejného primitivního datového typu, pokud nejsou, výsledkem porovnání všech proměnných je mutace. Pokud jsou proměnné společné oběma komponentám shodné, dále se rozhoduje podle počtu proměnných v obou komponentách. Jestliže je počet proměnných první komponenty vyšší, druhá komponenta tedy nějaké proměnné „ztratilaÿ a jedná se o generalizaci. V opačném případě se jedná o specializaci. Když jsou počty proměnných stejné, výsledkem porovnání je samozřejmě none (bez rozdílu). home interfaces Home rozhraní komponenty jsou standardními Java rozhraními, proto se porovnávají stejně jako normální Java třídy nebo rozhraní. home references Stejný případ jako business references, pouze se porovnávají rozhraní home nebo local-home.
Podtypové relace v ENT meta-modelu
30
msg activation Aktivační nastavení pro komunikaci message-driven komponent se týkají konkrétní implementace rozhraní JMS, proto nebudou zahrnuty do porovnání EJB komponent. msg consumed Tento trait reprezentuje třídu instancí, které mohou být přijaty message-driven komponentou. Tento trait se tedy porovnává jako standardní třída. msg destination references Cíle pro komunikaci pomocí zasílání zpráv. Upřesňují message-driven komponentu, která bude zpracovávat zprávy z této (session nebo entity) komponenty. Opět se porovnávají dvojice se stejnými JNDI názvy. U tohoto traitu je určen typ cíle zpráv (například javax.jms.Queue nebo javax.jms.Topic) a informace o užití cíle (buď je to producent nebo konzument zpráv nebo plní roli producenta i konzumenta). Pro druhy užití platí vztahy: P roducesConsumes <: P roduces P roducesConsumes <: Consumes dif f (P roduces, Consumes) = mutation resource env references Tento trait reprezentuje odkazy na zdroje, použité v EJB komponentě. Zdroj je určen svým jménem a Java třídou (rozhraním). Název opět určuje kontext v rozhraní JNDI, proto se opět budou porovnávat reference se stejnými JNDI názvy. Trait dále obsahuje určení typu (třídy) zdroje, který bude také porovnán. resource managers Tento trait reprezentuje potřebné zdroje komponenty (nejčastěji propojení s databázi u BMP entitních komponent). Tyto zdroje jsou určeny svým jménem (opět JNDI, budou se tedy porovnávat dvojice se stejnými jmény), typem (třídou) a oblastí sdílení tohoto zdroje (zdroj lze sdílet – shareable nebo ne – unshareable). Pro oblast sdílení zdroje platí, že shareable <: unshareable – shareable rozšiřuje vlastnosti unshareable. Dále se určuje, zda je tento zdroj ovládán aplikací (application) nebo kontejnerem (container). Mezi těmito dvěma vlastnostmi není žádný vztah na úrovni podtypování. Proto porovnáním této vlastnosti dostaneme výsledek none (bez rozdílu) nebo mutation (mutace). security roles Bezpečnostní role je reprezentována svým jménem a popřípadě i odkazem na jinou roli. Role se tedy porovná tak, že se porovnají názvy odkazovaných rolí (pokud neexistuje odkaz na jinou roli, v porovnání se užije název role).
Důležité algoritmy
31
timer service EJB umožňuje použít časovač, na nějž může komponenta reagovat. Třída pro vytvoření a nastavení časovače se získá z rozhraní EJBContext. Reakce komponenty (entitní, message-driven nebo bezstavová session komponenta) na časovou událost, kterou řídí kontejner, se vytvoří implementací rozhraní javax.ejb.TimedObject do ejb-třídy (ejb-class). Toto rozhraní obsahuje jedinou metodu – ejbTimeout(), v jejímž těle je reakce na událost vypršení časového intervalu časovače. Možné výsledky porovnání tohoto traitu jsou dvě – bez rozdílu (none) nebo mutace (mutation). Mutace nastane, když pouze jedna z komponent využívá služeb časovače. web service endpoint Specifikuje přístup k funkcionalitě webových služeb. Obsahuje plnou cestu k Javarozhraní služby. Tento trait se tedy porovná stejným způsobem jako standardní Java-třída nebo rozhraní. web service references Odkazy na webové služby. Obsahuje jméno služby (JNDI) a plnou cestu ke rozhraní služby. Názvy služeb se do porovnání zahrnují, porovnají se tedy opět dvojice odkazů se stejnými JNDI názvy. U dvojic se poté porovnají rozhraní služeb komponenty.
6.3
Důležité algoritmy
Většina zde popisovaných algoritmů se věnuje porovnávání dvou (nebo více) objektů. Objekty mohou být třídy, metody, atributy nebo rysy ENT metamodelu. Výsledkem jejich porovnání je prvek z množiny Differences, která byla definována v kapitole 4.1 na straně 17.
6.3.1
Porovnání tříd
Porovnání Java-tříd zajišťuje třída ClassComparator. Tato třída obsahuje metody pro porovnání dvou tříd, porovnání dvou množin atributů tříd, dvou množin funkcí a dvou množin výjimek. Porovnání tříd probíhá podle následujícího postupu: 1. Zjištění, zda není některá z porovnávaných tříd rovna hodnotě null. Pokud jsou obě null, vrátí se none, pokud je první třída null, vrátí se hodnota porovnání insertion, jinak deletion. 2. Zjištění, zda nejsou obě třídy stejné (porovnání metodou equals()). Pokud jsou, vrátí se rovnou hodnota porovnání none. 3. Zjištění, zda není některá ze tříd pole (typ Array). Pokud ano, porovnají se třídy podle postupu uvedeného v kapitole 6.1.3.
Důležité algoritmy
32
4. Zjištění, zda není některá ze tříd obalovou třídou primitivního datového typu. Pokud ano, porovnávají se třídy podle pravidel uvedených v kapitole 6.1.1, jinak se pokračuje dále. 5. Zjištění, zda není mezi třídami příbuzenský vztah (dědičnost) pomocí metody isAssignableFrom(). Pokud je, vrátí se buď generalization (první třída je potomkem druhé) nebo specialization (druhá třída je potomkem první). 6. Porovnání názvů tříd. Jestliže třídy mají názvy různé, může se porovnání ukončit s výsledkem mutation, protože příbuznost (dědičnost) byla testována v předešlém kroku a v jiném případě není různost názvů povolena. 7. Zjištění, zda nedochází při porovnávání k rekurzi (viz. dále na stránce 36). 8. Porovnání veřejných (public) atributů obou tříd. Výsledkem je jedna hodnota z množiny rozdílů. 9. Porovnání veřejných (public) metod obou tříd. Výsledkem je jedna nebo více hodnot z množiny rozdílů. 10. Porovnání konstruktorů tříd. Výsledkem může být opět více hodnot z množiny rozdílů. 11. Složení výsledků porovnání atributů, metod a konstruktorů do jedné hodnoty. Výsledná hodnota se vrátí jako výsledek porovnání daných tříd. Atributy Jak bylo popsáno v kapitole 4.3.3 na straně 19, atributy třídy jsou invariantní. Atributy se stejným názvem musí (při porovnání dvou tříd) mít i stejné typy. Pokud se některý atribut liší v typu, je výsledek porovnání všech atributů obou tříd mutace. Pokud jsou všechny atributy se stejnými názvy i se stejnými typy, dále se rozhoduje o výsledku porovnání na základě počtu atributů. Nejprve si označme počet atributů první třídy jako c1 , počet atributů druhé třídy jako c2 a počet stejných (společných) atributů jako cequal . Pak platí následující pravidla. (c1 = c2 = cequal ) ⇒ (dif fattributes = {none}) Pokud jsou počty atributů obou tříd stejné a zároveň rovny počtu všech atributů tříd, nedošlo k žádné změně a výsledkem porovnání je none (bez rozdílu). c1 = 0 ∧ c2 > 0 ⇒ (dif fattributes = {insertion} c2 = 0 ∧ c1 > 0 ⇒ (dif fattributes = {deletion} Pokud je počet atributů první třídy nulový a počet atributů druhé třídy nenulový, výsledkem porovnání je insertion (vložení). Pokud je naopak počet atributů druhé třídy nulový a počet atributů první třídy nenulový, výsledkem porovnání atributů je deletion (smazání). (cequal = c2 ∧ c2 < c1 ) ⇒ (dif fattributes = {generalization})
Důležité algoritmy
33
Pokud má druhá třída stejný počet atributů jako je počet stejných společných atributů, a počet atributů druhé třídy je menší než počet atributů první třídy, výsledkem porovnání je generalization (generalizace). (cequal = c1 ∧ c1 < c2 ) ⇒ (dif fattributes = {specialization}) Pokud jsou všechny společné (a stejné) atributy obou tříd zároveň všemi atributy první třídy a ve druhé třídě je více atributů než v první, jedná se o výsledek porovnání typu specialization (specializace). Metody Při porovnání metod platí pro návratové typy pravidlo kovariance a pro argumenty pravidlo kontravariance. Jazyk Java umožňuje tzv. přetížení metody, což znamená, že se může v jedné třídě vyskytovat více metod stejného názvu, ale argumenty těchto metod musí být různého typu nebo musí být různý počet argumentů. Díky tomu lze metody jednoznačně odlišit. Překladač pak při volání metody zkontroluje typ předávaného argumentu a podle toho určí, jaká konkrétní metoda se ve skutečnosti bude volat. V obou porovnávaných třídách se tedy může vyskytovat různý počet metod se stejnými názvy, lišících se pouze v typech argumentů (nebo v počtu argumentů). Před porovnáním se tedy musí metody roztřídit do skupin se stejnými názvy a počty argumentů. Tyto skupiny se uloží do instancí třídy ComparedPair, v níž jsou uloženy dva seznamy (pole) metod z obou porovnávaných tříd. Každá instance třídy ComparedPair obsahuje tedy metody, které se mohou účastnit nahrazení v obou komponentách. Protože nevíme přesně, která metoda nahrazuje kterou (všechny mají stejné názvy i počty argumentů), musíme porovnat každou metodu z první komponenty s každou metodou z druhé třídy. Výsledkem je tedy dvou-rozměrné pole, kde každý prvek odpovídá porovnání jedné metody z první třídy s jinou metodou z druhé třídy, jeho hodnota je tedy prvek z množiny Differences. Toto pole obsahuje všechny možné varianty porovnání, výsledkem ale může být pouze jeden prvek množiny Differences. Pole se porovnává podle následujícího postupu: 1. Zjištění výšky (height) a šířky (width) pole. 2. Pokud je šířka nebo výška rovna jedné, rozhoduje se takto: (a) Pokud je šířka i výška stejná (pole o velikosti 1 × 1), vrátí se jako výsledek porovnání hodnota tohoto jediného prvku pole. (b) Pokud je šířka rovna jedné (a výška je větší), výsledkem porovnání musí být buď generalizace (protože je počet metod první třídy větší než počet metod druhé třídy) nebo mutace. Generalizace může nastat pouze tehdy, pokud je jeden z prvků pole roven hodnotě none nebo generalization (nebo deletion, což je speciální případ generalizace, ale tento případ nemůže nastat, protože se porovnávají existující funkce). (c) Pokud je výška pole rovna jedné (a šířka je větší), znamená to, že se bude jednat buď o specializaci nebo mutaci. Specializace může nastat tehdy, pokud je aspoň jeden prvek pole roven hodnotě none nebo specialization.
Důležité algoritmy
34
3. Dále se postupuje tak, že se jde od prvního prvku pole buď směrem dolů (na další řádek) nebo směrem doprava (na další sloupec). Aktuální prvek má souřadnice [i,j]. 4. Podle pozice prvku se vytvoří nové pole, které obsahuje hodnoty původního pole, s výjimkou všech prvků, ležících na aktuálním řádku a sloupci – nové pole má tedy rozměry (m − 1) × (n − 1), kde m a n jsou rozměry původního pole. 5. Toto nové pole se znovu porovná od bodu 1 (rekurzivní volání porovnávací metody). Výsledek porovnání se zkombinuje s hodnotou prvku [i, j] a uloží se do seznamu možných výsledků porovnání. Dále se pokračuje od bodu 3, dokud nenastane konec řádku nebo sloupce (podle toho, zda se postupuje po sloupcích nebo po řádcích). 6. Z výsledného seznamu hodnot porovnání se vybere ta „nejlepšíÿ. Hodnoty porovnání si můžeme seřadit tak, že nejlepší je none (nejlepší je, když nejsou žádné rozdíly) a nejhorší je mutation (nebo také unknown). Hodnoty mezi těmito dvěma jsou na stejné úrovni a nedá se rozhodnout o jejich prioritě mezi sebou – insertion, specialization, deletion a generalization mají stejnou prioritu. Toto seřazení také vychází z toho, že pokud se v porovnání vyskytne mutace, žádnou jinou hodnotu už dalšími porovnáními nemůžeme dostat (kromě unknown). Pokud ale je průběžné porovnání dvou objektů rovno hodnotě none, může se ještě změnit (zhoršit) na jakoukoliv jinou hodnotu (včetně mutace). Příklad: Mějme v nějaké třídě dvě metody void foo(int, int) a String foo(int, int). V nové (nahrazující) třídě přibyla k těmto dvěma metodám ještě jedna – void foo(short, short). Výsledkem porovnání by měla být hodnota specialization, protože nové třídě přibyla funkčnost. Na obrázku 6.1 jsou znázorněny všechny (je jich 6) varianty porovnání metody foo(). Z těchto šesti výsledků bude nakonec vybrána hodnota specialization, protože ta je „lepšíÿ než mutation. 1. třída \ 2.třída void foo(int, int) String foo(int, int)
void foo(int,int) none mutation
String foo(int, int) mutation none
void foo(short, short) generalization mutation
Tabulka 6.2: Tabulka rozdílů mezi funkcemi (diff(f1 , f2 ))
Výjimky Pokud klient při volání metody „odchytáváÿ výjimku (výjimky), musí být stejného typu nebo může (můžou) být jejím podtypem – příkladem je odchycení výjimky typu (rozhraní) java.lang.Throwable. Pokud klient odchytává tuto výjimku (ze které jsou všechny ostatní výjimky implementovány), odchytí tím nejen výjimku typu java.lang.Throwable, ale zároveň i všechny její potomky (tedy všechny výjimky). Výjimky mají z hlediska nahraditelnosti podobné vlastnosti jako návratové typy metod. Porovnání dvou polí výjimek (symbolizující všechny výjimky první
Důležité algoritmy
35
Obrázek 6.1: Možnosti porovnání metod metody a všechny výjimky druhé metody) probíhá podle následujícího algoritmu: 1. Zjištění, zda není jedno z polí rovno hodnotě null. Pokud jsou obě pole stejná (obě hodnoty null), vrátí se samozřejmě hodnota none. Pokud je právě jedno z polí nedefinované (nebo je to pole délky 0), vrátí se automaticky hodnota porovnání mutation. 2. Vytvoření pole hodnot o rozměrech m×n, kde m je počet výjimek původní metody a n je počet výjimek nové metody. 3. Porovnání jednotlivých tříd a uložení výsledků do pole. 4. Pokud m = n, může být výsledná hodnota porovnání none, specialization nebo generalization. Nejprve se tedy zjišťuje, zda se ve všech možnostech porovnání tříd výjimek nenalezena ta, která by zaručila výslednou hodnotu porovnání none. Aby to mohlo nastat, musí být v každém řádku i v každém sloupci alespoň jedna hodnota none. Pokud je toto splněno, může se vrátit výsledná hodnota porovnání none. Jinak se zkusí stejným způsobem najít hodnota specialization a generalization. Při hledání těchto hodnot může none zastupovat jakoukoliv jinou hodnotu. Pokud nebyla zajištěna ani podmínka pro specialization ani pro generalization, je výsledkem porovnání hodnota mutation. 5. Pokud m > n, může být výsledkem porovnání buď hodnota generalization, specialization nebo mutation (hodnota none nemůže nastat, protože je různý počet propagovaných výjimek). Případ, kdy je výsledkem specializace, je zvláštní, ale nastat může. Například původní metoda propagovala dvě výjimky, kde jedna je potomkem druhé. Nová metoda propaguje pouze potomka. Klient zachytává nejprve potomka a pak rodiče, u nové metody
Důležité algoritmy
36
tedy může zachytit pouze potomka, catch-blok rodičovské výjimky zůstane nevyužitý. V tomto případě překladač zobrazí varovné hlášení o nedosažitelném kódu („Unreachable catch blockÿ), ale program půjde přeložit i spustit, proto nová metoda může původní nahradit. původní \ nahr. ParentException ChildException AnotherException
ChildException specialization none mutation
AnotherException mutation mutation none
Tabulka 6.3: Porovnání výjimek dvou metod Opět platí, že aby výsledkem porovnání výjimek mohla být jedna z výše uvedených hodnot, musí se tato hodnota vyskytovat v každém řádku i sloupci pole (a opět hodnota none může nahradit kteroukoliv jinou hodnotu). Zkusí se tedy zjistit, zda rozdíly mezi oběma poli tříd nevyhovuje specializaci nebo generalizaci. Pokud nevyhovuje ani jednomu, výsledkem porovnání je mutation. 6. Pokud m < n, může být výsledek porovnání specialization, generalization nebo mutation. Uplatní se stejný postup, jako v předchozím bodu. Příklad V tabulce 6.3 je zobrazeno pole porovnání všech tříd výjimek původní a nahrazující metody. Třída ChildException je potomkem třídy ParentException. V každém sloupci i řádku se vyskytuje alespoň jedna hodnota specialization (nebo none, která ji může nahradit), proto je tato hodnota i výsledkem porovnání těchto tříd výjimek. Rekurze Protože Java umožňuje použití rekurzivních datových typů, musí se případná nekonečná rekurze při porovnávání tříd eliminovat. Rekurze může ve nastat, jestliže je ve třídě atribut (návratový typ, argument funkce) stejné třídy, nebo pokud je ze třídy odkazováno na jinou třídu, ze které je opět odkazováno (i přes několik dalších tříd) na původní třídu. Kdyby se rekurze neošetřila, došlo by k nekonečnému porovnávání, respektive by mohlo dojít k „přetečení zásobníkuÿ při nekonečném volání porovnávající metody. Proto je třeba uchovávat historii již porovnávaných tříd. Z této historie lze zjistit, zda se metoda nepokouší porovnat třídy, které už před tím byly porovnány. Rozlišujeme dva druhy výskytů rekurze: • Rekurze, které nemají vliv na celkový výsledek porovnání. Mezi tyto rekurze patří například použití stejné třídy v atributu, návratovém typu nebo argumentu metody, jako je třída sama (třída Clovek obsahující metodu public Clovek klonuj(Clovek)). Tato rekurze je případem, kdy se z porovnání dvou tříd dif f (C1, C2) dostaneme dalšími porovnáními jejich atributů (návratových typů, výjimek nebo argumentů metod) opět k dif f (C1, C2).
Důležité algoritmy
37
• Rekurze, které mají vliv na celkový výsledek porovnání. Pokud se tento druh rekurze vyskytne, automaticky se celkový výsledek porovnání tříd změní na unknown (neznámý). O těchto třídách nelze rozhodnout, jaký je mezi nimi rozdíl, proto se vrací hodnota unknown. Tímto případem je, pokud se z porovnání dvou tříd dif f (C1, C2) dostaneme k porovnání opačnému – dif f (C2, C1). Je jasné, že zde nelze vztah obou tříd určit, proto rozdíl mezi nimi bude typu unknown. Nalezení rekurzí a jejich rozlišení se provádí podle následujícího algoritmu: 1. Přidání nové dvojice porovnávaných tříd do dvou seznamů, reprezentujících historii. Současně se musí přidat hodnota typu boolean určující, zda se jedná o kontravarianci (obě třídy pocházejí z argumentů funkcí) nebo kovarianci (třídy pocházejí z atributů nebo návratových typů metod). Údaj o kovarianci (kontravarianci) se musí rovněž uložit do seznamu. 2. V historii se vyhledá dvojice porovnávaných tříd, která je shodná s právě přidanou dvojicí. Pokud se nalezne, rozhoduje se o druhu rekurze na základě seznamu kontravariancí. Tento seznam se prochází od pozice dané nalezenou dvojicí tříd až na konec, s cílem zjistit směr (buď je to kovariantní nebo kontravariantní směr)porovnání poslední dvojice tříd. Pokud se tento směr shoduje se směrem nalezené dvojice, nalezená rekurze nevadí a propaguje se výjimka OKRecursionException, kterou volající metoda pouze zachytí a pokračuje v dalším porovnání (tato výjimka pouze slouží k zachycení rekurze, čímž se předejde nekonečnému porovnávání). Pokud jsou oba směry různé, propaguje se výjimka BadRecursionException, kterou volající metoda zachytí a změní výsledek porovnání na hodnotu unknown. 3. Pokud se nenalezla dvojice tříd z bodu 2, musí se zkusit nalézt v historii dvojice se třídami stejnými jako právě přidané, ale v opačném pořadí (prohozené). Pokud se taková dvojice nalezne, musí se opět pomocí seznamu kontravariancí zjistit směr porovnání obou tříd. Pokud byl u obou dvojic tento směr různý, propaguje se výjimka OKRecursionException, která neovlivní výsledek porovnání. Pokud byly ale oba směry stejné, propaguje se výjimka BadRecursionException, která způsobí změnu výsledku porovnání na unknown.
Příklad 1 Na obrázku 6.2 jsou dva případy, kdy rekurze nemá vliv na konečný výsledek porovnání dvou tříd. V horní polovině obrázku jsou zobrazeny tři seznamy – do prvního se ukládají třídy, které jsou druhým parametrem funkce dif f (C1, C2), do druhého seznamu se ukládají třídy z druhého parametru. Do třetího seznamu se ukládá hodnota typu boolean, true znamená, že při porovnání došlo k prohození směru (porovnání argumentů metod, kontravariance). Na obrázku jsou zobrazeny čtyři třídy – ZamestnanecNew, ZamestnanecOld, ClovekNew a ClovekOld. Jejich struktura je naznačena v dolním rámečku. V příkladu a) je rekurze nalezena při porovnání návratového typu metody klonuj(). Tato metoda vrací instanci třídy, jejíž je součástí. V příkladu b) jsou porovnávány argumenty metody klonuj(), které jsou také stejného typu (ClovekOld
Důležité algoritmy
38
Obrázek 6.2: Dva případy rekurze, která nemá vliv na výsledek porovnání. nebo ClovekNew). Nalezená dvojice tříd je prohozená (vzhledem k poslední dvojici v seznamu) a mezi oběma dvojicemi došlo ke změně směru porovnání (hodnota true ve třetím prvku seznamu), proto se tato rekurze zachytí a přeskočí, stejně jako v příkladu a).
Obrázek 6.3: Dva případy rekurze, která způsobí nemožnost zjištění rozdílů mezi třídami.
Důležité algoritmy
39
Příklad 2 Na obrázku 6.3 jsou znázorněny dva případy rekurze, která má za následek nemožnost určení rozdílů mezi dvěma třídami. Příklad a) se týká dvou tříd, znázorněných na obrázku dole (ClovekOld a ClovekNew). Metoda klonuj() v obou třídách vždy vrací instanci druhé třídy – ClovekOld.klonuj() má návratový typ ClovekNew a ClovekNew.klonuj() má návratový typ ClovekOld. Z toho je jasné, že tyto třídy nelze porovnat. Příklad b) obsahuje porovnání tříd ClassOld a ClassNew. Obě třídy obsahují 1 veřejný atribut atribut typu AnotherOld, respektive AnotherNew. Tyto třídy obsahují veřejný atribut atribut, který je typu ClassNew, respektive ClassOld. V seznamu změn směru jsou pouze hodnoty false, proto nedošlo k žádné změně směru a směry porovnání obou dvojic jsou stejné. Proto se tento případ propaguje výjimkou BadRecursionException, která má za následek změnu hodnoty porovnání na unknown.
6.3.2
Operace nad množinou rozdílů
Rozdíly definované v kapitole 4.1 na straně 17. Množina rozdílů: Dif f erences = {none, insertion, specialization, deletion, generalization, mutation, unknown} Pokud se porovnávají dva objekty, složené z několika součástí (typicky Java třídy složené z atributů a metod), musí se porovnat nejprve součásti a výsledkem srovnání obou objektů je „průnikÿ všech rozdílů mezi všemi součástmi. Jestliže například třídy měly shodné atributy, ale rozdíl mezi jejími dvěma metodami je typu mutation, celkový výsledek musí být také mutací. Prvky z množiny rozdílů jsem rozdělil do čtyř skupin podle „agresivityÿ rozdílů (první skupina je nejméně agresivní a pátá skupina je nejvíce agresivní): 1. none 2. insertion, deletion 3. specialization, generalization 4. mutation 5. unknown Sloučení rozdílu se sebou samým dá opět stejný druh rozdílu: rozdil ∩ rozdil = rozdil Platí, že rozdíl z vyšší skupiny „pohltíÿ rozdíl z nižší skupiny (operace průniku rozdílu z nižší skupiny s rozdílem z vyšší skupiny dá za výsledek rozdíl z vyšší skupiny). Výjimku tvoří sloučení (průnik) rozdílů z druhé a třetí skupiny: insertion ∩ none = specialization Pokud byl nějaký prvek přidán (insertion) a má se sloučit s prvkem, který zůstal bez rozdílů, výsledkem musí být typ specialization.
Důležité algoritmy
40
insertion ∩ specialization = specialization deletion ∩ none = generalization Podobně jako u typu insertion. Jestliže byla nějaká součást porovnávaného objektu odstraněna a ostatní součásti zůstaly bez rozdílu, výsledkem porovnání musí být generalization. deletion ∩ generalization = generalization Rozdíl typu unknown je nejagresivnější, protože jestliže nemůžeme porovnat nějakou součást dvou objektů, nemůžeme zároveň porovnat ani celé tyto objekty, obsahující neporovnatelné součásti.
6.3.3
Načtení EJB komponent
EJB komponenty se načtou do instance třídy EntEjb (z balíku cz.zcu.stuna. beancmp.entejb) pomocí tříd pro práci s JAR-soubory JarFile a tříd implementujících model DOM (balík org.w3c.dom). Načtená komponenta má strukturu podle ENT meta-modelu, obsahuje tedy tagy a rysy (traits) uvedené v kapitole 3.5. Jednotlivým tagům nebo rysům odpovídá jedna třída, která vždy implementuje rozhraní Comparable nebo ComparableByNames (které je potomkem Comparable). Rozhraní Comparable obsahuje jedinou metodu compare(). Do těla této metody se v každé třídě implementuje nalezená podtypová relace mezi danými tagy nebo rysy. Výsledkem porovnání je instance třídy Diff. Rozhraní ComparableByNames navíc obsahuje metodu hasSameNames(), která určuje, zda mají dva porovnávané objekty stejné názvy (většinou to jsou názvy JNDI kontextu). Obě rozhraní jsou využita ve statické třídě Comparator, která obsahuje metody pro porovnání dvou polí, jejichž prvky implementují rozhraní Comparable nebo ComparableByNames.
6.3.4
Porovnání EJB komponent
Algoritmus porovnání je implementován v metodě compare(EntEjb), která vrací instanci třídy BeanDifferences. 1. Porovnání typu obou komponent (session, entity nebo message-driven). Pokud jsou různého typu, vrátí se instance s příslušnou informací, dále se neporovnává (komponenty jsou odlišné, není třeba je dále porovnávat). 2. Jestliže jsou druhy komponent stejné, porovnávají se dále jednotlivé tagy a rysy podle ENT meta-modelu. Rysy označené jako provided (prvek množiny Role) se porovnají ve stejném směru, jako jsou porovnávány celé komponenty (kovariance). 3. Tagy na úrovni komponent se porovnají také ve stejném směru, protože jsou to součásti určující vlastnosti komponenty.
Výsledek porovnání EJB komponent
41
4. Rysy označené jako required se porovnají v opačném směru (kontravariance), protože jsou vyžadovány (ne poskytovány) a mají tedy charakter argumentů funkce. 5. Výsledky porovnání jednotlivých částí komponent se ukládají do instance třídy BeanDifferences, jejíž odkaz je na konci porovnání metodou navrácen.
6.4
Výsledek porovnání EJB komponent
Výsledkem porovnání dvou EJB komponent je instance třídy BeanDifferences. Tato instance obsahuje metody pro přístup k jednotlivým rozdílům mezi porovnávanými komponentami a metody pro zjištění názvů komponent. Také obsahuje metodu, která vytvoří XML dokument se všemi informacemi o porovnání. Tento dokument je instancí třídy org.w3c.Document. Definice typu dokumentu (DTD) je uvedena v příloze („Výpis souboru entdiff.dtdÿ). Dokument lze i uložit do souboru, při standardním použití programu se uloží do aktuálního adresáře do souboru entdiff.xml. Kořenem dokumentu je element entdiff. Ten obsahuje jediný subelement component, jehož atribut model určuje použitý komponentní model (pro EJB má hodnotu „EJBÿ). Element component obsahuje šest subelementů. První tři upřesňují název porovnávané komponenty. Jsou to elementy provider (poskytovatel komponenty), namespace (jmenný prostor komponenty) a name (název komponenty). Následují tři elementy data, odlišené atributem level (úroveň) – „categoriesÿ, „traitsÿ a „tagsÿ. Data na úrovni categories obsahují rozdíly mezi třemi kategoriemi ENT meta-modelu, data na úrovni traits obsahují rozdíly jednotlivých traitů a data na úrovni tags obsahují rozdíly mezi tagy obou EJB komponent.
6.4.1
Příklad výsledku porovnání
Tento příklad je výsledkem porovnání dvou verzí EJB komponenty MatematikaBean. V druhé verzi komponenty došlo ke změnám v remote a home rozhraní (traity business interfaces a home interfaces). Všechny ostatní části zůstaly bez rozdílu. Výsledkem porovnání komponent je tedy generalizace. <entdiff>
<provider> MatematikaBean <part name="Exports"> Generalization <part name="Needs"> None <part name="Ties"> None <part name="attributes">
Technické nároky programu
42
None <part name="business_interfaces"> Generalization <part name="business_references"> None <part name="env_entries"> None <part name="home_interfaces"> Generalization <part name="home_references"> None <part name="msg_consumed"> None <part name="msg_destination_references"> None <part name="resource_env_references"> None <part name="resource_managers"> None <part name="security_roles"> None <part name="timer_service"> None <part name="web_service_endpoint"> None <part name="web_service_references"> None <part name="msg_type"> None <part name="persistence"> None <part name="reentrancy"> None <part name="state"> None <part name="transaction"> None
6.5
Technické nároky programu
Program byl vyvíjen pro platformu Sun J2EE 1.4. Program lze přeložit a spustit i tam, kde je „pouzeÿ nainstalována platforma J2SE, protože je distribuován s potřebnými balíky pro práci s EJB komponentami (balík javax). Hardwarové požadavky jsou stejné jako pro jakoukoliv jinou Java (J2SE) aplikaci.
Ověření funkčnosti
6.6
43
Ověření funkčnosti
Porovnání tříd bylo ověřeno na několika typických třídách. Většina těchto tříd je uložena v balíku exampleclasses, porovnání primitivních datových typů je také ověřeno. Porovnání lze ověřit spuštěním programu s parametrem -d (nebo spuštěním metody demo() v hlavní třídě Main. Při demonstraci je u některých tříd (například T1 a T2) vynecháno porovnání názvů tříd, protože se porovnávají třídy z jednoho balíku, proto nemohou mít stejné názvy. Tato vlastnost není při standardním porovnání nastavena. Porovnání celých komponent bylo náročnější. Několik komponent jsem byl schopen vytvořit sám, ale ty samozřejmě nemohou nahradit skutečně používané komponenty. Vytvořil jsem „sessionÿ komponenty Fyzika, Matematika nebo MatematikaBean a entitní komponentu Album. Jednotlivé verze těchto komponent se liší číslem uvedeným za názvem (například Matematika1). Funkčnost samotných komponent lze celkem snadno zpochybnit, protože některé z metod ve třídách jsou napsány bez svého těla („prázdnéÿ metody, vracející „nesmyslnéÿ hodnoty). Komponenty jsem měnil podle předpokládaného scénáře – do nových verzí byla přidána nová funkčnost (metoda). Pro otestování jsem rovněž použil některé z komponent, přiložených k J2EE tutoriálu (ke stažení na stránkách firmy Sun). „Reálnéÿ komponenty se mi podařilo získat od Michala Valdmana, jemuž za ně děkuji. Jeho komponenty byly typu session a entity (perzistence řízená kontejnerem). U jedné z verzí těchto komponent jsem se setkal s tím, že pokud jsou třídy komponenty kompilovány v jiném Java SDK (zde bylo použito například SDK firmy IBM, verze Java 1.3.x), introspekce tříd může skončit výjimkou (java.lang.LinkageError. Tím je zčásti program omezen. Ale můžeme předpokládat, že bude použit pro porovnání komponent pro jeden aplikační server, které budou kompilovány stejným překladačem, který je použit i v aplikačním serveru. Při standardním načítání (parsování) XML dokumentu (deployment descriptoru) se třída pokouší nalézt DTD soubor, který je uveden v hlavičce. Protože v deployment descriptoru je DTD soubor odkazován na server firmy Sun, parser se pokouší připojit k Internetu a DTD dokument stáhnout. Tím se načítání citelně zpomalí (nebo i zastaví úplně, jestliže není internetové připojení k dispozici). Proto jsem program upravil tak, aby se DTD soubor nestahoval. Samotné porovnání komponent je nejvíce závislé na rychlosti porovnání tříd. To jsem se snažil urychlit využitím hashovacích tabulek, do kterých se ukládají již nalezené rozdíly mezi třídami. Porovnání tříd lze také urychlit tím, že výsledek porovnání je znám už ve chvíli, kdy jedna ze součástí třídy je porovnána s hodnotou mutation. Tím bychom ale ztratili informace o dalších porovnávaných částech, proto jsem se rozhodl toto urychlení neprovádět.
44
Kapitola 7
Struktura programu Program se nazývá BeanComparator a je uložen v adresáři src, v balíku cz.zcu. stuna.beancmp. Do tohoto balíku je umístěna třída Main, která reprezentuje program spouštěný z příkazové řádky. Dále balík obsahuje třídu umožňující porovnání EJB komponent a třídu, jejíž instance slouží k uchování výsledků porovnání (a převedení výsledků porovnání do XML dokumentu). Hlavní balík je dále rozčleněn na další tři balíky – entejb, classcmp a utilities. Tyto balíky obsahují třídy pro načtení porovnávaných komponent do paměti (entejb), porovnání Java tříd (classcmp) a pro další nezbytné funkce (utilities). Diagramy tříd jednotlivých balíků (kromě utilities, který je pouze pomocný a obsahuje rozdílné třídy) jsou uvedeny v příloze na konci dokumentu.
7.1
cz.zcu.stuna.beancmp
Hlavní balík programu. Obsahuje tyto třídy a rozhraní: • Main – hlavní třída programu. Rozpoznává argumenty z příkazové řádky spouští buď porovnání dvou EJB komponent nebo demonstraci porovnání vybraných tříd nebo vypíše nápovědu k užití programu. • BeanComparator – porovnává zadané EJB komponenty (cesty k nim jsou zadány v konstruktoru). Obsahuje jedinou metodu compare(), která porovná načtené komponenty a vrátí instanci třídy BeanDifferences, ve které jsou uloženy rozdíly mezi komponentami. • BeanDifferences – obsahuje rozdíly mezi jednotlivými tagy a rysy EJB komponenty dle ENT meta-modelu. Poskytuje metody pro zjištění celkových rozdílů všech tagů, traitů nebo kategorií (E,N,T). Také poskytuje metodu pro určení celkového rozdílu mezi oběma komponentami. • Diff – třída reprezentující typ rozdílu. Umožňuje základní operace pro práci s nimi. • Comparable – jestliže objekty toto rozhraní implementují, lze je mezi sebou porovnávat. Obsahuje jedinou metodu Compare().
cz.zcu.stuna.beancmp.utilities
45
• ComparableByNames – toto rozhraní implementuje Comparable. Obsahuje navíc jedinou metodu boolean hasSameNames(). Slouží pro porovnání těch částí EJB, kde záleží na jejím názvu (nejen). Je to například trait env entries. • Comparator – třída obsahující metody pro porovnání dvou instancí, implementujících rozhraní Comparable. Umožňuje také porovnání dvou polí objektů implementujících rozhraní Comparable nebo ComparableByNames.
7.2
cz.zcu.stuna.beancmp.classcmp
Balík obsahuje třídy umožňující porovnání Java tříd: • ClassComparator – tato třída obsahuje jedinou veřejnou metodu compare(), která porovná dvě zadané třídy (v konstruktoru) a jako výsledek vrátí instanci třídy Diff (v hlavním balíku). • PrimitiveTypesComparator – třída umožňující porovnání dvou primitivních datových typů v Javě. • ComparedPair – instance této třídy obsahují dva seznamy porovnávaných atributů tříd, metod nebo konstruktorů. • ClassesHistory – třída pro ukládání historie porovnávaných tříd. Zároveň rozpoznává rekurze a reaguje na ně propagací výjimek OKRecursionException nebo BadRecursionException.
7.3
cz.zcu.stuna.beancmp.entejb
Obsahuje třídy pro načtení EJB komponent. Hlavní třídou tohoto balíku je EntEjb.java, která se s pomocí ostatních tříd tohoto balíku pokusí načíst obsah všech traitů a tagů zadané komponenty. Poskytuje také metodu pro porovnání s jinou komponentou. Všechny ostatní třídy tohoto balíku odpovídají traitům a tagům ENT metamodelu Enterprise JavaBeans komponent.
7.4
cz.zcu.stuna.beancmp.utilities
• MyIO – statická třída, poskytující metody pro nalezení třídy podle jejího názvu (v aktuálním ClassLoaderu), nalezení třídy v jiném Java-archívu nebo pro vytvoření nového ClassLoaderu. • MyLogger – třída pro výpis informací o porovnávání tříd. Informace vypisuje buď na příkazovou řádku (na konzoli) nebo do souboru (classdiff.log v aktuálním adresáři) nebo na konzoli i do souboru. • MyResolver – pomocná třída, díky níž lze načítat XML soubory (deployment descriptor) bez stahování DTD souboru z Internetu. Úkolem této práce je porovnání EJB komponent, ne kontrola správnosti deployment descriptoru.
cz.zcu.stuna.beancmp.utilities
46
• MyXMLParser – statická třída umožňující načtení a uložení XML dokumentů.
47
Kapitola 8
Závěr Podrobně jsem prostudoval technologii Enterprise JavaBeans a způsoby nasazení komponent v EJB serverech. Dále jsem se seznámil s principy teorie nahraditelnosti softwarových komponent a na základě této teorie jsem vytvořil program, umožňující porovnání dvou EJB komponent a určení možností nahraditelnosti. Komponenty jsem porovnával na úrovni ENT meta-modelu. Porovnávané komponenty měly být k dispozici bez zdrojového kódu, proto jsem se zpočátku obával možných komplikací při načítání přeložených Java tříd (.class soubory s „byte-kódemÿ). Díky třídám, umožňujícím introspekci, se však parsování byte-kódu výrazně zjednodušilo. Vytvořený program lze používat z příkazového řádku nebo je možnost použití jeho programového rozhraní pro začlenění do další aplikace. Program byl testován na několika „reálnýchÿ EJB komponentách, nejvíce však na komponentách z J2EE tutoriálu [JT04] a na několika komponentách, které jsem vytvořil výhradně pro testovací účely. Výkonnost programu je závislá na rychlosti načtení komponent a na rychlosti porovnání tříd komponent. Při načítání komponenty do paměti standardně probíhá (v DOM) i pokus o připojení k Internetu a stažení DTD souboru z deployment descriptoru. Tuto vlastnost jsem v programu potlačil a tím i značně urychlil načtení komponenty. Při porovnání tříd komponent se porovnávají všechny jejich součásti (atributy, metody), což poskytuje kompletní informace. Pokud ale hodnota porovnání některé části tříd je typu mutace, není třeba již další součásti porovnávat (celkové porovnání bude totiž také typu mutace). Toto urychlení jsem ale neprováděl, protože detailní informace jsou pro zjištění možnosti nahrazení důležitější než samotná rychlost porovnání. Program je omezen tím, že byl vytvořen pouze pro Enterprise JavaBeans verze 2.1. Dalším omezením je, že program nedokáže pracovat s komponentami, přeloženými jiným JDK. Setkal jsem se například s komponentou přeloženou v IBM Java 1.3.x, jejíž třídy nebylo možné introspekcí načíst (program byl přeložen překladačem firmy Sun, verze 1.4.2).
I
Přehled zkratek • COM – Component Object Model (komponentní model) • CORBA – Common Object Request Broker Architecture (komponentní model) • DOM – Document Object Model (model dokumentu) • EJB – Enterprise JavaBeans (komponentní model) • ENT – Exports, Needs, Ties (komponentní meta-model) • JMS – Java Message Service (rozhraní pro komunikaci pomocí zpráv) • JNDI – Java Naming and Directory Interface (rozhraní pro jmenné a adresářové služby v Javě) • JSP – Java Server Pages (skripty psané v Javě pro použití v HTML stránkách) • J2SE – Java 2 Platform, Standard Edition (platforma pro vývoj appletů a aplikací v jazyce Java) • J2EE – Java 2 Platform, Enterprise Edition (platforma pro vývoj vícevrstvých aplikací v jazyce Java) • (Java) RMI – (Java) Remote Method Invocation (model pro vzdálenou komunikaci mezi dvěma programy v Javě)
II
Literatura [BR03] Brada, P.: Specification-Based Component Substitutability and Revision Identification. Univerzita Karlova, Matematicko-fyzikální fakulta, 2003 [BR04] Brada, P.: The ENT Meta-Model of Component Interface, version 2. Západočeská univerzita v Plzni, srpen 2004 [HA01] Haefel, R.: Enterprise JavaBeans O’Reilly, USA, 2001 [HE01] Herout ,P.:Učebnice jazyka Java Kopp, Česká republika, 2001 [JT04] Armstrong, E. – Ball, J. – Bodoff, S. – Carson, D. B. – Evans, I. – Green, D. – Haase, K. – Jendrock, E.: The J2EE 1.4TM Tutorial Sun Microsystems, USA, 2004 [LI88] Liskov, B. H.: Data abstraction and hierarchy, ACM Sigplan Notices 1988. [SI02] Simons, A. J. H.: The Theory of Classification – Part 4: Object Types and Subtyping. Department of Computer Science, University of Sheffield, UK, 2002 [SI03] Simons, A. J. H.: The Theory of Classification – Part 6: The Subtyping Inquisition. Department of Computer Science, University of Sheffield, UK, 2003 [WZ88] Wegner, P. – Zdonik, S.B.: Inheritance as an incremental modification mechanism or what like is and isn’t like. In Proceedings of the European Conference on ObjectOriented Programming (ECOOP), volume 322, pages 55–77 SpringerVerlag, 1988 [ZE02] Zelený, J. – Nožička, J.: COM+, CORBA, EJB BEN, Česká republika, 2002
[ANT] Apache Ant http://ant.apache.org [COM] Component Object Model Technologies http://www.microsoft.com/com [DOM] Document Object Model Level 2 Core Specification http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113 [EJB] Enterprise JavaBeans http://java.sun.com/products/ejb [Ecl] Eclipse http://www.eclipse.org [Java] The Java Language Specification http://java.sun.com/docs/books/jls/second edition/html/ j.title.doc.html [JB] Java Beans http://java.sun.com/products/javabeans [JBOS] JBoss Aplication Server http://www.jboss.org [JMS] Java Message Service http://java.sun.com/products/jms [JNDI] Java Naming and Directory Interface http://java.sun.com/products/jndi [J2EE] Java 2 Platform, Enterprise Edition http://java.sun.com/j2ee [OMG] Open Management Group http://www.omg.org [RMI] Remote Method Invocation http://java.sun.com/products/jdk/rmi/ [XDOC] XDoclet http://xdoclet.sourceforge.net
IV
Přílohy Uživatelská dokumentace Adresářová struktura Všechny potřebné soubory se nacházejí v adresáři BeanComparator. Ten obsahuje následující podadresáře a soubory: • beans – zde je uloženo několik EJB komponent, určených k demonstraci porovnávání. • bin – cz/zcu/stuna/beancmp – obsahuje přeložené třídy programu. – javax/... – třídy J2EE nutné pro kompilaci a běh programu (EJB verze 2.1, XML). • dist – sem se ukládá distribuce programu v Java archívu (soubor BeanComparator.jar). • doc – dokumentace programu ve formátu HTML souborů (hlavním je index.html). • src – v tomto adresáři jsou uloženy zdrojové kódy programu. • beancmp.bat – dávkový soubor pro spuštění programu (spustí třídu Main v adresáři bin). • beancomparator.bat – dávkový soubor pro spuštění programu uloženém v Java archívu (dist/BeanComparator.jar). • demoX.bat – několik demonstrací porovnání komponent (X je číslo od 1 do 5) • entdiff.dtd – definice typu dokumentu u výstupu porovnání (souboru entdiff.xml). • classdiff.log – do tohoto souboru se ukládají komentáře při porovnávání tříd (dočasný soubor). • MyManifest.MF – vzor souboru MANIFEST.MF, který se vkládá do Javaarchívu.
V
• build.xml – soubor s popisem cílů určených ke kompilaci pomocí programu Apache Ant. • entdiff.xml – výsledek porovnání komponent.
Překlad programu Překlad programu se provede s pomocí programu Ant. Pro tento účel je v hlavním adresáři programu uložen soubor build.xml, který obsahuje popis úkolů (cílů), které lze provést při kompilaci. Prvním z nich je cíl init, sloužící přípravě na kompilaci (vytvoří adresář bin). Dalším cílem je compile, který přeloží program a uloží ho do adresáře bin. Následující cíl dist vygeneruje z přeloženého programu Java archív a vloží do něj informaci o hlavní třídě (do souboru META-INF/MANIFEST.MF). Výsledek této operace je uložen v dist/BeanComparator.jar. Posledním cílem je clean, který slouží k vyčištění, to jest ke smazání přeložených tříd a jar-souboru. Cílem, který se provede standardně (pokud není specifikován žádný cíl při použití programu Ant) je dist. Tento cíl nejprve provede init, pak compile a pak až vlastní akci vytvoření Java archívu. Kompilaci lze tedy provést z příkazového řádku následovně (předpokladem je, že aktuální adresář je nastaven na adresář programu – BeanComparator): ant -f build.xml dist Nebo stačí spustit program Ant bez parametrů („antÿ), protože soubor build.xml má standardní název a cíl dist je uveden jako implicitní.
Spuštění programu Program lze spustit klasickým způsobem: java -jar "./dist/BeanComparator.jar" [param1] [param2] Pro zjednodušení je v hlavním adresáři programu uložen už zmiňovaný dávkový soubor beancomparator.bat. Další z dávkových souborů – beancmp.bat spouští přeložený program z adresáře bin (nepoužívá tedy balík BeanComparator.jar). Pro porovnání dvou komponent se musí zadat každá komponenta ve tvaru jarPath#beanName, kde jarPath je cesta k JAR-souboru, ve kterém je komponenta uložena a beanName je název komponenty, který je uveden v deployment descriptoru. Po spuštění programu se provede porovnání dif f (bean1, bean2), viz tabulka 8.1. Parametry -h (nebo žádné) -d jar1Path#bean1Name jar2Path#bean2Name
Význam Zobrazení nápovědy Demonstrace porovnání tříd Porovnání dvou komponent
Tabulka 8.1: Popis argumentů při spuštění programu Po skončení práce program vypíše do konzole obsah souboru entdiff.xml, který byl vytvořen v hlavním adresáři programu. Dále vypíše, zda jedna z komponent může nahradit druhou. Detailní informace o porovnání tříd je uložena v souboru classdiff.log.
VI
Příklad výpisu ze souboru classdiff.log V tomto příkladu jsou použity třídy z balíku exampleclasses, jehož UML diagram je uveden dále v příloze. exampleclasses.RecursionClass1 exampleclasses.RecursionClass1 exampleclasses.RecursionClass1 exampleclasses.RecursionClass1 exampleclasses.RecursionClass1 exampleclasses.RecursionClass1 exampleclasses.RecursionClass1 exampleclasses.RecursionClass1 exampleclasses.RecursionClass1 exampleclasses.RecursionClass1
... ... ... ... ... ... ... ... ... ...
diff(hashCode(0 params, 1, 1)) = None diff(getClass(0 params, 1, 1)) = None diff(wait(2 params, 1, 1)) = None diff(wait(0 params, 1, 1)) = None diff(wait(1 params, 1, 1)) = None diff(equals(1 params, 1, 1)) = None diff(notify(0 params, 1, 1)) = None diff(notifyAll(0 params, 1, 1)) = None diff(toString(0 params, 1, 1)) = None diff(exampleclasses.RecursionClass1 (0 params, 1, 1)) = None exampleclasses.RecursionClass2, exampleclasses.RecursionClass1 -> BadRecursionException! *********************** Classes history: ********************** exampleclasses.RecursionClass1 exampleclasses.RecursionClass2 false exampleclasses.RecursionClass2 exampleclasses.RecursionClass1 false *************************************************************** exampleclasses.RecursionClass1 ... attributes_diff = Unknown diff(exampleclasses.RecursionClass1, exampleclasses.RecursionClass2) = Unknown
Na prvních řádcích jsou porovnání jednotlivých metod (v závorce je vždy uveden: počet parametrů, počet metod první třídy, počet metod druhé třídy). Pod místem označeným BadRecursionException je zobrazena historie porovnání. V prvním sloupci historie jsou vypsány třídy, které jsou při porovnání na prvním místě (třídy c1 ve výrazu diff(c1, c2)), ve druhém sloupci jsou „druhéÿ třídy. Hodnoty typu boolean v posledním sloupci udávají, zda došlo ke změně směru porovnání. Na posledním řádku je uveden výsledná hodnota porovnání (celkový rozdíl) mezi oběma třídami.
VII
Výpis souboru entdiff.dtd - "attributes", "business_interfaces", "business_references", "env_entries", "home_interfaces", "home_references", "msg_consumed", "msg_destination_references", "resource_env_references", "resource_managers", "security_roles", "timer_service", "web_service_endpoint", "web_service_references" as parts of
element - "msg_type", "persistence", "reentrancy", "state", "transaction" as parts of element -->
VIII
UML diagramy cz.zcu.stuna.beancmp
IX
cz.zcu.stuna.classcmp
X
cz.zcu.stuna.beancmp.entejb (1.část)
XI
cz.zcu.stuna.beancmp.entejb (2.část)
XII
cz.zcu.stuna.exampleclasses (část)