Tekijä: Marko Seppänen
Teostyyppi: harjoitustyö
Oppilaitos: Saimaan ammattikorkeakoulu
Julkaisuajankohta: Tammikuu 2010
Kurssi: Ohjelmistotekniikan harjoitustyö II
1 Johdanto
2 PubMed slicer
2.1 Avainominaisuudet
2.2 Havainnekuvia
3 Nutrition tactician
3.1 Avainominaisuudet
3.2 Havainnekuvia
4 Toteutus
4.1 Taustaa
4.2 Käytetyistä teknologioista ja standardeista
4.3 Luokallisuus
4.4 Syötteiden tarkistus
4.5 Etusivun rakenne ja ulkoasu
4.6 jQueryn hyödyntämistä
4.7 Poimintoja palvelinpuolella tapahtuvasta prosessoinnista
4.7.1 PubMed
4.7.2 Nutrition tactician
4.8 Kaaviointia
5 Yhteenveto
6 Linkkejä
Tämä kahdesta eri hakupalvelusta koostuva webpalvelu on käynyt läpi lukuisia muuntaumisia, useimmiten ohjastavana tekijänä ollen jonkin uuden ohjelmointitekniikan oppiminen ja halu saada oppi käyttöön. Data calmers (kehitetty viimeeksi vuonna 2008) koostuu kahdesta eri hakupalvelusta, joista toinen (PubMed slicer) on vaihtoehtoinen käyttöliittymä PubMed-etätietokantaan (tieteellisien julkaisujen abstrakteja) ja toinen (Nutrition tactician) public domainina julkaistavaan USDA-ravintotietokantaan, joka on asennettuna Sigmaticin palvelimelle. Data calmersia kehittäessäni huomasin, että Copsapolitan-lehteen kirjoittamani artikkelin (”Opiskelu on tanssi”) teesi siitä, että jonkin asian opittuaan, sille näkee paljon enemmän käyttöä kuin muuten näkisi, pitää täysin paikkansa.
Lisäksi, minusta oli kovin kiehtovaa vertailla omia palveluitani yliopistoissa ja yksityisissä yrityksissä kehiteltyihin vastaaviin, mikä osin johti tiettyyn pettymyksen tunteeseen siitä, miten prameammassa kontekstissa jokin monilta osin samankaltainen palvelu voi vaikuttaa paljon enemmältä kuin oikeasti onkaan. Tällä viittaan erityisesti GoPubMed-palveluun, jonka oletin ajallaan muuntuvan joksikin, joka hyödyntää jotain sellaista teknologiaa tai matemaattista hienoutta, jonka toteuttamiseen en itse kykenisi, mutta sellaista ei sitten koskaan tapahtunutkaan. Tuokin palvelu käsittelee samansisältöistä massiivista > 60 Gt:n tietokantaa, joka koostuu pelkästään miljoonista tieteellisissä julkaistujen artikkelien abstrakteista, asiasanoineen ja muine metatietoineen, XML-rakenteisena.

PubMed slicer on vaihtoehtoinen käyttöliittymä PubMed-tietokannan käyttöön tiedon hakemisen ja esittämisen osalta, toteutettuna seuraavilla teknologioilla ja standardeilla: PHP, Javascript, XML, XSLT, XHTML, CSS, SQL, REST ja jQuery
|
Kuva: Yksittäinen hakutulos, jossa esillä lähes kaikki saatavilla oleva tieto.
Hakutulosten abstraktitekstestä on pyritty korostamaan tietyt (ja vain tietyt) jäsentävät sanat, mutta koska kukin abstraktitekstin kirjoittaja käyttää näitä sanoja miten haluaa – tai ei käytä ollenkaan (niitä ei ole standardoitu) – ei korostuksia aseteta aivan joka kohtaan. Hakutulokset sisältävät linkkejä, joita klikkaamalla hakukenttään lisätään hakutarkentimia (esim. avainsanat ja julkaisun nimet).

Nutrition tactician on vaihtoehtoinen käyttöliittymä USDA-ravintotietokannan tiedon käsittelyyn, toteutettuna seuraavilla teknologioilla ja standardeilla: PHP, Javascript, XML, XSLT, XHTML, CSS, SQL, REST ja jQuery. Lisäksi siitä on johdettu versiot Adobe Airille ja iPhonelle, sekä se on helposti konfiguroitavissa toimimaan myös kotimaisen Fineli-ravintotietokannan kanssa.
|
Kuva: Haettu hakusanalla apple, saaden eri annoskokoineen
tuloksena 497 kpl ruokia
|
Kuva: Koriin kerätyn kolmen erilaisen juuston ravintotiedot taulukkomuodossa
|
Kuva: Adobe AIRille sovitettu versio
|
Kuva: iPhonen nettiselaimelle sovitettu versio
Ennen kuin Data calmers saavutti sen vaiheen, jossa sillä on selkeä arkkitehtuurinen rakenne, harkiten muodostettuine luokkineen, ohjelmointikoodi jäsenneltynä sekä asiakas- (Javascript) että palvelinpuolella (PHP), oli sitä edeltänyt lukuisten kokeilujen vaiheet. Väliin on mahtunut erilaisten tarjolla olleiden APIen kokeiluja (esim. Flickr), omaa uteliaisuutta tyydyttäneitä testejä (esim. josko saisin rakenneltua toimivan kuva- tai uutis hakukoneen), sekä opittujen tekniikoiden ja standardien harjoituttamista (esim. UML ja XSL).
Tätä kirjoittaessa teen jo kokeiluja PubMed slicerin toteuttamiseksi Javalla, hyödyntäen etäpalvelimen SOAP-rajapintaa, mutta nykyinen PHP-pohjainen versio on aina hyödyntänyt pelkästään REST-kutsuja tiedon kyselemiseen ja sen XML-muotoisena saamiseen. Nutrition tacticianin käyttämä SQL-tietokanta on alun perin koostunut ASCII-muotoisista tiedostoista, joiden pohjalta tietokannan taulut on muodostettu.
Molemmat palvelut ovat Ajax-toteutuksia eli asiakaspuoli ei tuhlaa kaistaa sivujen uudelleenlatailuun hakuja tehtäessä ja muita toimintoja suoritettaessa, vaan hakee palvelimelta vain välttämättömän määrän tietoa. Usein tämä tieto on jo valmiiksi palvelinpuolella prosessoitu ja saatettu XHTML tai XML-muotoiseksi, XSLT-tyylisivujen avulla raakadatasta konvertoiden. Raakadatana toimii joko tietokannasta saatu rivitieto tai etäkutsujen avulla haettu XML-rakenteinen tieto.
Palvelu hyödyntää suuressa määrin Javascript-kirjastoa jQuery. Se nopeuttaa toiminnallisuuksien toteuttamista asiakaspuolella, sekä samalla sitä käyttäessä voi olla varma, että palvelu toimii moitteetta eri nettiselaimissa, eikä tarvitse käyttää tarpeettomasti aikaa eri selainten välisten erojen paikkaamiseen niitä varten räätälöidyllä koodilla. Sillä on myös erittäin kätevää hakea tietoa DOM-puusta, joka sisältää kulloisenkin nettisivun sisällön hierarkisena rakenteena. Sitä käytetään myös kyseisen puun muokkaamiseen ja palvelun toiminnallisuuden kontrollointiin, sekä kaikki viestiliikenne asiakas- ja palvelinpuolen välillä tapahtuu jQuery-kirjaston tarjoamia metodeita käyttäen.
Palvelinpuolella tarpeellisimpaan PHP:n ominaisuuksiin kuuluu xsltProcessor-luokka, jota käytetään kaiken XML-muotoisen tiedon prosessointiin ja saattamiseen haluttuun muotoon. Tietokantaa (MySQL) hyödynnetään PubMed slicerilla haetun tiedon välimuistitukseen, sekä Nutrition tactician käyttämän USDA-ravintotietokannan säilömiseen ja tiedon hakemiseen siitä.

Sekä asiakas-, että palvelinpuoli on toteutettu olio-ohjelmoinnin periaatteiden mukaisesti eli niin sanottua spaghettikoodia on pyritty välttämään. Selainpuolella koodia on vähemmän, joten siellä on tyydytty sijoittamaan metodeita kategorisoivasti sopivien nimikkeiden alle (esim. Javascript-luokkiin Search, Nutrition, PubMed). PHP-luokat on jaoteltuina omiin paketteihinsa ja lukuisiin luokkiin.
Luokat on myös dokumentoitu käyttäen lähes de facto -asemiin nousseita malleja, joita mm. phpDocumentor ja Eclipse osaavat hyödyntää.
/**
* Shows foods for a given foodgroup
* @function showFoodNames
* @param {int|String} foodgroup
*/
this.showFoodNames
= function(foodgroup){
(Dokumentointia Javascript-koodissa)
class common
{
/**
*
Transform XML using XSL
*
* @static
* @param string|DomDocument $data
* @param string $xslfile
* @param array|null $parameters
* @param boolean $registerphpfunctions
* @param string|null $pathprefix
* @return string
*/
public
static function printXMLData($data, $xslfile, $parameters, $registerphpfunctions, $pathprefix = null) {
(Dokumentointia PHP-koodissa)
Palvelu itsessään ei vaadi sisäänkirjautumista, mutta ei-julkisessa versiossa on jo toteutettuna MD5-suojattu sisäänkirjautuminen, sekä mm. käyttäjäkohtaiset leikekirjat. Tätä tarkoitusta varten on kuitenkin jo olemassa niin monta vapaasti käytettävissä olevaa nettipalvelua, ettei niitä kehitelty julkistettavaksi asti – kyse on kuitenkin vain vapaa-ajan harrastuksena työstetystä palvelusta.
Kaikki käyttäjän syöttämät tiedot tarkistetaan sekä asiakaspuolella, että varmuuden vuoksi myös palvelinpuolella. Epäkelvot syötteet eivät pääse etenemään, eikä SQL-injektointi ole mahdollista. Asiakaspuolella tarkistus on yksinkertainen merkkijonon sisältämien merkkien tarkistus, mutta palvelinpuolella tarkistetaan paljon muutakin kuten selaimen lähettämät parametrit.
if (/^[\"#a-zåäö0-9\-\/\.\,\(\)\[\] :]{1,5000}$/i.test(searchstring)) {
(selainpuolen merkkijonon tarkistus)
if (preg_match('/^(medline|oldmedline|all)$/i',
$this->argumentstring['medlinesubset'])) {
$this->adjustment['medlinesubset'] = $this->argumentstring['medlinesubset'];
(eräs palvelinpuolen tarkistuksista)
Ulkoasu ja sivun rakenne on pyritty erotettu toisistaan sijoittamalla kaikki CSS (2.1) -tyylit omiin tiedostoihinsa ja viittaamalla tyyleihin XHTML-rakenteisena määritellyssä aloitussivussa class- ja id-attribuuteilla. Tämä tavoite toteutuu vain osittain, sillä tältä osin siivoustyö on jäänyt ns. vaiheeseen (kehitelty viimeeksi vuonna 2008).
<input type="button" value="Show abstract
basket" onclick="pubmed.showBasket();" />
<input type="button" value="Show
statistics (alpha)" onclick="pubmed.showStatistics();" />
<div style="margin-top: 24px; margin-bottom: 0px; font-size:
8pt; cursor: pointer;" onclick="pubmed.togglePubMedSearchForm();
$(this).hide();">
<span class='altlink'><a>More search options</a></span>
Etusivu sisältää tarkoituksella ripauksia PHP-koodia siellä täällä, tarkoituksena ollen sijoittaa url-osoitteessa annetut parametrit oikeille paikoilleen.
<input type="hidden" name="earliestyear" value="<?php if (preg_match('/^[0-9]{4}$/',$_GET["earliestyear"])) echo $_GET["earliestyear"]; else echo 1800;?>" />
Tässä on huomioitava se, että sivulla olevan PHP-koodin käsittely tapahtuu ennen kuin sivu lähetetään selaimen käsiteltäväksi ja näytettäväksi. Javasript ei itseasiassa tarjoa valmiita metodeita osoitteessa olevien parametrien käsittelyyn, joten aikanaan tuntui luontevalta käsitellä erilaiset parametrikombinaatiot PHP:llä.
if ($_GET["toolname"] == "nutritiontactician") {
echo "$('#tabs > ul').tabs({selected: 1});
$('#nutritiontactician_searchstring').attr('name','searchstring');";
if (preg_match('/^(usda|fineli)$/i',$_GET['nutrisource'])) $nutrisource = $_GET['nutrisource'];
Siitä pidän erityisen paljon, kuinka helppoa jQueryllä on manipuloida kerralla monia eri DOM-puusta esiintyviä solmuja/elementtejä. Se on vain yksinkertaisesti kätevää. Sama silkalla Javascriptillä tehtynä vaatisi kymmenen kertaa enemmän koodia.
if ($("#pubmed_items
> ul:first-child > li > div[id^='pubmed']").length
=== 1) {
$("#pubmed_items").empty();
Palvelimelta haetun tiedon voi sijoittaa haluttuun elementtiin sisällöksi parilla rivillä koodia.
$("#pubmed_singleitem").load(that.prefixdir
+ "loadcontent.php", {
action: "loadsingleabstract",
abstractid: abstractid
});
jQueryyn voi helposti liittää erilaisia plugineja, kuten tässä Slider-pluginin, jota käytetään PubMed slicerissa aikajakson tarkentamiseen.
$('#pubmed_yearrange').slider({
min: earliestyear,
max: latestyear,
stepping: 1,
range: true,
slide: function(e, ui){
Erilaisia herättimiä voi asettaa mihin elementtiin tai toimintoon tahansa, kuten esim. välilehden klikkaukseen.
$('#stattabs > ul').bind('tabsselect', function(event, ui) {
Toimintoja voi ketjuttaa ja elementtejä animoida.
$("#abstractinfo"+pos).html("Changed").fadeOut(1000);
PHP:ssä ei tarvitse välittää tuon taivaallista vahvoista tietotyypityksistä, toisin kuin esim. Javassa, mikä ei sinänsä haittaa, jos vain tietää mitä tekee ja muistaa mitä on tehnyt. Tämän vuoksi metodien määrityksissäkään ei kerrota minkätyyppistä tietoa ne palauttavat vai jättävätkö ne palauttamatta mitään (void).
Tässä voidaan nähdä käytetyn XPathia tiedon hakemiseen XML-datasta. Muutamalla koodirivillä saatiin talteen yksittäisen abstraktin sisältämät avainsanat (Descriptor names) ja sijoitettiin ne array-tyyppiseen muuttujaan data.
public function processOftenoccurringDescriptornames()
{
$xpath = new Domxpath($this->sourcefeed);
$querypath = "//PubmedArticleSet/PubmedArticle/MedlineCitation[@Status
= 'MEDLINE']/".
"MeshHeadingList/MeshHeading/DescriptorName";
$descriptornames = $xpath->query($querypath);
$i = 0;
foreach ($descriptornames as $dname) {
$this->data['descriptorname'][$i] = $dname->nodeValue;
$i++;
}
PubMedin REST-API tarjoaa käytettäväksi mm. ehdotuksia hakusanoille, jotka eivät sellaisenaan palauta mitään, mutta muistuttavat hiukan jotain sellaista hakusanaa, joka palauttaisi tuloksia. Alla sitä varten oma metodinsa, joka lisäksi välimuistittaa haun. Tätä voisi kehittää edelleen sen varalta, että verkkoyhteyksissä ilmenee häiriöitä; nyt metodin asenne on luottavaisen optimistinen.
public function getSuggestions()
{
$url = 'http://eutils.ncbi.nlm.nih.gov/entrez/eutils/espell.fcgi?db=pubmed&term='.$this->search['string'];
$dom_object = new DomDocument();
$cache = new cache($search['retstart'], $search['string'], 'suggs');
if ($this->search['retstart'] == 0) $dom_object = $cache->newloadXML($dom_object, $url, "suggs");
else @$dom_object->load($url);
$xpath = new Domxpath($dom_object);
$node = $xpath->query("//eSpellResult/CorrectedQuery");
$this->data['suggestion'] = $node->item(0)->nodeValue;
Usein tarvittavat metodit olen koonnut luokkiin common ja stringhandler. Molemmat sisältävät pelkästään staattisia metodeita. Parametrina annettu tiedostonnimi antaa viitteitä siitä, miten XSLT:tä hyödynnetään (sama data voidaan sovittaa useille eri alustoille).
echo common::printXMLData($this->dataxml,'pubmed-singleabstract-web.xsl', array(), true);
Ennen varsinaisen datan lähettämistä asiakaspuolelle lähetetään lähetetään header-kenttä, joka kertoo selaimella minkälaista tietoa on odotettavissa.
public
static function printHeaderString($type = 'xhtmlxml') {
switch ($type) {
case "xhtmlxml": {
header("Content-Type: application/xhtml+xml;
charset=iso-8859-1");
echo "<?xml version=\"1.0\"
encoding=\"iso-8859-1\" ?".">";
break;
}
case "texthtml": {
header('Content-type: text/html');
break;
}
Välimuistettu tieto expiroituu tietyn ajan kuluessa, joten sitä varten tehdään hakujen yhteydessä tarkistus. SQL-kyselyissä ei ole käytetty PEAR MDB2:ta, koska haluna tuolloin oli käskyttää tietokantaa SQL-kielellä suoraan. PEAR on lyhenne sanoista PHP Extension and Application Repository ja MDB2 on tietokanta-API (vrt. JDBC).
// Has cached version expired?
$query = "SELECT
IF(UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(addingtime) > ".$this->cachelength.",1,0)
AS expired,
ss.id
AS searchid
FROM
".$config['mysqltable_search_string']."
AS ss
INNER
JOIN ".$config['mysqltable_search_adjustment']."
AS sa
ON
ss.id = sa.id
WHERE
searchstring='".$this->searchstring."'
AND source='".$this->source."'
AND
sa.pubmedlowerrange = ".$this->adjustment['earliestyear']."
AND
sa.pubmedhigherrange = ".$this->adjustment['latestyear']."
AND
sa.pubmedmedlinedb = '".$this->adjustment['medlinesubset']."'
AND
sa.pubmedonlyfreefulltext = ".$this->adjustment['onlyfreefulltext'];
Välimuistitettu XML-data serialisoidaan, gzipataan ja tallennetaan tiedostoon cache-hakemistoon. Tiedostoa tarvitessa sama tehdään käänteisesti. Miukumerkki viimeisellä rivillä aiheuttaa mahdollisten virheilmoitusten hukkaamisen.
$gzippedxml = "";
$gzfp = gzopen("cache/".$this->searchid."_".$part.".xml.gz","r");
while (!gzeof($gzfp)) {
$gzippedxml .= gzread($gzfp, 1024);
}
gzclose($gzfp);
$xmlstring = unserialize($gzippedxml);
@$dom_object->loadXML($xmlstring);
Koska toiminnallisuus myös Fineli-ravintotietokannan käyttöön on jo valmiina, on Food-luokasta johdettu kaksi muuta luokkaa, jotka tarjoavat melko lailla samat metodit, mutta erilaisen toteutuksen.
class food_Fineli extends food {
class food_USDA extends food {
Ennen XSLT-tyylisivujen käyttöä tietokannasta saatu tieto muutetaan XML-rakenteiseksi tiedoksi. Parametri MYSQL_ASSOC mahdollistaa kulloisenkin rivin sarakkeen nimeen viittaamisen, järjestysnumeron kertomisen sijaan. Ääkkösten ja muiden erikoisempien merkkien vuoksi tekstitieto muunnetaan UTF8-standardin mukaiseen muotoon, jottei XSLT-prosessoija ilmoittaisi virheellisestä syötetiedosta.
$this->dataxml
.= "<root>";
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
$this->dataxml .= "<ingredientclass>";
$this->dataxml .= "<igclassp>".$row['igclassp']."</igclassp>";
$this->dataxml
.= "<desc>".utf8_encode($row['descript'])."</desc>";
$this->dataxml
.= "</ingredientclass>";
}
$this->dataxml
.= "</root>";
SQL-käskyt saattavat olla pitkiäkin, mutta suurin osa niistä on aika tyypillisiä taulujen liittämisineen (JOIN), ryhmittelyineen (GROUP BY), funktiokäyttöineen (TRUNCATE) yms.
SELECT
compval.eufdname, eufd.descript AS nutrname,
TRUNCATE(compval.bestloc/100*".$this->data['weights']['mass'][$i].",3)
AS nutrval, comp.compunit, comp.cmpclassp, cmpc.descript AS compclassdesc
FROM
".$config['mysqltable_fineli_componentvalue']."
AS compval
INNER
JOIN ".$config['mysqltable_fineli_eufdname']."
AS eufd
ON
compval.eufdname = eufd.thscode
Data calmersin kehittämisen yhteydessä tulin eräässä vaiheessa innostuneeksi UML-kaavioista, joihin sitten myöhemmin ammattikorkeakoulussa perehdyin vielä lisää. Tuolloin aiemmin tulin kuitenkin kokeilleeksi mm. Sybasen Power Designeria (versio 15 beta), Visual Paradigm UML:lää, Enterprise Architectiä yms. jotka olivat saatavilla. Lisäksi käytin MySQL Workbenchia työstääkseni Fineli-ravintotietokannasta mallin viiteavaimineen.



Data calmersin kehittäminen on ollut erittäin palkitsevaa ja se on avannut mielenkiinnolleni lukuisia reittejä tutkailtavaksi, joiden varrelta olen myöhemmin löytänyt paljonkin sellaista, josta on ollut laajempaakin hyötyä. Tälläisiä ovat olleet esim. jQueryn, XSLT:n ja UML:n löytäminen ja niiden käytössä harjaantuminen. Sittemmin minussa on tapahtunut merkittävä siirtymä pois PHP-pohjaisesta ohjelmoimisesta ja käytännössä siirtymä Java-ohjelmointikieleen on jo tapahtunut. Tosin, tätä dokumenttia kirjoittaessa huomasin, että sen minkä on kerran oppinut pystyy palauttamaan mieleensä varsin helposti pitkänkin ajan päästä – jos vain niin haluaa. Saatan vielä jossain vaiheessa kehittää tätä PHP-pohjaista palvelua pidemmällä tai sitten teen sen kokonaan uusiksi Java-pohjaisia teknologioita hyödyntäen.
Data calmers
http://datacalmers.hoito.org
PHP:n XSLTProcessor -luokka
http://www.php.net/manual/en/class.xsltprocessor.php
PubMed-API – Entrez Programming Utilities
http://eutils.ncbi.nlm.nih.gov/corehtml/query/static/eutils_help.html
USDA National Nutrient Database for Standard Reference
http://www.ars.usda.gov/Services/docs.htm?docid=8964
GoPubMed – eräs toinen vaihtoehtoinen käyttöliittymä PubMediin
http://www.gopubmed.org
jQuery – Javascript-kirjasto
http://jquery.com
PEAR MDB2 – tietokanta API
http://pear.php.net/package/MDB2
phpDocumentor
http://www.phpdoc.org
Ajax (Wikipediassa)
http://fi.wikipedia.org/wiki/Ajax_%28ohjelmointi%29
Visual Paradigm UML
http://www.visual-paradigm.com/product/vpuml/
MySQL
http://www.mysql.com
PHP
http://php.net
<Oxygen/> XML Editor
http://www.oxygenxml.com
Aptana Studio – ohjelmistokehitysympäristö
http://www.aptana.org
PHP Development Tools – ohjelmistokehitysympäristö
http://www.eclipse.org/pdt/