2017-10-06

Sestavování textových zpráv pomocí šablon nejen v Ujorm

Ujorm verze 1.74 obsahuje nový, samostatný modul, který nabízí třídy pro sestavování textových zpráv pomocí šablony a parametrů. Pro srovnání přikládám vzorové použití standardní Java knihovny. Všechny třídy se liší především způsobem zápisu parametrů v šabloně. Použité ukázky kódu jsem zkopíroval z jUnit testů projektu Ujorm.

Třída MsgFormatter

Metoda MsgFormatter.format() z knihovny Ujorm je určena pro jednoduché použití šablony, která označuje umístění parametrů dvojicí složených uvozovek "{}", pořadí parametrů vkládaných do šablony je dáno pořadím v metodě. Vzorová ukázka použití je tady:

    assertEquals("TEST"    , MsgFormatter.format("TE{}T", "S"));
    assertEquals("TE, S, T", MsgFormatter.format("TE", "S", "T"));
    assertEquals("TES{}"   , MsgFormatter.format("TE{}{}", "S"));
 
Výhodou je absence výjimek způsobených chybným počtem parametrů, protože přebytečné parametry se zapisují na konec zprávy oddělené čárkou s mezerou a ty chybějící se pouze nenahradí za značku. Pokud má výsledná zpráva obsahovat sekvenci "{}", je nutné ji vložit do šablony jako parametr. Třída nepodporuje formátování hodnot parametrů, v případě takové potřeby použijte raději následující třídu ze stejné knihovny.

Třída MessageService

Tato třída je také z knihovny Ujorm. Parametry metody MessageService.format() v šabloně se označují výrazem typu "${PRICE}", kde PRICE je název parametru, který může být volitelně doplněný formátem podle vzoru "${PRICE,%.2f}". Popis formátu je shdoný je shodný s metodou String.format(). Parametry šablony se předávají v objektu typu  Map<String, Object>, ukázka použití následuje:

    String expected = "On 2017-01-15, we spent 254.00 EUR.";
    String template = "On ${DAY,%tF}, we spent ${PRICE,%.2f} EUR.";
    MessageService instance = new MessageService();
    Map<String,Object> params = instance.map
          ( "DAY", LocalDateTime.of(2017, Month.JANUARY, 15, 12, 30)
          , "PRICE", new BigDecimal("254"));
    String result = new MessageService().format(template, params);
    assertEquals(expected, result);

Další ukázky použití najdete v jUnit testu. Pro srovnání uvádím dále ještě dvě metody ze standardní Java knihovny.

Třída String

Statická metoda String.format() vytváří interně instanci třídy Formatter. Pozice parametrů se označují výrazem začínajícím znakem procenta (například "%s") a volitelné formátování hodnoty parametru. Příklad použití:

    String expected = "On 2017-01-15, we spent 254.00 EUR.";
    String template = "On %tF, we spent %.2f EUR.";
    LocalDateTime day = LocalDateTime.of(2017, Month.JANUARY, 15, 12, 30);
    String result = String.format(ENGLISH, template, day, new BigDecimal("254"));
    assertEquals(expected, result);

Pokud je parametrů méně, než značek, metoda vyhazuje výjimku MissingFormatArgumentException, pokud je jich naopak více, tak jsou ignrorovány.

Třída MessageFormat

Poslední metoda MessageFormat.format() je také statická metoda ze standardní Java knihovny a také podporuje formátování parametrů. Místo určené pro vkládání pametrů se označuje výrazy číslovanými od nuly podle vzoru "{0}, "{1}" a konkrétní parametry se vkládájí pomocí pole. Příklad použití:

    String expected = "On 2017-01-15, we spent 254.00 EUR.";
    String template = "On {0,date,yyyy-MM-dd}, we spent {1,number,#.00} EUR.";
    Date day = Date.from(LocalDateTime.of(2017, Month.JANUARY, 15, 12, 30)
              .atZone(ZoneId.systemDefault()).toInstant());
    Object[] params = { day, new BigDecimal("254")};
    String result = new MessageFormat(template, ENGLISH).format(params);
    assertEquals(expected, result);

Výchozí Locale se bere z operačního systému, argument typu java.time.LocalDateTime není bohužel podporovaný a vyhazuje výjimku IllegalArgumentException.

Performance

Přikládám přibližnou výkonnost jednotlivých tříd, tolerance naměřených hodnot může být až 10%. Pro každý formatter proběhlo 5_000_000 iterací ve kterých se do jednoduché šablony (bez formátování) dosadily tři krátké argumenty.
Třída Čas [ms]
Výkon
(větší je lepší)
MsgFormatter 1158 100.00%
MessageService 2268 51.06%
String 12243 9.46%
MessageFormat 5023 23.05%

Další podrobnosti o testu najdete tady, Na závěr přikládám popis Maven závislosti:

   <dependency>
     <groupId>org.ujorm</groupId>
     <artifactId>ujo-tools</artifactId>
     <version>1.74</version>
   </dependency>

2017-09-18

Hodnocení voleb Schulzovou metodou aneb kam na dovolenou


Plánujete skupinovou dovolenou v přírodě? Hledáte zajímavé místo pro sraz maturantů po letech a nebo připravujete firemní teambuilding s kolegy? Pro určení optimální lokality ke spokojenosti všech zúčastněných se nabízí provést hlasování. V tomto článku bych rád poukázal na to, jak zásadní vliv na výsledek může mít algoritmus hodnocení vítěze voleb. Výklad se pokusím demonstrovat na výběru lokality pro tuzemskou dovolenou. Pro zjednodušení mějme tři kandidáty:
Většina voličů má svoji vlastní představu o pořadí favoritů. Pokusme se tedy vyjádřit formálně přání sedmi voličů tak, že každý volič zapíše své preference na jeden řádek, přitom nejdříve uvede svého nejlepšího favorita a směrem doprava budou postupovat kandidáti s nižší preferencí. Pokud nějakého kandidáta volič vynechá, tak mu tím přiřadí poslední místo. Přání voličů by mohlo vypadat třeba takto:
1. Adršpach - Bílé Karpaty - Česká Kanada (A-B-C)
2. Adršpach - Česká Kanada - Bílé Karpaty (A-C-B)
3. Adršpach - Česká Kanada (A-C)
4. Bílé Karpaty - Česká Kanada (B-C)
5. Česká Kanada - Bílé Karpaty (C-B)
6. Bílé Karpaty - Česká Kanada (B-C)
7. Česká Kanada - Bílé Karpaty (C-B)

Pokud vyhodnotíme přání voličů tradiční metodou, vezmeme v úvahu pouze jednoho (nejvíce preferovaného) kandidáta každého voliče. Výsledek hlasování pak bude vypadat takto:
1. místo vyhrává kandidát A - Adršpach se 3 hlasy
2-3. místo obsadí kandidát B - Bílé Karpaty s 2 hlasy
2-3. místo obsadí kandidát C - Česká Kanada s 2 hlasy
Někteří voliči však mohou mít oprávněný pocit, že jejich přání nebylo dostatečně zohledněno. Zkusme se tedy poohlédnout po nějakém spravedlivějším hodnocení. Jedno z těch nejlepších se jmenuje Schulzova metoda, která posuzuje každou dvojici kandidátů tak, jako by se jednalo výhradně o jejich volbu. Při této metodě nemá význam provádět vícekolové hlasování, které proběhlo třeba při posledních prezidentských volbách ČR. Nechci vás zatěžovat postupem Schulzovy metody (detailní popis v angličtině najdete zde), pojďme se podívat rovnou na výsledek:
1. místo vyhrává kandidát C - Česká Kanada
2. místo obsadí kandidát B - Bílé Karpaty
3. místo obsadí kandidát A - Adršpach
Není to zajímavé? Vítěz první metody se propadl na konec hodnocení metody druhé. Většina voličů si nepřála lokalitu A, ale zároveň ta stejná většina se neshodla mezi lokalitou B a C. Konečného vítěze určila nakonec skupina, která svoji hlavní lokalitu sice neprosadila, ale rozhodly její preference na druhém pořadí. Druhý volební výsledek je přesnějším odrazem přání voličů. Hodnocení lze ověřit například na této stránce:

Screenshot kalkulačky

Začíná být zřejmé, že způsob hodnocení hlasování může mít zásadní vliv na výsledky voleb nejen u společného výletu, ale i v případě politických kandidátů. Je dobré ještě připomenout, že ani Schulzova metoda nemůže zabránit taktizování voličů.

Technické informace o projektu

Webový projekt z přiloženého screenshotu umožňuje testování vítěze hlasování Schulzovou metodou, pro reálné využití bude třeba posbírat preference voličů. Kód je napsaný v jazyce Java 8 s využitím GWT, vyhodnocení vítěze probíhá na straně klienta (konkrétně internetového prohlížeče), na server se odesílají jen statistické údaje o počtu hlasů pro logování. Algoritmy důležité pro určení vítěze jsou pokryté jUnit testy. Projekt je volně šiřitelný open-source pod licencí Apache License 2 a je možné ho stáhnout z GitHub. Přikládám doporučený postup pro spuštění na lokálním počítači:
  1. nainstalujte si JDK verze 8, Maven a Git
  2. z konzole založte nový adresář projektu a v něm proveďte posloupnost příkazů:
  3. git clone https://github.com/pponec/schulze-method .
  4. mvn install -DskipTests
  5. mvn gwt:run # alternativně můžete provést deploy WAR souboru na vhodný webový kontejner, například Tomcat
Pro zajímavost dodávám, že aplikace byla vytvořena v prostředí integrovaného editoru NetBeans na linuxovém operačním systému Xubuntu. Článek vyšel na blogu ORM frameworku Ujorm, webová aplikace pro hodnocení vítěze však nemá se zmíněným frameworkem téměř nic společného, snad kromě autora :).

Užitečné odkazy





Ujorm verze 1.72

V polovině srpna byla uvolněna nová verze frameworku, která od verze 1.56 doznala řadu změn. Přehled těch nejzajímavějších přikládám:

  • je podporována pouze Java 1.8 a vyšší
  • ORM modul podporuje datové typu z balíčku java.time date pomocí implementace JDBC 4.2+
  • ORM modul podporuje SQL dotazy typu LEFT-OUTER-JOIN přímo - pomocí API frameworku
  • spojování některých typů podmínek je optimalizované pro případy (x OR true) nebo (x AND false), v takových případech se nevytváří zbytečně nové instance třídy Criterion a v ORM modulu se generuje jednodužší SQL dotaz
  • zápis argumentů do interní třídy  java.uitl.Logger byl opraven tak, aby byl nezávislý na použitém frameworku. Pro zápis parametrů do šablony se pužívá znak "{}".
  • API některých metod je doplněno anotacemi @Nonnull a @Nullable podle specifikace jsr305 pro lepší podporu statické analýzy
  • bylo opraveno několik nekritických chyb v ORM i dalších modulech

Úplný popis změn v angličtině je tady závislost na ORM modulu se definuje pro Maven projekt takto:
<dependency>
     <groupId>org.ujorm</groupId>
     <artifactId>ujo-orm</artifactId>
     <version>1.72</version>
</dependency>

Popis vzorové implementace Ujorm frameworku je tady.

2015-09-20

Aliasy databázových tabulek v Ujorm

Jak se používají aliasy databázových tabulek v ORM frameworku Ujorm? Rád bych vyjasnil nejdříve některé základní pojmy (nejen) z frameworku Ujorm a jejich vzájemný vztah:
  • atribut / property - je vlastnost objektu, výklad můžeme demonstrovat na objektech typu POJO
  • vazba / relationship - speciální "atribut" objektu na jinou třídu typu POJO
  • propertyDescriptor - třída popisující vlastnosti "atributu" POJO (meta-model)
  • relationshipDescriptor - je propertyDescriptor, který popisuje "vazbu" mezi dvéma POJO (meta-model)
  • Key (dříve UjoProperty) - je propertyDescriptor frameworku Ujorm (immutable object)
  • Key<?,RelatedUjo> - je relationshipDescriptor frameworku Ujorm. Pokud je RelatedUjo perzistentní objekt ORM, tak instance Key popisuje vazbu na databázovou tabulku.

Při použití ORM se při startu aplikace sestavuje meta-model, kde každá databázová tabulka dostane pevně přiřazený svůj alias, to je vlastnost frameworku. Takový přístup sice vyhovuje mnoha databázovým dotazům, ale najdeme jistě případy, kdy se statickými aliasy nevystačíme, jsou to:
  1. rekurzivní dotazy (typicky filtrování zaměstnanců podle atributů nadřízeného)
  2. dvě relace na stejnou DB tabulky (například pro filtrování tabulky měnových kurzů, kde pro filtrování potřebujeme jeden alias na tabulky prodejní měny a jiný alias na tabulku nákupní měny). Tento případ zobrazuje následující class-model:
Pro popis odlišných aliasů se ve frameworku Ujorm využívá metoda Key.alias(String) pro Klíče typu relationshipDescriptor. Protože Klíče jsou vždy neměnné instance, tak metoda alias(String) vytváří ve skutečnosti nový kompozitní klíč, který obsahuje ten původní klíč společně a požadovaným alias jménem. Na příkladu měnových kurzů by pak mohla podmínka pro filtrování kurzu EUR/CZK vypadat takto:

import static Currency.CODE;

final Criterion<ExchangeRate> crn1, crn2, crn3;
crn1 = ExchangeRate.BUY.alias(
"aliasBuy").add(CODE).whereEq("EUR");
crn2 = ExchangeRate.SELL.alias(
"aliasSell").add(CODE).whereEq("CZK");
crn3 = crn1.and(crn2);


kde výrazy BUY a SELL jsou vazby (relationshipDescriptor) na číselník kurzových měn Currency. V servisních metodách však není dobré uvádět názvy alias explicitně a proto si připravíme nový kompozitní klíč ve třídě ExchangeRate:

public static final Key<ExchangeRate,Currency> SELL = ExchangeRate.$_DIRECT_SELL.alias(
"aliasSell");

kde výraz $_DIRECT_SELL představuje původní přímý klíč na tabulku prodejní měny. Vazba BUY si ponechá svůj původní alias, vazba SELL použije vždy explicitně zadaný alias. Java kód dotazu se pak zjednoduší na:

final Criterion<ExchangeRate> crn1, crn2, crn3;
crn1 = ExchangeRate.BUY.add(CODE).whereEq("EUR
")
crn2 = ExchangeRate.SELL.add(CODE).whereEq(
"CZK")
crn3 = crn1.and(crn2);


Při častějším použití dotazu si můžeme ve třídě ExchangeRate připravit dva nové kompozitní klíče ukazující přímo na ten kód měny, pak se sestavení podmínky dále zjednoduší na výraz:

final Criterion<ExchangeRate> crn1, crn2, crn3;
crn1 = ExchangeRate.BUY_CODE.whereEq("EUR
")
crn2 = ExchangeRate.SELL_CODE.whereEq(
"CZK")
crn3 = crn1.and(crn2);


Podobné řešení můžeme použít také pro případ rekurzivních SQL dotazů.

2015-04-18

Tvorba indexů v Ujorm 1.56

Nová verze Ujorm 1.56 nabízí rozšířené možnosti tvorby databázových indexů pomocí anotace @Column (umístěné nad klíčem typu Key<A,B>) se dvěma metodami:
  • index()
  • uniqueIndex()
kde argumentem metod může být nově seznam indexů. Pokud se index jednoho jména vyskytuje v několika anotacích, vznikne kompozitní index.

Příklad použití:
   private static final String IDX_STATE_COUNT = "idx_state_count";
   private static final String IDX_COUNT = "idx_count";

   @Column(index = IDX_STATE_COUNT)
   public static final Key STATE = newKey();

   @Column(index = {IDX_STATE_COUNT, IDX_COUNT})
   public static final Key COUNT = newKey();

Databázový sloupec COUNT tedy bude obsažen ve dvou různých indexech, z toho jeden bude kompozitní.

Aby vznikl unikát kompozitní index, musí být jeho jméno vloženo vždy metodou uniqueIndex(). Pořadí sloupců v kompozitním klíči je určeno pořadím Klíčů. Pokud budeme potřebovat jiné pořadí, můžeme použít rozšířenou implementaci třídy pro sestavení indexů z frameworku Ujorm, která akceptuje pořadí sloupce (v komp. indexu), které se vkládá za název indexu. Výchozí hodnota je 10, dva sloupce se stejným číslem pak kopírují pořadí Klíče. Ukázka použití je tady:
   private static final String IDX_STATE_COUNT = "idx_state_count";

   @Column(index = IDX_STATE_NOTE + "#30")
   public static final Key STATE = newKey();

   @Column(index = IDX_STATE_NOTE + "#20")
   public static final Key COUNT = newKey();

Rozšířenou implementaci třídy pro sestavení indexů lze nakonfigurovat (v době sestavení meta-modelu) příkazem:
   MetaParams params = new MetaParams();
   params.set(MetaParams.INDEX_MODEL_BUILDER, IndexModelOrderedBuilder.class);
   ormHandler.config(params);

Je zřejmé, že parametr INDEX_MODEL_BUILDER může referencovat také vlastní implementaci, která pak může sestavovat model indexů podle úplně jiných pravidel a anotací. Specifické požadavky lze potom řešit zcela mimo framework Ujorm.

2015-02-19

2014-03-24

Key-value programování v jazyce Java

Ujorm je knihovna postavená na key-value architektuře doménových objektů, které přistupují ke svým atributům pomocí tak zvaných Klíčů. Na několika krátkých příkladech bych rád demonstroval zajímavé či nové vlastnosti jádra frameworku Ujorm verze 1.45.

Odkaz na celý článek:
http://ujorm.org/sample/key-value-cz.html

Obsah:
  • Zápis a čtení hodnot
  • Obnovení defaultních hodnot
  • Mělká kopie objektu
  • Validace atributů při zápisu
  • Kompozitní Klíče
  • Criterion jako popis podmínky
  • Criterion pro filtrování kolekce
  • Řazení kolekcí
  • Import CSV formátu

2014-03-11

Ujorm verze 1.44

V Maven repozitářích je k dispozici nová verze Ujorm, která obsahuje dlouho očekávanou podporu pro tvorbu hierarchických dotazů, ve kterých se cizí relace databázové tabulky odkazují na sama sebe. Dříve bylo nutné pro tento typ dotazů používat nativní SQL příkazy, nově lze tyto dotazy modelovat pomocí Ujorm klíčů typu relace, za které se vkládá alias referencované tabulky. Ukázka použití je demonstrována na dotazu, který vyhledá všechny zákazníky s příjmenín "Brown" a s prarodičem "Smith":


     Criterion<Customer> crn1, crn2, crn3;
     crn1 = Customer.PARENT.alias("parent1")
       .add(Customer.PARENT).alias("parent2")
       .add(Customer.SURENAME)
       .whereEq("Smith");
     crn2 = Customer.SURENAME.whereEq("Brown");
     crn3 = crn1.and(crn2);


     Customer customer = session.createQuery(crn3).uniqueResult();


Uvedený Aliasy lze vyuźít také v případech, kdy jedna entita obsahuje více relací na stejnou entitu, příkladem mohou být dvěr relace Person.MOTHER, Person.FATHER.


Mezi další novinky Ujorm řady 1.4x patří:
  • XML konfiguraci ORM meta-modelu lze validovat pomocí XSD souboru
  • k dispozici je nový modul (ujo-xsd) pro generování XSD souborů podle UJO třídy
  • ORM podporuje alternativně nativní DB sekvence pomocí třídy NativeDbSequencer
  • ORM lazy loading je možné povolit volitelně na uzavřené session, v takových případech se otevírá nová DB connection na nezbytně nutnou dobu
  • došlo k odstranění zastaralého interface UjoProperty, který byl od verze 1.30 deprecated a by nahrazen novým interfacem Key (včetně souvisejících metod)

Podrobnějśí popis změn je v release notes.

2013-09-09

Demo aplikace pro Ujorm a Wicket

Knihovna Ujorm byla představena na portálu java.cz v roce 2007, tehdy ještě pod starým názvem UJO Framework. Od té doby uběhlo 6 let a zdrojový kód prošel mnoha úpravami, základní myšlenka však zůstala stejná: přístup k vlastnostem objektu se provádí pomocí konstant zvaných klíče.

Pro prezentaci knihovny Ujorm jsem se rozhodl připravit malý webový projekt, který provádí rezervaci hotelů. Grafické rozhraní vykresluje framework Apache Wicket, jádrem aplikace jsou přehledové tabulky a editační dialogy, pro přístup k databázi se využívá ORM modul projektu Ujorm.
Pro ilustraci přikládám screenshot editačního dialogu a odpovídajícího zdrojového kódu:





Detailní článek k demo aplikaci je k dispozici zde.