Tietokantapohjaisten WWW-liittymien rakentaminen

Tommi Lahtonen


Ohjelmistotekniikan seminaari
12.12.1997


Jyväskylän yliopisto
matematiikan laitos


Tiivistelmä

Seminaariesitelmä käsittelee tietokantoja hyödyntäen rakennettavia dynaamisia World Wide Web-sivuja. Erityisesti kiinnitetään huomiota eri valmistajien tarjoamiin toteutustekniikoihin. Varsinaiset toteutusvälineet jätetään vähemmälle huomiolle.

Avainsanoja: WWW, tietokannat, JDBC, Active Server Pages, IntraBuilder, Java

Sisällysluettelo

1. Johdanto 1
1.1 Miksi tietokantaliittymiä WWW:hen? 1
1.2 Mitä merkitystä toteutustekniikalla? 1
2. Erilaisia toteutustekniikoita 3
2.1 Active Server Pages (ASP) 3
2.1.1 OLE DB 4
2.1.2 ActiveX Data Objects (ADO) 5
2.1.3 Remote Data Service (RDS) 7
2.2 IntraBuilder Server 7
2.2.1 IntraBuilder Broker 9
2.2.2. IntraBuilder Agentit 9
2.2.3 Borland Database Engine (BDE) 9
2.3 Java Database Connectivity (JDBC) API 9
2.3.1 Yksitasoiset järjestelmät (One Tier Systems) 11
2.3.2 Kolmitasoiset järjestelmät (Three Tier Systems) 11
2.4 Tietokantapalvelimia 12
2.4.1 Sybase Adaptive Server Enterprise 12
2.4.2 JBMS 13
3. Huomioita tietokantapohjaisen WWW-liittymän toteuttamisesta 13
3.1 Käyttäjien erottaminen 13
3.2 Tietueiden lukitus 14
3.2.1 Pessimistinen lukitus 14
3.2.2 Optimistinen lukitus 14
3.3 Transaktiot 15
3.4 Tietoturva 15
3.4.1 WWW-palvelimien turvallisuus 15
3.4.2 Tietokantapalvelimien turvallisuus 15
3.4.3 Sovelluskohtainen turvallisuus 16
3.5 Ei kaikkea dynaamiseksi 16
4. Yhteenveto 16
5. Lähteet 17
5.1 Lisämateriaalia 18
6. Liitteet 20
6.1 ASP:llä toteutettu opiskelijoiden demoaktiivisuuslista 20
6.2 ASP-koodin tuottama HTML-koodi 23
6.3 IntraBuilderilla toteutettu opiskelijoiden demoaktiivisuuslista 26
6.4 IntraBuilderin Javascript-koodista tuotettu HTML-koodi 31
1.Johdanto

Viime vuosien Internet-villiintymisen aikana on ollut muotia tehdä jokaiselle isolle ja pienellekin firmalle omat WWW-sivut. Jonkinlaisen sivuston kasaaminen ei tällä hetkellä maksa paljoakaan ja onnistuu periaatteessa keneltä vain. Sivujen suurena ongelmana on niiden pitäminen ajan tasalla. Liian usein näkee suurienkin yrityksien WWW-sivuja, joiden tietosisältö on ollut sivuja tehdessä ajan tasalla, mutta jotka sen jälkeen on täysin unohdettu. Yrityksen tiedot täytyy pitää ajantasalla WWW-sivuillakin, jotta niistä olisi jotain hyötyä. Sivujen manuaalinen päivittäminen on hidasta, vaivalloista ja kallista. Useimmiten suurin osa muuttuvasta informaatiosta on tallennettuna johonkin tietokantaan. Tällöin kannattaakin toteuttaa WWW-sivunsa tavalla, joka mahdollistaa niiden dynaamisen päivittymisen suoraan tietokannasta löytyvän informaation mukaan.

Ensimmäisessä luvussa pohditaan hieman sitä missä kaikkialla voidaan hyödyntää dynaamista sivun generointia ja mikä toteutustekniikka olisi paras mihinkin tilanteeseen. Luvussa kaksi tutkitaan tarkemmin eri valmistajien tarjoamia vaihtoehtoja tietokantaliittymän rakentamiseksi. Luvussa kolme mietitään mitä kaikkea tulee ottaa huomioon dynaamisten sivujen luomisessa.

1.1 Miksi tietokantaliittymiä WWW:hen?

Suuri osa maailman tietokoneiden sisältämästä informaatiosta on talletettuna tietokantoihin. Internetin kasvattaessa jatkuvasti suosiotaan erityisesti mainiona tiedonlähteenä on tullut ajankohtaiseksi kehittää menetelmiä jo valmiiden tietokantojen tietojen saattamiseksi helposti WWW:hen. [Borland 1, 1996]

Toisaalta tietokantoja kaivataan dynaamiseen sivujen ylläpitoon. Yritykset haluavat ottaa kaiken hyödyn irti WWW-sivuistaan ja tähän liittyy oleellisesti tietojen ylläpitäminen. Tämä onnistuu helpoiten rakentamalla WWW-sivut muotoutumaan dynaamisesti kulloinkin tietokannassa olevan tiedon mukaan.

Muokattavuus on päivän sana varsinkin uutisia tarjoavilla WWW-sivuilla. Kävijä voi itse määritellä minkätyyppisiä uutisia hän haluaa lukea ja uutissivut muodostetaan automaattisesti sen mukaan. Taustalla piilee jälleen kerran tietokanta.

Tietenkään ei saa unohtaa tiedon keräämistä. Monella WWW-sivulla kerätään rekisteröimistietoja tai tehdään jopa ihan galluppeja. On varmasti tehokkainta, että kyseiset asiat kerätään suoraan tietokantaan sen sijaan, että ne lähetettäisiin sähköpostitse jollekulle manuaalisesti käsiteltäväksi.

1.2 Mitä merkitystä toteutustekniikalla?

Lähes jokaisella valmistajalla, jolla on tarjolla työkaluja WWW-sivujen tuottamiseen, on oma ratkaisunsa tietokantapohjaisten sivujen rakentamiseen. Suurien tietokantaohjelmistojen tuottajat ovat myös tulleet mukaan WWW-villitykseen. He ovat kehittäneet omia ratkaisujaan, joilla heidän tietokantansa saadaan saumattomasti liitettyä World Wide Webiin.

Ensimmäiseksi on syytä miettiä, millaiseen käyttöön WWW-sivuja ollaan tekemässä. Samoin on mietittävä, millaisille käyttäjille sivut suunnataan. Voiko käyttäjäkunnan rajata pelkästään tietyn selaimen käyttäjiin, vai pitääkö sivuja pystyä katsomaan aivan millä tahansa selaimella? Entä täytyykö tietokannan tietoja pystyä päivittämään suoraan selaimelta?

Pelkästään dynaamisesti luotavien sivujen tuottaminen ei aiheuta erityisiä ongelmia. Palvelin hoitaa kaiken varsinaisen koodin suorittamisen ja yhteydet tietokantaan. Selaimille lähetetään pelkästään standardin mukaista HTML:ää sisältäviä dokumentteja (Kuva 1.). Tärkeintä on varmistaa, että palvelin on tarpeeksi tehokas tehtäväänsä. Dynaaminen sivujen generointi kuluttaa huomattavasti enemmän palvelimen resursseja kuin tavallisten staattisten HTML-dokumenttien jakaminen. Mitä suuremmalle yleisölle sivut on tarkoitettu, sitä enemmän palvelin joutuu työskentelemään. Monesti dynaamisten sivujen kohdalla huomaa, että sivut ovat hitaita palvelimen kuormituksen takia, eivätkä niinkään verkkoyhteyden takia, kuten useimmiten ensin ajatellaan.

Kuva 1. Tietokantayhteys WWW-palvelimen kautta.

Ongelmia nousee vastaan, kun halutaan mahdollisuus muokata tietokannan sisältöä selaimelta käsin. Perinteisesti tietokantapalvelimen ja asiakkaan välillä on ollut jatkuva yhteys, jonka ylitse tietojen lisääminen ja päivittäminen on voinut tapahtua. World Wide Web on kuitenkin luonteeltaan hyvin erilainen. Yhteys palvelimen ja asiakkaan välillä ei ole olemassa kuin aina tarvittaessa. Tämä aiheuttaa huomattavia ongelmia tietojen lisäämisessä, poistamisessa ja päivittämisessä. Toimitukset, joissa ei ole vaaraa, että useampi henkilö olisi yhtäaikaa käsittelemässä samaa tietoa onnistuvat vielä WWW:ssäkin. Tietojen poistaminen ja päivittäminen vaativat kuitenkin erityisiä lukituksia tietokannan puolelta, jotta ne saadaan toimimaan luotettavasti. Lukitseminen tarkoittaa samaan tietoon kohdistuvien yhtäaikaisten muutosten estämistä. Tietokannasta riippuen voidaan käyttää rivikohtaista lukitusta tai huonossa tapauksessa on tyydyttävä koko kannan lukitsemiseen. Ongelmaksi muodostuukin se, kuinka pitkäksi ajaksi lukitus täytyy muodostaa. Palvelin ei voi koskaan varmasti tietää, onko käyttäjä päättänytkin yhtäkkiä lähteä kesken tietojen päivittämisen kahvitauolle. Lukituksiin tulee siksi määritellä aikarajat, joiden jälkeen ne poistetaan automaattisesti, tällöin ei enää hyväksytä kyseisen käyttäjän päivitystä suoraan tietokantaan.

Kuva 2. Suora yhteys tietokantaan

Helpommalla päästään jos pystytään luomaan suora tietokantayhteys selaimesta tietokantaan (Kuva 2.). Tätä ei ikävä kyllä ole mahdollista toteuttaa jokaisella selaimella toimivaksi. Java Database Connectivity (JDBC) mahdollistaa Javalla ohjelmoituihin sovelluksiin suoran tietokantayhteyden. Selaimen täytyy luonnollisesti tukea Javaa, jotta JDBC:n hyödyntäminen onnistuu. Toinen mahdollisuus on käyttää Microsoftin Remote Data Service (RDS) tekniikkaa. RDS vaatii toimiakseen Microsoftin Internet Explorer selaimen.

2. Erilaisia toteutustekniikoita

Tietokantapohjaisen dynaamisen liittymän luomiseen on olemassa monta eri tekniikkaa ja työkalua. Tässä luvussa käsitellään muutama toisistaan eroava tapa. Muitakin kuin allamainittuja tekniikoita on olemassa, mutta ne noudattavat hyvin läheisesti samoja ideoita kuin kyseiset tekniikat.

2.1 Active Server Pages (ASP)

Active Server Pages (ASP) on Microsoftin kehittämä skriptiympäristö, jolla voidaan rakentaa dynaamisia, interaktiivisia ja tehokkaita WWW-palvelinsovelluksia. ASP-ympäristöön toteutetut sovellukset pyörivät kokonaisuudessaan palvelimella, eikä niitä käyttävän selaimen tarvitse välttämättä ymmärtää kuin tavanomaista HTML:ää. Active Server Pages on saatavilla vain Microsoftin omille WWW-palvelimille kuten Microsoft Internet Information Server 3.0, Microsoft Peer Web Services 3.0 ja Microsoft Personal Web Server. ASP:llä voidaan käsitellä tietokantoja tehokkaasti käyttämällä ActiveX Data Objects tekniikkaa ja OLE DB:tä. ([PC Magazine, 1997]).

ASP-tiedostot ovat tavallisia tekstitiedostoja, jotka sisältävät joko VBScriptillä tai Jscriptillä kirjoitettua ohjelmakoodia ja tavallista HTML:ää.

<table border="0">
<form action="syotto.asp" method="post">
<%
For I = 0 To Rs.fields.Count - 1
%>
<tr>
<td><%=Rs(I).Name%></td>
<td>
<input type="text" name="<%=Rs(I).Name%>" value="<%=Rs(I)%>">
</td>
</tr>
<%
Next
%>

VBScript on supistettu versio Microsoftin Visual Basic kielestä ja Jscript on Microsoftin versio Javascript-kielestä. Varsinainen koodi erotetaan HTML:stä merkinnöillä <%ja %>. ASP-koodia tuotetaan useimmiten Microsoftin Visual InterDev kehittimellä, mutta periaatteessa pelkkä tekstieditorikin riittää. HTML-koodin tuottamiseen ei ole mitään erityistä editoria, vaan se on kirjoitettava joko puhtaasti käsin tai tehtävä jonkinlainen pohja esim. Microsoft Frontpagella. ASP-koodin lisäämisen jälkeen sivun HTML-osaan ei uskalla enää koskea millään editorilla. Ainakin Frontpage sotkee erittäin varmasti HTML:n sekaan upotetun ASP-koodin.

Kuva 3. ASP-tiedosto muuttuu HTML:ksi

Selain pyytää palvelimelta tiedostoa. Jos kyseessä on ASP-tiedosto, palvelin suorittaa tiedoston sisältämän VBScript tai Jscript-koodin ja lähettää muodostuneen HTML-koodin selaimelle (Kuva 3). ASP-koodi on siis täysin selainriippumatonta. Halutessa voidaan koodissa ottaa huomioon eri selaimet ja tuottaa HTML:ää sen mukaan, minkä tyyppinen selain on sivua palvelimelta pyytänyt.

2.1.1 OLE DB

OLE DB on Microsoftin tekniikka joka on rakennettu tekemään se mihin Open Database Connectivity (ODBC) ei ole pystynyt. ODBC tarjoaa yhteyden relaatiotietokantoihin mutta se asettaa sille pieniä rajoituksia. ODBC ei toimi yhteydettömissä ympäristöissä, kuten WWW, eikä ODBC:n ylitse pysty käsittelemään kuin SQL:ää tukevia relaatiotietokantoja. OLE DB:n pitäisi korjata nämä puutteet. OLE DB:n on tarkoitus mahdollistaa tiedon hakeminen ja käsittely mistä tahansa tietolähteestä ilman, että tietoa pitäisi ensin siirtää SQL-tietokantaan ("universaalitietokanta") (Kuva 4). ([Microsoft 1, 1997] [Microsoft 2, 1996]).

Kuva 4. OLE DB:n suomia mahdollisuuksia

2.1.2 ActiveX Data Objects (ADO)

ASP:n vahvuudet tulevat esille erityisesti tietokantojen käsittelyssä. ASP käyttää tietokantayhteyksissä hyväkseen Microsoftin ActiveX Data Objects (ADO) -mallia. ADO mahdollistaa tietokantayhteyden luomisen OLE DB:n kautta mihin tahansa tietokantaan, jolle on olemassa Open Database Connectivity (ODBC) -ajuri.

Kuva 5. ADO:n sisältämät luokat

ActiveX Data Objects (ADO) -malli määrittelee kolme yleiskäyttöistä luokkaa: Connection, Commandja Recordset, joista ohjelmoija voi luoda esiintymiä ja käyttää tiedon käsittelyyn . Näiden lisäksi ovat luokatField, Parameterja Property, joista ei voi suoraan luoda esiintymiä, mutta joita käytetään ensiksi mainittujen luokkien sisällä. Lisäksi on vielä erillinen Error-luokka. (Kuva 5). ([Microsoft 3, 1997]).

Connection-luokkaa käytetään ohjelman ja tietokannan välisen yhteyden muodostamiseen. Command-ja Recordset-luokat käyttävät Connection-luokkaa toiminnassaan.

Command-luokkaa käytetään tietokantakyselyn tekemisessä. Yleensä kyselyt tehdään käyttäen Standard Query Languagea (SQL), mutta ADO mahdollistaa minkä tahansa kyselykielen käyttämisen, kunhan kohteena oleva tietokanta ymmärtää, mistä on kyse. Tehdyn kyselyn tulokset talletetaan Recordset-olioon.

<%
Set Command = Server.CreateObject("ADODB.Command")

Command.ActiveConnection = ConnectionString

Command.CommandText = "DELETE FROM uusittu2"

RecordsAffected = 0

Command.Execute(RecordsAffected)

%>
Recordset-luokka on hyödyllisin kaikista ADO:n sisältämistä luokista. Recordsetsisältää tietokantahaun tuloksena tulleet tietueet. Recordset-oliolla voidaan käsitellä jokaista tietuetta yksitellen. Esimerkiksi haetaan tietokannasta lista opiskelijoista ja heidän tenttituloksistaan. Käydään Recordsetläpi tietue kerrallaan ja tulostetaan ne halutussa muodossa HTML-sivuksi. Recordsetin kautta voidaan myös päivittää tietoja suoraan tietokantaan.

Connection, Commandja Recordsettoimivat kaikki kolme kiinteässä yhteistyössä. Connectionvoidaan luoda yksinäänkin. Pelkkä Connection-olio on hyödytön, jollei käytössä ole Command-oliota komentojen suorittamiseen. Recordsettaas vaatii toimiakseen Command-olion, jotta saadaan tehtyä kysely, jonka tuloksena saatavat tietueet talletetaan Recordsetiin. Kaikkia kolmea oliota ei kuitenkaan tarvitse aina erikseen luoda, jotta pystyttäisiin hyödyntämään Recordsetia. Sekä Recordsetettä Commandluokat sisältävät tarvittavat ominaisuudet, joiden avulla ne pystyvät luomaan lennosta tarvittavan Connectioninja/tai Commandin.

Esimerkissä luodaan uusi Recordset-olio, joka luo automaattisesti tarvitsemansa Connection- ja Command-luokkien esiintymät.

<%
Set Rs = Server.CreateObject("ADODB.Recordset")
Rs.Open "SELECT * FROM uusittu2 ORDER BY sukunimi", _
ConnectionString, 0, 1
%>

2.1.3 Remote Data Service (RDS)

Perinteinen tietokantapohjainen WWW-sivu on sekä dynaaminen että staattinen. Sivu luodaan palvelimella dynaamisesti tietokannasta saatavan tiedon perusteella, mutta selaimen esittämä sivu on kuitenkin mitä suurimmassa määrin staattinen. Sivulla näkyviä tietoja ei voi mitenkään helposti päästä muokkaamaan, koska WWW-palvelimelle ja sitä kautta tietokantapalvelimelle ei ole jatkuvaa yhteyttä, joka mahdollistaisi tiedon suoran muuttamisen tietokantaan.

Remote Data Service (RDS) on Microsoftin kehittämä tekniikka, joka mahdollistaa ODBC:n kautta tietokantayhteyden suoraan WWW-selaimesta. RDS toimii suoraan HTTP, HTTPS (HTTP over Secure Sockets layer) ja DCOM-protokollien ylitse. RDS käyttää datasidonnaisia ActiveX-komponentteja, joten myös selaimen on tuettava ActiveX:ää. RDS on aikaisemmalta nimeltään Microsoft Advanced Data Connector (ADC). ([Microsoft 4, 1997]).

RDS:n datasidonnaiset ActiveX-komponentit voidaan suoraan upottaa HTML-sivuun, josta ne sitten käsittelevät ODBC:n kautta saamaansa tietovirtaa. Näitä komponentteja käyttäen voi selaimen käyttäjä tarvittaessa suoraan muokata tietokannan sisältöä.

2.2 IntraBuilder Server

IntraBuilder on Borlandin vastaisku Microsoftin tekniikalle. IntraBuilder jakaantuu kahteen osaan: IntraBuilder Designeriin, joka on hyvin samankaltainen Rapid Application Development (RAD) ympäristö mikä löytyy monesta muustakin Borlandin tuotteesta, kuten Delphi ja C++-Builder, ja IntraBuilder Serveriin. Designerilla luodaan data-pohjaisia lomakkeita ja raportteja World Wide Webissä julkaistavaksi. IntraBuilder Server taasen toimii yhteistyössä jo olemassa olevien WWW-palvelimien kanssa ja tuottaa niille tarvittavan HTML-koodin. ([Borland 2, 1996]).

IntraBuilderin ja Microsoftin Active Server Pages -teknologian väliltä löytyy suuri eroavaisuus heti sivujen luomisvaiheesta. ASP-sivut ovat pohjimmiltaan HTML-koodia, jonka väliin on upotettu VBScriptiä tuottamaan lennosta halutut muutokset valmiiseen koodipohjaan. IntraBuilder taas käyttää hyväkseen Borlandin laajennettua versiota Javascriptistä. Borland on lisännyt Javascriptiin luokkamäärittelyt, perinnän ja poikkeusten käsittelyn tuoden näin Javascriptin huomattavasti lähemmäksi varsinaista Java-kieltä. IntraBuilder Designerilla luodaan lomake aivan samaan tyyliin kuin Delphissä rakennettaisiin omaa dialogia. Tuloksena muodostuu Javascript-koodia, jonka pohjalta IntraBuilder Server luo lennosta HTML-koodia aina, kun sivua pyydetään WWW-palvelimelta. IntraBuilderin käyttäjän ei siis tarvitse osata ollenkaan HTML:ää pystyäkseen tekemään tietokantapohjaisia WWW-sivuja.

IntraBuilderilla luodut sivut voivat sisältää myös selaimessa suoritettavaa Javascript-koodia, Java appletteja, ActiveX kontrolleja ja kuvatiedostoja.

IntraBuilder Server toimii yhteistyössä varsinaisen WWW-palvelimen kanssa. IntraBuilderin mukana tulee Borlandin oma Borland Web Server mutta IntraBuilder Server toimii kaikkien niiden WWW-palvelimien kanssa, jotka tukevat NSAPIa, ISAPIa tai Win-CGI:tä.

IntraBuilder Server jakautuu kolmeen osaan: IntraBuilder Brokeriin, IntraBuilder Agentteihin ja Borland Database Engineen (BDE). (Kuva 6)

Kuva 6. IntraBuilder Serverin rakenne

2.2.1 IntraBuilder Broker

IntraBuilder Broker hoitaa kommunikoinnin WWW-palvelimen ja IntraBuilder Agenttien kanssa. IntraBuilder Broker tukee tunnetuimpia WWW-palvelimien Application Programming Interfaceja (API), esim. NSAPIa (Netscape Server API), ISAPIa (Internet Server API) ja Win-CGI:itä (Windows Common Gateway Interface). ([Borland 2, 1996]).

IntraBuilder Broker osaa reitittää WWW-palvelimen pyynnöt älykkäästi aina juuri sille IntraBuilder Agentille, joka parhaiten pystyy pyynnön toteuttamaan. IntraBuilder Agentteja voi olla useammalla kuin yhdellä koneella, jolloin saavutetaan maksimaalinen suorituskyky.

IntraBuilder Brokereita on käynnissä aina vain yksi kerrallaan.

2.2.2. IntraBuilder Agentit

IntraBuilder Agentit tulkitsevat lomakkeet ja raportit sekä generoivat niiden pohjalta dynaamista HTML:ää. Agentit hoitavat myös eri käyttäjien istuntoihin liittyvät järjestelyt. Tavanomaisilla staattisilla WWW-sivuilla ei ole tarvetta tietää, kuka vaati mitäkin tietoa, mutta tietokantapohjaisessa toteutuksessa tämä on tiedettävä. IntraBuilder Agentit lisäävät jokaiseen luomaansa WWW-sivuun yksikäsitteisen ID-numeron, jonka perusteella pystytään lähettämään jokaiselle käyttäjälle oikea tulos. Esimerkiksi kaksi ihmistä katsoo samaa dynaamista WWW-sivua, jolla selaillaan tehtyjä tilauksia. Kummankin käyttäjän istunto on identifioitava jotta tiedetään tietue, joka on kullakin seuraavaksi vuorossa. Tämä säästää huomattavasti vaivaa sovelluksen ohjelmoijalta. WWW-palvelin ei näe millään tavalla, milloin käyttäjä lopettaa sovelluksen käyttämisen. Tälläistä tilannetta varten on IntraBuilder Agentteihin sisällytetty aikalaskuri, jonka määräämän ajanjakson kuluttua istunto lopetetaan ja siihen kuluneet resurssit palautetaan muuhun käyttöön. ([Borland 2, 1996]).

2.2.3 Borland Database Engine (BDE)

Borland Database Engine (BDE) pitää huolen tietokantayhteyksistä. BDE on käytössä myös monen muun Borlandin tuotteen yhteydessä (Delphi, Paradox jne). BDE pystyy käyttämään mitä tahansa ODBC-lähdettä. Tunnetuimpiin tietokantoihin BDE:stä löytyvät omat ajurit, jotka toimivat tehokkaammin kuin vastaavat ODBC-ajurit. ([Borland 2, 1996]).

2.3 Java Database Connectivity (JDBC) API

Java Database Connectivity (JDBC) on Javasoftin määrittelemä SQL-tietokantojen liittymärajapinta. JDBC on Javan vakio-osa ja on mukana JDK 1.1:ssä. Myös JDK 1.0.2:een voidaan lisätä JDBC-tuki. JDBC:n perusta on sama kuin ODBC:nkin, mutta JDBC on toteutettu puhtaasti Javalla, eikä C:llä niin kuin ODBC. Java-ohjelmat voivat hyödyntää jo olemassa olevia ODBC-ajureita käyttämällä Intersolvin julkaisemaa JDBC-ODBC-siltaa. Java-sovellukset voidaan ohjelmoida käyttäen JDBC-rajapintaa jolloin JDBC APIn kutsut ajetaan JDBC-ODBC-sillan läpi, joka muokkaa ne ODBC- kutsuiksi ja laittaa ne eteenpäin ODBC-ajurin käsiteltäväksi. (Kuva 7). ([Javasoft , 1997], [Hamann , 1997])

Kuva 7. JDBC-ODBC silta

JDBC sallii minkä tahansa kyselyn suorittamisen tietokannalle. Kyselyn ei edes tarvitse olla SQL:ää, kunhan vastaanottava tietokanta ymmärtää kyselyn. Saadakseen Javasoftilta JDBC-yhteensopivuusmerkinnän täytyy ajurin tukea vähintään ANSI SQL92 -standardia.

Javasoft on päättänyt käyttää Uniform Resource Locator (URL) -standardia tietokantojen nimeämiseen JDBC:ssä. Käytetty syntaksi on seuraavanlainen: jdbc:<protokolla>:<nimi>.

Kyselyjen tekeminen tapahtuu java.sql.Statement-luokan avulla. Ensimmäiseksi luodaan yhteys, määritellään kysely ja suoritetaan kysely. Kyselyn tulokset saadaan Resultset-tyyppiseen olioon, jolta voidaan pyytää haluttujen kenttien sisältöä.

Esimerkissä suoritetaan kysely, joka hakee kolme saraketta Taulu-nimisestä taulusta. Saatujen sarakkeiden arvot sijoitetaan vastaavan tyyppisiin Java-kielen muuttujiin ja tulostetaan ruudulle sopivasti järjesteltyinä.

java.sql.Statement stmt = conn.createStatement();

ResultSet r = stmt.executeQuery("SELECT a,b,c FROM Taulu");

while (r.next()) {
int i = r.getInt("a");
String s = r.getString("b");
byte b[] = r.getBytes("c");

System.out.println("ROW = "+i+" "+s+" "+b[0]);

}

2.3.1 Yksitasoiset järjestelmät (One Tier Systems)

Tietokantayhteyttä asiakkaan ja palvelimen välillä JDBC:n kautta sanotaan yksitasoiseksi (One Tier), jos käytetty JDBC-ajuri on kirjoitettu kokonaan Javalla. Selain, Java-appletti, JDBC-ajurimanageri ja varsinainen ajuri ovat kaikki yhdellä koneella. WWW-palvelin ja tietokantapalvelin ovat molemmat samassa koneessa. Javan tietoturvaominaisuudet rajoittavat applettien mahdollisuuden verkkoyhteyden luomiseen vain siihen samaan koneeseen, josta se itse siirrettiin. ([Hamann , 1997]).

Kuva 8. Yksitasoinen järjestelmä

2.3.2 Kolmitasoiset järjestelmät (Three Tier Systems)

Aina käytetty JDBC-ajuri ei ole täysin kirjoitettu Javalla, vaan se tarvitsee avukseen jonkin tiettyyn käyttöjärjestelmään sidotun kirjaston. Tämä kirjasto sijoitetaan samalle koneelle WWW-palvelimen kanssa, jotta JDBC ajuri saa siihen yhteyden. Kirjastoa ei enää ole sidottu Java-applettien turvamääräyksillä vaan se voi edelleen ottaa yhteyden mihin tahansa tietokantapalvelimeen tai tietokantapalvelimiin riippumatta siitä, missäpäin verkkoa ne sijaitsevat. Kirjasto keskustelee tietokantapalvelimen kanssa ja välittää tiedot edelleen asiakkaan koneessa sijaitsevalle JDBC-ajurille. (Kuva 9). ([Hamann , 1997]).

Kuva 9. Kolmitasoinen järjestelmä

2.4 Tietokantapalvelimia

Suurimmat tietokantapalvelimien valmistajat ovat jo pidemmän aikaa lisäilleet palvelimiinsa ominaisuuksia, jotka mahdollistavat niiden hyödyntämisen paremmin World Wide Webissä. Ajan- ja materiaalin puutteen takia näistä otetaan esille tässä esitelmässä vain joitakin mielenkiintoisia yksityiskohtia.

2.4.1 Sybase Adaptive Server Enterprise

Sybasen uusin tietokantapalvelin Adaptive Server tuo mukanaan myös PowerDynamo- nimisen WWW-palvelimen. PowerDynamo tuo mukanaan tarpeelliset työkalut dynaamisten tietokantapohjaisten WWW-sivujen luomiseksi. Mielenkiintoiseksi WWW-sivujen kehittäjän kannalta Adaptive Serverin tekee varsinainen tietokantapalvelin. Sybase kutsuu uusinta tietokantaansa olio-relaatiotietokannaksi. Adaptive Server tukee suoraan Java-kielen olioiden sijoittamisen tietokantaan. Seuraavaksi esitetään muutama esimerkki. ([Sybase 1, 1997], [Sybase 2, 1997])

Lisätään tyontekijat tauluun uusi työntekija, jonka osoitteena on Java-luokan Osoite esiintymä.

INSERT INTO tyontekijat(ID, nimi, Osoite)
VALUES (1235, 'Kalle Kehveli',
new Osoite ('piilokuja 13', '11111 mörskäkylä')

Lisätään tyontekijat-tauluun uusi työntekijä, jonka osoitteena on Java-luokasta Osoite perityn SuomOsoite-luokan esiintymä.

INSERT INTO tyontekijat (ID, nimi, osoite)
VALUES (1235, 'Ville Kehveli',
new SuomOsoite('Piilokuja 14', '11111 Mörskäkylä')

Etsitään työntekijät, joiden katuosoite on Piilokuja 14.

SELECT name
FROM tyontekijat
WHERE Osoite.katu = 'Piilokuja 14'

2.4.2 JBMS

JBMS on Javalla ohjelmoitu relaatiotietokantapalvelin. JBMS on erittäin pieni ja kevyt, sillä se on pienimmillään alle 1 megatavun kokoinen. JBMS:ään voi helposti integroida minkä tahansa Java-luokkakirjaston. Kyselykielenä JBMS käyttää standardinmukaista SQL:ää ja tietokantayhteyksissä protokollana on luonnollisesti JDBC. Tietokannan sisäinen kieli on Java. ([Murphy, 1997]).

3. Huomioita tietokantapohjaisen WWW-liittymän toteuttamisesta

Tietokantapohjaisen WWW-sivun luominen tuo mukanaan heti aivan uusia huomioonotettavia asioita. Enää kaikki ei olekaan niin helppoa ja yksinkertaista kuin staattisten WWW-sivujen aikana.

3.1 Käyttäjien erottaminen

Tietokannan käyttäminen WWW-sivujen pohjana tuo heti WWW-palvelimelle lisää vastuuta. Perinteisten sivujen kohdalla palvelimen ei tarvitse kuin vastaanottaa pyyntöjä halutuista sivuista ja lähettää niitä eteenpäin. Tietokantapohjaisissa dynaamisissa sivuissa tämä ei enää riitä. Palvelimen täytyy koko ajan olla selvillä siitä, missä "kohtaa" tietokantaa kukakin palvelun käyttäjä on. Perinteinen palvelin ei millään tavalla erottele sitä, käykö sama käyttäjä 100 kertaa minuutissa noutamassa samaa sivua vai onko kyseessä 100 eri käyttäjää minuutin aikana. ([Borland 3, 1996]).

Tietokantasovelluksessa on jotenkin pystyttävä tallentamaan jokaiselle käyttäjälle oma ns. istunto. Tämä tapahtuu yleisimmin luomalla jokaiselle käyttäjälle oma yksikäsitteinen ID-numero, joka lähetetään asiakaskoneelle HTML-sivun mukana. Seuraavan kerran, kun sama käyttäjä ottaa yhteyden palvelimeen, palvelin tarkistaa, löytyykö käyttäjän koneelta ID-numeroa. Jos se löytyy, niin palvelin määrittää jatkotoimensa sen mukaan. Yleensä ID-numerot talletetaan asiakaskoneelle cookiena. ID-numeron avulla pystytään luomaan esimerkiksi sovellus, jolla selaillaan tietokannasta saatavia tuotteita. Palvelin pitää muistissaan sitä, kuinka monennessa tuotteessa kukakin käyttäjä on menossa. Sekä Borlandin IntraBuilder Server että Microsoftin Active Server Pages -tekniikat osaavat erotella käyttäjät ID-numeroilla.

3.2 Tietueiden lukitus

Tehtiinpä tietokantapohjaista sovellusta mihin ympäristöön tahansa, niin aina vastaan tulee sama ongelma. Kuinka käsitellä tilanne, jossa kaksi tai useampi käyttäjä yrittää muuttaa samaa tietoa? Tälläisten tilanteiden varalle tietokannoista löytyy erilaisia lukitusominaisuuksia. Eri tietokantapalvelimet tukevat erilaisia lukitustapoja. Toisissa on mahdollista lukita yksittäinen tietue, kun taas toisissa lukitus täytyy tapahtua kokonainen taulu kerrallaan. ([Borland 3, 1996], [Microsoft 5, 1997]).

3.2.1 Pessimistinen lukitus

Lukitusta vaativaan tilanteeseen voidaan suhtautua joko pessimistisesti tai optimistisesti. Pessimistiseksi lukitukseksi kutsutaan tilannetta, jossa oletetaan ilman muuta kahden tai useamman yrittävän muokata samaa tietuetta yhtäaikaa. Käytännössä tämä tapahtuu siten, että yritettäessä asettaa haluttu tietue muokkaustilaan tarkistetaan, onko kukaan muu jo muokkaamassa kyseistä tietuetta. Jos tietue on jo muokkauksen alla, ei päästetä ketään muuta tekemään muutoksia ennen kuin ensimmäinen muokkaaja on tietueen vapauttanut. Lukittua tietuetta voi kyllä katsoa, mutta ei muuttaa.

3.2.2 Optimistinen lukitus

Optimistinen lukitus on yleisemmin käytössä kuin pessimistinen. Optimistisessa lukituksessa pidetään epätodennäköisenä tilannetta, että useampi sattuisi muokkaamaan samaa tietuetta samanaikaisesti. Tietokantojen tietueiden lukumäärä on yleensä suuri, jolloin todennäköisyys tietueen joutumisesta useamman muokattavaksi yhtäaikaa on pieni. Optimistisen lukituksen oletus vastaakin oikeaa käyttötilannetta paremmin kuin pessimistisen lukituksen oletus. Asiakkaan annetaan aivan rauhassa muokata sille annettua kopiota halutusta tietueesta. Mitään tarkistuksia ei tehdä ennen kuin asiakas yrittää päivittää tekemiään muutoksia tietokantaan. Tällöin tarkistetaan, onko tietueeseen tullut muutoksia sillä välin, kun asiakas on sitä muokannut. Jos muutoksia on ollut, niin asiakkaan haluama päivitys epäonnistuu ja siitä ilmoitetaan asiakkaalle. Asiakas voi nyt ottaa uuden jonkun muun muokkaaman tietueen nähtäväkseen ja päättää, haluaako yrittää muutoksiaan uudelleen. Näin jatketaan, kunnes asiakas onnistuu tekemään haluamansa muutokset tai luovuttaa. Erityisen suurissa tietokannoissa optimistinen lukitus toimii erittäin hyvin, koska todennäköisyys sille, että useampi yrittää muokata samaa yhtäaikaa, on hyvin pieni. Mitä enemmän tietueita, sitä todennäköisimmin optimistinen lukitus riittää, Mitä vähemmän, sitä todennäköisimmin tarvitaan pessimististä lukitusta.

Borland IntraBuilder Server tukee automaattisesti optimistista tai pessimististä lukitusta sen mukaan kumpaa tapaa käytössä oleva tietokantapalvelin tukee. Microsoftin ASP mahdollistaa myös lukituksien käytön, muttei niin tehokkaasti, eikä automaattisesti kuin IntraBuilder.

3.3. Transaktiot

Transaktiot ovat erittäin tärkeä osa asiakas/palvelin-pohjaisissa tietokannoissa. Transaktio on useasta osatoimituksesta muodostuva kokonaisuus. Jokainen osanen pitää onnistua, jotta koko transaktio toteutuisi. Jos jokin osa pettää voidaan vielä peruuttaa kaikki siihen mennessä toteutetut osatapahtumat. ASP:ssä ja IntraBuilderissa on kummassakin mahdollista käyttää transaktioita. ([Borland 3, 1996]).

IntraBuilderissa on lisäksi transaktioon verrattavissa oleva lisäominaisuus. IntraBuilder voidaan määrätä tallettamaan kaikki tapahtumat lokaaliin välimuistiin, kunnes kaikki vaiheet on käyty läpi. Tämän jälkeen määrätään sitten koko tapahtumasarja suoritettavaksi kerralla tietokantapalvelimelle. Perinteisessä transaktiossa jokainen vaihe toteutetaan suoraan tietokantaan josta ne kuitenkin ovat vielä peruttavissa. IntraBuilderin käyttämässä välimuistitalletuksessa säästetään tietokantapalvelinta mahdollisesti turhalta rasitukselta. ASP:lla voidaan myös rakentaa vastaavantyyppinen toiminto kuin IntraBuilderissa.

3.4 Tietoturva

Tieto on valtaa. Siksipä tieto pitää osata tarvittaessa pitää poissa epätoivotuista käsistä. World Wide Webin tietoturva on ollut parin viime vuoden aikana jatkuvasti otsikoissa, eikä dynaamisten WWW-sivujen ilmaantuminen markkinoille ole yhtään vähentänyt otsikointia. Päinvastoin, suojattavaahan on nyt entistä enemmän, kun yrityksien yksityiset tietokannat siirretään WWW-aikaan.([Borland 3, 1996]).

3.4.1 WWW-palvelimien turvallisuus

ASP:llä ja IntraBuilderilla toteutetut sovellukset pyörivät suurimmaksi osaksi WWW-palvelimen taustalla, joten niiden tietoturvaan vaikuttaa suoraan käytetyn WWW-palvelimen turvallisuus. Tietovirta WWW-palvelimen ja selaimen välillä voidaan tarvittaessa salata esim Secure Socket Layer (SSL) -tekniikalla.

3.4.2 Tietokantapalvelimien turvallisuus

Käytännössä kaikista tietokantapalvelimista löytyy sisäänrakennettuna valmiit käyttäjäoikeusmäärittelyt. Tietokantaan pääsemiseksi voidaan vaatia jokin määrätty käyttäjätunnus ja salasana. Eri tunnuksille voi olla määritelty eritasoisia oikeuksia, jolloin toiset pääsevät vain lukemaan tietoja ja jotkut taas päivittämään. Käyttäjätunnus- ja salasanatiedot voivat olla suoraan koodattuna palvelinpäähän tai ne voidaan kysyä jokaiselta käyttäjältä vaikkapa WWW-sivukohtaisesti.

3.4.3 Sovelluskohtainen turvallisuus

Tietokantapohjaisiin sovelluksiin on tietenkin vielä helppo lisätä tarpeen mukaan eritasoisia käyttäjiä, salasanoja ja oikeustasoja luomalla oma tietokanta näitä varten.

3.5 Ei kaikkea dynaamiseksi

Kaikkien näiden dynaamisesti luotujen tietokantapohjaisten WWW-sivujen keskellä kannattaa pitää pää kylmänä. Yhdessäkään artikkelissa ei suoraan neuvottu milloin oikeastaan kannattaa tehdä dynaamisesti luotava WWW-sivu ja milloin ei. Kaikki vaan tuntuu olevan itsestään selvästi aina dynaamista. Dynaaminen sivun luominen on kuitenkin hyvin paljon resursseja kuluttavaa puuhaa ja tehokaskin palvelin on nopeasti polvillaan. Ajatellaan tilannetta, jossa WWW-sivu sisältää listauksen opiskelijoiden hyväksytyistä harjoitustöistä. Lista luodaan tietokannasta löytyvien tietojen perusteella aina, kun joku selain pyytää sivua. Tietoja ei kuitenkaan päivitetä kuin ehkä kerran tai pari päivässä, mutta sitä saatetaan selailla kymmeniä kertoja päivässä. Sama sivu siis luodaan monta kertaa turhaan, koska luontikertojen välillä siihen ei ole tullut muutoksia. Tällöin kannattaisikin siirtää sivun luominen tapahtumaan uuden harjoitustyön syöttämisen yhteyteen. Miten tämä sitten toteutetaan, riippuu taas siitä, kuinka uuden syöttäminen tapahtuu. Tällä muutoksella säästettäisiin kuitenkin huomattavasti koneresursseja, koska nyt sivu käyttäytyisi useimmiten kuin mikä tahansa tavallinen pelkkää tekstiä sisältävä WWW-sivu, eikä aiheuttaisi turhaa dynaamista sivun luomista. Tämä kannattaa muistaa, kun seuraavan kerran selailee esimerkiksi Microsoftin kotisivuja ja ihmettelee miksi sivut ovat niin hitaat.

4. Yhteenveto

Alunperin niin siisti ja yksinkertainen World Wide Web on muutamassa vuodessa paisunut pelkästä jokapojan kotisivuhiekkalaatikosta todelliseksi sovellusten työmaaksi. Enää ei riitä, että osaa ulkoa muutaman yleisimmän HTML-muotoilukomennon, jotta pääsee rakentamaan suuryrityksien Intranet/Extranet/Internet-kokonaisuuksia. WWW:hen on alkanut ilmestyä aivan oikeita sovelluksia, jotka vaativat ammattitaitoisia tekijöitä. Työkalut ovat myös onneksi kehittyneet huimasti. Enää ei tarvitse kaikkea vääntää hiki hatussa CGI-ohjelmointina vaan valmiin sovelluksen voi saada tehtyä parilla hiiren näpäytyksellä. Laadukkaan ja käyttökelpoisen sovelluksen tekeminen vaatii kuitenkin vielä hieman enemmän.

Kaikki tässä esitelmässä läpikäydyt tekniikat ovat käyttökelpoisia kuitenkin hieman käyttötarkoituksesta riippuen. Jos välttämättä on toteutettava kiinteäyhteyksinen tietokantaliittymä, niin vaihtoehdoiksi ei jää kuin Microsoftin ASP ja RDS tai Java ja JDBC. Kummankin ongelmana on tietynlaiset vaatimukset käytettävälle selaimelle. Microsoftin tekniikka ei tällä hetkellä toimi kuin Microsoftin omalla selaimella. Java taas vaatii selaimelta Java-tuen, joka ei tunnu Microsoftin selaimelle olevan kovin tärkeä ominaisuus. Kummassakin tapauksessa ulkopuolelle jäävät tekstipohjaisten selainten käyttäjät. Tietyn yrityksen sisäisessä intranetissä, jossa kaikilta voidaan olettaa löytyvän tietty selain voi esim. ASP ja RDS olla hyvinkin onnistunut valinta. Molemmat vaihtoehdot vaativat myös, että verkkoyhteyden on oltava suhteellisen stabiili. Valittiin kumpi tahansa tekniikka, niin tietokantasovelluksen rakentaminen ei ole kuitenkaan aivan helppoa. Java-toteutus vaatii selkeästi eniten ohjelmointitaitoa, eikä ASP ja RDS versiokaan aivan tumpelolta onnistu. Active Server Pages ratkaisun ulkonäön muokkaamiseen ei edes ole vielä kunnollista työkalua.

Jos unohdetaan kiinteä yhteys tietokantaan, niin Borlandin IntraBuilder nostaa heti selkeästi osakkeitaan. IntraBuilderin tapa on kaikista esitellyistä tekniikoista kätevin. IntraBuilder on helpoin käyttää ja se on alusta alkaen suunniteltu juuri tietokantojen käyttämiseen. Vahvuuksiin voidaan lukea myös tuki useammalle WWW-palvelimelle kuin Microsoftin ASP:llä. IntraBuilderilla rakennetut sovellukset toimivat haluttaessa kaikilla selaimilla. Tällä hetkellä valitsisin työkalukseni Borlandin IntraBuilderin.

5. Lähteet

[PC Magazine, 1997] Lam John, "Database Connectivity and the Internet", <URL:http://www.zdnet.com/pcmag/pctech/content/16/22/it1622.001.html>, 1997

[Microsoft 1, 1997] Microsoft, "OLE DB Overview", <URL: http://www.microsoft.com/data/oledb/prodinfo.htm>, 1997

[Microsoft 2, 1996] Cameron David ja Pizzo Michael, "OLE DB, Data Access for the masses", <URL:http://www.microsoft.com/data/oledb/resource/presentations/PDCNov96a/sld001.htm>, 1996

[Microsoft 3, 1997] Microsoft, "ADO Product Information", <URL: http://www.microsoft.com/data/ado/prodinfo.htm>, 1997

[Microsoft 4, 1997] Microsoft, "RDS Product Information",<URL:http://www.microsoft.com/data/rds/prodinfo.htm>, 1997

[Microsoft 5, 1997] Microsoft, "Microsoft Visual InterDev Documentation", 1997

[Borland 1, 1996] Borland, "Enabling Data-Driven Intranets", <URL:http://www.borland.com/intrabuilder/papers/enablewp/>, 1997

[Borland 2, 1996] Krull Klaus, "IntraBuilder Architecture Overview", <URL: http://www.borland.com/intrabuilder/papers/intraarch/>, 1996

[Borland 3, 1996] Kaufman Dana S., "IntraBuilder Client / Server Development", <URL: http://www.borland.com/intrabuilder/papers/csdevwp/>, 1997

[Murphy, 1997] Murphy Kieron, "Cloudscape readies light, Java-powered database manager", <URL:http://www.developer.com/news/tmp-112597_jbms.html>, 1997

[Javasoft , 1997] Javasoft, "THE JDBC DATABASE ACCESS API", <URL: http://java.sun.com/products/jdbc/>, 1997

[Hamann, 1997] Hamann Jerrid, "Analysis of Java Database Connectivity and its Application in a Multi-Platform, Multi-DBMS Environment", <URL: http://www.cs.tamu.edu/people/jhamann/jdbc/report/>, 1996

[Sybase 1, 1997] Sybase, "Sybase Adaptive Server 11.5 Datasheet ", <URL:http://www.sybase.com/adaptiveserver/whitepapers/datasheet.html>, 1997

[Sybase 2, 1997] Sybase, "Sybase Adaptive Server: Java in the Database", <URL: http://www.sybase.com/adaptiveserver/whitepapers/java_wps.html>, 1997

5.1 Lisämateriaalia

Microsoft, "Universal Data Access", <URL: http://www.microsoft.com/data/>, 1997

Microsoft, "Visual InterDev Start Page", <URL:http://www.microsoft.com/vinterdev/>, 1997

Microsoft, "SiteBuilder Network Server" , <URL:http://www.microsoft.com/workshop/server/default.asp>, 1997

LanTimes Online, "Borland International IntraBuilder 1.5", <URL: http://www.lantimes.com/lantimes/97/97nov/711a088b.html>, 1997

Borland, "Borland DataGateway for Java", <URL: http://www.borland.com/datagateway/papers/datagateway/>, 1997

Karjalainen Marko, Koivula Heidi ja Korva Jari, "OLE ja ActiveX tietokantaohjelmoinnissa", <URL:http://www.student.oulu.fi/~jpkorva/thj/thj.html>, 1997

Oracle, "Oracle Web Application Server 3.0", <URL: http://www.oracle.com/products/asd/was/collateral/was30_twp.html>, 1997

The Yankee Group, "What's So Hot About Domino?", <URL: http://www.lotus.com/core/content.nsf/61a518eb6fbc296d8525630f004e7ac0/3352ddd034a24b48852565150050468e?OpenDocument>, 1997

Mahon Andrew, "Lotus Domino and the Intranet Strategy", <URL: http://www.lotus.com/core/content.nsf/61a518eb6fbc296d8525630f004e7ac0/2758da4e06e845e3852564f7005c5789?OpenDocument>, 1997

Lotus, "Lotus Domino 4.5, Powered by Notes: Part I - Technical Overview", <URL: http://www.lotus.com/core/content.nsf/61a518eb6fbc296d8525630f004e7ac0/761b47361da8780d852564f20073b9e1?OpenDocument>, 1997

Javasoft, "JDBC - Answers to Frequently Asked Questions", <URL:http://java.sun.com/products/jdbc/jdbc-frequent.html>, 1997

Hazarika Deva, "Developing and Deploying Interactive Applications on the Internet", <URL:http://www.microsoft.com/workshop/prog/prog-gen/mspaper.htm>, 1996

Rauch Stephen, "Manage Data from Myriad Sources with the Universal Data Access Interface", <URL:http://www.microsoft.com/msj/0997/universaldata.htm>, 1997

6. Liitteet

6.1 ASP:llä toteutettu opiskelijoiden demoaktiivisuuslista

<%@ LANGUAGE="VBSCRIPT" %>

<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual InterDev 1.0">
<META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
<TITLE>Tietokone ja perusohjelmistot</TITLE>
</HEAD>
<BODY bgcolor="#FFFFFF" text="#333333" leftmargin="0" topmargin="0">
<h1><strong>TIETOKONE JA PERUSOHJELMISTOT</strong></h1>
<%
if (Request("htyot") <> "") then
call Listaahtyot
else
call listaa
end if

%>

</BODY>
</HTML>
<%
sub listaa
%>
0 tarkoittaa mitä tahansa demoryhmää<br>
1-8 ovat demoryhmien numerot<br>
tyhjä tarkoittaa puuttuvaa demosuoritusta<br>
<br>
<a href="lista.asp?htyot=kukkuu"><strong>Hyväksytyt harjoitustyöt</strong></a>
<br>
Jos listauksen tiedoissa on puutteita tai vikoja niin lähetä sähköpostia<a href="mailto:hazor@iki.fi">
hazor@iki.fi</a>
<%
ConnectionString = Session("opiskelijat_ConnectionString")

Set Rs = Server.CreateObject("ADODB.Recordset")

Rs.Open "SELECT * FROM uusittu2 ORDER BY sukunimi", _
ConnectionString, 0, 1

if (Rs.BOF AND Rs.EOF) then
Response.Write "Ei yhtään opiskelijaa<br>"
exit sub
end if
%>
<table border="1" cellpadding="2" cellspacing="0">
<tr>
<td>&nbsp;</td>
<td><strong>Sukunimi</strong></td>
<td><strong>Etunimi</strong></td>
<td><strong>E-mail</strong></td>
<td><strong>KO</strong></td>
<td><strong>Demo1</strong></td>
<td><strong>Demo2</strong></td>
<td><strong>Demo3</strong></td>
<td><strong>Demo4</strong></td>
<td><strong>Demo5</strong></td>
<td><strong>Demo6</strong></td>
<td><strong>Demo7</strong></td>
<td><strong>Demo8</strong></td>
</tr>
<%
counter = 1
Do While Rs.EOF = false
%>
<tr>
<td><%=counter%></td>
<td><%=Rs("sukunimi")%>&nbsp;</td>
<td><%=Rs("etunimi")%>&nbsp;</td>
<td><a href="mailto:<%=Rs("sähköpostiosoite")%>"><%=Rs("sähköpostiosoite")%>&nbsp;</a></td>
<td><%=Rs("koulutusohjelma")%>&nbsp;</td>
<td><%=Rs("demo1")%>&nbsp;</td>
<td><%=Rs("demo2")%>&nbsp;</td>
<td><%=Rs("demo3")%>&nbsp;</td>
<td><%=Rs("demo4")%>&nbsp;</td>
<td><%=Rs("demo5")%>&nbsp;</td>
<td><%=Rs("demo6")%>&nbsp;</td>
<td><%=Rs("demo7")%>&nbsp;</td>
<td><%=Rs("demo8")%>&nbsp;</td>
</tr>
<%
counter = counter + 1
Rs.MoveNext
Loop
%>
</table>

<%
end sub
'-------------------------------------
sub listaahtyot
%>
<strong>Hyväksytyt harjoitustyöt</strong>
<%
ConnectionString = Session("opiskelijat_ConnectionString")

Set Rs = Server.CreateObject("ADODB.Recordset")

Rs.Open "SELECT * FROM uusittu2 WHERE htyo_palautettu IS NOT NULL ORDER BY sukunimi", _
ConnectionString, 0, 1

if (Rs.BOF AND Rs.EOF) then
Response.Write "Ei yhtään opiskelijaa<br>"
exit sub
end if
%>
<table border="1" cellpadding="2" cellspacing="0">
<tr>
<td>&nbsp;</td>
<td><strong>Sukunimi</strong></td>
<td><strong>Etunimi</strong></td>
<td><strong>E-mail</strong></td>
<td><strong>KO</strong></td>
<td><strong>Harjoitustyön tyyppi</strong></td>
<td><strong>Nimi</strong></td>
<td><strong>Pvm</strong></td>
<td><strong>Bonus</strong></td>
</tr>
<%
counter = 1
Do While Rs.EOF = false
%>
<tr>
<td><%=counter%></td>
<td><%=Rs("sukunimi")%>&nbsp;</td>
<td><%=Rs("etunimi")%>&nbsp;</td>
<td><a href="mailto:<%=Rs("sähköpostiosoite")%>"><%=Rs("sähköpostiosoite")%>&nbsp;</a></td>
<td><%=Rs("koulutusohjelma")%>&nbsp;</td>
<td><%=Rs("htyontyyppi")%>&nbsp;</td>
<td><%=Rs("nimi")%>&nbsp;</td>
<td><%=Rs("htyo_palautettu")%>&nbsp;</td>
<td><%=Rs("extrabonus")%>&nbsp;</td>
</tr>
<%
counter = counter + 1
Rs.MoveNext
Loop
%>
</table>

<%
end sub
%>
6.2 ASP-koodin tuottama HTML-koodi

<HTML>
<HEAD>
<META NAME="GENERATOR" Content="Microsoft Visual InterDev 1.0">
<META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
<TITLE>Tietokone ja perusohjelmistot</TITLE>
</HEAD>
<BODY bgcolor="#FFFFFF" text="#333333" leftmargin="0" topmargin="0">
<h1><strong>TIETOKONE JA PERUSOHJELMISTOT</strong></h1>

0 tarkoittaa mitä tahansa demoryhmää<br>
1-8 ovat demoryhmien numerot<br>
tyhjä tarkoittaa puuttuvaa demosuoritusta<br>
<br>
<a href="lista.asp?htyot=kukkuu"><strong>Hyväksytyt harjoitustyöt</strong></a>
<br>
Jos listauksen tiedoissa on puutteita tai vikoja niin lähetä sähköpostia<a href="mailto:hazor@iki.fi">
tlahtone@cc.hut.fi</a>

<table border="1" cellpadding="2" cellspacing="0">
<tr>
<td>&nbsp;</td>
<td><strong>Sukunimi</strong></td>
<td><strong>Etunimi</strong></td>
<td><strong>E-mail</strong></td>
<td><strong>KO</strong></td>
<td><strong>Demo1</strong></td>
<td><strong>Demo2</strong></td>
<td><strong>Demo3</strong></td>
<td><strong>Demo4</strong></td>
<td><strong>Demo5</strong></td>
<td><strong>Demo6</strong></td>
<td><strong>Demo7</strong></td>
<td><strong>Demo8</strong></td>
</tr>

<tr>
<td>1</td>
<td>Aittola&nbsp;</td>
<td>Jussi&nbsp;</td>
<td><a href="mailto:">&nbsp;</a></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>2&nbsp;</td>
<td>2&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>

<tr>
<td>2</td>
<td>Arhosalo&nbsp;</td>
<td>Touko&nbsp;</td>
<td><a href="mailto:tparhosa@itu">tparhosa@itu&nbsp;</a></td>
<td>Fys&nbsp;</td>
<td>0&nbsp;</td>
<td>7&nbsp;</td>
<td>7&nbsp;</td>
<td>&nbsp;</td>
<td>7&nbsp;</td>
<td>7&nbsp;</td>
<td>3&nbsp;</td>
<td>7&nbsp;</td>
</tr>

<tr>
<td>3</td>
<td>Forsblom&nbsp;</td>
<td>Hannu&nbsp;</td>
<td><a href="mailto:hmforsbl@itu">hmforsbl@itu&nbsp;</a></td>
<td>Kemko&nbsp;</td>
<td>0&nbsp;</td>
<td>3&nbsp;</td>
<td>3&nbsp;</td>
<td>&nbsp;</td>
<td>3&nbsp;</td>
<td>7&nbsp;</td>
<td>3&nbsp;</td>
<td>3&nbsp;</td>
</tr>

<tr>
<td>4</td>
<td>Haapala&nbsp;</td>
<td>Elina&nbsp;</td>
<td><a href="mailto:elhaapal@itu">elhaapal@itu&nbsp;</a></td>
<td>Kem&nbsp;</td>
<td>0&nbsp;</td>
<td>3&nbsp;</td>
<td>3&nbsp;</td>
<td>2&nbsp;</td>
<td>3&nbsp;</td>
<td>3&nbsp;</td>
<td>1&nbsp;</td>
<td>3&nbsp;</td>
</tr>

<tr>
<td>5</td>
<td>Hakanen&nbsp;</td>
<td>Leila&nbsp;</td>
<td><a href="mailto:">&nbsp;</a></td>
<td>Matko&nbsp;</td>
<td>0&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>

<tr>
<td>6</td>
<td>Halonen&nbsp;</td>
<td>Tomi&nbsp;</td>
<td><a href="mailto:tohalone@silmu">tohalone@silmu&nbsp;</a></td>
<td>Tie&nbsp;</td>
<td>0&nbsp;</td>
<td>8&nbsp;</td>
<td>8&nbsp;</td>
<td>8&nbsp;</td>
<td>8&nbsp;</td>
<td>8&nbsp;</td>
<td>8&nbsp;</td>
<td>8&nbsp;</td>
</tr>

<tr>
<td>7</td>
<td>Halttunen&nbsp;</td>
<td>Helena&nbsp;</td>
<td><a href="mailto:halttunen@jykem.jyu.fi">halttunen@jykem.jyu.fi&nbsp;</a></td>
<td>Kem&nbsp;</td>
<td>0&nbsp;</td>
<td>7&nbsp;</td>
<td>6&nbsp;</td>
<td>6&nbsp;</td>
<td>4&nbsp;</td>
<td>4&nbsp;</td>
<td>4&nbsp;</td>
<td>4&nbsp;</td>
</tr>

{ POISTETTU YLI 40 SIVUA TURHAA LISTAUSTA }

<tr>
<td>168</td>
<td>Äijänen&nbsp;</td>
<td>Jussi&nbsp;</td>
<td><a href="mailto:jpaijane@itu">jpaijane@itu&nbsp;</a></td>
<td>Fys&nbsp;</td>
<td>0&nbsp;</td>
<td>&nbsp;</td>
<td>5&nbsp;</td>
<td>5&nbsp;</td>
<td>5&nbsp;</td>
<td>5&nbsp;</td>
<td>5&nbsp;</td>
<td>5&nbsp;</td>
</tr>

<tr>
<td>169</td>
<td>Äyrämö&nbsp;</td>
<td>Sami&nbsp;</td>
<td><a href="mailto:samiayr@silmu">samiayr@silmu&nbsp;</a></td>
<td>Fys&nbsp;</td>
<td>0&nbsp;</td>
<td>4&nbsp;</td>
<td>4&nbsp;</td>
<td>4&nbsp;</td>
<td>4&nbsp;</td>
<td>4&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>

</table>



</BODY>
</HTML>
6.3 IntraBuilderilla toteutettu opiskelijoiden demoaktiivisuuslista

//
// Generated on 11.12.1997
//
var r = new BARReport();
if (BAR.arguments.length == 2) {
r.startPage = BAR.arguments[0];
r.endPage = BAR.arguments[1];
}
r.render();
class BARReport extends Report {
with (this) {
title = "Uusittu2";
linkText = "Next Page";
}


with (this.opiskelijat1 = new Database()){
left = 0;
top = 0;
databaseName = "OPISKELIJAT";
active = true;
}


with (this.uusittu21 = new Query()){
left = 0;
top = 0;
database = parent.opiskelijat1;
sql = "SELECT * FROM uusittu2";

active = true;
}


with (this.uusittu21.rowset) {

indexName = "PrimaryKey";
}


with (this.StreamSource1 = new StreamSource(this)){

}


with (this.StreamSource1.detailBand) {
height = 2890;
}


with (this.StreamSource1.detailBand.html1 = new HTML(this.StreamSource1.detailBand)){
height = 240;
width = 1700;
color = "black";
fontBold = true;
text = "Sukunimi";
}


with (this.StreamSource1.detailBand.html2 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["Sukunimi"].value};
}


with (this.StreamSource1.detailBand.html3 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 240;
width = 1700;
color = "black";
fontBold = true;
text = "Etunimi";
}


with (this.StreamSource1.detailBand.html4 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 240;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["Etunimi"].value};
}


with (this.StreamSource1.detailBand.html5 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 480;
width = 1700;
color = "black";
fontBold = true;
text = "Sähk÷postiosoite";
}


with (this.StreamSource1.detailBand.html6 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 480;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["Sähk÷postiosoite"].value};
}


with (this.StreamSource1.detailBand.html7 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 720;
width = 1700;
color = "black";
fontBold = true;
text = "Koulutusohjelma";
}


with (this.StreamSource1.detailBand.html8 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 720;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["Koulutusohjelma"].value};
}


with (this.StreamSource1.detailBand.html9 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 960;
width = 1700;
color = "black";
fontBold = true;
text = "demo1";
}


with (this.StreamSource1.detailBand.html10 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 960;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["demo1"].value};
}


with (this.StreamSource1.detailBand.html11 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 1200;
width = 1700;
color = "black";
fontBold = true;
text = "demo2";
}


with (this.StreamSource1.detailBand.html12 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 1200;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["demo2"].value};
}


with (this.StreamSource1.detailBand.html13 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 1440;
width = 1700;
color = "black";
fontBold = true;
text = "demo3";
}


with (this.StreamSource1.detailBand.html14 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 1440;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["demo3"].value};
}


with (this.StreamSource1.detailBand.html15 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 1680;
width = 1700;
color = "black";
fontBold = true;
text = "demo4";
}


with (this.StreamSource1.detailBand.html16 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 1680;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["demo4"].value};
}


with (this.StreamSource1.detailBand.html17 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 1920;
width = 1700;
color = "black";
fontBold = true;
text = "demo5";
}


with (this.StreamSource1.detailBand.html18 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 1920;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["demo5"].value};
}


with (this.StreamSource1.detailBand.html19 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 2160;
width = 1700;
color = "black";
fontBold = true;
text = "demo6";
}


with (this.StreamSource1.detailBand.html20 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 2160;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["demo6"].value};
}


with (this.StreamSource1.detailBand.html21 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 2400;
width = 1700;
color = "black";
fontBold = true;
text = "demo7";
}


with (this.StreamSource1.detailBand.html22 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 2400;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["demo7"].value};
}


with (this.StreamSource1.detailBand.html23 = new HTML(this.StreamSource1.detailBand)){
height = 240;
top = 2640;
width = 1700;
color = "black";
fontBold = true;
text = "demo8";
}


with (this.StreamSource1.detailBand.html24 = new HTML(this.StreamSource1.detailBand)){
height = 240;
left = 1750;
top = 2640;
width = 7610;
color = "black";
text = {||this.form.uusittu21.rowset.fields["demo8"].value};
}


with (this.PageTemplate1 = new PageTemplate(this)){
height = 16837;
width = 11905;
marginTop = 1080;
marginLeft = 1080;
marginBottom = 1080;
marginRight = 1080;
gridLineWidth = 1;
}


with (this.PageTemplate1.StreamFrame1 = new StreamFrame(this.PageTemplate1)){
height = 11376;
left = 360;
top = 1584;
width = 9360;
}


with (this.PageTemplate1.html1 = new HTML(this.PageTemplate1)){
height = 414;
left = 360;
top = 360;
width = 1640;
color = "black";
fontBold = true;
text = "<H2>Uusittu2</H2>";
}


with (this.PageTemplate1.html2 = new HTML(this.PageTemplate1)){
height = 296;
left = 360;
top = 784;
width = 2320;
color = "black";
fontBold = true;
text = {||new Date()};
}


with (this.PageTemplate1.html3 = new HTML(this.PageTemplate1)){
height = 296;
left = 360;
top = 13140;
width = 1285;
color = "black";
fontBold = true;
text = {||this.parent.parent.reportPage};
}


with (this.printer) {
duplex = 1;
orientation = 1;
paperSource = 7;
paperSize = 9;
resolution = 4;
color = 1;
trueTypeFonts = 3;
}


with (this.reportGroup) {
groupBy = "";
}


with (this.reportGroup.headerBand) {
height = 0;
}


with (this.reportGroup.footerBand) {
height = 0;
}

this.firstPageTemplate = this.form.PageTemplate1
this.form.PageTemplate1.nextPageTemplate = this.form.PageTemplate1
this.form.PageTemplate1.StreamFrame1.streamSource = this.form.StreamSource1
this.form.StreamSource1.rowset = this.form.uusittu21.rowset
}

6.4 IntraBuilderin Javascript-koodista tuotettu HTML-koodi

<html>
<head>
<title>Uusittu2</title>
</head>
<body bgcolor="ffffff">
<form method="post" action="http:intrasrv.isv?bar.jrp"><input type="hidden" name="sessionID" value=881840734><input type="hidden" name="session.sequenceNumber" value=0>
<input type="hidden" name="session.FormHandle" value="13179512"><!-- Non table objects -->
<!-- Table objects -->
<table cellspacing=0 cellpadding=0 border=1 width=719>
<tr>
<td width=96><img src="/ibserver/trans.gif" width=96 height=1 border=0></td>
<td width=85><img src="/ibserver/trans.gif" width=85 height=1 border=0></td>
<td width=31><img src="/ibserver/trans.gif" width=31 height=1 border=0></td>
<td width=507><img src="/ibserver/trans.gif" width=507 height=1 border=0></td>
</tr>
<tr><td colspan=4 height=96> </td>
<tr>
<td> </td>
<td align=left valign=top width=109 height=27 colspan=2><b><H2>Uusittu2</H2></b></td>
<tr><td colspan=4 height=1> </td>
<tr>
<td> </td>
<td align=left valign=top width=154 height=19 colspan=3><b>11.12.1997 13:44:39</b></td>
<tr><td colspan=4 height=34> </td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>Sukunimi</b></td>
<td align=left valign=top width=507 height=16>Arhosalo</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>Etunimi</b></td>
<td align=left valign=top width=507 height=16>Touko</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>Sähk÷postiosoite</b></td>
<td align=left valign=top width=507 height=16>tparhosa@itu</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>Koulutusohjelma</b></td>
<td align=left valign=top width=507 height=16>Fys</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo1</b></td>
<td align=left valign=top width=507 height=16>0</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo2</b></td>
<td align=left valign=top width=507 height=16>7</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo3</b></td>
<td align=left valign=top width=507 height=16>7</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo4</b></td>
<td align=left valign=top width=507 height=16></td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo5</b></td>
<td align=left valign=top width=507 height=16>7</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo6</b></td>
<td align=left valign=top width=507 height=16>7</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo7</b></td>
<td align=left valign=top width=507 height=16>3</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo8</b></td>
<td align=left valign=top width=507 height=16>7</td>
<tr><td colspan=4 height=1> </td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>Sukunimi</b></td>
<td align=left valign=top width=507 height=16>Forsblom</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>Etunimi</b></td>
<td align=left valign=top width=507 height=16>Hannu</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>Sähk÷postiosoite</b></td>
<td align=left valign=top width=507 height=16>hmforsbl@itu</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>Koulutusohjelma</b></td>
<td align=left valign=top width=507 height=16>Kemko</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo1</b></td>
<td align=left valign=top width=507 height=16>0</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo2</b></td>
<td align=left valign=top width=507 height=16>3</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo3</b></td>
<td align=left valign=top width=507 height=16>3</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo4</b></td>
<td align=left valign=top width=507 height=16></td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo5</b></td>
<td align=left valign=top width=507 height=16>3</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo6</b></td>
<td align=left valign=top width=507 height=16>7</td>
<tr>
<td> </td>
<td align=left valign=top width=113 height=16 colspan=2><b>demo7</b></td>
<td align=left valign=top width=507 height=16>3</td>

{ POISTETTU n.158 SIVUA TURHAA TULOSTUSTA }


<tr><td colspan=4 height=194> </td>
<tr>
<td> </td>
<td align=left valign=top width=85 height=19><b> 56</b></td>
</table>
</form>
</body>
</html>