Josef Kunhart

Bakalářská práce, Bc.

4 Implementace

4.1. Hierarchie balíčků a tříd

Projekt JkCrud se skládá z více než stovky tříd různého charakteru. Vzhledem k rozsáhlosti projektu je tedy nezbytné jeho vhodné členění na menší celky. Jednotlivé balíčky reprezentují jádro systému, rozšiřující prvky a doplňkové třídy. Jádro systému se skládá z aplikací JkCrud a JkCrossCrud, databázové vrstvy a několika společných tříd. Rozšiřující prvky zahrnují elementy, komponenty, filtry a validátory. Všechny zmíněné součásti budou detailně popsány v následujícím textu, s důrazem na významné a zajímavé prvky. Členění projektu přehledně znázorňuje diagram hierarchie balíčků na obrázku 20. V dalším textu bude často místo výrazu třída používán pojem objekt, který lépe vystihuje použití těchto tříd přímo v programu.

4.2. Jádro aplikace

Jádro vytvářeného systému tvoří zejména dvě hlavní aplikace, kterými jsou JkCrud a JkCrossCrud. Každá slouží specifickému účelu, ale jejich základní implementace je podobná. Nejprve budou diskutovány společné prvky, následně rozdíly pro každou z těchto dvou aplikací. Implementaci databázové vrstvy a uživatelského rozhraní jsou věnovány samostatné kapitoly. Jádro aplikace tvoří třídy z balíčků jkcrud.main, jkcrud.main.crud a jkcrud.main.crosscrud. Třídy, které obsahují prefix Crud, jsou součástí JkCrudu, obdobně to funguje u tříd JkCrossCrudu.

Základem celého systému je dvojice objektů Crud a CrossCrud, které slouží jako vstupní bod do programu a fasáda pro ostatní součásti. Oba tyto objekty mají společného abstraktního předka v hlavním balíčku main s názvem Composite. Název třídy je inspirován návrhovým vzorem, který tyto třídy implementují. Hlavním úkolem Crudu i CrossCrudu je poskytnout programátorovi metody pro definici konkrétní instance, která probíhá výhradně pomocí těchto tříd a rozšiřujících prvků. Dalším důležitým úkolem je zajištění spolupráce pro další objekty. Oba objekty obsahují větší množství interních proměnných pro uchování potřebných hodnot, naopak logika zpracování je zde omezena na nezbytné minimum. Nachází se zde například metody pro nastavení instance nebo kontrola správnosti definovaných parametrů.

Obrázek 20: Hierarchie balíčků a tříd

Hierarchie balíčků a tříd

Hlavní dvojici objektů doplňují třídy, které reprezentují přehled záznamů. Jejich názvy jsou CrudView a CrossCrudView. Oba objekty zabezpečují několik operací v přehledu. Starají se o sestavení a vykreslení uživatelského rozhraní, zapouzdřují objekty elementů. Dále zajišťují zpracování akcí z tlačítek a obsahují několik proměnných, potřebných pro přehled. S objekty přehledu úzce spolupracují mediátory. Mediátory za svůj název vděčí stejnojmennému návrhovému vzoru, jejich přesná pojmenování v aplikaci jsou CrudViewMediator a CrossCrudViewMediator. Tyto objekty se starají výhradně o spolupráci prvků uživatelského rozhraní. Mezi ně patří tabulka se záznamy, panel s filtry, stránkovač, přepínač počtu záznamů na stránku a přepínač konkrétní stránky. Mediátory, přehled a některé objekty pro potřeby grafického rozhraní mají také své abstraktní předky v hlavním balíčku main. Balíček main obsahuje i několik rozhraní. Executable umožňuje spuštění instance JkCrudu nebo JkCrossCrudu, Reloadable opětovné načtení dat v různém kontextu a Renderable vykreslení daného prvku.

4.2.1. JkCrud

JkCrud je aplikací, která pracuje nad jednou hlavní a několika doplňkovými databázovými tabulkami. Oproti JkCrossCrudu navíc pracuje se stavy pro zobrazení a s komponentami. Také umožňuje vnořování instancí obou typů. Stavy reprezentuje skupina objektů s příponou State. Název je opět odvozen z  pojmenování použitého návrhového vzoru. Pro detail slouží třída DetailState, pro úpravu EditState a pro přidávání AddState. Pro přehled existuje výchozí NullState. Každý ze stavu zobrazení se stará o vykreslení odpovídajícího obsahu a zpracování akcí uživatelského rozhraní. Akcí může být uložení, zavření dialogu nebo opětovné načtení dat. NullState nezobrazuje nic, jde o prázdný objekt. Příklad přechodu mezi stavy zobrazuje sekvenční diagram na obrázku 21. Diagram ukazuje zobrazení detailu záznamu při přechodu z přehledu. Původní nulový stav se zde změní na detail. Příklad využití a kooperace objektů při zobrazení přehledu ukazuje diagram spolupráce objektů na obrázku 22. Výčet objektů v diagramu není vyčerpávající, uvedeny jsou pouze ty nejdůležitější.

Obrázek 21: JkCrud, zobrazení detailu záznamu při přechodu z jejich přehledu

JkCrud, zobrazení detailu záznamu při přechodu z jejich přehledu

Obrázek 22: JkCrud, spolupráce objektů při zobrazení přehledu

JkCrud, spolupráce objektů při zobrazení přehledu

Metody pro práci s komponentami se nachází přímo ve třídě Crud. Tyto metody zajišťují definování komponent u vytvářené instance a navazující operace. Příkladem je sestavení hlavního dotazu pro výběr dat podle komponent nebo jejich prosté spočítání pro aktuální typ zobrazení. Pro práci s vnořenými instancemi v projektu existuje vnitřní třída Crudu s názvem InnerComposite. Seznam těchto instancí se ukládá do samostatné proměnné, pro jejich spuštění slouží příslušná tlačítka v detailu nebo v editaci.

Zajímavým doplňkem je čtveřice metod, které fungují podobně jako triggery v databázi. Tyto čtyři metody jsou pojmenovány doAfterInsert, doBeforeUpdate, doAfterUpdate a doBeforeDelete. Jak napovídají jejich názvy, lze je použít pro vykonání doplňující logiky u akcí pro odstranění, uložení a přidání záznamu. Jmenované metody se nachází přímo ve třídě Crud a ve výchozí implementaci nic nedělají. Předpokládá se jejich předefinování v odvozené třídě, ať již pomocí dědění nebo skládání. Původní akce i doplňkový kód probíhají vždy v uzavřené transakci, zejména kvůli zajištění konzistence dat.

4.2.2. JkCrossCrud

JkCrossCrud je jednodušší aplikací, která pracuje nad databázovou vazbou typu M:N. Jeho uživatelské rozhraní nepoužívá komponenty a liší se dvojicí vestavěných filtrů. Tyto filtry fungují stejně jako v JkCrudu, ale narozdíl od něj je zde nelze měnit. První z nich je standardní filtr pro hledání. Druhým je speciální filtr pro odlišení aktivních a neaktivních záznamů. Implementován je přímo jako vnitřní třída CrossCrudu. Jiné doplňkové objekty JkCrossCrud nepotřebuje, stačí výše zmíněné objekty CrossCrud, CrossCrudView a CrossCrudViewMediator.

4.3. Databázová vrstva

Databázová vrstva je další důležitou částí projektu. Skládá se z celkem čtyř tříd: Config pro potřeby konfigurace připojení k databázi, abstraktní třídy DB a dvou konkrétních tříd CrudDB a CrossCrudDB, každá pro odpovídající aplikaci. Třída DB implementuje rozhraní Dao se základními databázovými operacemi a rozhraní Transactable pro podporu transakčního zpracování. Config a DB se nachází v samostatném balíčku jkcrud.db, zbylé dvě přímo v balíčcích obou aplikací, kterými jsou jkcrud.main.crud a jkcrud.main.crosscrud. Důvodem jejich vyčlenění je spolupráce s množstvím dalších objektů v dané aplikaci. Tyto dvě třídy také implementují návrhový vzor singleton. V aplikaci tedy existuje nejvýše jeden objekt od každé z této dvojice tříd.

Nastavení připojení k databázi probíhá pomocí konfiguračního objektu, odvozeného od třídy Config. Tento objekt požaduje při svém vytváření parametr s cestou k souboru typu *.properties s přístupovými údaji k databázi. Direktivy pro nastavení tohoto připojení jsou: db.driver, db.url, db.login a db.pwd. Objekt s konfiguračními údaji je následně předán konstruktoru konkrétní instance JkCrudu nebo JkCrossCrudu. Tento způsob zajišťuje široké možnosti nastavení podle potřeb aktuálního nasazení. Například při jednotkovém testování se použijí údaje pro testovací databázi, je možné pracovat nad několika databázemi stejného typu apod.

4.3.1. Generování SQL dotazů

Rizikovou součástí vytvářeného projektu je generování databázových dotazů při práci s daty v databázi. Součástí programu je proto celá řada opatření, která kontrolují parametry z nastavení dané instance i uživatelských vstupů a znemožňují nebezpečné operace nebo narušení integrity dat v databázi. Základem jsou parametrizované dotazy, které brání útokům typu SQL injection. Principem tohoto útoku je podsunutí nebezpečného kódu přes neošetřený vstup a následné vykonání takto pozměněného dotazu. Všechna data z uživatelských vstupů jsou do dotazů předávána výhradně pomocí parametrů těchto předpřipravených dotazů. Jiné součásti dotazů ale tímto způsobem nelze kontrolovat, jde zejména o názvy sloupců z komponent a filtrů nebo nastavení klauzulí join, where aj. v definici instance. Názvy sloupců jsou proto ověřovány pomocí regulárních výrazů, které nedovolí použití problémových znaků. Kromě těchto kontrol jsou v nastavení vždy vyžadovány základní parametry (název instance, tabulka, primární klíč, unikátní identifikátor instance) a alespoň jedna komponenta pro každou dostupnou operaci.

Přes zmíněná opatření zůstává správná definice parametrů na zodpovědnosti programátora, který je nastavuje. JkCrud nijak neověřuje, jestli zadaná tabulka (sloupec, klíč, parametr) v databázi doopravdy existuje. Na druhou stranu aplikace poskytuje zpětnou vazbu, pokud některá součást není nastavena správně. Obvykle ve formě výjimky se stručným popisem nedostatku.

4.3.2. Logování SQL dotazů

Pro účely ladění, testování a optimalizace databázových dotazů je možné využít jednoduchý vestavěný systém logování dotazů. Jejich ukládání lze zapnout nastavením souboru pro ukládání logu v konfiguračním souboru aplikace. Odpovídající direktiva má název db.logfile a ve výchozím nastavení je prázdná (dotazy se neukládají).

4.4. Uživatelské rozhraní

Uživatelské prostředí JkCrudu je postaveno nad javovou knihovnou Swing a intenzivně používá jejich prvků a prostředků. Základem každé instance z pohledu grafického prostředí je ComponentFrame, odvozený od standardní třídy JFrame. Toto základní okno se typicky spouští ve vlastním vlákně a obsahuje všechny související části uživatelského rozhraní. V dalších odstavcích bude popsána tvorba uživatelského rozhraní v přehledu, stavy a několik dalších prvků uživatelského rozhraní. O elementech, komponentech, filtrech a validátorech pak pojednávají samostatné kapitoly. Pro lepší orientaci v uživatelském rozhraní programu je vhodné se podívat do 3. kapitoly (návrh) na obrázky 11, 13, 16 a 18 a také do 6. kapitoly (ukázková aplikace Music) na obrázky 29, 30, 31 a 32. Tyto náhledy zobrazují postupně přehled, detail, editaci a přidávání záznamu z uživatelského pohledu.

Primárním zobrazením po spuštění instance je přehled. Jak již bylo zmíněno, přehled se skládá z několika panelů s dalšími prvky. Nejdůležitější a zároveň nejkomplexnější částí přehledu je tabulka se záznamy. Tato tabulka je instancí třídy CrudViewTable v případě JkCrudu nebo třídy CrossCrudViewTable v případě JkCrossCrudu. Hlavním úkolem tabulky je vypsat data z databáze v požadovaném tvaru. U JkCrossCrudu je výpis dat daný: první sloupec je hodnota, druhý obsahuje zaškrtávací box pro výběr. Tento box je implementován pomocí vlastního editoru a rendereru pro buňky v tabulce. U JkCrudu je situace nepatrně složitější. Počet sloupců a jejich formát zde závisí na definovaných komponentách pro přehled. Tabulka dále obsahuje tlačítka pro spuštění akcí a některé sloupce lze řadit kliknutím na záhlaví tohoto sloupce. Tlačítka fungují opět na principu vlastního rendereru a editoru pro odpovídající buňky v tabulce, řazení používá standardní zpracování událostí po kliknutí myší. Zbylé panely v přehledu obsahují elementy a budou popsány v dalším textu.

Stavy pro zobrazení v JkCrudu se vykreslují v běžném dialogovém okně, které je rozšířeno o implementaci vlastních stavů. V každém okamžiku může být zobrazen právě jeden stav, nelze tedy editovat více záznamů najednou. Je-li zobrazen detail (DetailState), načtou se data záznamu do tabulky s využitím komponent pro detail z definice dané instance. Pokud uživatel pracuje s editací (EditState) nebo přidáváním (AddState), vygeneruje se formulář pro požadované zobrazení. Formulář se skládá z komponent podle jejich nastavení pro editaci, respektive přidání. Přidání zobrazuje výchozí hodnoty položek, při editaci jsou předvyplněny původní hodnoty z databáze.

Uživatelské prostředí aplikace dále obsahuje velké množství doplňujících prvků. Základem je možnost jazykové lokalizace programu. Jazyková nastavení se načítají z balíčku jkcrud.resources ze souboru i18n_*.properties. Hvězdička v názvu tohoto souboru značí, pro jakou jazykovou mutaci se soubor používá. Například cs_CZ odpovídá češtině, en_GB nebo en_US angličtině. Ta je zároveň výchozím jazykem. Obsah souboru tvoří standardní dvojice klíč-hodnota pro snadný překlad do nové mutace. Lokalizační soubor slouží i pro nastavení klávesových zkratek pro jednotlivé akce, tzv. mnemonics. Klávesové zkratky je možné definovat pro všechny standardní operace (zobrazení detailu, uložení záznamu, filtrování a mnohé další). S klávesovými zkratkami úzce souvisí ikonky pro lepší orientaci uživatele. Ikonky se načítají opět z balíčku jkcrud.resources, a to pomocí statické třídy Icon. Pro práci s překlady slouží statická třída I18N. Posledním doplňkem uživatelského rozhraní je třída MessageFactory. Tato třída se používá pro zobrazení zpětné vazby po provedení akce, která změnila data v databázi.

4.5. Elementy

Elementy se používají výhradně v přehledu záznamů. Každý z elementů se v konkrétní instanci používá právě jednou a zajišťuje specifickou funkci. FilterTrigger je nejjednodušší, slouží jako fasáda pro jednotlivé filtry, se kterými se pak pracuje dohromady. Paginator zajišťuje typické stránkování záznamů, PageSelector pak přechod na konkrétní stránku z výběru. Tento výběr je omezen na předchozích a následujících 100 stránek. Příklad spolupráce objektů při přepnutí stránky znázorňuje sekvenční diagram pro stránkování na obrázku 23. RecordSwitcher dovoluje přepnout počet záznamů, které se najednou zobrazují na jedné stránce.

Obrázek 23: JkCrud, přechod na jinou stránku při stránkování

JkCrud, přechod na jinou stránku při stránkování

V případě potřeby může být každý element nahrazen svým potomkem s upravenou funkcionalitou. Všechny elementy se nachází v balíčku jkcrud.elements. Balíček doplňuje abstraktní třída AbstractElement a továrna ElementFactory pro vytváření instancí elementů.

4.6. Komponenty

Myšlenka použití komponent patří k základním konceptům připravovaného projektu. Komponent je několik druhů, které se liší jejich použitím a zařazením do balíčků. Všechny se používají pro aktivní práci s daty, ať už jejich zobrazení nebo úpravu. Komponenty se používají pouze v JkCrudu, v JkCrossCrudu nejsou potřeba. Každý níže uvedený balíček obsahuje kromě vlastních komponent i odpovídající tovární třídu a abstraktního předka ostatních objektů.

4.6.1. Komponenty pro zobrazení

Komponenty pro zobrazení slouží pro potřeby zobrazení dat v přehledu a detailu. Rozlišují se dva typy těchto komponent. První typ pracuje nezávisle na databázových sloupcích a je umístěn v balíčku jkcrud.components.displayables. Zástupci této skupiny jsou pouze dva: Info pro zobrazení vlastního textu a Count, který zobrazí počet záznamů v navázané tabulce podle zadaných kritérií. Druhý typ komponent pro zobrazení je umístěn v balíčku s názvem jkcrud.components.database. Tato skupina je rozšířením výše uvedených komponent a pracuje právě nad jedním databázovým sloupcem, jehož název se definuje v nastavení instance.

Způsob zpracování dat u databázových komponent je nepatrně odlišný. Komponenta tohoto typu funguje jako filtr, který na vstupu obdrží data z databáze a před jejich zobrazením je může upravit. Skupina databázových komponent zahrnuje celkem šest zástupců: Text pro jednoduché vypsání dat z hlavní nebo doplňkové tabulky; Date, Time a DateTime pro zobrazení data a času v nastaveném formátu; CustomChoice pro výběr z vlastního seznamu možností a BitChoice pro zobrazení více hodnot uložených v bitovém formátu. Oba zmíněné typy zobrazovacích komponent implementují rozhraní Displayable, které předepisuje metody pro úpravu vypisovaných dat.

4.6.2. Komponenty pro úpravu

Komponenty pro úpravu představují další typ komponent, který se používá pro úpravu dat v editaci a při přidávání nového záznamu. Komponenty tohoto typu se nachází v balíčku jkcrud.components.updatables a implementují rozhraní Updatable. Toto rozhraní zahrnuje metody pro úpravu dat a předání hodnoty z/do použitého formulářového prvku komponenty. Stejně jako databázové komponenty pro zobrazení, každá komponenta pro editaci musí mít vždy definován databázový sloupec, nad kterým pracuje. Princip práce je také podobný. Vypsání dat v editaci probíhá stejně jako v komponentách pro zobrazení, tj. vypisovaná data mohou být před zobrazením upravena. Opačný princip je uplatněn při ukládání změn, data z komponenty mohou být před uložením podle potřeby upravena. Jednoduchým příkladem je datum, se kterým se pracuje jedním způsobem v databázi a jiným způsobem v komponentě (formáty typu yyyy-mm-dd, dd.mm.yyyy).

Komponent pro editaci je velké množství, programátor má navíc možnost snadno doplnit další. Pro práci s textem se používají Input a TextArea. Datum a čas lze editovat pomocí komponent DateSelect, TimeSelect, DateTimeSelect a YearSelect. Pro výběr z doplňkové tabulky nebo číselníku slouží DbSelect, pokud je potřeba vybírat z vlastního seznamu, lze použít ListSelect. OnOff se používá pro reprezentaci booleovské hodnoty, BitSelect pro výběr z několika možností najednou ukládaných jako bitové pole do databáze. Výběr doplňuje Password pro práci s uživatelským heslem nebo Hidden pro předvyplnění hodnoty pole v přidávání. Většina jmenovaných komponent umožňuje nastavení výchozí hodnoty, která se použije ve formuláři pro přidání nového záznamu. U datových komponent lze nastavit také rozsah výběru. Metody pro nastavení komponent jsou uvedeny v programové dokumentaci, která je umístěna na přiloženém médiu v adresáři /jkcrud/javadoc.

4.6.3. Dekorátory

Dekorátory slouží jako doplněk pro ostatní komponenty. Obvykle zapouzdřují funkcionalitu, používanou na více místech a v různých komponentách. Základní sada komponent obsahuje celkem čtyři dekorátory. Prefix a Suffix lze použít pro prosté vypsání textu před nebo za obsahem, který je načten z databáze. Tooltip slouží pro zobrazení vlastní nápovědy u komponenty, Null umožňuje pracovat s hodnotou null pro daný databázový sloupec. Tooltip a Null je možné použít pouze u komponent pro editaci. Kromě této podmínky lze dekorátory libovolně kombinovat a přidávat ke komponentám.

4.7. Filtry

Úkolem filtrů je poskytnout možnost omezit množinu záznamů, která se zobrazuje v přehledu. Filtry spolupracují s elementem FilterTrigger a implementují rozhraní Filterable a Resettable, která poskytují metody pro nastavení a vynulování filtrů. Pokud je filtrování aktivní, hlavní dotaz pro výběr záznamů tento fakt zohlední. Z filtru si vyžádá část dotazu pro zohlednění výběru společně s odpovídajícími parametry. Pro zapnutí nebo vypnutí filtru se používají tlačítka v horní části přehledu záznamů. Projekt nabízí celou řadu filtrů: SearchFilter pro obyčejné hledání, SelectFilter a ListFilter pro výběr z databázové tabulky nebo seznamu hodnot; CompareFilter pro porovnávání s uživatelem zadanou hodnotou; NullFilter pro výběr null nebo naopak not null hodnot. Příklad práce filtru demonstruje obrázek 24 se sekvenčním diagramem pro použití filtrů v přehledu.

Obrázek 24: JkCrud, použití filtrů v přehledu záznamů

JkCrud, použití filtrů v přehledu záznamů

4.8. Validátory

Validátory slouží pro kontrolu uživatelem zadaných údajů v přidávání nebo úpravě záznamu. Validátory se definují vždy pro konkrétní editační komponentu, typické použití je kontrola správného formátu textu nebo délky textu. V projektu se validátory nachází v balíčku jkcrud.validators, která kromě samotných validátorů obsahuje abstraktní třídu AbstractValidator a tovární třídu ValidatorFactory. Validace údajů probíhá po spuštění akce pro uložení dat. V tomto okamžiku se projdou všechny komponenty a jejich aktuální hodnoty jsou ověřeny podle nastavení validátorů, které byly těmto komponentám definovány. Pokud není něco v pořádku, uživateli se zobrazí zpětná vazba s popisem nedostatku.

Validátorů je v základní sadě celkem šest: NotEmptyValidator pro kontrolu, jestli bylo pole vyplněno; RegexpValidator pro ověření hodnoty vůči regulárnímu výrazu; StringLengthValidator pro kontrolu délky řetězce; ContainValidator, který kontroluje existenci podřetězce ve vyplněném řetězci. EqualValidator porovnává hodnoty pomocí javové metody equals; CompareValidator porovná zadanou hodnotu s nastavením, u kterého lze specifikovat operátor a hodnotu pro porovnání. Většina validátorů slouží pro ověření správnosti řetězců. Důvod je prostý, ostatní komponenty validaci často nepotřebují, pro výběr data lze nastavit rozsah od-do, u výběru z omezené množiny hodnot kontrolu také není potřeba.

4.9. Doplňkové třídy

Doplňkové třídy jsou umístěny v balíčku jkcrud.helpers a využívají se na různých místech projektu. Některé již byly popsány, například MessageFactory, I18N a Icon, které se používají pro načítání překladů a ikonek grafického uživatelského rozhraní. Statická třída Checker obsahuje metody pro kontrolu nastavení instance, které za použití regulárních výrazů kontrolují definované parametry. Třída Hash poskytuje jedinou metodu pro převod pole byte na řetězec. Ta se používá v komponentě Password. Pair je generická třída, která najde použití u combo boxů. Zde reprezentuje dvojici klíč-hodnota pro jednotlivé položky výběru. Zbývá třída Logger, kterou používá databázová vrstva pro logování databázových dotazů.

[Strany 39-52]