31. prosince 2009

Největší problémy při zavádění testování

Mám teď možnost zavádět testování do již existujícího projektu. Projekt je to celkem velký - přes 10 vývojářů, přes 100 tabulek, přes 50 tisíc tříd, několik let vývoje. Technologicky je to postavené nad Springem, Hibernate a spoustou dalších knihoven. O zavedení testování se již pár lidí přede mnou snažilo, ale vždy bez úspěchu, tak jsem zvědavý, jak teď dopadnu já. Hodně důležité je, že testování chce nyní i vedení, což je moc důležitý předpoklad - psaní a údržba testů stojí spoustu úsilí a času a pokud není podpora ze shora, tak se to musí pak "pytlíkovat" za zády a to není moc dobré.

Mám dojem, že většina vývojářů by testy psala celkem ráda, ale vždy diskuze na toto téma skončí u toho, že aplikace je to velice složitá, a že je problém vůbec nějak nastavit počáteční data pro testy.

Ještě je brzy na to, abych psal jak to celé dopadlo, to ještě bude nějaký čas trvat. Dnes bych chtěl psát o největších problémech, které jsem zatím musel řešit, abych vůbec mohl napsat nějaké testy:

  • "špagety kód" - určitě největší problém. Aplikace se vyvíjí již řadu let a zejména ten starý kód není nic moc. Když to přeženu, tak vše souvisí se vším a pak je problém inicializovat pro testy jen ty části aplikace, které potřebuji. Musel jsem refaktorovat, psát stub objekty, abych se dokázal vymezit. Tady není možné praštit do stolu a říci, že zítra se celá aplikace zrefaktoruje a bude to ok. Je to postupný proces. Tento problém se netýká jen samotného kódu, ale také konfigurace aplikace. Musel jsem jí rozdělit do částí tak, abych mohl např. inicializovat jen připojení k databázi nebo webové služby.

  • propojení s dalšími aplikacemi - aplikace závisí na spoustě dalších dílčích aplikací a proto je nezbytné se umět vymezit, nejlépe pomocí nové implementace konektorů pro testy (stub vs. mock objekty).

  • špatná dokumentace - to neplatí jen pro testy, ale obecně. Abych mohl co nejlépe a nejjednodušeji používat nebo implementovat rozhraní, tak potřebuji kvalitní dokumentaci (JavaDoc), jinak si pak musím vše dohledávat v kódu sám.

  • používání auto-wiringu - Springovský auto-wiring se používá pro konfiguraci celé aplikace, takže na první pohled nejsou vůbec jasné vazby mezi třídami, komponentami. Zde jsem musel hodně zapojit IDEU (zejména výborný nástroj na analýzu závislostí v kódu), abych se dopátral všech možných závislostí. Držel bych se doporučení autorů Springu a auto-wiring bych používal jen pro testy.

  • generované ID beanů - pokud v konfiguraci beanů neuvedeme atribut id nebo name, tak to neznamená, že daný bean identifikátor nemá, ale že se automaticky generuje, standardně podle jména třídy (rozhraní BeanNameGenerator). Takže když pak změním jméno třídy, tak se mi změní i identifikátor beanu. Je proto určitě lepší vždy definovat identifikátor, ideálně přes atribut id.

  • transakční propagace REQUIRES_NEW - testy nemají mít žádný vedlejší efekt, žádná uložená data v databázi po testech. Sem tam je v aplikaci použita propagace REQUIRES_NEW, což znamená, že v případě existující transakce se aktuální transakce pozastaví (suspenduje), spustí se nová transakce, která se pak kamitne a pak se znovu aktivuje původní transakce. Toto nemám ověřené, ale vidím zde možný problém pro testy, kdy by se vlastně měly všechny transakce na konci rollbacknout, ale co tyto vnořené transakce, které již jsou po komitu? REQUIRES_NEW se používá minimálně, tak možná to nebude potřeba ani testovat a když bude potřeba, tak zkusím následující možnosti - upravit transakčního managera nebo refaktorovat produkční kód.

Opět se mi potvrdila moje dřívější zkušenost, že čím kvalitnější produkční kód bude, tím lépe se mi budou psát testy. V tomto také vidím jeden z největších přínosů testování.

20. prosince 2009

Generování class diagramů

Class diagramy dnes umí vygenerovat mnoho nástrojů, ale přesto jsme raději nakonec použili vlastní řešení pro generování class diagramů. Mnohdy nám přišla nedostatečná kvalita vygenerovaných diagramů, jindy zase bylo málo možností konfigurace generování a nakonec se ukázalo, že bychom rádi celý proces generování class diagramů zautomatizovali, což u většiny nástrojů nebylo možné.

Takto vygenerované class diagramy používáme jednak pro vlastní potřebu, abychom se v určitém kódu sami lépe vyznali a pak je také daváme jako součást naší dokumentace, zejména pro rozhraní webových služeb.

Pro naše potřeby jsme použili knihovnu UmlGraph. Tato knihovna se stará o zjišťování vazeb mezi třídami a funguje na principu JavaDoc docletu. Buď je možné zvolit zcela automatický řežim, kdy knihovna se sama snaží podle konfigurace zjistit vazby mezi třídami a nebo je možné každou třídu doplnit v JavaDocu třídy speciálními tagy, např. pro vyjádření dědičnosti. Automatický řežim celkem funguje, ale není to 100% - pokud bych rád zobrazoval např. kardinalitu vazeb, pak musím použít tagy. My standardně používáme automatický řežim a jen jednou se nám stalo, že určité vazby se nezobrazily, tak jak jsme chtěli - potom jsme použili tagy a bylo vše ok.

UmlGraph pouze generuje DOT soubor, který je nutný následně zpracovat pomocí nástroje Graphviz, který teprve vykreslí výsledný obrázek. Bohužel Graphviz je nutné lokálně instalovat, což je snad jediná nevýhoda celého řešení. Je možné zase spoustu věcí nastavit dle vlastních potřeb, včetně výstupního formátu obrázku.



Přikládám ukázku kódu:

package cz.marbes.daisy.modules.generator.diagramgenerator;

import org.apache.commons.lang.StringUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.PrintWriter;

/**
* Generator class diagramu.
*
* <p>Generator je spousten pres {@link #main(String[]) main} metodu a generuje
* class diagram pro zadany balik (package) trid.
*
* @author <a href="mailto:petr.juza@marbes.cz">Petr Juza</a>
* @see <a href="http://www.graphviz.org">graphviz</a>
* @see <a href="http://www.umlgraph.org/">UML graph</a>
*/
public class ClassDiagramGenerator {

private final static String DOT_FILENAME = "graph.dot";
private static final String UMLGRAPH_MAIN_CLASS = "org.umlgraph.doclet.UmlGraph";

/**
* Vystupni format obrazku [png|gif]
*/
private static final String OUTPUT_IMAGE_FORMAT = "gif";


private ClassDiagramGenerator() {
}


/**
* Main metodu, vstupni bod pro generovani class diagramu.
*
* @param args Vstupni parametry generovani:
* <ol>
* <li>Package, pro ktery se ma generovat class diagram
* (napr. {@code cz.marbes.daisy.modules.aa.wscommon.komu.v1_1_2})
* <li>Vystupni adresar pro DOT soubor a vysledny obrazek
* <li>Absolutni cesta k adresari projektu se zdrojovymi soubory, kde je zadany package
* (napr. {@code /Volumes/Obelix/projects/daisy/apl/aa/trunk/aa-core/src/main/java})
* <li>Cesta ke graphviz DOT souboru (napr. {@code /usr/local/bin/dot})
* <li>Seznam nazvu trid (staci pouze nazev, nemusi byt vcetne baliku), ktere budou vynechany
* pri vykreslovani class diagramu. Jedn. tridy budou oddeleny carkou.
* </ol>
* @throws IllegalArgumentException pokud "nesedi" vstupni parametry
* @throws ClassNotFoundException pokud neni dostupna UmlGraph knihovna
*/
public static void main(String[] args) throws Exception {
// args = new String[]{
// "cz.marbes.daisy.modules.aa.wscommon.smlouvy.v1_1_3",
// "/Volumes/Obelix/projects/daisy/diagram_output",
// "/Volumes/Obelix/projects/daisy/apl/aa/trunk/aa-core/src/main/java",
// "/usr/local/bin/dot",
// "TypPripadService,SqlBuilderQuery,AaSqlBuilderHook"
// };

//kontrola existence UmlGraphu v classpath
try {
Class.forName(UMLGRAPH_MAIN_CLASS);
}
catch (ClassNotFoundException ex) {
throw new ClassNotFoundException("UmlGraph neni dustupny - " +
"pridejte knihovnu UmlGraph do classpath", ex);
}


//validace a zpracovani vstupnich hodnot
if (args == null
|| args.length < 4
|| args.length > 5) {
throw new IllegalArgumentException("Generator ocekava 4 povinne a 1 nepovinny vstupni parametr ...");
}

String packagePath = args[0];
String outputFolder = args[1];
String srcFolder = args[2];
File graphvizDotFile = new File(args[3]);

String excludeClasses = null;
if (args.length == 5) {
excludeClasses = args[4];
}

//kontrola existence graphviz DOT souboru
if (!graphvizDotFile.exists()
|| !graphvizDotFile.canExecute()) {
throw new IllegalArgumentException("Graphviz DOT soubor neexistuje ["
+ graphvizDotFile.getAbsolutePath() + "].");
}

System.out.println("--------------------------------------------------------------");
System.out.println("Generovani class diagramu pro - " + packagePath);
System.out.println("--------------------------------------------------------------");

//generovani
ClassDiagramGenerator generator = new ClassDiagramGenerator();
File diagramDotFile = generator.generateDotFile(packagePath, outputFolder, srcFolder, excludeClasses);

if (diagramDotFile != null) {
generator.generateImage(graphvizDotFile, diagramDotFile);
}

System.out.println("Konec generovani ...");
}


/**
* Generuje DOT soubor.
*
* <p>DOT soubor (format pro <a href="http://www.graphviz.org">graphviz</a>)
* je generovan knihovnou <a href="http://www.umlgraph.org/">UML graph</a>.
*
* @param packagePath Package, pro ktery se ma generovat class diagram
* (napr. {@code cz.marbes.daisy.modules.aa.wscommon.komu.v1_1_2}).
* @param outputFolder Vystupni adresar pro DOT soubor
* @param srcFolder Absolutni cesta k adresari se zdrojovymi soubory, kde je zadany package
* @param excludeClasses Seznam nazvu trid, ktere budou vynechany pri vykreslovani class diagramu.
* Jedn. tridy budou oddeleny carkou.
* @return generovany DOT soubor
*/
private File generateDotFile(String packagePath,
String outputFolder,
String srcFolder,
String excludeClasses) {
File fileDot = new File(outputFolder + File.separator + DOT_FILENAME);

if (StringUtils.isNotEmpty(excludeClasses)) {
//vymazou se mezery a nahradi se carky lomitky (, -> |)
excludeClasses = StringUtils.deleteWhitespace(excludeClasses);
excludeClasses = StringUtils.replaceChars(excludeClasses, ',', '|');
}

excludeClasses = StringUtils.trimToNull(excludeClasses);

//viz http://www.umlgraph.org/doc/cd-opt.html,
// http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/javadoc.html#javadocoptions
String[] args = new String[]{
"-all", //=-attributes -operations -visibility -types -enumerations -enumconstants
"-hide",
"^(java|com\\.sun|org.apache)" + (excludeClasses != null ? "|" + excludeClasses : ""),
"-inferrel",
"-inferdep",
"-inferdepinpackage",
"-collpackages",
"java.util.*",
"-verbose",
"-output",
fileDot.getAbsolutePath(), //napr.: "/Volumes/Obelix/D/graph.dot",
"-sourcepath",
srcFolder,
packagePath
};


//vytvoreni writeru kvuli logovani
PrintWriter errWriter = new PrintWriter(System.err);
PrintWriter warnWriter = new PrintWriter(System.out);
PrintWriter noticeWriter = new PrintWriter(System.out);

System.out.println("Generovani DOT souboru: ");
System.out.println(" Source folder - " + srcFolder);
System.out.println(" Package path - " + packagePath);
System.out.println(" Dot file - " + fileDot.getAbsolutePath());
System.out.println(" Exclude classes - " + excludeClasses);

//vygenerovani DOT souboru
com.sun.tools.javadoc.Main.execute("UmlGraph",
errWriter, warnWriter, noticeWriter, UMLGRAPH_MAIN_CLASS, args);

return fileDot;
}


/**
* Generuje vysledny obrazek class diagramu.
*
* @param graphvizDotFile Graphviz DOT soubor
* @param diagramDotFile DOT soubor ke zpracovani
* @throws Exception v pripade problemu pri generovani obrazku
*/
private void generateImage(File graphvizDotFile, File diagramDotFile) throws Exception {
try {
String[] args = new String[]{
graphvizDotFile.getAbsolutePath(),
"-T" + OUTPUT_IMAGE_FORMAT,
"-O",
diagramDotFile.getAbsolutePath()
};

System.out.println("Generovani obrazku: ");
System.out.println(" Graphviz DOT soubor - " + graphvizDotFile.getAbsolutePath());
System.out.println(" DOT soubor ke zpracovani - " + diagramDotFile.getAbsolutePath());

Process p = Runtime.getRuntime().exec(args);

BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String line;
while ((line = reader.readLine()) != null) {
System.err.println(line);
}

int result = p.waitFor();
if (result != 0) {
System.err.println("Errors occured during running Graphviz - return code is " + result);
}
}
catch (Exception ex) {
System.err.println("Errors occured during running Graphviz - " + ex.getLocalizedMessage());
throw ex;
}
}
}


A ještě obecný ANT task pro spouštění generování diagramů:
    <target name="gen_diagram" description="Generovani class diagramu pro zadany package." depends="clean">
<input message="Zadejte package, pro ktery chcete generovat class diagram (napr. cz.marbes.daisy.modules.aa.wscommon.pvs.v1_1_1)?"
addproperty="packagePath" />

<input message="Zadejte cestu ke zdrojovym souborum relativni k projektu, kde je zadany package (napr. /apl/aa/trunk/aa-core/src/main/java)?"
addproperty="srcPath" />

<input message="(nepovinne) Zadejte seznam nazvu trid oddelenych carkou (nemusi byt vcetne baliku), ktere budou vynechany z class diagramu."
addproperty="excludeClasses" />

<java classname="cz.marbes.daisy.modules.generator.diagramgenerator.ClassDiagramGenerator"
classpathref="project.class.path" fork="true">
<arg value="${packagePath}"/>
<arg value="${diagram.output.folder}"/>
<arg value="${basedir}/${srcPath}"/>
<arg value="${graphviz.dot.path}"/>
<arg value="${excludeClasses}"/>
</java>
</target>

7. prosince 2009

Výhody spolupráce s externisty

Rád bych dnešním příspěvkem navázal na předcházející článek, kde jsem psal o tom, jaké je to pracovat na volné noze. Dnes bych rád navázal a podívám se na výhody, které firmy mohou získat z najímání vývojářů na kontrakt, tzv. body-shopping.

(Pozn.: jen pro upřesnění - práci formou kontraktu si představuji tak, že veškeré náklady spojené s výkonem mé práce jsou čistě mojí záležitostí, neočekávám žádné zaměstnanecké výhody, pouze domluvenou finanční odměnu. Tedy žádný Švarc systém).

  • flexibilní zdroje - vždycky se nedá vše naplánovat a ovlivnit na 100%, často se objevují nové požadavky během vývoje, ale termín zůstává, analýza se opozdila, je nutné řešit více projektů najednou atd. Podobné situace asi všichni dobře znáte, a proto může být výhodné pro firmy mít "někoho" v záloze, kdo může v těchto situacích pomoci. Pokud si navíc firma může vybrat lidi s přesně danými znalostmi a zkušenostmi, tak pak je to opravdu velká výhoda. Určitě nejčastější důvod využívání externích vývojářů.

  • flexibilní náklady - pokud nemusím někoho vést jako zaměstnance, nemusím vést jeho mzdy, vydávat mu stravenky a řešit jeho zaměstnanecké výhody, nemusím mu dávat počítač, mobil, auto, tak budu schopen ušetřit nemalé náklady a hlavně to bude pro mě znamenat velkou flexibilitu z pohledů nákladů firmy - skončí projekt, skončí potřeba.

  • získání know-how - lidi na volné noze mají často hodně co nabídnout, protože vidí jak to funguje v jiných firmách, pracovali na více projektech než standardní zaměstnanci a mohou tedy vnést do firmy nové myšlenky a znalosti. Bohužel tento přínos je hodně často opomíjen a zbytečně ze strany firem nevyužíván.

  • dobře fungující firma - čím lépe bude mít firma nastaveny interní procesy vývoje (konvence psaní kódu, kvalitní analýza, odpovědnosti členů týmu, komunikace v týmu, ...), tím lépe dokáže využít externích spolupracovníků. Pro firmu to tedy může být hodnotná zpětná vazba, zda ve firmě vše funguje jak má.

  • přidaná hodnota - není nutné mít vlastní vývojový tým složený ze všech profesí od analytika, přes architekta až po testera. Často může být pro firmy zajímavé držet know-know projektu a na dílčí "akce" si najímat externisty. Firma se bude soustředit na věci s největší přidanou hodnotou a ostatní potřebné profese si bude najímat.


Doplnili by jste ještě nějaké další výhody?

Samozřejmě, že vše může mít i své nevýhody (fluktuace lidí, problém s údržbou a opravou chyb na projektech, držení know-how, kvalita znalostí externistů, ...), ale myslím si, že vhodnou firemní strategií pro spolupráci s externisty lze mnohem více získat než ztratit.

24. listopadu 2009

Jaké je to pracovat na volné noze?

Často se mě kamarádi a kolegové ptají, jaké to vlastně je být na volné noze, jaké výhody či nevýhody mi to přináší. Tímto článkem bych rád zrekapituloval moje zkušenosti, které jsem za dobu jednoho a půl roku získal.

  • Vždy jsem se hodně snažil učit nové věci, četl články, zkoušel nové knihovny. Ale bylo to takové "umělé". Nyní jsem za relativně krátkou dobu získal spoustu praktických zkušeností, které bych normálně získával mnohem déle. Nemyslím teď jen technické znalosti (různé projekty, různé přístupy k řešení problémů, nové knihovny, atd.), ale zejména jsem měl možnost nahlédnout do mnoha různorodých firem, vidět různé styly komunikace a řízení, různé přístupy k vývoji, přemýšlet o tom, co kde funguje a nefunguje atd. Připadám si jak houba, která se v každé nové firmě pořádně nasákne.

  • Práce na volné noze mě drží v neustálé pohotovosti. Musel jsem si zvyknout na to, že nic není jisté, a že jediný důvod, proč si mě někdo bude chtít najmout bude ten, že budu "dobrý", že budu nabízet něco více než ostatní - znalosti, zkušenosti, profesionální přístup. To mě pořád nutí přemýšlet o sobě, o mých znalostech a to mě tedy žene dopředu. Samozřejmě to je zároveň i nevýhoda, protože vždy není jednoduché se prosadit a není vždy jednoduché přijít do nové firmy a začínat od začátku.

  • Vždy jsem byl placen od hodiny a je to tedy jen o vzájemné důvěře, že opravdu odpracuji uvedený počet hodin, který na konci měsíce vykážu, a který chci zaplatit. Jako zaměstnanec jsem takový tlak nikdy necítil, ale teď si prostě nemohu dovolit číst hodinu články na internetu a pak to vykázat jako nějakou práci. Samozřejmě člověk není robot a někdy se mi pracovat nechce nebo to prostě ani moc nejde - pak to ale musím zohlednit ve výkazu, bohužel.

  • Jsem sám a nikam vlastně nepatřím. Musím tedy zapomenout na jakékoliv zaměstnanecké výhody - tím nemyslím jen stravenky, připojištění, služební auto, atd., ale i to, že se mě netýkají školení, výuka angličtiny, společné výjezdní zasedání apod. To cítím asi jako největší nevýhodu, někdy jsem prostě sám. Na druhou stranu jsem oproštěn od interních dohadování a problémů, což je zase mnohdy výhoda.

  • Jsem sám svým pánem, ale stejně vždy šéfa mám. Spíše se tedy cítím volněji, ale i tak musím plnit termíny, pořád mi někdo zadává práci a pak ji kontroluje, takže v tomto změna není. Mohu si ale vybrat počítač, mobil, auto jaký chci aniž bych porušoval firemní předpisy a jsem větším pánem svého času. Také se dívám na věci více z nadhledu, protože vím, že nikde nebudu napořád.

  • Člověk pozná spoustu nových kolegů, známých, což je určitě také příjemná výhoda.

Svého rozhodnutí přejít na volnou nohu jsem nikdy nelitoval a jsem za něj moc rád. Pokud to jen půjde, tak v nejbližších letech bych rád neměnil.

A jaké jsou vaše zkušenosti? Zkoušeli jste pracovat na volné noze? Myslíte, že pro firmy i do budoucna bude zajímavé si najímat lidi na kontrakt?

14. listopadu 2009

Definice rozhraní dohromady se třídami

Nedávno jsem procházel cizí kód a narazil jsem na definici rozhraní dohromady s definicí použitých tříd, viz následující příklad (příklad je uměle vytvořen):


public interface Connector {

Status getStatus();


/**
* Connection status.
*/
public static class Status {
private int code;


/**
* Constructor
*
* @param code Connection status code
*/
public Status(int code) {
this.code = code;
}


public int getCode() {
return this.code;
}
}
}

Mě by tato konstrukce nikdy nenapadla, proto mě to hned praštilo. Čím více jsem ale o tom přemýšlel a rozebíral s autorem, tak jsem vlastně zjišťoval, že moc argumentů, proč by to tak nemělo být, vlastně nemám.

Zde jsou mé dva argumenty, proč se mi to "nezdá":
  • První argument asi moc neobstojí, ale pro mě je celkem důležitý - rozhraní se definují pomocí rozhraní, třídy pomocí tříd. Proč to tedy míchat dohromady? Autor mi na to řekl, že chtěl zdůraznit spojitost mezi rozhraním a použitými třídami. Tomu moc nerozumím, protože všechny třídy použité v rozhraní jsou automaticky součástí API. Možná měl na mysli to, že kdykoliv budu pracovat se třídou Status, tak budu muset používat prefix rozhraní, tedy Connector.Status a tím tedy vždy bude zřejmá příslušnost třídy k danému rozhraní.

  • Všechny mé další výtky směřují na možné úpravy, rozšíření v budoucnu - to je jeden z důležitých aspektů kvalitního API, aby bylo možné zachovat zpětnou kompatibilitu a přitom mít možnost dané API rozšiřovat. Co když budu chtít třídu Status použít v budoucnu i v jiném rozhraní? Co když budu chtít vytvořit třídu StatusExt jako rozšíření třídy Status?

Přijde mi tedy, že kvůli minimální "výhodě" mohu mít v budoucnu problémy.

Spousta lidí má představu, co to asi tak API je, ale už jen pár lidí si dokáže představit, jak je těžké vytvořit kvalitní API. Já sám se v této oblasti hodně učím a to zejména z knížky "Practical API Design" od Jaroslava Tulacha. Ale je hlavně potřeba praxe a mít vůbec možnost taková API navrhovat a rozvíjet, což se ve standardních webových aplikacích moc často neděje.

Proto by mě moc zajímal názor ostatních - máte nějaký další argument na mojí podporu? Nebo se vám zdá, že to ani nemá cenu řešit, a že je vše v pořádku?

28. října 2009

Testování s Mockitem

Pokud to někdo myslí s testováním vážně, tak se asi bez mockování neobejde. Já jsem dříve používal jMock a musím říct, že mě to celkem na nějaký čas od mockování odradilo - samotný zápis mi přišel velice upovídaný, měl jsem problémy s refactoringem a hlavně to bylo celé hodně náročné na údržbu. Zhodnotil jsem to tehdy, že je to moc práce bez velkého přínosu. Ještě bych měl možná podotknout, že jsem psal testy vždy až po produkčním kódu.

Na nedávném setkání s vývojáři v Ostravě mi byla doporučena knihovna Mockito. Sice jsem s ní ještě nic nedělal, ale už mě chytla - kód je hezky čitelný (stylem mi to připomíná hamcrest), bezproblémový refactoring, není nutné psát metody pomocí řetězců a celé mi to prostě přijde takové intuitivní. Jinak o důvodech vzniku Mockita je možné si přečíst na stránkach autorova blogu.

Pokud se vám Mockito líbit nebude, tak zkuste něco z následujících knihoven (na které mám uloženy odkazy): jMockit, EasyMock, jMock a nebo rMock (pokud máte s těmito knihovnami nějaké zkušenosti, tak se rád přiučím).

Nakonec ještě posílám odkaz na zajímavý článek "Mocks Aren't Stubs" od Martina Fowlera.

Jaký build nástroj používáte? - výsledky

V poslední anketě mě zajímalo, jaký build nástroj se nejčastěji používá. Celkem hlasovalo 86 lidí s tímto výsledkem:

  1. Maven (59%)
  2. ANT (55%)
  3. Něco jiného (9%)
  4. GANT (1%)


Dnes bez komentáře, protože k tomu není potřeba nic dodávat :).

1. října 2009

Programátoři vs. kodeři

Při své práci na volné noze jsem již pár firem navštívil a všude mi to přijde stejné - část vývojářů jsou programátoři a část kodeři.

Programátor

Schopnost myslet - asi to zní úsměvně, ale toto je největší schopnost (a tedy rozdíl oproti koderovi) programátora. O své práci přemýšlí, přemýšlí, zda to co dělá dává smysl, zda zadání, které dostal neobsahuje nějaké nesmysly, nesrovnalosti. Programátorovi jde také často o "dobrý pocit" z práce - nejde vždy jen o to udělat nějaký úkol co nejrychleji, něco narychlo zbastlit, ale dělat věci tak, aby s nimi byl člověk sám spokojený.
Programátor bude obecně asi také více nespokojený člověk, protože mu záleží na tom, aby dělal v "prostředí", které dává smysl, které ho někam posouvá dále.

Kodér

Kodéra ze všeho nejvíce zajímá zdrojový kód - rád kód vytváří, ale nebaví ho si vymýšlet zadání, přemýšlet nad ním, diskutovat o něm. Ideálně dostane zadání, které obsahuje úplně vše - od informace, jaké metody má udělat a kam je umístit, přes informaci, že je nutné přidat tlačítko "Odeslat" k formuláři (toto je realita, jeden kolega udělal webový formulář bez tlačítek na odeslání). Díky velké orientaci na samotný kód dokáží psát velice efektivně a kvalitně.


To, zda je vývojář programátorem nebo kodérem nesouvisí ani tak s tím, jak kdo je dobrý v Javě, jak moc zná různé knihovny, frameworky, ale asi spíše s tím, jaký má přístup k práci, co ho baví více. Někdo je od přírody přemýšlivý, zkoumavý, zodpovědný, pečlivý, ...

Z pohledu sestavení vývojového týmu je zřejmé, že každý tým potřebuje oba dva typy vývojářů - někoho, kdo bude udávat směr a přemýšlet o aplikaci a někdo, kdo bude dělat (kvalitní) kód. Jen je otázka v jakém poměru to namíchat - má zkušenost je zatím taková, že poměr programátorů vs. koderů je přibližně 1:2.

27. září 2009

Vytváření testovacích dat

Každý, kdo píše testy, tak řeší problém s tím, jak nainicalizovat strukturu svých (doménových) objektů, aby mohl otestovat určitou funkcionalitu. Způsobů řešení je více.

"ruční" inicializace pomocí Javy

Pokud test potřebuje nějaké objekty, tak si je v rámci samotného testu prostě vytvoříme resp. přes IoC napojíme, stejně jako když vytváříme produkční kód.

Nevýhodou tohoto přístupu je, že ve výsledku vzniká spoustu duplicitního kódu, protože každý si vytváří své objekty. A naopak pokud se objekty vytvářejí centrálně, tak pak je většinou ten problém, že chybí možnost si pro určité testy nastavit určité atributy jinak než implicitně.

externí popis dat

Požadovaná data nadefinuji pomocí vhodného frameworku, tak abych oddělil vlastní testovací kód a testovací data. Ideálním představitelem pro databázové systémy je DbUnit, ale vlastně se jedná o jakýkoliv systém, který mi umožňuje externě definovat data, např. pomocí XML.

Výhod v tomto případě vidím několik - testy obsahují pouze kód k ověření funkčnosti a již nemusí řešit "omáčku" okolo, vytváření podobných a často se opakujících testů bude rychlejší a nakonec také testovací data a samotné testy nemusí vytvářet jedna osoba.

Na druhou stranu vím, že tímto způsobem nelze moc dobře vytvářet nějaké větší grafy objektů a také údržba aktuálnosti dat dá celkem zabrat, zejména když se pustíme do refactoringu.

testovací databáze

Pro účely testování bude k dispozici testovací databáze, která bude obsahovat nějaký konzistentní stav a k tomuto stavu budou psány testy.

Kromě náročnosti na údržbu dat zde vidím problém v případě, kdy nad touto testovací databází bude pracovat více vývojářů, kteří si budou navzájem zamykat data (např. při krokování programu). Také je jen otázka času, kdy někdo omylem komitne změny v testech. Řešením by zde bylo, kdyby každý vývojář měl svojí testovací databázi.

automatické vytváření grafu objektů

Při vytváření testovacích dat nám jde většinou o několik málo atributů, které ověřujeme a ostatní atributy mohou být zcela libovolné. To je základní myšlenka našeho jednoduchého řešení, které se nám stará o centrální vytváření grafu objektů, aniž bychom museli sami tyto objekty vytvářet pro každý test.

Postup je asi následující: každý objekt, který potřebujeme vytvářet během testů, bude mít odpovídající třídu "testData", která se bude starat o jeho inicializaci (nejen jednoduchých atributů, ale i o referencí na další objekty). Pro psaní těchto "testData" tříd jsme napsali dvě podpůrné třídy AbstractReadTestData a AbstractCreateTestData, takže stačí implementovat jen jednu resp. dvě metody ve vlastní "testData" třídě. Kdykoliv pak během testu potřebuji nějaký objekt, tak si zavolám "testData" třídu, (metodu get() nebo createAndSave()) a ta mi testovací objekt vrátí. Buď to může být objekt s hodnotami nastavenými v "testData" třídě nebo s hodnotami přebranými ze šablony - tím mám možnost si nastavit určité atributy nebo i celé objekty dle svých potřeb.

Ještě musím dodat, že jsme na našem projektu rozlišovali dva typy objektů - jeden typ objektu byl pouze pro čtení a byl vždy součástí testovací databáze. Typicky se jedná o číselníky - ty se nemění, ty se můžou pouze číst. Druhý typ objektu je normální objekt, který je nejdříve potřeba vytvořit a uložit do databáze.

Dle mého názoru tento přístup k vytváření testovacích objektů řeší většinu nevýhod zmíněných v předchozích způsobech inicializace. Sami jsme to otestovali na projektu, na kterém dělalo cca 15 programátorů a kde byl velmi košatý a složitý datový model. Snad jediná nevýhoda tohoto a podobných řešení je ta, že vkládaná data do databáze v rámci testů nejsou nikdy komitnuta, takže nemáme možnost si prohlédnout co skutečně v databázi je, např. během ladění.

Tento nápad nebyl pouze z mé hlavy, ale rád bych uvedl spoluautora Pavla Jetenského, který o našem řešení mluvil na letošním jOpenSpace, přednáška Lepší než Dbunit.

Samotný kód určitě napoví více než nějaká slova, tak přikládám jednotlivé třídy včetně ukázek použití.


AbstractReadTestData:

/**
* Abstraktni trida pro nacitani testovacich dat.
*
* <p>Tato trida nabizi pristup k jiz existujicim datum v DB (napr. registry).
* Pokud chcete testovaci data i vytvaret, pak je nutne pouzit tridu {@link AbstractCreateTestData}.
*
*/
public abstract class AbstractReadTestData<BO extends IIdentifiable> {

private static final String SET_METHOD_PREFIX = "set";
private static final String GET_METHOD_PREFIX = "get";

protected final Calendar now = Calendar.getInstance();

/** Mapa objektu, se kterymi se pracovalo. [ID objektu, BO] */
protected Map<Serializable, BO> dataMap = new TreeMap<Serializable, BO>();

/**
* Constructor
*/
protected AbstractReadTestData() {
}

/**
* Metoda vrati prvni (libovolnou) instanci test. dat z DB.
* Pokud dosud zadna takova instance nebyla vytvorena, tak je vyhozena vyjimka.
* Data v DB musi existovat.
*
* @return prvni (libovolnou) instanci test. dat
* @throws IllegalStateException pokud DB neobsahuje data pozadovaneho typu
*/
public BO get() {
BO data;
if (dataMap.isEmpty()) {
data = getExistingRegistryEntity(getDataDao());

if (data != null) {
dataMap.put(data.getId(), data);
}
else {
throw new IllegalStateException("There is no test data for loading.");
}
}
else {
data = dataMap.values().iterator().next();
}

return data;
}

/**
* Metoda vrati BO z DB.
*
* @param dataDao DAO pro pristup k datum
* @return BO nebo null pokud neexistuje
*/
protected BO getExistingRegistryEntity(IPlainDaoSupport<BO> dataDao) {
BO byId = null;
List<BO> findList = dataDao.findList();
if (findList.size() > 0) {
byId = findList.get(0);
}

return byId;
}

/**
* Metoda vyresetuje mapu testovacich dat, se kterymi se dosud pracovalo.
*/
public void cleanTestDataCache() {
dataMap.clear();
}

/**
* Metoda je urcena k implementaci v podtridach a vraci referenci na DAO
* pro nacitani dat z DB (a pripadne manipulaci s daty).
*
* @return DAO pro pristup k datum
*/
protected abstract IPlainDaoSupport<BO> getDataDao();

/**
* Metoda automaticky najde vsechny atributy (fieldy) tridy typu {@link Calendar}
* a nastavi jim aktualni datum a cas (pokud jsou null).
*
* @param data BO
*
* @throws UnsupportedOperationException pokud neni mozne nastavit pres setter Calendar
*/
protected void fillAllCalendarFields(IIdentifiable data) {
Method[] methods = data.getClass().getMethods();
for (Method method : methods) {
try {
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getName().startsWith(SET_METHOD_PREFIX)
&& parameterTypes.length == 1
&& Calendar.class.isAssignableFrom(parameterTypes[0])) {

// nastavime aktualni cas pouze pokud neni nic nastaveno
String getterMethodName = method.getName().replaceFirst(SET_METHOD_PREFIX, GET_METHOD_PREFIX);
Method getter = data.getClass().getMethod(getterMethodName);
Object value = getter.invoke(data);

if (value == null) {
method.invoke(data, this.now);
}
}
}
catch (Exception ex) {
throw new UnsupportedOperationException("Cannot automatically setup calendar method "
+ method.getName() + " of " + data.getClass().getName() + " to default not null value", ex);
}
} //end-for
}

}



AbstractCreateTestData:

/**
* Abstraktni trida pro vytvareni testovacich dat.
*
*/
public abstract class AbstractCreateTestData<BO extends IIdentifiable>
extends AbstractReadTestData<BO> {

/**
* Metoda vrati prvni (libovolnou) instanci test. dat (BO), ktera jiz byla drive vytvorena.
* Pokud dosud zadna takova instance nebyla vytvorena, tak se vytvori objekt s implicitnima hodnotama.
*
* <p><b>Pozor, tato metoda nenacita jiz existujici data z DB, pouze z interni cache po
* naslednem vytvoreni.</b>
*
* @return prvni (libovolnou) instanci BO - bud primo z DB
* @see #createAndSave
*/
final public BO get() {
BO data;
if (dataMap.isEmpty()) {
//vytvorime a ulozim impl. data
data = createAndSave();
}
else {
data = dataMap.values().iterator().next();
}
return data;
}

/**
* Metoda v databazi vytvori instanci, naplnenou vychozimi implicitnimi testovacimi daty.
* Instanci prideli nove logicke id s DB sekvence.
*
* @return BO s implicitnimi daty
*/
final public BO createAndSave() {
return createAndSave(null);
}

/**
* Metoda v databazi vytvori novou instanci test dat,
* naplnenou kombinaci vychozich testovacich hodnot a hodnot ze vstupni sablony.
*
* @param data Sablona pro vytvoreni test. objektu
* @return vytvoreny BO nebo null, pokud se nic nevytvori
*/
final public BO createAndSave(BO data) {
//vytvorime novy test. objekt
BO createdBo = create(data);

//ulozim je do DB a do cache
if (createdBo != null) {
getDataDao().save(createdBo);
dataMap.put(createdBo.getId(), createdBo);
}
return createdBo;
}

/**
* Metoda vytvari (neuklada!) novy testovaci objekt.
* Metoda je zodpovedna za vytvoreni nove instance objektu s nastavenim vsech "not-null" atributu, vcetne
* reference na dalsi objekty.
*
* <p>Vsude, kde je nektera z vlastnosti parametru sablony <i>data</i> nenulova, je pouzita pro novy objekt.
* V ostatnich pripadech se pouzije <i>nejaka</i> vychozi hodnota.
*
* @param data Sablona pro vytvoreni test. objektu
* @return nova testovaci data (BO)
*/
protected abstract BO create(BO data);
}



Aby byl přehled základních tříd kompletní, tak uvádím i rozhraní IIdentifiable, které používáme jako rozhraní pro všechny naše doménové objekty:

public interface IIdentifiable<T extends Serializable> {

/**
* Identifikator.
*
* @return Vraci identifikator objektu.
*/
T getId();

/**
* Metoda zjistuje, jestli ma objekt <i>id</i>.
*
* @return Vraci <tt>true</tt>, pokud ma objekt korektni id, jinak <tt>false</tt>.
*/
boolean isIdentified();
}



A nakonec ukázka použití - jsou použity doménové objekty IPersonBo a IRoleBo, vztah je takový, že osoba (Person) může mít jednu roli (Role). Nejdříve "testData" třídy:

public class PersonReadTestData extends AbstractReadTestData<IPersonBo> {

@Autowired
private IPersonDao personDao;

protected IPlainDaoSupport<IPersonBo> getDataDao() {
return personDao;
}
}



public class PersonCreateTestData extends AbstractCreateTestData<IPersonBo> {

@Autowired
private IPersonDao personDao;

@Autowired
private RoleCreateData roleCreateData;

protected IPersonBo create(IPersonBo data) {
if (data == null) {
data = personDao.createNewInstance();
}

if (StringUtils.isEmpty(data.getName())) {
data.setName("Petr");
}
if (StringUtils.isEmpty(data.getSurname())) {
data.setSurname("Kolousek");
}
if (data.getRole() == null) {
data.setRole(roleCreateData.createAndSave());
}

return data;
}

protected IPlainDaoSupport<IPersonBo> getDataDao() {
return personDao;
}
}



public class RoleCreateTestData extends AbstractCreateTestData<IRoleBo> {

@Autowired
private IRoleDao roleDao;

protected IRoleBo create(IRoleBo data) {
if (data == null) {
data = roleDao.createNewInstance();
}

if (StringUtils.isEmpty(data.getName())) {
data.setName("READ");
}

return data;
}

protected IPlainDaoSupport<IRoleBo> getDataDao() {
return roleDao;
}
}


... a pak použití v rámci testů:

public class CreateDataTest extends AbstractTransactionalJUnit4SpringContextTests {

@Autowired
private PersonCreateTestData personCreateData;

@Autowired
private RoleCreateTestData roleCreateData;

@Before
public void cleanCache() {
personCreateData.cleanTestDataCache();
roleCreateData.cleanTestDataCache();
}

@Test
public void testCreatingNewData() {
//vytvorime zaznamy v DB
IPersonBo person = personCreateData.createAndSave();
assertThat(person, is(notNullValue()));
assertThat(person.getName(), is("Petr"));
assertThat(person.getSurname(), is("Kolousek"));
assertThat(person.getRole(), is(notNullValue()));
assertThat(person.getRole().getName(), is("READ"));
assertThat(countRowsInTable("T_PERSON"), is(1));

IPersonBo retPerson = personCreateData.get();
assertThat(retPerson, is(person));
}

@Test
public void testCreatingNewDataWithGet() {
//vytvorime zaznamy v DB
IPersonBo person = personCreateData.get();
assertThat(person, is(notNullValue()));

IPersonBo retPerson = personCreateData.get();
assertThat(retPerson, is(person));
}

@Test
public void testCreatingExistingData() {
//vytvorime roli podle sablony
IRoleBo roleTempl = new RoleBoImpl();
roleTempl.setName("EXEC");
IRoleBo role = roleCreateData.createAndSave(roleTempl);
assertThat(role, is(notNullValue()));

//vytvorime person ze sablony
IPersonBo personTempl = new PersonBoImpl();
personTempl.setName("Jan");
personTempl.setSurname("Vavra");
personTempl.setRole(role);

IPersonBo person = personCreateData.createAndSave(personTempl);
assertThat(person, is(notNullValue()));
assertThat(person.getName(), is("Jan"));
assertThat(person.getSurname(), is("Vavra"));
assertThat(person.getRole(), is(notNullValue()));
assertThat(person.getRole().getName(), is("EXEC"));
assertThat(countRowsInTable("T_PERSON"), is(1));

IPersonBo retPerson = personCreateData.get();
assertThat(retPerson, is(person));
}

}

14. září 2009

Distribuované transakce bez JEE kontejneru

Dlouho dobu jsem si myslel, že pokud potřebuji řídit transakce přes více datových zdrojů, pak se nikdy neobejdu bez plnohodnotného JEE kontejneru resp. serveru.

Před pár lety jsem na projektu potřeboval propojit do jedné transakce události ze dvou datových zdrojů (databáze, souborový systém) a bez znalosti dané problematiky jsem si sám napsal jednoduchý mechanismus, který mi dokázal v 98% případů zaručit, že se uloží buď obě události a nebo žádná. Ve zbylých dvou procentech případů to nefungovalo, ale to mi z pohledu logiky aplikace moc nevadilo. Pro mě bylo důležitější, že jsem si vystačil pouze se Springem a Tomcatem, a že to celé běhalo celkem rychle.

Proč o tom píšu? Nedávno jsem si všimnul, že samotný Spring bude mít tento mechanismus zabudovaný přímo v jádře.

Já se vždy snažím vyhnout "těžkým" řešením při návrhu aplikací a zde je hezky vidět, že i distribuované transakce se dají řešit "lehkou" cestou. Je ale potřeba si předem jasně říci, co je pro nás nejdůležitější - zda 100% spolehlivost bez výjimek nebo rychlost celého transakčního systému nebo výběr technologií. Nejde bohužel splnit všechny tři kritéria najednou k plné spokojenosti, je potřeba si vybrat jedno a ostatní k tomu přizpůsobit.

Doplňující články:

28. srpna 2009

Java na Macu - ach jo

Kdykoliv přijedu na Java konferenci (spíše to tedy platí pro zahraničí), tak vidím samé přednášející s Mac notebooky. Člověk z toho nabude dojmu, že s Javou na Mac OS nejsou žádné problémy, že je to zaslíbený operační systém pro vývoj Java aplikací.

Po měsíci programování v Mac OS takový pocit bohužel nemám. Mám (měl jsem) dva základní problémy.

Dělám na velkém projektu (cca 30 tisíc tříd) a nejsem schopen tento projekt se standardní Javou na Macu (poslední dostupná verze je Java for Mac OS X 10.5 Update 4) vůbec zkompilovat kvůli nedostatku paměti. Mac OS je 64 bitový operační systém, Java na Macu také a mých 4GB paměti na to nestačí. Abych mohl zůstat na Macu, tak jsem musel začít používat SoyLatte - BSD Java port na Mac OS. S touto 32 bitovou verzí Javy už s kompilací není problém. Pak jsem ale musel ještě řešit problémy s tím, že na projektu používané JDK 6 má některé knihovny v trochu jiné verzi. Musel jsem do classpath podstrčit ty správné.
Po těchto peripetiích funguji bez problému, možná jsem i trochu získal tím, že náběh aplikace se mi zrychlil přibližně dvojnásobně. HotSwap a JavaRebel fungují bez problémů. Ale 100% náhrada to určitě není, protože SoyLatte některé části klasického JDK nemá, např. podporu Java Web Start.

Druhý problém je s Intellij IDEA. Sem tam (v průměru jednou denně) se mi IDEA z ničeho nic zavřela. To bych ještě přešel, jenže neočekávaným ukončením aplikace se zneplatní veškeré cache, takže pak musím čekát cca 20 minut než se mi celý projekt znovu natáhne a zkompiluje. Nejdříve jsem "nadával" na moje oblíbené IDE, a když už mi to fakt vadilo, tak jsem začal hledat příčinu - a našel. Problém je opět v Javě. Na uvedené stránce je nějaký workaround, který se zdá, že funguje. Nyní tedy čekám na novou verzi Javy pro Mac OS, kde by měla být chyba opravena.

Není to vždy jednoduché, když se člověk chce držet svého oblíbeného IDE a operačního systému ...

11. srpna 2009

VMware získal SpringSource

Dnes ráno jsem v mé RSS čtečce našel celkem (aspoň pro mě) překvapivou zprávu - společnost VMware získala společnost SpringSource.

Více o této akvizici lze dočíst na blogu Roda Johnsona nebo na blogu Steve Herroda (VMware CTO) a nebo si přečíst oficiální PR vyjádření.

Podle informací z těchto článků se SpringSource stane novou divizí Vmwaru, kterou bude i nadále vést Rod Johnson. Rod Johnson ubezpečuje, že se nic měnit nebude, že model fungování firmy, produktová řada, investice do open-source zůstanou beze změny. Je jasné, že hned na začátku nikdo nebude tvrdit, že se to celé změní, ale jak znám společnost VMware, tak mi to dává naději, že opravdu vše zůstane jako dříve a z mého pohledu (z pohledu vývojáře, který rád používá Spring produkty) se nic nezmění.

5. srpna 2009

Přechod z Acegi na Spring security - pokračování

O zkušenostech z upgradu Acegi security na Spring security jsem již jeden článek napsal. Teď jsem dělal upgrade podruhé a narazil jsem na dvě nekompatibilní změny v API.

Jedná se o tyto změny:


Ono těch změn bude asi více, ale bohužel jsem nikde nenašel jejich seznam.

Jaký build server používáte? - výsledky

V poslední anketě mě zajímalo, jaký build server nejčastěji používáte. Celkem vás hlasovalo 51 s následujícími výsledky:

  1. Hudson (52%)
  2. TeamCity (23%)
  3. CruiseControl a Apache Continuum (6%)
  4. Něco jiného (9%)
  5. Luntbuild (7%)
  6. Bamboo (3%)

Výsledky jsou dle mého očekávání, možná jen jsem nečekal, že tolik lidí bude používat TeamCity, protože se jedná o placené řešení.

Já sám jsem dlouho používal Hudson ke vší spokojenosti do doby, než jsem měl možnost se setkat s TeamCity. Možnosti tohoto řešení jsou opravdu veliké (přehled vlastností zde) a když k tomu připočtu pěkné uživatelské rozhraní a možnost propojení s IDE nástroji, tak pro mě je to nyní nástroj číslo jedna - i za cenu toho, že to není zadarmo.

Kromě uvedených nástrojů mám uložen ještě odkaz na tyto nástroje: Apache Gump a Gradle. Také se dá využít řešení od Microsoftu - Team Foundation Server (samotného by mě nenapadlo spojovat toto řešení s Javou, ale zrovna nedávno mi kamarád popisoval, jak celá firma funguje nad Microsoftem, tak TFS byla prý logická volba).

Nakonec přikládám odkaz na článek s radami k výběru open-source řešení.

28. června 2009

Konec Spring Modules, nastupuje Spring Extensions

Opožděně jsem si všimnul, že projekt Spring modules již není aktivně podporován (je deprecated) a místo toho je doporučeno přejít na Spring Extensions.

Spring Modules obsahuje rozšíření, moduly, pro jednodušší napojení Spring frameworku na knihovny třetích stran. Kromě toho nabízí pro určité oblasti odstínění od konkrétních technologií, např. obsahuje jednotné rozhraní pro kešování bez ohledu na to, jaká implementace se vybere. Já osobně jsem používal moduly integrující Lucene, EhCache a Jackrabbit.

Již delší dobu jsem měl pocit, že projekt "moc neběží", proto mě ani konec nijak nepřekvapil. Ono je opravdu hodně těžké sledovat všechny ty verze různých knihoven a k tomu pořád dokola rozšiřovat své API a svoji funkčnost. Takto zní i oficiální stanovisko:
"Spring Modules was developed to serve a similar goal and was very successful. In fact, it was too successful and grew beyond anybody's expectations. As such, Spring Modules isn't really flexible enough or scaleable enough to manage a large number of independent projects. Spring Modules has now reached end of life. The modules that are still undergoing active development may be ported to Spring Extensions.".

Spring Extensions mají jiný koncept fungování. Snaží se přitáhnout třetí strany k tomu, aby oni vytvářeli tyto rozšíření (takže něco jako SourceForge, Google code). Rozdíl je zejména v tom, že kromě potřebné infrastruktury budete mít podporu během celého vývojového cyklu včetně podpory při propagaci projektu. Asi budete i zaplaceni - roli interního sponzora jsem moc nepochopil, ale když jim dáte práva na zdrojový kód, když s nimi uzavřete smlouvu, tak bych nějakou odměnu čekal.

Zatím v přehledu aktuálních projektů není ani jeden z původních Spring Modules a doufám, jak se uvádí, že dojde k přesunu.

24. června 2009

Spring ROO

Poprvé jsem o projektu Roo slyšel na konferenci SpringOne v červnu v roce 2007. Tehdy vlastně jen oznamovali začátek projektu a musím říct, že mě to nijak příliš nezaujalo. Dokonce jsem si na tento projekt vzpomněl minulý rok na podzim a myslel jsem si, že ten projekt již dávno skončil.

A neskončil. Po dvou letech se objevila první verze, oznámení proběhlo na SpringOne v letošním roce.

Obecně mám k různým generátorům negativní přístup, proto mi trvalo chvíli, než jsem si našel čas a podíval se na to, co vlastně Roo nabízí. A byl jsem pozitivně překvapen:

  • Roo shell se zdá být mocný nástroj.

  • Sami autoři také asi nemají nejlepší zkušenosti se "standardními" generátory kódu a již z deklarace hlavních myšlenek projektu je cítit jiný přístup. Tedy snaha o to, aby Roo byl pomocníkem a ne omezovačem. Roo je možné použít pro rychlé nastartování projektu, dokonce je možné zapojit Roo do již existujícího projektu. Nebo naopak kdykoliv Roo opustit. To je díky tomu, že Roo vlastně negeneruje přímo Java zdrojový kód, ale využívá AOP pro generování do zkompilovaného class souboru.

  • Vygenerovaný kód je postavený na jiných Springových projektech (Spring framework, Spring security), a proto mi nedělá žádný problém se orientovat v těchto souborech a případně je upravovat, doplňovat. Jediné co je navíc, jsou specifické @Roo anotace pro generování výsledného kódu resp. zkompilovaného kódu. Zde se intenzivně používá AspectJ inter-type declarations.

  • Líbí se mi, jak lidé ve Springu se stále drží svých ideologických představ o správném vývoji aplikací, což je vidět i zde. Kromě produkčního kódu se generují unit testy, dokonce s napojením na Selenium.

  • Samotný projekt Roo je napsán hodně flexibilně - kromě základních služeb Roo obsahuje rozšíření (tzn. add-ons), které právě dodávají Roo všechny ty možnosti generování. Takže teď je např. rozšíření pro Maven nebo Hibernate, ale do budoucna může být pro ANT nebo TopLink. Navíc je tu možnost vývoje vlastních rozšíření.


Někde jsem četl názor o tom, proč má Spring dva nástroje na generování kódu - nyní nově Roo a dříve získané Grails. Oba nástroje znám jen velmi povrchně, takže je nemohu moc porovnat. Snad jen, že Grails vyžadují Groovy oproti Roo. Určitě jsou nástroje z výsledného přínosu hodně podobné, ale Roo míří svými možnostmi o dost dále. Kromě toho byl projekt Roo spuštěn dávno před tím, než bylo jasné, že SpringSource získá G2One a tedy Grails. Navíc se domnívám, že hlavním důvodem akvizice nebyly Grails, ale jazyk Groovy. Co bude dále nevím, ale vypadá to, že oba dva projekty pojedou stále dále bez nějakého omezení.

Projekt je nyní ve verzi 1.0.0.M2 a v brzké době bude uvolnění verze 1.0. Hlavním představitelem projektu je Ben Alex, který je mimo jiné hlavním autorem Spring security. Tedy aspoň pro mě záruka kvality.


Odkazy pro více informací:

11. června 2009

Rozcestník knihoven

Na intranet jsem psal takový rozcestník odkazů na různé knihovny a třídy, který by měl posloužit k tomu, aby se programátoři nejdříve podívali, zda to co potřebují řešit, není již náhodou někde (lépe) vyřešeno. Myslím, že tento rozcestník se může hodit i někomu z vás.

Následující rozcestník slouží k rychlé navigaci, pokud potřebujete pracovat s určitými datovými typy nebo provést nějaké obecné operace:

  • řetězce (String) -> StringUtils, StringEscapeUtils, StrTokenizer nebo WordUtils z Commons Lang.
    • kontrola na prázdné řetězce, hledání a náhrada znaků (řetězců) v řetězci, mazání částí řetězce, převod na malá/velká písmena, dělení řetězce na části, mazání mezer a doplňování řetězců, kontrola na čísla, alfa znaky apod., otáčení řetězců, porovnání řetězců,
    • práce s escape znaky pro Java, Java Script, HTML, XML, and SQL
    • dělení řetězce, práce s částmi řetězců
    • práce se slovy v řetězci

  • datumy (Date) -> DateUtils
    • posun datumů, zaokrouhlování, získání určité složky z datumu

  • čísla (Number) -> NumberUtils
    • převod řetězců na čísla, min. a max. hodnoty, porovnání desetinných čísel

  • boolean -> BooleanUtils
    • převod řetězců na boolean, převod čísel na boolean a obráceně

  • pole (Array) -> ArrayUtils
    • přidávání, mazání, hledání prvků v polích, vytváření pod-polí, převod na pole primitiv

  • kolekce (Collection) -> ListUtils, MapUtils nebo SetUtils z Commons - Collections
    • množinové operace s kolekcemi

  • equals() a hash() -> EqualsBuilder a HashCodeBuilder
  • toString() -> ToStringBuilder
  • soubory, adresáře (File) -> FileUtils nebo FilenameUtils z Commons IO
    • vytváření, mazání souborů a adresářů, čtení a zápis dat, kopírování souborů a adresářů, porovnání obsahu souborů
    • práce se jménem souborů a celou cestou k souboru

  • streamy (InputStream, OutputStream) -> IOUtils
    • čtení a zápis dat, kopírování mezi streamy, zavírání streamů, porovnání streamů

  • kódování (Base64, Hex, SHA, MD5, ...) -> Common Codes


Pokud hledáte snad ještě něco jiného, tak doporučuji se podívat na následující projekty:

2. června 2009

Jaký máte vztah k Java anotacím? - výsledky

Poslední anketa se týkala anotací a dopadla následovně (hlasovalo 125 lidí):

  • Používám, ale jen někde (43%)
  • Mám je rád, používám, kde se dá (38%)
  • Nemám je rád, snažím se jim vyhnout (10%)
  • Nevadí mi (8%)


Já sám jsem hlasoval pro "Používám, ale jen někde" a nejsem moc zastáncem používání anotací všude, kde se dá. Osobně nejčastěji používám Hibernate Annotations nebo JPA anotace a Spring a jUnit anotace při psaní testů.

Zeptal jsem se pár kolegů na jejich názor na anotace (1, 2) a v následujícím resumé se to pokusím nějak shrnout:

Argumenty pro:
  • jednoduché, intuitivní - toto se může lišit anotace od anotace, ale dost často je zápis pomocí anotace jednodušší než nějaká jiná podoba konfigurace.

  • větší flexibilita - díky používání anotací může být určité řešení flexibilnější než standardní verze bez anotací. Jako příklad bych uvedl Spring MVC - klasická verze vs. verze s anotacemi. Anotace přináší určitě větší flexibilitu, asi i větší jednoduchost používání a menší nároky na učení se klasického API kontrolerů, ale dle mého názoru to jeden nedostatek má. Zejména u větších projektů se hodí, když má projekt nějakou strukturu, nějaká pravidla a toto se hůře vynucuje pomocí anotací než při používání klasického API. Uvedu jeden konkrétní příklad: myslím, že je vhodné mít odkazy na JSP stránky a URL adresy mimo vlastní Java kód kontrolerů. V klasickém API mám settery na viewname, ale co u anotací? Vytvořím si vlastní předky, ale už to zase bude na úkor nějaké funkcionality, které mi anotace nabízejí.

  • pro popis metadat - anotace mi umožňují přidat klasickým třídám (POJO) nové vlastnosti, které není potřeba implementovat přímo v Java kódu. Např. že je třída resp. metoda deprecated, že je třída volána v transakci pouze pro čtení, že je metoda pouze pro uživatele s rolí READ apod.


Argumenty proti:
  • již není POJO - pokud chcete psát "lehké", testovatelné a flexibilní aplikace (z pohledu budoucích změn), pak je určitě dobré používat POJOs. Jen pak je možné používat dané objekty v různých scénářích a prostředích - testy vs. produkční nasazení, objekty ve webové vrstvě vs. DAO vrstva apod. Neplatí to obecně pro všechny anotace, ale některé anotace vyjadřují určitou implementační závislost nebo předepisují dané třídě určité chování, které je platné jen pro určité použití. Konkrétně třeba @Transactional. Nebo nastavení zabezpečení objektů pomocí anotací, EJB anotace apod.

  • konfigurace aplikace nepatří do Java kódu - já sám mám raději několik konfiguračních souborů, kde najdu veškerou konfiguraci než to vše mít roztroušené po jednotlivých Java třídách. Na to zase většina řekne, že je super vidět u každé třídy hned dané vlastnosti (např. nastavení transakce, zabezpečení) než to hledat někde v separátních souborech. Dle mého názoru je lepší mít konfiguraci oddělenou od Java kódu a mít tak možnost nezávislého nastavení aplikace (nebo jejich částí) pro více prostředí.


Anotace mi přijdou jako velice flexibilní nástroj, ale i tak pořád spíše tíhnu ke klasickému vývoji bez anotací. Ale zase je potřeba se na vše dívat z různých pohledů, takže vlastně nelze obecně říci, co je lepší - takto dopadly i výsledky ankety :).

25. května 2009

JavaDoc nedostatky

V poslední době často pracuji s cizím kódem a je to opravdu zázrak narazit na kvalitně napsaný a okomentovaný kód. Největší problém komentářů je ten, že buď vůbec nejsou a nebo jsou, ale jen papouškují to, co je hned zřejmé ze samotného kódu. O přínosu komentování jsem již psal, dnes bych rád uvedl několik nedostatků v JavaDoc komentářích, na které jsem měl možnost narazit (malá poznámka pro upřesnění - i když často používám slovíčko komentáře, tak v tomto článku dále vždy myslím JavaDoc komentáře, ne komentáře přímo v kódu):

  • míchání češtiny (někdy i s diakritikou) a angličtiny - dříve jsem preferoval pouze angličtinu pro psaní komentářů, ta čeština mi tam prostě neseděla. Dnes jsem spíše pro používání češtiny z těchto důvodů: čeština vždy bude pro nás čitelnější než jakýkoliv jiný jazyk a i psaní komentářů je jednodušší, větší přínos z pohledu poskytované informace. Osobně s angličtinou nemám problém, ale sám jsem zjistil, že v češtině jsem schopen se vyjádřit o dost lépe. Dělám nyní většinou na interních projektech bez zahraničních kolegů, takže není problém s mateřským jazykem. Každopádně když už, tak zvolit jeden jazyk, aby to nevypadalo jako na jednom projektu, kde bylo možné narazit na české, slovenské, anglické a dokonce německé komentáře.

  • zvýraznění první věty komentáře - První věta komentáře má větší význam oproti dalším větám - jednak je to první informace, kterou uživatel čte a jednak je používaná pro stručný popis v přehledu metod. První věta (z pohledu JavaDoc nástroje) končí tečkou, kterou následuje mezera, tabulátor, znak konce řádku a nebo první blokový tag. Takto tedy ne:

    /**
    * <p>
    * If is set, it use special ScrollableResultsDecorator, that enable or
    * disable to add object in final list
    * </p>
    * <h4>NOTE:</h4>
    * <p>
    * Also, it respects the total count of entries that overlap your paged
    * list.
    * </p>
    */


  • @author tag u metod - používat @author tag u metod nemá žádný význam, protože to JavaDoc nástroj nepodporuje.

  • nepopisovat to, co za nás udělá JavaDoc automaticky - Je zbytečné kopírovat komentáře z metod rozhraní do implementace, je zbytečné uvádět implementující třídy rozhraní atd. JavaDoc nástroj má algoritmus na kopírování komentářů z nadřazených tříd a na rozdíl od verze 1.3 se vždy nekopíruje celý JavaDoc komentář (buď celý nebo nic), ale třeba i jen jeho části jako @param, @throws a @return. Někdo to sice nekopíruje, ale používá @inheritDoc a @see, ale to nejsou srovnatelné alternativy. Např. při použití @inheritDoc se kopíruje jen určitá část celého JavaDoc komentáře a tag @see má zcela odlišnou sémantiku. @inheritDoc je vhodné používat, pokud chci rozšířit určitou část dokumentace nadřazené třídy nebo metody.

  • zdokumentovat synchronized a native modifikátory - V JavaDoc API nenajdete informace o tom, zda je metoda synchronizovaná nebo ne (je to implementační záležitost). Proto je nutné tuto informaci uvést v JavaDoc.

  • zbytečně neopakovat význam tagů - Zde mám na mysli toto "@return Returns the statementBuilder." Proč psát 2x, že metoda vrací statementBuilder?

  • místo názvů tříd a metod používat odkazy @link - Hodně často se v komentářích odkazujeme na jiné třídy nebo metody a o dost větší přidaná hodnota bude, když si uživatel mezi jednotlivými komentáři může překlikávat, než aby to musel manuálně hledat. Proto je vhodné používat tagy @link resp. @linkplain.

  • používat @literal nebo @code pro psaní s <> - S příchodem Javy 5 a možností parametrizace může být problém zápisu těchto konstrukcí do JavaDoc komentářů. To stejné platí i o HTML a XML kódu. Než používat &lt; &gt; jako náhradu za <>, tak je lepší použít tagy @literal nebo @code. Tím se výrazně zvýší čitelnost JavaDoc i bez nutnosti zpracování JavaDoc nástrojem. Bohužel tyto tagy nelze použít na víceřádkové ukázky kódu, kde nám jde o přesné zarovnání kódu. Snad v příští verzi, pořád je tedy (bohužel) nutné používat tento způsob:

    * <p>Ukazka konfigurace ve web.xml:
    * <pre>
    &lt;listener&gt;
    &lt;listener-class&gt;com.o2bs.globals.web.common.envconfig.EnvAwareLog4jConfigListener&lt;/listener-class&gt;
    &lt;/listener&gt;
    * </pre>


  • psát přehledné komentáře i bez nutnosti použití JavaDoc nástroje - Hodně lidí hledí zejména na výslednou HTML podobu JavaDoc komentářů a ne na to, jak to vypadá přímo v Java kódu. Je pravda, že výsledná HTML podoba je asi nejdůležitější, ale dost často se prochází čistý Java kód bez IDE (a nebo i v IDE je rychlejší číst JavaDoc bez nutnosti si je zobrazovat v HTML podobě) a pak se i ocení, když to vypadá pěkně bez JavaDoc nástroje. Dle mého názoru se nemá cenu snažit o správně formátované HTML (tedy používání ukončovacích tagů), protože JavaDoc nástroj generuje HTML ve verzi 3.2 (i když hlavičku stránek dává pro HTML 4.0).


Psát správné (krátké, jasné s vypovídající hodnotou) JavaDoc komentáře není vůbec jednoduché a sám se občas nachytám, když se po nějakém čase vracím ke svému kódu. Pro ty, kdo chtějí JavaDoc dotáhnout k dokonalosti, doporučuji referenční příručku k JavaDoc nástroji a dokument s popisem konvence psaní komentářů.

20. května 2009

Spring security namespaces

Koncept "namespaců" resp. možnost vytváření vlastních konfiguračních XML tagů je ve Springu již od verze 2.0 a již je celkem hodně zajímavých tagů - ať už přímo ve Spring frameworku nebo v jiných Spring knihovnách nebo i v knihovnách třetích stran, např. DWR. Cíl je jasný - umožnit jednodušší (= rychlejší, přehlednější, jasnější, ...) konfiguraci Spring beanů.

Spring security přišel s podporou namespaců ve verzi 2.0. Sice moc namespaci nevyužívám (zvyk je železná košile), ale nedalo mi to, abych možnosti namespaců ve Spring security nevyzkoušel na jedné menší testovací aplikaci.

Začal jsem následující magickou ukázkou v dokumentaci:

<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>

<authentication-provider>
<user-service>
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>

A pak jsem to začal upravovat podle mojí "standardní" konfigurace Spring security. Většinu jsem dokázal pořešit pouze konfigurací na úrovní namespaců, ale ne vždy to bylo dostačující:
  • u LogoutFiltru jsem zvyklý používat vlastní LogoutHandler pro zalogování potřebných informací při odhlášení. Abych mohl použít vlastní handler, tak jsem si musel filter nakonfigurovat standardním způsobem s použitím <custom-filter position="LOGOUT_FILTER"/>

  • podobně u použití vlastního AccessDeniedHandleru. V rámci konfigurace <http> je již implicitní ExceptionTranslationFilter nastaven. Stejně jako u LogoutFiltru jsem musel provést konfiguraci filtru standardní cestou a uvést, že se jedná o vlastní filtr. Pozice vlastního filtru nesmí kolidovat z pozicí implicitního filtru, takže buď je potřeba dát implicitní filtr úplně pryč (neuvádět ho v <http>) a nebo umístit vlastní filtr před/za nějaký již existující, např. u ExceptionTranslationFilteru.

Takto jsem se dostal hodně daleko a konfigurace aplikace již vypadala skoro dle mých představ - uměla vše co jsem požadoval, místy byl zápis opravdu čitelnější a kratší. A i s použitím namespaců jsem mohl být flexibilní, snad všechny implicitní konfigurace jsem mohl nahradit svými, např. použít vlastní AccessDecisionManager

Jen jedna věc mi pořád vadila - při použití <http> se všechny uvedené filtry aplikují na všechny URL. Nemám tedy možnost říci, že na nějaké URL použiji nějaké filtry a na jiné URL zase jiné filtry. Použité filtry je např. vhodné odlišit pro dynamický a statický obsah. Z tohoto důvodu jsem opustil konfiguraci pomocí <http> a použil jsem standardní FilterChainProxy s <filter-chain-map>.

Na začátku jsem měl pár tagů a na konci jsem skončil skoro u "normálního" nastavení bez namespaců. I tak jsem byl velice mile překvapen, jak pěkně to mají navržené a jakou míru flexibility to má. Já Spring Security již celkem znám a i při použití namespaců vím, co se děje pod pokličkou, proto nevím, zda je tato zjednodušená forma konfigurace vhodná i pro začátečníky. Začátek s namespaci bude určitě rychlý, ale v každé aplikaci je potřeba něco nastavit jinak než standardně a pak bude problém.

Pro možnou inspiraci posílám moji výslednou konfiguraci:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"
>

<description>
Konfigurace Spring security.
</description>

<!--
Base filter chain of used filters. Filter's order is important,
more info here http://static.springframework.org/spring-security/site/reference/html/ns-config.html#ns-custom-filters.
-->
<bean id="springSecurityFilterChain" class="org.springframework.security.util.FilterChainProxy">
<security:filter-chain-map path-type="ant">
<security:filter-chain pattern="/img/**" filters="none" />
<security:filter-chain pattern="/css/**" filters="none" />
<security:filter-chain pattern="/js/**" filters="none" />
<security:filter-chain pattern="/accessdenied.*" filters="anonymousProcessingFilter" />
<security:filter-chain pattern="/error*.*" filters="anonymousProcessingFilter" />
<security:filter-chain pattern="/**"
filters="sessionContextIntegrationFilter, logoutFilter, userProfileStubFilter, authProcessingFilter,
userProfileAwareFilter, anonymousProcessingFilter, exceptionTranslationFilter,
filterInvocationInterceptor"
/>
</security:filter-chain-map>
</bean>


<!-- Musi byt, i kdyz neni mozne vyuzivat session. -->
<bean id="sessionContextIntegrationFilter"
class="org.springframework.security.context.HttpSessionContextIntegrationFilter">
<property name="allowSessionCreation" value="true"/>
</bean>

<bean id="logoutFilter" class="org.springframework.security.ui.logout.LogoutFilter">
<!-- URL redirected to after logout -->
<constructor-arg value="/"/>
<constructor-arg>
<list>
<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingLogoutHandlerImpl"/>
<bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
<property name="filterProcessesUrl" value="/j_spring_security_logout"/>
</bean>

<!-- "natvrdo" nastaveni prihlaseneho uzivatele (pouze pro vyvoj) -->
<bean id="userProfileStubFilter" class="com.o2bs.globals.web.springsecurity.utils.UserProfileStubFilter">
<constructor-arg value="123"/>
<constructor-arg value="bob"/>
<constructor-arg value="heslo"/>
<constructor-arg>
<list>
<value>ROLE_WRITE</value>
<value>ROLE_READ</value>
</list>
</constructor-arg>
</bean>

<!-- Processes an authentication form. -->
<bean id="authProcessingFilter"
class="com.o2bs.globals.web.springsecurity.auth.blocking.AuthenticationBlockingProcessingFilter">
<security:custom-filter after="AUTHENTICATION_PROCESSING_FILTER" />
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/login.do?login_error=1"/>
<property name="defaultTargetUrl" value="/"/>
<property name="authenticationBlockingManager" ref="authBlockingManager"/>
<property name="mandatoryRoles">
<list>
<value>ROLE_WRITE</value>
<value>ROLE_READ</value>
</list>
</property>
</bean>

<bean id="authBlockingManager"
class="com.o2bs.globals.web.springsecurity.auth.blocking.PeriodBlockingManagerMemoryImpl">
</bean>

<bean class="com.o2bs.globals.web.springsecurity.auth.blocking.AuthenticationFailureListener">
<property name="authenticationBlockingManager" ref="authBlockingManager"/>
</bean>

<bean id="userProfileAwareFilter"
class="com.o2bs.globals.web.springsecurity.utils.UserProfileHolderAwareRequestFilter">
</bean>

<!-- I need to use IS_AUTHENTICATED_ANONYMOUSLY - anonymous user has to be handled -->
<bean id="anonymousProcessingFilter"
class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="anonymKey"/>
<property name="userAttribute" value="anonymousUser,ROLE_READ"/>
</bean>

<bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.do"/>
</bean>
</property>
<property name="accessDeniedHandler">
<!-- errorPage neni definovana, protoze se vyhazuje 403 a ta je namapovana ve web.xml -->
<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingAccessDeniedHandlerImpl"/>
</property>
</bean>

<!-- protect web URIs -->
<bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="objectDefinitionSource">
<security:filter-invocation-definition-source>
<security:intercept-url pattern="/create*" access="ROLE_WRITE" />
<security:intercept-url pattern="/**" access="ROLE_READ" />
</security:filter-invocation-definition-source>
</property>
</bean>





<!--
AffirmativeBased implementation will grant access if one or more ACCESS_GRANTED votes were
received (ie a deny vote will be ignored, provided there was at least one grant vote).
In other words principal must have corresponding ROLE and particular level of authentication.
-->
<bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<!-- class will vote if any ConfigAttribute begins with PERM_. -->
<bean class="org.springframework.security.vote.RoleVoter">
<property name="rolePrefix" value="ROLE_"/>
</bean>
<!-- allow attributes IS_AUTHENTICATED_FULLY or IS_..._REMEMBERED or IS_..._ANONYMOUSLY -->
<bean class="org.springframework.security.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>

<!-- There is implicit authenticationManager when namespaces are used. -->
<security:authentication-manager alias="authenticationManager" />

<security:authentication-provider>
<security:user-service>
<security:user name="jimi" password="heslo" authorities="ROLE_READ"/>
<security:user name="bob" password="heslo" authorities="ROLE_READ, ROLE_WRITE"/>
</security:user-service>
</security:authentication-provider>

<security:global-method-security access-decision-manager-ref="accessDecisionManager">
<security:protect-pointcut
expression="execution(* com.o2bs.globals.example.competence.service.*.save*(..))"
access="ROLE_WRITE"/>
</security:global-method-security>

<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingAuthenticationListener"/>

</beans>