30. listopadu 2007

Unit testy - ty přece píše kodér, ne?

Tentokrát spíš chci prodiskutovat jedno téma, než vám něco sdělit. Právě jsem dočetl článek Akshay Sharmi o návrhu, který myslí na testovatelnost kódu (Design to Unit Test). Jediná věc, která mě na tomto článku přímo praštila do očí, je skutečnost, že je psán tak, jako že unit testy píše někdo jiný, než ten, kdo testovaný kód psal.

Já jsem vždy přemýšlel o unit testech jako o věci, kterou si kodér ověřuje, že jeho kód funguje tak, jak má. Navíc si kodér musí již dopředu rozmyslet, jak svůj kód otestuje, což bezesporu vede k jeho větší kvalitě. V neposlaení řadě unit testy vnímám také jako "dokumentaci" ke kódu, což v případě, že je píše někdo jiný opět selhává.

Je pravdou, že psaní testů je trochu jiná káva, než psaní kódu. Je nutné se to naučit. Ale není to zase tak složité. V případě integračních a akceptačních testů je již zřejmé, že je může (nebo má?) implementovat někdo jiný než kodér.

Obávám se, že tento přístup není pro mě. Navíc bych si troufl s ním nesouhlasit.

29. listopadu 2007

XML Signature - použít nebo ne?

V naší aplikaci přenášíme poměrně velké XML soubory a přenášíme jich více najednou. Proto jsou zazipované v ZIP archivu. Ovšem potřebujeme nějak zabezpečit, aby nebylo možné měnit obsah archivu (tj. nemožnost přidávat či mazat soubory) a dále aby nešlo měnit jednotlivé XML soubory. Navíc tato komunikace neprobíhá mezi naším softwarem, ale mezi naším a cizím. Jak věc vyřešit?

Použít XML Signature (specifikace) nebo ne? Než jsem se pustil do zkoušení hledal jsem na netu a našel jsem pár mailů v konferencích a blogů, že XML Signature implementace nefunguje dobře na "large documents". Už jsem přemýšlel o vlastním řešení, kde bych hashoval XML soubory jako stream bytů, nebo bych vytvořil nějaký vlastní algoritmus pracující přímo nad SAXem, který používáme jak pro čtení tak i pro zápis dokumentů. Ale nakonec jsem si řekl, že to zkusím přímo s XML Signature a šáhl jsem po implementací, která je součástí JDK 6. Pro XML Signature hovoří skutečnosti, že je to standard (nemusím specifikovat jak se podpis vypočítá) a existuje implementace pro skoro všechny rozšířené jazyky.

Napsal jsem tedy velmi jednoduchou třídu XMLSignatureTest (při jejím psaní jsem převážně vycházel z příkladu v XML Digital Signature API Examples):

public class XMLSignatureTest {

protected static final String KEY_ALIAS = "ALIAS";

public static void main(String[] args) {
try {
long milis = System.currentTimeMillis();
if ("sign".equals(args[0])) {
signing(new File(args[1]), new File(args[2]));
} else {
System.out.println(verify(new File(args[1])) ? "Verified - ok" : "Changed");
}
System.out.println("Done in " + (System.currentTimeMillis() - milis) + " ms.");
} catch (Throwable t) {
t.printStackTrace();
}
}

protected static void signing(File xmlFile, File signatureFile) throws Exception {
//načti vstupní XML dokument
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(new FileInputStream(xmlFile));

//otevři keystore a načti z něj privátní klíč k podpisu hashe
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("keystore"), "XMLSig".toCharArray());
PrivateKey key = (PrivateKey) keyStore.getKey(KEY_ALIAS, "MEkeypwd".toCharArray());

//nadefinuj context podpisu
DOMSignContext dsc = new DOMSignContext(key, doc.getDocumentElement());
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

//podepiš celý dokument, na canonicalizaci použij metodu INCLUSIV, jako hash použij SHA1 algoritmus
//podpis bude vložen do podepisovaného dokumentu a výsledný hash bude zakryptovát DSA klíčem
Reference ref = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (XMLStructure) null)),
null, null);
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (XMLStructure) null),
fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null), Collections.singletonList(ref));

//do podpisu jako informaci o použitém klíči vložíme jeho alias
//tj. obě strany se musí domluvit na aliasech a předat si veřejné klíče
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyName kn = kif.newKeyName(KEY_ALIAS);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kn));

//spočítej podpis
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);

//ulož výsledný dokument do souboru
OutputStream os = new FileOutputStream(signatureFile);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
os.flush();
os.close();
}

protected static boolean verify(File xmlFile) throws Exception {
//načti XML document s vloženým podpisem
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(new FileInputStream(xmlFile));

//najdi v dokumentu podpis
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element");
}

//otevři keystore pro načtení veřejného klíče
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("keystore"), "XMLSig".toCharArray());

//vytvoř validační kontext (node s podpisem a KeySelector pro získání
//klíče podle identifikace v podpisu - v našem případě je to ALIAS)
DOMValidateContext valContext =
new DOMValidateContext(new KeyNameKeySelector(keyStore), nl.item(0));
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = factory.unmarshalXMLSignature(valContext);

//ověř podpis
return signature.validate(valContext);
}

//tato třída slouží k nalezení veřejného klíče použitelného k odkryptování podpisu
private static class KeyNameKeySelector extends KeySelector {
protected KeyStore keyStore;

public KeyNameKeySelector(KeyStore keyStore) {
super();
this.keyStore = keyStore;
}

//tato metoda je volána knihovnou
public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose,
AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {

if (keyInfo == null) {
throw new KeySelectorException("Null KeyInfo object!");
}
SignatureMethod sm = (SignatureMethod) method;
List list = keyInfo.getContent();

for (int i = 0; i < list.size(); i++) {
XMLStructure xmlStructure = (XMLStructure) list.get(i);
//my hledáme klíč podle jména (aliasu)
if (xmlStructure instanceof KeyName) {
try {
//natáhnem veřejný klíč z keystoru
Certificate certificate = keyStore.getCertificate(((KeyName) xmlStructure).getName());
PublicKey pk = certificate.getPublicKey();
//ověříme, že v podpisu je stejný algoritmus jako používá klíč
if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
return new SimpleKeySelectorResult(pk);
}
} catch (KeyStoreException kse) {
throw new KeySelectorException(kse);
}
}
}
throw new KeySelectorException("No KeyValue element found!");
}

//pouze porovnává algoritmy
static boolean algEquals(String algURI, String algName) {
if (algName.equalsIgnoreCase("DSA")
&& algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return true;
} else if (algName.equalsIgnoreCase("RSA")
&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return true;
} else {
return false;
}
}

//jenom holder pro klíč
private static class SimpleKeySelectorResult implements KeySelectorResult {
private Key pk;

SimpleKeySelectorResult(Key pk) {
this.pk = pk;
}

public Key getKey() {
return pk;
}
}

}

}
Úpravy spočívají pouze v použití key-storu pro získání klíču a nepřenášení veřejného klíče v podpisu, ale pouze jeho aliasu.

A nyní k samotným testům. Zajímal jsem se o dva faktory: rychlost a peměťová náročnost.

Takže nejprve jsem test prováděl na XML dokumentu o velikosti 6,5 kB. Výpočet podpisu i ověření proběhlo i s nejmenším možným heapem, tj. 2M (1M nestačilo ani na nastartování JDKčka) a výpočet podpisu a serializace trvala 0,6 s. Co se verifikace podpisu týče, došel jsem k úplně stejnému číslu.

Následně jsem přitvrdil a použil jsem soubor o velikost 4,1 MB. Minimální heap, nad kterým jsem se dobral výsledku a nedostav pouze OOM exception byl 53MB a výpočet podpisu včetně serializace výsledku trval 10s, ověření podpisu trvalo pouze 4,8s a dokonce potřebovalo minimálně pouze 47MB. Při použití heapu o velikosti 400MB jsem se u podpisu dostal na čas 4,8s a u ověření na 2,8s.

Závěr

V podstatě jsem byl velmi mile překvapen, jak vše hladce a na první pokus proběhlo a spíš jsem hledal skulinu, jak XML Signature použít. Z prvu mi vadilo, že je nutné použít DOM, protože používáme SAX (a použít Transaformer se vstupem SAXSource a s výstupem DOMResult se mi nechtělo). Pak jsem si uvědomil, že bych stejně nemohl výpočet podpisu zařadit přímo do zpracování dokumentu, protož zpracování může trvat i dost dlouho (třeba i desítky minut). Souborů se může zpracovávat více najednou. Pokud by každý, nedejbože, byl rovnou podepisován a vytváření podpisu by si slízlo 50MB heapu, pak bych měl na serveru pamět jenom pro podepisování.

Nakonec jsem se rozhodl, že prvním krokem zpracování bude načtení dokumentu pomocí DOM a ověření podpisu. Následně teprve, pokud bude vše v pořádku, budu dokument načítat pomocí SAX (do budoucna stejně přejdeme na StAX) a dokument budu zpracovávat a zároveň budu generovat výstup (také pomocí SAX) a ten budu serializovat do souboru. Na závěr výstupní soubor načtu pomocí DOM a vygeneruju podpis.

V neposlední řadě, tak trochu pod čarou, zmíním canonicalizaci, která funguje opravdu pěkně, přidávání mezer, nových prázdných řádků, přehazování pořadí atributů neovlivní platnost podpisu.

19. listopadu 2007

JavaScript a Dependency Injection

Všichni jste už asi slyšeli o Spring IoC, Picocontaineru, HaveMindu či Tapestry IoC. Ale což takhle IoC pro JavaScript?

Ano je to možné. Jmenuje se Squirel Ioc. Jedná se o velmi jednoduchou knihovna, která má 4kB a používá jQuery.

Použití je velmi jednoduché (jak už se v mých příspěvcích stalo pravidlem). Máme následující třídy, které budeme propojovat:


function MyModel(){
//obsah třídy MyModel
}

function MyDAO(model){
var _model=model;

this.get = function() {
return _model;
}

this.save = function() {
//uloz
}

this.delete = function() {
//smaz
}
}

function MyService(){
var _dao = null;

this.get = function() {
return _dao.get();
}

this.getDao = function() {
return _dao;
}

this.setDao = function(dao) {
_dao = dao;
}
}

function MyApp(){
var _service=null;
//rikame IoC containeru, ze az vse nastavi, ma zavolat metodu onContextSupport
ContainerSupport.call(this);

this.onContextSupport = function(containerContext){
var model=_service.get();
//udelej cokoliv dalsiho s hodnotou ...
}

this.setService = function(service){
_service = service;
}
}
Z pohledu IoC v Javě máme POJO objekty a teď je potřebuje spolu nějak svázat (ve Springu se k tomu např. používá xml soubor nebo anotace). Při použití Squirrel IoC nám poslouží velmi jednoduchý kód:
var definition={
'model':{type:MyModel},
'dao':{type:MyDAO,args:[{ref:'model'}]},
'service':{type:MyService,props:[{name:'dao',ref:'dao'}]},
'app':{type:MyApp,props:[{name:'containerContext',ref:'containerContext'},{ref:'service'}]}
};


var container = new IContainer();
container.load(definition);
V příkladu je vidět constructor injection (na příkladu definice dao objektu) či setter injection (na příkladu objektu service).

Když to funguje v Javě, proč ne v JavaScriptu. Opět se ukazuje jednoduchost, kterou nám JavaScript jako dynamický jazyk nabízí. A příště až budete potřebovat propojit pár objektů mezi sebou, zamyslete se, zda vám Squirrel Ioc neusnadní práci.

10. listopadu 2007

Nejde říci co je cennější, zda znalosti nebo zkušenosti

Jak je naznačeno v příspěvku Znalosti a zkušenosti, zkušenost je víc než znalost.

Podle mě jde hlavně o rozdíl mezi znalostí a zkušeností. Znalosti jsou laciná věc. Stačí zajít do školy, projít si pár článku na internetu a nebo nedej bože přečíst knihu. Pokud máte funkční paměť nejen v počítači ale i v hlavě, tak znalosti nabudete velice snadno.
A hned jsem si vzpomněl na strýčka z Jirotkova Saturnina, který měl malou chemickou továrnu a nebyl vzdělán v oboru, na všechno si přišel sám. Vše co bylo napsáno v moudrých knihách on složitě vymýšlel. Neměl žádné znalosti, ale nespočetně mnoho zkušeností. Bohužel, vždy když vyráběl nové mýdlo, netušil co se mu v kádích uvaří ...
Jsou to zkušenosti co se počítá. Můžu si přečíst kolik knih chci o tom jak se mají správně dělat softwarové projekty. Dokud to ale nezažiji na vlastní kůži, mají tyto znalosti malou váhu (bojím se že nulovou). Zkušenosti mají také jednu obrovskou výhodu. Málo stárnou. Když mám zažito jak psát software, jaká pravidla dodržovat a čeho se vyvarovat, je mi více méně jedno jakou technologii používám. Detaily si dokáži nastudovat.
Není přeci nutné mít zkušenost s popáleninou od rozpálených kamen, když mám znalost, že to je dost hrozné. Zkušenost dává znalostem rozměrem. Dává jim ten osobní kontakt s nimi. Ale zároveň nám umožňuje tento rozměr v budoucnu dát znalostem přímo, bez zkušenosti. Znalost nás směřuje správným směrem a zaměřuje naše zkoumání a vytváření zkušeností k užitečným cílům.

A teď musím navázat na Vlastůvovu reakci Co se počítá, ve které zase uhodil hřebíček na hlavičku (jak on to dělá?). Nejde o to, jak své dovednosti získáme, či zkušeností nebo znalostí, každý jsme jiný (někomu vyhovuje si přečíst referenční příručku, někdo jiný si věc raději osahá), ale jde o to, jak svých schopností využijeme k plnění cílů, které jsou nám předkládány.

Takže čtěme, vzdělávejme se, nebojme se poučit od druhých, zkušenějších, berme jejich rady vážně, protože jenom tak nezopakujeme chyby, kterých se dopustili, a získáme více drahoceného času. A třeba to dotáhneme ještě dál než oni.

1. listopadu 2007

A je to tady - XQuery Update v pureXML

Kdo si počká, ten se dočká. Od včera je možné si stáhnout IBM DB2 UDB 9.5. A proč tolik povyku. Kromě mnoha další vylepšení, je konečně implementován i draft XQuery Update Facility. Asi nebudu sám, kdo cítil velký nedostatek v nemožnosti updatovat část XML dokumentu uloženého pomocí pureXML.

Nebudu dlouho běhat okolo horké kaše a ukážeme si pár příkladů.

Máme tabulku friend, která má sloupec info. V této tabulce máme jeden záznam:

<friend>
<name>Jiri Mares</name>
<phone type="work">222 222 222</phone>
<phone type="home">333 333 333</phone>
</friend>
A nyní si ukážeme prostou změnu mého čísla do práce (využiji skutečnosti, že mám v tabulce jeden řádek a vypustím klauzili where z SQL update příkazu):
update friend
set info = xmlquery( 'copy $new := $INFO
modify do replace value of $new/friend/phone[@type="work"] with "111 111 111"
return $new');
Což přidat telefon na mobil:
update friend
set info = xmlquery('copy $new := $INFO
modify do insert <phone type="cell">444 444 444</phone>
as last into $new/friend
return $new' );
Nebo můžeme přejmenovat tag name na full-name:
update friend
set info = xmlquery('copy $new := $INFO
modify do rename $new/friend/name as "full-name"
return $new' );
Dále jsem neukázal mazání nodu:
update friend
set info = xmlquery('copy $new := $INFO
modify do delete $new/friend/phone[@type="cell"]
return $new');
Můžeme nahradit více tagů najednou:
update friend
set info = xmlquery(' copy $new := $INFO
modify (
do replace value of $new/friend/phone[@type="home"] with "999 999 999",
do replace value of $new/friend/phone[@type="work"]/@type with "cell")
return $new' );
Začíná se mi XQuery líbit čím dál tím víc, je jednoduché, čitelné, skoro bych řekl sexy :-).

Na závěr nutno říci, že XQuery Update nemusíte používat jenom při SQL updatu, ale i v selectu. Takže pokud nechcete vracet uživateli hodnotu telef. čísel na mobil, pak stačí udělat dotaz:
xquery copy $new := db2-fn:sqlquery("select info from friend")
modify for $j in $new/friend/phone[@type="cell"] return
do replace value of $j with "XXX XXX XXX"
return $new;

A co mě ještě v pureXML zaujalo

Je toho hodně nového, ale ... zajímavá je rychlost, takže je fajn, když se XML dokument může uložit ve stejné stránce jako ostatní sloupce řádku tabulky, protože celý řádek je k dispozici pomocí jediné I/O operace (i když platí omezení na velikost XML dokumentu max. 32k - nebojte, je-li dokument větší, neznamená to chybu, ale pouze se standardně uloží do XDA).

Zajímavá je intergrace XSLT enginu přímo do DB2 (když už je XML uloženo v podobě stromu, přímo si to o tuto integraci říká). Díky nové funkci parameter padá nemožnost předávat parametry do sqlquery funkce. A další ...