1 České vysoké učení technické v Praze Fakulta elektrotechnická ČVUT FEL katedra počítačů Bakalářská práce Internetové technologie na platformě JAVA M...
ˇ e vysok´e uˇcen´ı technick´e v Praze Cesk´ Fakulta elektrotechnick´a
ˇ VUT FEL katedra pocˇı´tacˇu˚ C
Bakal´aˇrsk´a pr´ace
Internetov´ e technologie na platformˇ e JAVA Miroslav Hr´ uz
Vedouc´ı pr´ace: Ing. Andrej Zachar
Studijn´ı program: Elektrotechnika a informatika strukturovan´e bakal´aˇrsk´e Obor: Informatika a v´ ypoˇcetn´ı technika srpen 2007
ii
Podˇ ekov´ an´ı Chtˇel bych podˇekovat firmˇe SimpleWay s.r.o., kde jsem mˇel tu moˇznost sezn´amit se s technologiemi, kter´e popisuji ve svoj´ı bakal´aˇrsk´e pr´aci. iii
iv
Prohl´ aˇ sen´ı Prohlaˇsuji, ˇze jsem svou bakal´ aˇrskou pr´aci vypracoval samostatnˇe a pouˇzil jsem pouze podklady uveden´e v pˇriloˇzen´em seznamu. Nem´am z´ avaˇzn´ y d˚ uvod proti uˇzit´ı tohoto ˇskoln´ıho d´ıla ve smyslu §60 Z´akona ˇc. 121/2000 Sb., o pr´avu autorsk´em, o pr´ avech souvisej´ıc´ıch s pr´avem autorsk´ ym a o zmˇenˇe nˇekter´ ych z´akon˚ u (autorsk´ y z´ akon).
Abstract The goal of this bachalor thesis is to show possibilities of the Java platform for building web applications. Content of this paper is from on piece a recherche and from other piece an implementation. In the beginning I make the reader acquainted with technologies like Java EE, Spring framework and Hibernate ORM. In the next section I show the principles of Extreme programming and Test-Driven development. In the last section I apply knowledges of foregoing parts to the practical project, to the portal for seeking roommates. I show illuminating apply of Extreme programming in the real life.
Abstrakt C´ılem t´eto bakal´ aˇrsk´e pr´ ace je uk´ azat moˇznosti JAVA platformy pro tvorbu webov´ ych aplikac´ı. Obsah d´ıla je z ˇc´ asti reˇserˇsn´ı, z ˇc´ asti implementaˇcn´ı. V u ´vodu seznamuji ˇcten´aˇre s technologiemi jako Java EE, Spring framework a Hibernate ORM. V dalˇs´ı ˇc´asti ukazuji principy Extr´emn´ıho programov´ an´ı a Testem ˇr´ızen´eho v´ yvoje. V posledn´ı ˇc´asti aplikuji teoretick´e znalosti z pˇredchoz´ıho textu na praktick´em projektu, port´alu pro hled´an´ı spolubydl´ıc´ıch. Ukazuji n´azornou aplikaci Extr´emn´ıho programov´an´ı v kaˇzdodenn´ım ˇzivotˇe.
Z´ akladn´ı UI prototyp . . . Vkl´ ad´ an´ı nov´eho inzer´ atu Validace poloˇzky . . . . . Nov´ y inzer´ at v´ıce lokalit . Hled´ an´ı v popt´ avk´ ach . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
36 37 43 45 48
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
xiii
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
xiv
´ KAPITOLA 1. UVOD
1
´ 1 Uvod Internet se v dneˇsn´ı dobˇe stal nepostradatelnou z´aleˇzitost´ı, m˚ uˇzeme ho vyuˇz´ıvat r˚ uzn´ ymi zp˚ usoby a vydolovan´ ych informac´ı je v´ıce neˇz dost. Je to v dneˇsn´ı dobˇe, st´ale, nejperspektivnˇejˇs´ı reklamn´ı m´edium na svˇetˇe. Z´akazn´ık˚ u, pˇrej´ıc´ıch si m´ıt na internetu svoji prezentaci st´ale pˇrib´ yv´ a. Nen´ı proto od vˇeci se sezn´amit s technologiemi pouˇzit´ ymi pro v´ yvoj internetov´ ych aplikac´ı. Tato pr´ ace pojedn´ av´ a o tvorbˇe rozs´ahlejˇs´ıch aplikac´ı na platformˇe JAVA. JAVA spolu s technologiemi .NET a Ruby on rails vede ˇzebˇr´ıˇcky statistik pouˇzit´ ych platforem [12] a pokud si zvol´ıme vhodnou metodiku v´ yvoje, aplikace n´am roste pod rukama velice rychle.
1.1
N´ aroky a technick´ a obt´ıˇ znost textu
Pr´ ace by mˇela slouˇzit jako studijn´ı materi´al vˇsem, kteˇr´ı maj´ı z´ajem vyv´ıjet internetov´e aplikace na platformˇe Java. Pˇredpokl´ad´am proto patˇriˇcnou znalost tohoto jazyka. Je dobˇre, pokud m´a ˇcten´ aˇr znalost v´ yvoje webov´ ych aplikac´ı na jin´e platformˇe (napˇr´ıklad s PHP). Na ˇskodu nejsou zkuˇsenosti s v´ yvojem pro Java EE, znalost tvorby JSP str´anek nebo JPA, plnˇe postaˇcuje absolvov´an´ı pˇredmˇetu X36TJV - Technologie programov´an´ı v jazyku Java, vyuˇcovan´eho u n´ as na fakultˇe.
1.2
Typografick´ au ´ prava a ˇ clenˇ en´ı textu
Veˇsker´ y text je ps´ an pomoc´ı n´astroje LATEX, s pouˇzit´ım pˇripraven´e ˇsablony, kterou poskytuje katedra. Text je ˇclenˇen hierarchicky do kapitol, sekc´ı a podsekc´ı. Pro lepˇs´ı ˇcitelnost a orientaci v textu budu veˇsker´ y k´ od ps´at takto, pokud budu v textu hovoˇrit o rozhran´ıch, zapisuji je takto. Tˇr´ıdy a metody zapisuji takto. Pr´ace je ps´ana v ˇceˇstinˇe, aˇckoliv je IT odvˇetv´ı charakteristick´e vˇsemoˇzn´ ymi anglick´ ymi term´ıny, tam, kde to lze, jsem pouˇzil jejich ˇcesk´ y ekvivalent s origin´ aln´ım znˇen´ım v z´ avork´ach.
2
´ KAPITOLA 1. UVOD
ˇ SE ˇ KAPITOLA 2. RESER
3
2 Reˇ serˇ se 2.1
Java EE
Java Enterprice Edition je platforma urˇcen´a pro v´ yvoj byznys aplikac´ı. St´avaj´ıc´ı specifikace Java EE 5 definuje ˇradu technologi´ı a API potˇrebn´ ych pro v´ yvoj rozs´ahl´ ych syst´em˚ u. Dˇr´ıve se platforma oznaˇcovala zkratkou J2EE, z marketingov´ ych d˚ uvod˚ u, stejnˇe jako JRE 1.5 se oznaˇcuje Java 5, se pˇrejmenovala na Java EE. Aktu´alnˇe je cel´e JVM opensourceov´ano a Javˇe nic nestoj´ı v cestˇe st´ at se nejpouˇz´ıvanˇejˇs´ı platformou od mobiln´ıch telefon˚ u po velk´e byznys aplikace. Kromˇe standardn´ıch API jsou k dispozici stovky opensourceov´ ych knihoven, framework˚ u a projekt˚ u. Nejdˇr´ıve se sezn´ am´ıme se z´aklady.
Obr´ azek 2.1: Kontext Java EE technologi´ı, zdroj: [6]
2.1.1
V´ıcevrstv´ a architektura (N-tier architecture)
Architektura byznys aplikace v Java EE se typicky skl´ad´a z ˇc´ast´ı, jeˇz ukazuje obr´ azek 2.1. Vˇzdy se jedn´ a o model klient/server [2], resp. request/response, serverov´a ˇc´ast se skl´ ad´ a z webov´eho kontejneru, doplnˇen´eho v pˇr´ıpadˇe pouˇzit´ı specifikace EJB o EJB kontejner, d´ ale jak´ehokoliv perzistentn´ıho datov´eho u ´loˇziˇstˇe (pˇredstavme si napˇr´ıklad klasickou relaˇcn´ı datab´azi) a nˇejak´eho klienta, v naˇsem pˇr´ıpadˇe webov´eho prohl´ıˇzeˇce. 2.1.2
Koncept kontejneru
Kontejner je takov´ y softwarov´ y objekt, kter´ y je alokov´an uvnitˇr dan´eho aplikaˇcn´ıho serveru nebo aplikaˇcn´ıho frameworku. • Zodpov´ıd´ a za pˇridˇelov´ an´ı zdroj˚ u dan´ ym komponent´am. ˇ ıd´ı ˇzivotn´ı cyklus objekt˚ • R´ u. Nˇekdy se pojmy jako framework a kontejner z´amˇernˇe zamˇen ˇuj´ı, napˇr. EJB a EJB kontejner, Spring a Spring kontejner. 2.1.3
Aplikaˇ cn´ı servery
Aplikaˇcn´ı server je j´ adrem Java EE technologie. Podle poˇctu implementovan´ ych specifikac´ı je m˚ uˇzeme rozdˇelit do dvou skupin
ˇ SE ˇ KAPITOLA 2. RESER
4 2.1.3.1
Servletov´ e kontejnery
Implementuj´ı pouze webov´ y kontejner a technologie v nˇem dostupn´e, viz 2.1. Jeho nasazen´ı je vhodn´e obecnˇe pro menˇs´ı a stˇredn´ı aplikace. C´ılem moj´ı pr´ace je uk´azat, ˇze i bez tˇeˇzkoton´aˇzn´ıho kan´ onu jm´enem EJB jsme schopni vytvoˇrit robustn´ı aplikaci pˇresnˇe podle naˇsich pˇredstav. Mezi z´ astupce t´eto kategorie patˇr´ı • Apache Tomcat - opensource projekt vydan´ y pod Apache licenc´ı, nejbˇeˇznˇejˇs´ı a nejdostupnˇejˇs´ı v˚ ubec. • Jetty - opensource, webov´ y server pro statick´ y a dynamick´ y obsah, optimalizace pro v´ ykon. 2.1.3.2
Java EE aplikaˇ cn´ı servery Narozd´ıl od pˇredchoz´ı kategorie, Java EE AS implementuj´ı plnou specifikaci z Java EE
stacku. • GlassFish (Sun Java AS) - opensource ˇreˇsen´ı od Sun Microsystems, referenˇcn´ı implementace • JBoss AS - opensource • IBM Websphere AS - komerˇcn´ı, vlastn´ı implementace JVM • BEA WebLogic AS - komerˇcn´ı 2.1.4
V´ ybˇ er technologi´ı
Pˇri v´ ybˇeru aplikaˇcn´ıho serveru mus´ıme zv´aˇzit, co dan´ y projekt opravdu potˇrebuje, jak´ y pouˇz´ıv´ame aplikaˇcn´ı framework, jak´ y ORM n´astroj nebo jak´e dalˇs´ı API jsou nezbytn´e, popˇr´ıpadˇe jak´e pouˇz´ıv´ ame IDE. V z´ asadˇe bychom mˇeli zvolit jednoho dodavatele ˇreˇsen´ı a nekombinovat dodavatele JVM, AS, frameworku a dalˇs´ıch API a knihoven nebo zvolit takovou kombinaci, kter´ a je odzkouˇsen´ a a bezprobl´emov´a; napˇr´ıklad Sun JVM, Glassfish AS, JSF, EJB, Toplink Essentials a NetBeans IDE, vˇse pˇeknˇe standardn´ı nebo v naˇsem pˇr´ıpadˇe Sun JVM, Apache Tomcat, Spring framework, Spring Web MVC a WebFlow, Hibernate a Eclipse IDE.
2.2 2.2.1
Spring framework Aplikaˇ cn´ı framework
Aplikace se dnes netvoˇr´ı cel´e od z´aklad˚ u, sp´ıˇse se podobaj´ı stavebnic´ım. Proˇc bychom mˇeli znovu objevovat kolo, kdyˇz uˇz ho nˇekdo pˇred n´ami vymyslel a funguje dobˇre. Framework je softwarov´ y syst´em, kter´ y n´ am pom´ ah´a ˇreˇsit nejˇcastˇejˇs´ı probl´emy pˇri implementaci, mˇel by zrychlovat a usnadˇ novat vlastn´ı v´ yvoj, ˇr´ıdit ˇzivotn´ı cyklus aplikace a pom´ahat oddˇelit n´ızko´ urovˇ nov´ y k´ od od vlastn´ı aplikaˇcn´ı logiky dan´eho zad´an´ı. 2.2.2
Historie
Odlehˇcen´ y J2EE framework Spring vznikl p˚ uvodnˇe jako demo aplikace knihy Roda Johnsonna: Expert One-to-One: J2EE design and development z roku 2002, kter´a uk´azala v dobˇe EJB 2.0, ˇze lze v J2EE programovat tak´e jednoduˇse. Pozdˇeji, roku 2003 byl zaloˇzen opensource projekt na port´ alu sourceforge.net, kter´ y vych´azel z p˚ uvodn´ıho k´odu a postupnˇe se stal mezi v´ yvoj´ aˇri velmi popul´ arn´ı. Dnes je Spring ve stabiln´ı verzi 2.0, ˇcas uk´azal, ˇze koncept,
ˇ SE ˇ KAPITOLA 2. RESER
5
kter´ y Rod Johnsonn navrhl je spr´ avn´ y. St´avaj´ıc´ı Java EE 5 se velmi Springem inspirovala a budouc´ı specifikace Java EE 6 se pl´ anuje Springu pˇribl´ıˇzit jeˇstˇe v´ıce. 2.2.3
Hlavn´ı pˇ rednosti
• Snazˇs´ı a pˇr´ıjemnˇejˇs´ı pr´ ace s J2EE, • neinvazivnost, nenut´ı pouˇz´ıvat nic, co pr´avˇe nepotˇrebujeme, • objektovˇe orientovan´ y n´ avrh je d˚ uleˇzitˇejˇs´ı neˇz jak´akoliv implementaˇcn´ı technologie, • JavaBeans jako siln´ y konfiguraˇcn´ı n´astroj, • testov´ an´ı je z´ asadn´ı, Spring dˇel´a k´od snadnˇeji testovateln´ y, • modularita, umoˇzn ˇuje zvolit jen ty komponenty, kter´e potˇrebujeme, • usnadˇ nuje pr´ aci s dalˇs´ımi frameworky, pouˇzit´ı jako sjednocuj´ıc´ı pˇr´ıstup rozliˇcn´ ych technologi´ı a implementac´ı, • usnadˇ nuje pr´ aci s vyj´ımkami, kdy je zaobaluje do l´epe ˇciteln´ ych.
Obr´ azek 2.2: Spring framework, zdroj: [13]
2.2.4
Moduly
D˚ uleˇzit´ ym aspektem je, ˇze Spring lze pouˇz´ıt i na servletov´em kontejneru, dokonce i ˇcistˇe s Java SE. Modul´ arnost je tak´e doc´ılena t´ım, ˇze se skl´ad´a z v´ıce .jar archiv˚ u, z´akladn´ıch i voliteln´ ych, pˇriˇcemˇz si vybereme, co z nˇej chceme pouˇz´ıt. • Spring Core - hlavn´ı j´ adro frameworku, jedin´ y povinn´ y modul, metodika Inversion of Control, rozhran´ı BeanFactory • Spring DAO - abstrakce a zjednoduˇsen´ı pr´ace s JDBC
ˇ SE ˇ KAPITOLA 2. RESER
6
• Spring ORM - integrace s ORM frameworky, vlastn´ı implementace perzistentn´ıho API i na JRE 1.4 bez JPA • Spring Web - integrace s webov´ ymi frameworky jako JSF, Struts2, Velocity, vlastn´ı implementace webov´eho frameworku Spring Web MVC • Spring AOP - podpora aspektovˇe orientovan´eho programov´an´ı • Spring J2EE - podpora pro EJB, JMX, JMS, web service 2.2.5
Inversion of Control
N´avrhov´ y vzor IoC je zaloˇzen na tzv. Hollywoodsk´em principu: “Nevolejte mi, j´a zavol´am v´am.”[5]. Aplikace se vzd´ av´ a odpovˇednosti za vytv´aˇren´ı a nastaven´ı instanc´ı ve prospˇech frameworku, resp. jeho IoC kontejneru. Jedn´a se o stˇeˇzejn´ı vlastnost, proto ji uk´aˇzu na pˇr´ıkladu.
Obr´ azek 2.3: Princip IoC, zdroj: [13]
Zap´ıˇseme tˇr´ıdu SampleContoller dvˇema zp˚ usoby, jednak klasicky a jednak s vyuˇzit´ım techniky IoC. SampleContollerWithoutIoC.java public class SampleContollerWithoutIoC implements org.springframework.web.servlet.mvc.Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { Connection connection = DatabaseUtils.getConnection(); UserDAO dao = DAOFactory.createUserDAO("hibernate", connection); List users = dao.getUsers(); DatabaseUtils.closeConnection(conn); return new ModelAndView("userList", "users", users); } }
SampleContollerWithIoC.java public class SampleContollerWithIoC implements org.springframework.web.servlet.mvc.Controller { private UserDAO dao = null; public void setUserDAO(UserDAO userDAO) { this.dao = userDAO; } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { List users = dao.getUsers();
ˇ SE ˇ KAPITOLA 2. RESER
7
return new ModelAndView("userList", "users", users); } }
Ve tˇr´ıdˇe SampleContollerWithoutIoC jsme ruˇcnˇe nastavovali komponentu dao. Ve tˇr´ıdˇe SampleContollerWithIoC jsme ji dostali jiˇz nakonfigurovanou. Pˇredstavme si pˇr´ıklad v kontextu re´aln´eho ˇzivota, komponentu dao vyuˇz´ıvaj´ı des´ıtky tˇr´ıd. Co by se stalo, kdybychom byli nuceni zmˇenit rozhran´ı tov´ arn´ı metody createUserDAO? Museli bychom zmˇenit i implementaci v des´ıtk´ach tˇr´ıd, kter´e dao pouˇz´ıvaj´ı, coˇz odporuje prvn´ı z´asadˇe OOP: “Pˇrid´avej vlastnost jen na jednom m´ıstˇe!”.1 ´ cel souboru action-servlet.xml a vlastn´ı konfigurace bean, jak vid´ıme v pˇr´ıkladu, Uˇ ˇreˇs´ım v kapitole 2.2.6. 2.2.5.1
Dependecy lookup
N´ avrhov´ y vzor IoC m˚ uˇzeme rozdˇelit do dvou technik, prvn´ı z nich je dependecy lookup. Je to n´avrhov´ y vzor, pomoc´ı nˇehoˇz objekt ˇz´ad´a kontejner o beanu. Toto ˇreˇsen´ı bylo pouˇzito v EJB 2.X v technologii JNDI, kdy poˇzadovan´a beana musela implementovat API specifick´e pro dan´ y kontejner. Evoluce uk´ azala, ˇze toto nen´ı nejvhodnˇejˇs´ı ˇreˇsen´ı. Spring jej pouˇz´ıv´a pro z´ısk´an´ı beany “z ruky”. XmlBeanFactory beanFactory = new XmlBeanFactory("action-servlet.xml"); DemandService demandService = (DemandService) beanFactory.getBean("demandService");
2.2.5.2
Dependecy injection
Druhou technikou IoC je n´avrhov´ y vzor dependecy injection, kter´ y ˇreˇs´ı samotn´e nastaven´ı a nainjektov´ an´ı potˇrebn´ ych objekt˚ u a jejich z´avislost´ı. Vlastn´ı vloˇzen´ı z´avislost´ı m˚ uˇze prob´ıhat pomoc´ı Setter injection - Nainjektov´ an´ı z´avislost´ı pomoc´ı JavaBeans setter˚ u. Pˇr´ıklad je uveden v kapitole 2.2.5. Constructor injection - Nainjektov´an´ı z´avislost´ı pomoc´ı konstruktoru. public class SampleContollerWithIoC implements Controller { private UserDAO dao = null; public SampleContollerWithIoC(UserDAO userDAO) { this.dao = userDAO; } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { 1 V takto jednoduch´em pˇr´ıkladu to samozˇrejmˇe lze obej´ıt, v´ yvoj´ aˇr komponenty dao by byl nucen prov´est zmˇenu s ohledem na kompatibilitu, kaˇzdop´ adnˇe je to probl´em, do kter´eho se nemus´ıme dostat.
ˇ SE ˇ KAPITOLA 2. RESER
8
List users = dao.getUsers(); return new ModelAndView("userList", "users", users); } }
action-servlet.xml
Method injection - Pouˇzit´ı pro speci´ aln´ı pˇr´ıpady, pro netrivi´alnost zde neuvedu pˇr´ıklad.2 Mezi dalˇs´ı typ dependecy injection, kter´ y Spring avˇsak nepodporuje, patˇr´ı Interface injection, na kter´em je zaloˇzen kontejner frameworku Avalon. Nejpouˇz´ıvanˇejˇs´ı je metoda Setter injection, nejˇcastˇeji z tˇechto d˚ uvod˚ u: • je jednoduˇsˇs´ı pracovat s defaultn´ımi hodnotami properties dan´eho objektu, • pomoc´ı getteru z´ısk´ ame okamˇzitˇe hodnotu dan´e property, • settery mohou b´ yt v pˇr´ıpadˇe potˇreby vol´any v´ıcekr´at, coˇz konstruktor b´ yt nem˚ uˇze, • gettery/settery jsou zdˇedˇeny na rozd´ıl od konstruktor˚ u jako klasick´e metody. 2.2.6 2.2.6.1
Rozhran´ı BeanFactory a ApplicationContext BeanFactory
BeanFactory je u ´stˇredn´ı ˇc´ ast´ı cel´eho frameworku, ˇr´ıd´ı ˇzivotn´ı cyklus, konfiguraci a vlastn´ı injektaci vˇsech bean. Kontejner startuje podle nasazen´ı aplikace, napˇr. na aplikaˇcn´ım serveru pomoc´ı definice v deployment deskriptoru web.xml nebo v JUnit testovac´ı sadˇe pomoc´ı pˇripraven´ ych startovac´ıch API, m˚ uˇzeme ho tak´e samozˇrejmˇe nainicializovat ruˇcnˇe, jak jsme vidˇeli v kapitole 2.2.5.1. Manageovan´ y objekt m˚ uˇze b´ yt jak´ ykoliv, v t´eto souvislosti se mluv´ı o tzv. POJO, coˇz v pˇrekladu znamen´ a star´ y dobr´ y java objekt. Je to jak´ ykoliv objekt, kter´ y nen´ı nucen dodrˇzovat implementaˇcn´ı omezen´ı dan´e platformy. Vlastn´ı konfigurace vz´ajemn´ ych vztah˚ u bean m˚ uˇzeme definovat pomoc´ı: • XML - nejˇcastˇejˇs´ı a nejpˇrehlednˇejˇs´ı z´apis, oddˇeluje konfiguraci od vlastn´ıho pouˇzit´ı, • anotac´ı - podobnˇe jako EJB, je konfigurace zaps´ana v POJO, • JavaBean properties, • Jakarta Commons atributes. Pokud vyuˇz´ıv´ ame XML konfigurace, m´ame minim´alnˇe jeden koˇrenov´ y konfiguraˇcn´ı soubor, nazveme jej napˇr. applicationContext.xml, na kter´ y ve webov´em nasazen´ı ukazuje depoloyment descriptor. web.xml 2 Pokud chceme nainjektovat beany s r˚ uzn´ ymi scopy (viz. 2.2.6.2), nevystaˇc´ıme si s setter injection, ˇcten´ aˇre odkazuji na referenˇcn´ı dokumentaci Springu [13].
Kromˇe koˇrenov´eho aplikaˇcn´ıho kontextu3 nadefinujeme servlet, kter´ y se vˇzdy star´a o jeden typ poˇzadavk˚ u (v naˇsem pˇr´ıpadˇe o vˇse s pˇr´ıponou .html), kter´e zpracov´av´a DispacherServlet. Podrobnˇeji je tomu vˇenov´ ana kapitola 2.2.7.4. Jelikoˇz budu mluvit o jedn´e jedin´e konfiguraci, budu ApplicationContext.xml a action-servlet.xml vˇedomˇe zamˇen ˇovat. V´ıme, kam zapsat XML konfigurace, aby se automaticky naˇcetly, pod´ıv´ame se ted’ na z´akladn´ı syntaxi. Hodnotu do property dan´e beany m˚ uˇzeme zapsat bud’to rovnou, <property name="property" value="value"/>
nebo odkazem na jinou beanu. <property name="property" ref="bean2"/>
2.2.6.2
ˇ Zivotnost beany
3 Nic n´ am nebr´ an´ı m´ıt koˇrenov´ ych konfiguraˇcn´ıch soubor˚ u v´ıce, v tagu <param-value/> m˚ uˇze b´ yt regul´ arn´ı v´ yraz.
ˇ SE ˇ KAPITOLA 2. RESER
10
Kaˇzd´a beana m´ a z hlediska konverze IoC kontejneru s klientem svoji ˇzivotnost (obor viditelnosti), kter´e ˇr´ık´ ame scope. Kromˇe definice z´avislost´ı beany, Spring jednoduˇse dovoluje si zvolit i jej´ı scope. Od verze 2.0 m˚ uˇzeme vytv´aˇret beany 5ti (resp. 6ti) scop˚ u. • sigleton - existuje jen jedna instance, kter´a se vytvoˇr´ı pˇri prvn´ı injekci, u dalˇs´ıch se injektuje tato, • prototype - pro kaˇzdou injektaci se vytvoˇr´ı nov´a instance, • request - beana plat´ı po dobu HTTP requestu,4 • session - beana plat´ı po dobu HTTP session, • global session - beana plat´ı po dobu HTTP global session, • custom - pokud potˇrebujeme nestandardn´ı ˇreˇsen´ı, Spring dovoluje definovat vlastn´ı. prototype Defaultnˇe se vˇzdy pouˇzije singleton. Je to logick´e, ve vˇetˇsinˇe pˇr´ıpad˚ u n´as stav beany nezaj´ım´a a plnˇe si s t´ım vystaˇc´ıme, pokud ale potˇrebujeme vytv´aˇret pokaˇzd´e nov´e instance, tj. chceme zohledˇ novat stav beany, mus´ıme uv´est explicitnˇe parametr scope.
web scopes Jako pˇr´ıklad uvedu n´ asleduj´ıc´ı beany:
Po kaˇzd´em HTTP requestu se vytvoˇr´ı nov´a instance beany loginAction, zat´ımco intuitivnˇe beana userPreferences m´ a m´ıt platnost po celou dobu relace. Ve vˇetˇs´ınˇe pˇr´ıpad˚ u ve webov´e aplikaci si pˇri definici bean vystaˇc´ıme pouze s prvn´ımi dvˇema standardn´ımi scopy. Pokud potˇrebujeme nainjektovat beanu s webov´ ym (nebo custom) scopem do beany se standardn´ım scopem, mus´ıme k tomu poˇz´ıt chytr´eho objektu - proxy, kter´ y nainjektovanou beanu zastupuje. Pˇri vol´an´ı metody beany proxy z´ısk´a objekt ze scopu a deleguje jej´ı vol´ an´ı. <property name="userPreferences" ref="userPreferences"/>
K webov´ ym scop˚ um vˇsak nejˇcastˇeji pˇristupujeme jako ke kontejner˚ um, do kter´ ych ukl´ad´ame objekty (a z nich vyb´ır´ ame) programovˇe. Tuto praktiku ukazuji spolu s SWF scopy v kapitol´ ach 2.2.7.5, 2.2.8.5. 4 Tyto 3 tzv. webov´e scopy maj´ı smysl pouze ve webov´e aplikaci s pouˇzit´ım webov´e implementace ApplicationContextu - s XmlWebApplicationContext.
ˇ SE ˇ KAPITOLA 2. RESER 2.2.6.3
11
Typov´ e konverze
Typov´ a konverze je zp˚ usob pˇrevodu hodnot zapsan´ ych v XML deskriptoru na re´ aln´e objekty. Chtˇeli bychom napˇr´ıklad zapsat nˇeco jako <property name="measure" value="90/60/90"/> public Class SampleBean { private Measure measure; public Measure getMeasure() { return measure;} public void setMeasure(Measure measure) {this.measure=measure;} }
Spring n´ am nab´ız´ı moˇznost implementace Property editoru, na kter´ y se kontejner obr´ at´ı v pˇr´ıpadˇe, ˇze v setteru naraz´ı na dan´ y typ, kter´ y neum´ı automaticky dosadit. public class MeasurePropertyEditor extends java.beans.PropertyEditorSupport { @Override public void setAsText(String measure) throws IllegalArgumentException { try { //set data from String to value setValue(value); } catch (NumberFormatException e) { throw new IllegalArgumentException(); } } }
Implementaci metody setAsText nech´av´am na ˇcten´aˇri. Kromˇe n´ı existuje inverzn´ı metoda getAsText(). Property editor zaregistrujeme napˇr´ıklad do beany customEditorConfigurer, kterou um´ıst´ıme na viditeln´e m´ısto z ApplicationContextu. <property name="customEditors"> <map> <entry key="Measure">
2.2.6.4
Autowiring
Ohlednˇe Spring IoC kontejneru stoj´ı za to zm´ınit jeˇstˇe jednu zaj´ımavou vlastnost autowiring.
T´ım ˇrekneme Sprigu, aby se pokusil automaticky vyˇreˇsit z´avislosti injektovan´ ych bean s´am, bez nutnosti je explicitnˇe uv´ adˇet. Hodnota v parametru autowire m˚ uˇze nab´ yvat nejˇcastˇeji n´asleduj´ıc´ıch hodnot: • no - ˇz´ adn´ y autowiring,
ˇ SE ˇ KAPITOLA 2. RESER
12
• byName - Spring hled´ a ve sv´em ApplicationContextu beanu, podle jm´ena JavaBean property, • byType - Spring hled´ a beanu podle typu JavaBean property, funguje pouze pokud se v beanˇe nevyskytuje v´ıce properties stejn´eho druhu. 2.2.6.5
ApplicationContext
Rozhran´ı ApplicationContext obaluje BeanFactory a rozˇsiˇruje jej´ı funkcionalitu o internacionalizaci, publikov´ an´ı ud´ alost´ı nebo nahr´av´an´ı zdroj˚ u. Uk´aˇzi na pˇr´ıkladu internacionalizaci, resp. pouˇzit´ı souboru .properties, kde uv´ ad´ıme k dan´emu kl´ıˇci jeho hodnotu. Sample.properties bean.value = someOtherValue
Kdekoliv v naˇs´ı XML konfiguraci tyto promˇenn´e m˚ uˇzeme pomoc´ı Expression Language5 pouˇz´ıt, napˇr´ıklad takto. <property name="property" value="${bean.value}"/>
Vˇse bude fungovat, pokud dopln´ıme ApplicationContext o beanu <property name="locations"> <list> classpath:Sample.properties
2.2.7
Spring Web MVC
V t´eto kapitole pˇredstav´ım webov´e frameworky, vysvˇetl´ım cestu, jak jsme se k nim dostali a uk´aˇzu Springovskou implementaci webov´eho frameworku Spring Web MVC. Jako vˇsechny modern´ı webov´e frameworky je Spring MVC, jak uˇz jeho n´azev napov´ıd´a, zaloˇzen na n´avrhov´em vzoru Model View Controller. 2.2.7.1
N´ avrhov´ y vzor MVC
Z historick´eho pohledu se pˇred n´astupem JSP pouˇz´ıvaly pouze servlety, s nimiˇz se v´ ysledn´ y HTML v´ ystup generoval obt´ıˇznˇe. Z dneˇsn´ıho pohledu je tento zp˚ usob vhodn´ y jen pro nejjednoduˇsˇs´ı pˇr´ıpady. Pˇri pˇr´ıchodu specifikace JSP 1.0 byla zvolena architektura tzv. Model 1, kdy je HTTP poˇzadavek delegov´ an pˇr´ımo na pˇr´ısluˇsnou JSP str´anku, v n´ıˇz je um´ıstˇen veˇsker´ y k´od pomoc´ı 6 skriptlet˚ u. Tento pˇr´ıstup se dnes oznaˇcuje term´ınem “ˇspagetov´ y k´od”, postr´ad´ame tu jak´ekoliv 5
EL se prim´ arnˇe pouˇz´ıv´ a pro vyhodnocov´ an´ı v´ yraz˚ u na JSP str´ ank´ ach, pro pˇr´ıstup k hodnot´ am z Command Objekt˚ u a podobnˇe. 6 Skriptlet je speci´ aln´ı tag, v nˇemˇz je um´ıstˇen pˇr´ımo Java k´ od <% /*java code here*/ %>.
ˇ SE ˇ KAPITOLA 2. RESER
13
Obr´ azek 2.4: N´avrhov´ y vzor MVC, zdroj: [15]
rozdˇelen´ı do vrstev, centralizovan´ y pˇr´ıstup HTTP poˇzadavk˚ u a potenci´aln´ı moˇznost rozˇs´ıˇren´ı aplikace. Specifikace JSP 1.1, kromˇe pˇr´ıstupu psan´ı logiky aplikace pomoc´ı tag˚ u, pˇrinesla implementaci n´ avrhov´eho vzoru MVC (obr´azek 2.4), kterou oznaˇcujeme jako Model 2.
Obr´ azek 2.5: MVC implementace JSP Model 2, zdroj: [1]
MVC slouˇz´ı k oddˇelen´ı aplikaˇcn´ı, prezentaˇcn´ı logiky a datov´eho modelu. Nen´ı pouˇzit jen ve webov´ ych frameworc´ıch, ale napˇr´ıklad v desktopov´ ych aplikac´ıch u knihoven Swing a SWT. Jeho koˇreny m˚ uˇzeme naj´ıt ve Smalltalku, kdy byl p˚ uvodnˇe pouˇzit pro zakomponov´ an´ı klasick´eho postupu vstup-zpracov´ an´ı-v´ ystup do GUI program˚ u. • Controller - prostˇredn´ık mezi vrstvami Model a View • Model - pˇredstavuje vlastn´ı data zobrazovan´a ve View • View - transformuje Model do prezentaˇcn´ı podoby Jelikoˇz HTTP protokol je typu request/response, tedy bezestavov´ y, nem˚ uˇzeme zde aplikovat typick´ y MVC pattern. Model 2 je upraven´e MVC sch´ema, kdy jedin´ y rozd´ıl spoˇc´ıv´ a v tom, ˇze View nem˚ uˇze volat Controller. Vz´ ajemnou interakci si m˚ uˇzeme pˇredstavit tak, ˇze klient poˇsle poˇzadavek na webov´ y server, ten je vz´ apˇet´ı delegov´ an na servlet, kter´ y pˇredstavuje controller, servlet zavol´a aplikaˇcn´ı
ˇ SE ˇ KAPITOLA 2. RESER
14
logiku, ze kter´e z´ısk´ a model, ten zaregistruje do scopu viditeln´eho z JSP, kter´e pˇredstavuje view. N´aslednˇe provede pˇresmˇerov´ an´ı na View a posl´eze na JSP, kter´e si vyzvedne data ze scopu a ten v´ ysledek zobraz´ı.[15] V prostˇred´ı webu existuje nˇekolik implementac´ı, kter´e odpov´ıdaj´ı n´avrhov´ ym vzor˚ um: • Front Controller (Service to worker) - frameworky Struts, Spring Web MVC, WebWork, Ruby on Rails • Dispacher view (View helper) - frameworky JSF-based (JSF, Seam, Shale), Tapestry, Wicket, Echo2 Rozd´ıl mezi pˇr´ıstupy je v okamˇziku vol´an´ı aplikaˇcn´ı logiky. V pˇr´ıpadˇe Front Controller u se aplikaˇcn´ı logika vol´ a pˇred pˇred´ an´ım zpracov´an´ı do View, zat´ımco u Dispacher View se vol´a aplikaˇcn´ı logika uvnitˇr View pomoc´ı View Helper u[3]. 2.2.7.2
Rozdˇ elen´ı webov´ ych framework˚ u
ˇ ım vˇetˇs´ı je u Nejd˚ uleˇzitˇejˇs´ı roli hraje u ´roveˇ n abstrakce nad protokolem HTTP. C´ ´roveˇ n abstrakce, t´ım m´enˇe se mus´ı jednotliv´e vrstvy starat o samotn´ y protokol. Takto dˇel´ıme webov´e frameworky do dvou skupin • Poˇzadavkovˇe orientovan´e (Request based) - zaloˇzen´e na Front Controlleru, • Komponentovˇe orientovan´e (UI component based, event-based) - zaloˇzen´e na View Helperu. Budoucnost patˇr´ı komponentovˇe orientovan´ ym webov´ ym framework˚ um. Ty umoˇzn ˇuj´ı v´ yvoj´aˇri pracovat na u ´rovni UI komponent a ud´alost´ı, tedy stejnˇe jednoduˇse jako s dalˇs´ımi napˇr. s desktopov´ ymi frameworky (Swing, SWT). Snadno napˇr´ıklad vytvoˇr´ıme RAD n´astroj pro WYSIWYG editaci webov´e aplikace. 2.2.7.3
Architektura
Obr´ azek 2.6: Architektura aplikace ve Spring MVC, zdroj: [20]
Architektura aplikace ve Spring Web MVC je rozdˇelena do navz´ajem nez´avisl´ ych vrstev, jak vid´ıme na obr´ azku 2.6. User Interface - prezentaˇcn´ı vrstva, star´a se o generov´an´ı v´ ystupu k uˇzivateli (nejˇcastˇeji XHTML), vˇetˇsinou pouˇzito JSP, ke kter´emu existuj´ı implementace rozhran´ı View
ˇ SE ˇ KAPITOLA 2. RESER
15
org.springframework.web.servlet.view.JstlView. Jako pˇr´ıklad si uvedeme jednoduchou JSP str´ anku.7 SampleJSP.jsp we count in binary base we don’t count in binary base
Web - webov´ a vrstva ˇreˇs´ı pˇrechody mezi str´ankami a odstiˇ nuje servisn´ı vrstvu od vlastn´ı implementace. Hlavn´ı rozhran´ı v t´eto vrstvˇe je Controller. • AbstractController - z´akladn´ı abstraktn´ı kontrol´er • BaseCommandController,AbstractCommandController - pracuje s Command Objectem, tj. objekt, do kter´eho namapujeme vstupn´ı hodnoty z hodnot z´ıskan´ ych HTTP requestu, ten pak pouˇz´ıv´ame ve View vrstvˇe • SimpleFormController,AbstractSearchController - umoˇzn ˇuje napˇr´ıklad pˇrechody mezi jednotliv´ ymi obrazovkami pomoc´ı reakce na v´ ysledek metody • MultiActionController - umoˇzn ˇuje reagovat na v´ıce typ˚ u HTTP request˚ u, defaultnˇe se metoda obsluhuj´ıc´ı danou namapovanou str´anku jmenuje stejnˇe jak tato str´ anka • UrlFilenameViewController - pomoc´ı namapovan´e str´anky zobraz´ı JSP se stejn´ ym jm´enem Pˇr´ıklad na takov´ y jednoduch´ y kontrol´er je uveden hned v kapitole 2.2.5. Service - V t´eto vrstvˇe se nach´ az´ı implementace dan´ ych byznys requirement˚ u, jako jedin´a m´ a pˇr´ıstup k perzistentn´ı vrstvˇe. Jelikoˇz je tato vrstva obvykle v kaˇzd´e aplikaci jin´a, Spring k n´ı nenab´ız´ı ˇz´ adn´e rozhran´ı. Jako pˇr´ıklad uk´aˇzu rozhran´ı IUserService public interface IUserService { public boolean isLoginFree(String login); public AbstractUser getUser(String login); public boolean saveUser(AbstractUser user); }
Persistence - Perzistentn´ı vrstva implementuje ukl´ad´an´ı,nahr´av´an´ı a pr´aci s objekty datov´eho modelu z datab´ aze. Nejˇcastˇeji implementuje metody CRUD (create,retrieve,update,delete). Spring ve sv´em ORM modulu dovoluje pouˇz´ıt deklarativn´ı transakce imlementovan´e pomoc´ı AOP nebo zaobaluje m´enˇe ˇciteln´e vyj´ımky do ˇcitelnˇejˇs´ıch. Spolupr´ace s Hibernate frameworkem je hlavnˇe doc´ılena pomoc´ı tˇr´ıd • HibernateTemplate 7
Uveden´e tagy zaˇc´ınaj´ıc´ı XML namespacem c nebo ftm jsou souˇca ´st´ı knihovny JSTL, kde jsou definov´ any z´ akladn´ı tagy pro vˇetven´ı k´ odu, zobrazov´ an´ı, iterace, definov´ an´ı a pouˇz´ıv´ an´ı promˇenn´ ych nebo pro form´ atov´ an´ı textu.
ˇ SE ˇ KAPITOLA 2. RESER
16 • HibernateDaoSupport
Jako pˇr´ıklad zde uvedu rozhran´ı IOfferDao. Uk´azku deklarativn´ıch transakc´ı spolu s implementaci rozhran´ı IOfferDao si uk´aˇzeme v kapitole 2.3.8.1. public interface IOfferDao { void saveOrUpdate(Offer offer); Offer getOfferById(long id); Offer getOfferBy(Inzerent inzerent); void delete(Offer offer); List getTopItems(int maxResult); }
Domain model - datov´ y model, soubor tzv. Bussiness Object˚ u, kter´e abstrahuj´ı entity z dom´enov´eho modelu do programu, kter´ y s nimi pracuje, nakonfigurovan´ ych pro O/R mapov´an´ı dan´ ym perzistentn´ım n´ astrojem. Uk´aˇzeme si jednoduch´ y pˇr´ıklad se z´apisem pomoc´ı JPA anotac´ı. Inzerent.java @Entity public class Inzerent extends AbstractUser { private String firstName = ""; private String lastName = ""; private String telephone = ""; //appropriate getters and setters }
Podrobnˇeji se t´eto problematice vˇenuji v kapitole 2.3. 2.2.7.4
Dispacher servlet
DispacherServlet je Front Controller, jak jsem uk´azal v kapitole 2.2.7.1. Nyn´ı si uk´aˇzeme jednoduch´ y pˇr´ıklad, kde d´ ame vˇse probran´e v t´eto kapitole do kontextu. V deployment deskriptoru jsme nadefinovali pro vˇsechny pˇr´ıstupy na *.html obsluhu servletu action, jemuˇz odpov´ıd´a konfigurace action-servlet.xml8 , jak je uvedeno v kapitole 2.2.6. V action-servlet.xml nadefinujeme: <property name="alwaysUseFullPath" value="true" /> <property name="mappings"> <props> <prop key="/sample-web-page.html">sampleController2 <property name="view" value="sampleJSP" /> 8
Podle jm´ena servletu “action” se pˇrid´ a automaticky “-servlet”.
ˇ SE ˇ KAPITOLA 2. RESER
17
Tedy kaˇzd´ y poˇzadavek o sample-web-page.html bude zpracov´avat beana sampleController2, ta m´ a “nasetov´ anu” JSP str´anku9 , kter´a se ve v´ ysledku zobraz´ı. Implementace tˇr´ıdy SampleController2 bude velmi podobn´a pˇr´ıkladu SampleContollerWithIoC u kapitoly 2.2.5. public class SampleContoller2 implements org.springframework.web.servlet.mvc.Controller { private String view; public void setView(String view) { this.view = view; } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { return new ModelAndView(view); } }
Pokud potˇrebujeme jen jednoduˇse podle poˇzadavku na sample-web-page.html zobrazit jen JSP str´ anku a nic jin´eho, tak k tomu pouˇzijeme kontrol´er UrlFilenameViewController, kter´ y hled´ a View a posl´eze JSP se stejn´ ym jm´enem jako je html poˇzadavek. V pˇr´ıkladu by staˇcilo zmˇenit odpov´ıdaj´ıc´ı kontrol´er.
2.2.7.5
Webov´ e scopy
Jak jsem zm´ınil v kapitole 2.2.6.2, ˇcastˇeji k webov´ ym scop˚ um pˇristupujeme jako ke kontejner˚ um pro data, jeˇz maj´ı b´ yt viditeln´a v r´amci n´ami definovan´eho scopu. • Request - platnost jen v pr˚ ubˇehu jednoho HTTP poˇzadavku, v requestu pˇrich´az´ı parametry z HTTP GETu, • Session - platnost v pr˚ ubˇehu HTTP session, • Global session - platnost v pr˚ ubˇehu HTTP global session, Z´ akladn´ı moˇznosti pr´ ace uv´ad´ım v n´asleduj´ıc´ım pˇr´ıkladu, napˇr´ıklad s pomoc´ı tˇr´ıdy org.springframework.web.context.request.DispatcherServletWebRequest. HttpServletRequest request; //gets as method parameter HttpSession session = request.getSession(); DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request); Integer id = (Integer) request.getParameter("id"); id = (Integer) webRequest.getAttribute("id", RequestAttributes.SCOPE_REQUEST); session.setAttribute("string", id.toString()); webRequest.setAttribute("string", id.toString(), RequestAttributes.SCOPE_SESSION); webRequest.setAttribute("string", id.toString(), RequestAttributes.SCOPE_GLOBAL_SESSION); 9
V tomto jednoduch´em kontextu si m˚ uˇzeme pˇredstavit napˇr´ıklad tu, jeˇz jsem uk´ azal v kapitole 2.2.7.3.
ˇ SE ˇ KAPITOLA 2. RESER
18 2.2.8
Spring Web Flow
Spring Web Flow je mocn´ y n´ astroj pro definici toku obrazovek ve webov´e aplikaci. Snadno ˇreˇs´ı probl´emy, kter´e konvenˇcn´ı pˇr´ıstup ˇreˇs´ı velmi neefektivnˇe. Hlavn´ı idea spoˇc´ıv´a v kl´ıˇcov´e abstrakci konverzace mezi uˇzivatelem a serverem - ve flow. Flow je nˇeco v´ıce neˇz jednotliv´ y poˇzadavek, nˇeco m´enˇe neˇz cel´ a session. Jedin´e flow ˇr´ıd´ı celou konverzaci, kdyˇz se od uˇzivatele oˇcek´av´ a nˇejak´ y vstup, flow je pozastaveno a ˇcek´a na uˇzivatel˚ uv vstup. Klient ovl´ad´a konverzaci pomoc´ı ud´ alost´ı, podle kter´ ych se flow rozhoduje, co udˇelat d´al. 2.2.8.1
Struktura
Definice flow nen´ı nic jin´eho neˇz koneˇcn´ y automat s poˇc´ateˇcn´ım stavem, mnoˇzinou stav˚ u, ve kter´ ych se m˚ uˇze nach´ azet, mnoˇzinou koneˇcn´ ych stav˚ u a vlastn´ımi pˇrechody do dalˇs´ıch ’ stav˚ u. Takov´ yto z´ apis prov´ ad´ıme bud to do XML souboru nebo do java k´odu (pouˇzit´ı pˇri speci´aln´ıch pˇr´ıpadech a napojen´ıch na dalˇs´ı syst´emy). Pro zaˇc´atek zde uvedu u ´vodn´ı z´apis, ve kter´em flow konfigurujeme. sample-flow.xml
2.2.8.2
Stavy
Start State - Poˇc´ ateˇcn´ı stav, jako u kaˇzd´eho automatu je pr´avˇe jeden. Parametr idref odkazuje na jin´ y stav. <start-state idref="actionState" />
End State - Koncov´ y stav, pokud je ve flow na nejvyˇsˇs´ı u ´rovni, ukonˇc´ı se jeho prov´adˇen´ı, zresetuj´ı se vˇsechny promˇenn´e. <end-state id="endState" />
View State - Stav, ve kter´em doch´ az´ı k vykreslen´ı zadan´eho obsahu.
Action State - Stav, ve kter´em doch´ az´ı k nˇejak´e akci, tj. zavol´an´ı metody dan´e beany a obvykle podle n´ avratu n´ asleduje pˇrechod do jin´eho stavu.
Decision State - Stav, urˇcen´ y k vˇetven´ı logiky, testuje podm´ınku, podle kter´e pˇrejde do dalˇs´ıho stavu.
ˇ SE ˇ KAPITOLA 2. RESER
19
<decision-state id="decisionState" >
SubFlow State - Do flow m˚ uˇzeme vnoˇrit jin´e flow jako subflow a pomoc´ı koncov´eho stavu dan´eho subflow reagovat a pˇrej´ıt dle logiky aplikace. Vhodn´e pokud se urˇcit´e sekvence stav˚ u a pˇrechod˚ u opakuj´ı ve v´ıce flow. <subflow-state id="subflowState" flow="inner-flow">
• oddˇelen´ı navigace od vlastn´ıho k´odu, • automatick´e ˇr´ızen´ı stavu aplikace, • vyˇsˇs´ı u ´roveˇ n abstrakce, • dovoluje volat metody stˇredn´ı vrstvy bez nutnosti pouˇzit´ı kontrol´eru. 2.2.8.4
Nev´ yhody
Spring Web Flow nem˚ uˇzeme pouˇz´ıt tam, kde kv˚ uli velk´e pr´aci, neˇz se zprovozn´ı jen jedna jedin´ a obrazovka, se n´ am jej nevyplat´ı nasadit. Avˇsak po urˇcit´e velikosti a sloˇzitosti pˇrechod˚ u mezi str´ ankami v aplikaci tato nev´ yhoda pad´a. 2.2.8.5
Scope
Stejnˇe jako webov´e scopy ve Spring Web MVC, existuj´ı ve SWF podobn´e kontejnery pro uloˇzen´ı jak´ ychkoliv kr´ atkodob´ ych dat. Pˇristupuje se k nim pˇres rozhran´ı Map pomoc´ı instance tˇr´ıdy RequestContext. SampleFormAction.java public org.springframework.webflow.Event sampleAction(org.springframework.web.servlet.support. RequestContext context) { Object object = context.getXXXScope().get("key"); context.getXXXScope().put("key",object); context.getExternalContext().getRequestMap().put("key",object); Object object = context.getExternalContext().getSessionMap().get("key"); context.getExternalContext().getGlobalSessionMap().put("key",object); context.getExternalContext().getApplicationMap().put("key",object); return success(); }
kde za XXX dosad´ıme jeden za 4 scop˚ u pouˇziteln´ ych ve SWF: • request - maj´ı platnost pouze pro dan´ y poˇzadavek,
ˇ SE ˇ KAPITOLA 2. RESER
20 • flash - plat´ı dokud uˇzivatel neopust´ı aktu´aln´ı stav, • flow - plat´ı pro cel´e flow, • conversation - plat´ı po dobu ˇzivotnosti mateˇrsk´eho flow.
Na pˇr´ıkladu vid´ıme, ˇze pomoc´ı tˇr´ıdy RequestContext m´ame pˇr´ıstup i ke klasick´ ym webov´ ym scop˚ um pˇredstaven´ ym v kapitole 2.2.7.5, s t´ım rozd´ılem, ˇze k nim pˇristupujeme jako k mapˇe. Posledn´ı dosud nepˇredstaven´ y je application scope, kter´ y plat´ı v pr˚ ubˇehu chodu cel´e aplikace. Nutno poznamenat, ˇze data v jak´emkoliv scopu, kromˇe singletonu, nejsou viditeln´a vˇsemi uˇzivateli dohromady. 2.2.8.6
Syntax
- Definujeme pˇrechod do jin´eho stavu, parametry on, to, on-exception. Parametr to je povinn´ y. Pokud prov´ ad´ıme ve stavu v´ıce akc´ı, pˇrechod m˚ uˇzeme podm´ınit v´ ysledkem pr´ avˇe jedn´e metody.
- Vhodn´e pouˇz´ıt, pokud se pˇrechody v kaˇzd´em stavu z flow opakuj´ı.
<xxx-actions/> - Pomoc´ı tagu definujeme akce: • <start-actions/> - akce se provedou po vstupu do flow, • <end-actions/> - akce se provedou pˇred koncem flow, • <entry-actions/> - akce se provedou po vstupu do stavu, • <exit-actions/> - akce se provedou pˇred koncem stavu, • - akce se provedou pˇred vlastn´ım renderov´an´ım ve viewState. <xxx-mapper/> - Tagem definujeme mapov´an´ı do flow/z subflow: • - vstupn´ı mapov´an´ı, • - v´ ystupn´ı mapov´an´ı. Pokud chceme v subflow pˇristoupit k dat˚ um z nadˇrazen´eho flow, m´ame 2 moˇznosti, jak toho dos´ ahnout. Prvn´ı moˇznost´ı je pouˇz´ıt conversation scope, kter´ y je v subflow viditeln´ y. T´ımto ale dan´ a data zviditeln´ıme i pro vˇsechny ostatn´ı subflow. Dalˇs´ı moˇznost´ı je pouˇz´ıt mapov´ an´ı atribut˚ u. V hlavn´ım flow definujeme:
Z´ apisem <mapping source="flowScope.number" target="number"/> vezmeme promˇennou number uloˇzenou ve flow scope a pˇred´ame j´ı pomoc´ı input-mapperu do subflow. Z´ apisem z´ısk´ame promˇennou result z output-mapperu a pˇred´ ame j´ı do flow scope tohoto flow. V subflow je ovˇsem mus´ıme definovat tak´e: ... some logic <end-state id="finish">
Z´ apisem z´ısk´ame pomoc´ı input-mapperu promˇennou number do flow scope, z´apisem um´ıstˇen´ ym v koncov´em stavu mapujeme promˇennou result z flow scope do output-mapperu. redirect - Pokud chceme n´ asilnˇe pˇresmˇerovat tok aplikace, pˇrev´aˇznˇe pouˇzijeme jeden ze dvou hlavn´ıch pˇr´ıstup˚ u: • flowRedirect - pˇresmˇerov´an´ı na jin´e flow, • externalRedirect - pˇresmˇerov´an´ı na kompletnˇe jin´ y HTTP poˇzadavek10 . <end-state id="finish" view="flowRedirect:sampleFlow"/> <end-state id="finish" view="externalRedirect:sample-web-page.html"/>
Pro kompletn´ı pˇrehled syntaxe a pr´aci s SWF odkazuji ˇcten´aˇre na referenˇcn´ı dokumentaci SWF. [14] 10 Rozd´ıl je samozˇrejmˇe ten, ˇze ve druh´em pˇr´ıpadˇe se postupuje k dispacher servletu, zat´ımco prvn´ı pˇr´ıpad z˚ ust´ av´ a ve flowControlleru.
ˇ SE ˇ KAPITOLA 2. RESER
22 2.2.8.7
Konfigurace
SWF zapoj´ıme do naˇs´ı aplikace t´ım, ˇze pˇrid´ame n´asleduj´ıc´ı beany do XML deskriptoru Springu. action-servlet.java <property name="flowExecutor" ref="flowExecutor" />
Uveden´ a konfigurace je opravdu z´ akladn´ı, pro vˇetˇs´ı poˇcet str´anek a flow se nehod´ı. Lepˇs´ı konfigurace je uvedena v implementaci port´alu www.chcispolubydlici.cz, jehoˇz zdrojov´e k´ody jsou obsaˇzeny na pˇriloˇzen´em CD.
2.3
Hibernate
Tato ˇc´ ast by mˇela ˇcten´ aˇri osvˇetlit pouˇzit´ı perzistentn´ı vrstvy ve webov´e aplikaci. Vysvˇetl´ım cestu k ORM framework˚ um, jejich dneˇsn´ı standard JPA a bl´ıˇze se pod´ıv´ame na jednu z implementac´ı - JBoss Hibernate framework. 2.3.1
ORM framework
Z historick´eho pohledu existuje v´ıce pˇr´ıstup˚ u, jak ˇreˇsit probl´em s napojen´ım perzistentn´ı vrtsvy k javovsk´e aplikaci. Probl´em spoˇc´ıv´a v tom, ˇze zde m´ame dva pˇr´ıstupy - OOP pˇr´ıstup pouˇz´ıvan´ y v Javˇe a relaˇcn´ı pˇr´ıstup pouˇz´ıvan´ y v relaˇcn´ıch datab´az´ıch. JDBC - vlastn´ı implementace perzistentn´ı vrstvy pomoc´ı JDBC API, z´apis SQL dotaz˚ u a metod DAO tˇr´ıd, kter´e volaj´ı pˇr´ısluˇsn´e procedury - pˇr´ıliˇs zdlouhav´e, dnes v podstatˇe pˇreˇzitek Serializace - ukl´ ad´ an´ı/naˇc´ıt´ an´ı serializovateln´ ych hierarchi´ı objekt˚ u do/z souboru nebo DB pracuje se jen s cel´ ymi hierarchiemi - nevhodn´e
ˇ SE ˇ KAPITOLA 2. RESER
23
Objektov´ a DB - pouˇzit´ı objektov´eho datab´azov´eho stroje, neexistuje jedin´e ˇreˇsen´ı, kter´e by se prosadilo v praxi EJB 2.x Entity Beany - starˇs´ı EJB 2.x specifikace, v 3.0 se pˇreˇslo k ORM-based JPA Dlouhou dobu pˇrevl´ adal prvn´ı zp˚ usob, aˇz do doby, kdy se objevily prvn´ı ORM n´astroje jako Castor a Hibernate. ORM je mapovac´ı n´astroj mezi OOP svˇetem, kde z´akladn´ı nosiˇc dat je tˇr´ıda, vztah mezi objekty je definov´ an kompozic´ı a je vˇzdy jednosmˇern´ y, a mezi svˇetem relaˇcn´ı DB, kde nosiˇcem dat je tabulka, sloupce definuj´ı atributy entity a vztah (relace) mezi tabulkami je definov´ an pomoc´ı ciz´ıch kl´ıˇc˚ u nebo vztahov´e tabulky a je vˇzdy obousmˇern´ y. ORM mapuje tˇr´ıdy na tabulky, jejich instance na jednotliv´e ˇr´adky tabulky. 2.3.2
JPA
Java Persistence API je dneˇsn´ı standard, kter´ y popisuje z´akladn´ı myˇslenky ORM. Hibernate 3.x byl pˇreps´ an, aby odpov´ıdal tomuto standardu. Jako referenˇcn´ı implementace byl zvolen framework Oracle Toplink Essentinals. Mezi dalˇs´ı hlavn´ı ORM ˇreˇsen´ı patˇr´ı: • Oracle Toplink - komerˇcn´ı • Apache iBATIS - opensource • JBoss Hibernate - opensource ˇ sen´ı pouˇz´ıvaj´ıc´ı JPA jej berou jako z´akladn´ı bal´ık vlastnost´ı, budeme-li pouˇz´ıvat ˇcistˇe Reˇ samotn´e JPA, teoreticky bychom mohli vymˇenit jeden ORM framework za jin´ y bez zmˇeny jedin´e ˇr´adky k´odu. JPA bohuˇzel neˇreˇs´ı vˇsechny probl´emy a tak jsou frameworky nuceny pouˇz´ıvat vlastn´ı nestandardn´ı ˇreˇsen´ı. 2.3.3
Mapov´ an´ı
Vlastn´ı mapov´ an´ı z objekt˚ u na tabulky zapisujeme v JPA pomoc´ı anotac´ı. U Hibernate se tak´e m˚ uˇzeme setkat se z´ apisem metadat pomoc´ı XML nebo XDoclet. Zde uk´aˇzu jen prvn´ı zp˚ usob, protoˇze se jedn´ a o standard. Anotace zapisujeme bud’to pˇred atribut tˇr´ıdy, kde framework pouˇz´ıv´ a pro pˇr´ıstup k poloˇzk´am Java Reflection API (gettery/settery nemus´ı b´ yt v˚ ubec implementov´ any), nebo do getteru JavaBean tˇr´ıdy, kdy framework pro pˇr´ıstup pouˇz´ıv´ a vol´an´ı getter˚ u/setter˚ u[19]. JavaBeans pˇr´ıstup je nejˇcastˇejˇs´ı. @Entity public class Customer implements Serializable { private Integer id; private String name; @Id @GeneratedValue public Integer getId() {return id;} public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name;} } @Entity public class Producer implements Serializable { private Integer id; private String name; private List customers;
ˇ SE ˇ KAPITOLA 2. RESER
24
@OneToMany public List getCustomers() {return customers;} public void setCustomers(List customers) {this.customers = customers;} @Id @GeneratedValue public Integer getId() {return id;} public void setId(Integer id) {this.id = id;} public String getName() {return name;} public void setName(String name) {this.name = name;} }
2.3.4
Anotace Mezi z´ akladn´ı anotace patˇr´ı:
@Entity - vlastn´ı oznaˇcen´ı objektu, kter´ y chceme mapovat @Id - oznaˇcen´ı prim´ arn´ıho kl´ıˇce @OneToOne - vyj´ adˇren´ı vztahu 1 : 1 @OneToMany - vztah 1 : N @ManyToOne - vztah N : 1 @ManyToMany - vztah M : N @Transient - metoda (poloˇzka) nebude nijak mapov´ana @MappedSuperClass - pro danou tˇr´ıdu se nevytv´aˇr´ı tabulka, pouˇzito u abstraktn´ıch tˇr´ıd, kdy potomci dˇed´ı jejich anotace @Emeddable - pro danou tˇr´ıdu se nevytv´aˇr´ı tabulka, poloˇzky tˇr´ıdy se pˇripoj´ı k jin´e jiˇz exisuj´ıc´ı tabulce, pomoc´ı n´ asleduj´ıc´ı anotace ve vztahu 1 : 1 nebo N : 1 (JPA neum´ı vztah 1 : N s @Emeddable entitou)11 @Embedded - poloˇzky tˇr´ıdy @Emeddable se pˇripoj´ı ke tˇr´ıdˇe @Version - oznaˇcen´ı atributu k optimistick´emu zamyk´an´ı Uveden´e anotace maj´ı mnoho nepovinn´ ych parametr˚ u, kter´e zde v u ´vodu nebudu popisovat. Vedle JPA anotac´ı podporuje Hibernate sv´e vlastn´ı @org.hibernate.anotations.xxx, kter´e pouˇzijeme, pokud potˇrebujeme cokoliv nestandardn´ıho. 2.3.5
Pr´ ace s objekty
JPA pro pr´ aci s objekty pouˇz´ıv´ a rozhran´ı EntityManager, s Hibernatem vˇsak ˇcastˇeji pracujeme starˇs´ım zp˚ usobem a to pomoc´ı Session. Jak vid´ıme na obr´ azku 2.7, novˇe vytvoˇren´ y objekt, kter´ y jeˇstˇe nem´a v datab´azi reprezentaci, je tranzientn´ı, jeho ID je zat´ım = 0. Pokud ho uloˇz´ıme, pˇrejde objekt do perzistentn´ıho stavu, tj. stavu, kde se reprezentace objektu v pamˇeti a v datab´azi shoduj´ı. Pokud objekt mˇen´ıme v t´e sam´e session, zmˇeny se okamˇzitˇe prom´ıtnou do datab´aze. Pokud se sessiona zavˇre, pˇrejdeme do Detached stavu, kdy se obˇe reprezentace neshoduj´ı. Po uloˇzen´ı Detached objektu se stane znovu perzistentn´ı. Perzistentn´ı objekt m˚ uˇzeme vymazat, Detached objekt po uzavˇren´ı session putuje do garbage collectoru. 11
Hibernate m´ a pro tento speci´ aln´ı pˇr´ıpad anotaci @org.hibernate.annotations.CollectionOfElements
ˇ SE ˇ KAPITOLA 2. RESER
25
Obr´azek 2.7: Stavy objektu a pˇrechody pomoc´ı metod persistent manageru, zdroj: [17]
2.3.6
Dotazy
Velkou v´ yhodou ORM je, ˇze dotazy p´ıˇseme v˚ uˇci objektov´e reprezentaci. Vlastn´ı relaˇcn´ı reprezentace n´ as ve vˇetˇsinˇe pˇr´ıpad˚ u dokonce nezaj´ım´a. 2.3.6.1
HQL API
HQL je podobnˇe jako standardn´ı JPQL urˇcen k dotaz˚ um do datab´aze. Syntax je podobn´a klasick´emu SQL s t´ım rozd´ılem, ˇze se pt´ame na objekty. Jako pˇr´ıklad uvedu jednoduch´ y HQL dotaz. SessionFactory factory = new Configuration().buildSessionFactory(); Session session = factory.openSession(); Transaction tx = session.beginTransaction(); Query cutomerQuery = session.find("SELECT Customer FROM Producer AS p " + "WHERE p.customer.name = :customerName", "mira"); List customers = customerQuery.list(); tx.commit(); session.close();
Takov´ yto kus k´ odu m˚ uˇzeme pouˇz´ıt kdekoliv v aplikaci s nakonfigurovan´ ym Hibernatem. Vˇsimnˇeme si, jak z´ısk´ av´ ame instanci Session a ˇze sami ˇr´ıd´ıme transakce. Takov´ yto zp˚ usob se oznaˇcuje jako programov´ y (programmatic). Existuje jeˇstˇe jin´ y pˇr´ıstup k transakc´ım - deklarativn´ı, kter´ y ukazuji v kapitole 2.3.8.1. 2.3.6.2
Criteria API
Hibernate vˇsak podporuje jeˇstˇe jin´ y zp˚ usob dotazov´an´ı - Criteria API. Dotazy zapisujeme pomoc´ı pˇrid´ av´ an´ı restrikc´ı k dan´emu krit´eriu. N´asleduj´ıc´ı pˇr´ıklad vrac´ı stejn´ y v´ ysledek jako HQL dotaz v minul´em odstavci. SessionFactory factory = new Configuration().buildSessionFactory();
Criteria se rozhodnˇe nehod´ı na vˇsechno. Jsou dotazy, kter´e pomoc´ı krit´eri´ı zapsat nejdou. Na druhou stranu jsou dotazy, kter´e pomoc´ı HQL vypadaj´ı sloˇzitˇe, a s krit´erii jdou zapsat velmi snadno. 2.3.7
Lazy, Eager loading
Hibernate podporuje v´ yznamnou optimalizaci datab´azov´ ych dotaz˚ u t´ım, ˇze m´ısto objektu samotn´eho vrac´ı tzv. dynamickou proxy. Proxy je potomek dan´e tˇr´ıdy, kter´ y je vytv´aˇren v runtimu pomoc´ı frameworku CGLIB. Proxy realizuje odloˇzen´e nahr´av´an´ı poloˇzek v objektu, od kter´eho dˇed´ı. Pokud objekt obsahuje reference na dalˇs´ı objekt, tato data se v dotazu nevr´at´ı. Defaultnˇe je vybr´ ana pr´ avˇe tato strategie. Probl´em nast´av´a v pˇr´ıpadˇe, ˇze chceme pˇristoupit k properties proxy v dobˇe, kdy se uˇz zavˇrela hibernate Session. Opakem t´eto strategie je Eager loading, pomoc´ı kter´e se nahraj´ı property do objektu vr´acen´eho v dotazu. Strategii zapisujeme pro kaˇzdou property v entitˇe pomoc´ı parametru k anotaci fetch = FetchType.EAGER. Tohoto se d´a vyuˇz´ıt jen v jednoduch´ ych dotazech, kter´e vrac´ı pˇr´ımo datab´ azov´e sloupce (poloˇzky). Dalˇs´ı moˇznost´ı jak “doloadovat” property k proxy je znovu tento objekt nahr´at do aktu´aln´ı session pomoc´ı metody refresh(). Existuje jeˇstˇe jin´ y zp˚ usob, kter´ y by se nemˇel zneuˇz´ıvat, pouˇz´ıt statickou metodu Hibernate.initialize(proxy), kterou zavol´ame bezprostˇrednˇe po dotazu v t´e sam´e session. 2.3.8
Integrace se Springem
Spring nab´ız´ı velk´e kvalitativn´ı zlepˇsen´ı pr´ace s Hibernatem. Jak jsem psal v kapitole 2.2.7.3, jsou k tomu urˇceny tˇr´ıdy • org.springframework.orm.hibernate.HibernateTemplate • org.springframework.orm.hibernate.support.HibernateDaoSupport 2.3.8.1
Deklarativn´ı transakce
V programov´em pˇr´ıstupu k transakc´ım se st´ale opakuje ten sam´ y k´od - z´ıskat session, otevˇr´ıt session, vytvoˇrit transakci...potvrdit transakci, uzavˇr´ıt session. Vˇse ˇreˇs´ı tˇr´ıda HibernateDaoSupport, kdy tento k´ od prov´ad´ı pˇred a po jak´ekoliv metodˇe v jeho potomkovi pomoc´ı Spring AOP. Pˇri pouˇzit´ı Hibernatu v prostˇred´ı webu se nejˇcastˇeji vyuˇz´ıv´a n´avrhov´eho vzoru Open Session in View, kdy je Hibernate session otevˇrena po celou dobu HTTP requestu. Jin´ y pˇr´ıstup je, ˇze v kaˇzd´e volan´e metodˇe se otev´ır´ a session nov´a. Open Session in View ˇreˇs´ı probl´em s lazy loadingem, kdy se tyto properties do proxy doloadov´avaj´ı pˇri renderov´an´ı view.12 Nyn´ı m´ ame dostateˇcn´e znalosti k tomu napsat implementaci rozhran´ı IOfferDao. 12 Avˇsak pokud naˇcteme objekt z datab´ aze pˇri jednom requestu, ale pouˇzijeme ho v jin´em requestu (v jin´e session), mus´ıme ho ruˇcnˇe do aktu´ aln´ı session nahr´ at.
ˇ SE ˇ KAPITOLA 2. RESER
27
public class OfferHibernateDao extends HibernateDaoSupport implements IOfferDao { public void saveOrUpdate(Offer offer) { getHibernateTemplate().saveOrUpdate(offer); } public Offer getOfferById(long id) { return (Offer) getHibernateTemplate().get(Offer.class, id); } public Offer getOfferBy(Inzerent inzerent) { List offers = getHibernateTemplate().findByNamedParam("from Offer as offer" + "where offer.inzerent.id = :userId ", "userId", inzerent.getId()); return (Offer) offers.get(0); } public void delete(Offer offer) { getHibernateTemplate().delete(offer); } public List getTopItems(int maxResults) { DetachedCriteria criteria = DetachedCriteria.forClass(Offer.class); criteria.addOrder(Order.desc("updated")); return getHibernateTemplate().findByCriteria(criteria, 0, maxResults); } }
2.4
Extr´ emn´ı programov´ an´ı
Extr´emn´ı programov´ an´ı nebo zkr´acenˇe XP je odlehˇcen´a discipl´ına v´ yvoje softwaru. Jako jednu z agiln´ıch metodik j´ı m˚ uˇzeme popsat manifestem agiln´ıho pˇr´ıstupu[10], kdy • m´a funguj´ıc´ı software pˇrednost pˇred obs´ahlou dokumentac´ı, • maj´ı individuality a interakce pˇrednost pˇred procesy a n´astroji, • m´a spolupr´ ace se z´ akazn´ıkem pˇrednost pˇred sjedn´av´an´ım smluv, • a nakonec reakce na zmˇenu m´a pˇrednost pˇred plnˇen´ım pl´anu. XP se navrhuje nasadit na projektov´e t´ ymy o menˇs´ı aˇz stˇredn´ı velikosti, kter´e potˇrebuj´ı vyv´ıjet software rychle a v prostˇred´ı, kde se ˇcasto mˇen´ı z´akazn´ıkovy poˇzadavky. 2.4.1
Historie
XP vytvoˇril roku 1996 v pr˚ ubˇehu sv´eho projektu pro firmu Chrysler Kent Beck, poprv´e vˇec publikoval spolu s Erichem Gammou v knize Extreme Programming Explained roku 1999. Kv˚ uli tomu, ˇze obracelo sw v´ yvoj “naruby”, se XP stalo velmi popul´arn´ı. 2.4.2
Role
Z hlediska XP rozdˇelujeme vˇsechny zainteresovan´e osoby do jedn´e ze tˇr´ı z´akladn´ıch rol´ı[9], kter´e mohou (ale nemus´ı) odpov´ıdat pˇr´ımo jednotliv´ ym osob´am. • Z´akazn´ık - P´ıˇse, vykl´ ad´ a uˇzivatelsk´e pˇr´ıbˇehy, m˚ uˇze a nemus´ı b´ yt koncov´ y uˇzivatel. • Program´ ator - Odhaduje ˇcas potˇrebn´ y na implementaci uˇzivatelsk´ ych pˇr´ıbˇeh˚ u, implementuje testy a produkˇcn´ı k´ od. • Tester - Implementuje funkˇcn´ı, integraˇcn´ı testy.
ˇ SE ˇ KAPITOLA 2. RESER
28
Obr´ azek 2.8: Pr˚ ubˇeh v´ yvoje v XP
2.4.3
Z´ akladn´ı postupy
Celou metodiku m˚ uˇzeme v podstatˇe shrnout do n´asleduj´ıc´ıch 12ti extr´emn´ıch pˇr´ıstup˚ u. ˇ ast z nich klidnˇe uplatn´ıme i pokud pracujeme sami. C´
2.4.3.1
Pl´ anovac´ı hra
Z´akazn´ık pˇripravuje uˇzivatelsk´e pˇr´ıbˇehy (user stories, podobn´ y v´ yznam jako use case), kter´e z´akazn´ık po konzultaci s program´ atory rozdˇel´ı do jednotliv´ ych iterac´ı (release planning). ˇ Casto jsou k tomu pouˇzity CRC karty (CRC cards, story cards), coˇz jsou pap´ırov´e kartiˇcky tak o velikosti vizitky, kde na kaˇzd´e z nich je jeden uˇzivatelsk´ y pˇr´ıbˇeh. Program´atoˇri pomoc´ı nich odhadnou pˇribliˇznou ˇcasovou sloˇzitost implementace uˇzivatelsk´eho pˇr´ıbˇehu (lepˇs´ı pouˇz´ıvat sofistikovanˇejˇs´ı sw n´ astroj - tracker). Z´ akazn´ık rozhodne, kter´e pˇr´ıbˇehy maj´ı pro nˇej vˇetˇs´ı byznys hodnotu a tak rozhoduje, ve kter´e iteraci je chce vidˇet.
2.4.3.2
Mal´ e iterace
Jak vid´ıme na obr´ azku 2.8, z´ akladn´ı motto m˚ uˇze zn´ıt: “Uvolˇ nuj rychle, uvolˇ nuj ˇcasto!”. Bˇehem jedn´e iterace, kter´ a trv´ a nejˇcastˇeji 1-4 t´ ydny, se uvolˇ nuje produkˇcn´ı verze sw s nov´ ymi vlastnostmi, kter´ a m˚ uˇze b´ yt okamˇzitˇe nasazena. Z´akazn´ık tak nejrychleji dostane poˇzadovanou funkcionalitu. Pokud z´ akazn´ık nevid´ı pokrok, m˚ uˇze projekt zruˇsit. Takov´ yto v´ yvoj se nˇekdy oznaˇcuje jako Rapid Application Development nebo zkr´acenˇe RAD.
2.4.3.3
Metafora
V´ yvoj´aˇri ˇcasto definuj´ı v´ ystiˇzn´e pˇrirovn´an´ı, metaforu, jak dan´ y syst´em m´a vypadat a co m´a dˇelat. Soubor tˇr´ıd a n´ avrhov´ ych vzor˚ u, kter´e odpov´ıdaj´ı dan´emu byznys probl´emu a ˇreˇsen´ı, pom´ah´a ˇclen˚ um t´ ymu v komunikaci o syst´emu.
ˇ SE ˇ KAPITOLA 2. RESER 2.4.3.4
29
Testov´ an´ı
V XP se ˇr´ıd´ıme metodikou programov´an´ı ˇr´ızen´e testy (test-driven development, testfirst design). Za prv´e, test p´ıˇseme pˇred vlastn´ı implementac´ı. Metodikou TDD pˇrid´av´ame novou vlastnost do syst´emu v 5ti f´ az´ıch: 1. Rychle pˇridat test. 2. Spustit vˇsechny testy, at’ vid´ıme, jestli test dopadne ˇspatnˇe. Pokud nov´ y test nepad´ a, je naps´ an ˇspatnˇe. 3. Prov´est drobnou zmˇenu. 4. Spustit vˇsechny testy, at’ vid´ıme, jak vˇse dopadne dobˇre. 5. Refaktorov´ an´ım odstranit duplicitu. Jednotkov´e a funkˇcn´ı testy nahrazuj´ı specifikaci. Pokud testy neproch´az´ı, mus´ı se k´ od opravit. V´ıce o programov´ an´ı ˇr´ızen´em testy nalezneme v [16]. 2.4.3.5
Jednoduch´ y n´ avrh
Software vyv´ıjen´ y pomoc´ı XP m´a zpravidla nejjednoduˇsˇs´ı design, kter´ y z´aroveˇ n splˇ nuje z´akazn´ıkovy poˇzadavky. Pomoc´ı TDD a refaktorov´an´ı je implementov´ano opravdu jen to, co je tˇreba. Jednoduch´ y design je z´ akladem snadn´eho pˇrid´av´an´ı dalˇs´ıch vlastnost´ı a udrˇzov´ an´ı syst´emu. 2.4.3.6
Refaktorov´ an´ı
Pomoc´ı refaktorov´ an´ı dok´ aˇzeme udrˇzet architekturu syst´emu ˇcistou, jednoduchou, kter´ a vˇzdy odpov´ıd´ a poˇzadavk˚ um na syst´em. Refaktorov´an´ım restrukturalizujeme k´od bez zmˇeny jeho funkcionality, dˇel´ ame k´ od ˇcitelnˇejˇs´ı, pˇrehlednˇejˇs´ı, l´epe odpov´ıdaj´ıc´ı aktu´aln´ımu ch´ap´ an´ı syst´emu. Pˇred refaktorov´ an´ım spust´ıme vˇsechny testy, refaktorujeme po mal´ ych kroc´ıch a pot´e znovu spust´ıme vˇsechny testy, abychom se ujistili, ˇze syst´em funguje stejnˇe. V´ıce o refaktorov´ an´ı nalezneme v [18]. 2.4.3.7
P´ arov´ e programov´ an´ı
P´ arov´e programov´ an´ı znamen´a dva lidi u jednoho poˇc´ıtaˇce s jednou kl´avesnic´ı. Produkuje lepˇs´ı k´ od neˇz pˇri pr´ aci kaˇzd´eho zvl´aˇst’, prob´ıh´a neust´al´a revize k´odu, lepˇs´ı porozumˇen´ı co syst´em dˇel´ a. Vˇsechen produkˇcn´ı k´od by mˇel b´ yt tvoˇren p´arov´an´ım. Tak´e vhodn´e pro v´ yuku (slabˇs´ı se uˇc´ı od zdatnˇejˇs´ıch a naopak). 2.4.3.8
Kolektivn´ı vlastnictv´ı k´ odu
Veˇsker´ y k´ od patˇr´ı vˇsem v´ yvoj´aˇr˚ um. Minimalizuje prodlevy v ˇreˇsen´ı probl´em˚ u. Kdokoliv potˇrebuje zmˇenu v k´ odu, kter´ y nenapsal, tak jednoduˇse udˇel´a. 2.4.3.9
40-ti hodinov´ y pracovn´ı t´ yden Unaven´ y v´ yvoj´ aˇr p´ıˇse mizern´ y k´od, dˇel´a v´ıce chyb.
ˇ SE ˇ KAPITOLA 2. RESER
30 2.4.3.10
Z´ akazn´ık na spr´ avn´ em m´ıstˇ e (On-site customer)
Z´akazn´ık by mˇel aktivnˇe pˇrisp´ıvat do v´ yvoje. Vykl´ad´a uˇzivatelsk´e pˇr´ıbˇehy, ovlivˇ nuje poˇzadavky, nastavuje priority, zodpov´ıd´ a ot´azky od v´ yvoj´aˇr˚ u. Dokumentace je m´enˇe v tiˇstˇen´e podobˇe, je sp´ıˇse sd´ılena vˇsemi ˇcleny t´ ymu. 2.4.3.11
Standardn´ı podoba k´ odu
Jelikoˇz kaˇzd´ y m˚ uˇze editovat cokoliv, je d˚ uleˇzit´e zav´est urˇcit´ y standard v psan´ı k´odu, kter´ y by mˇeli vˇsichni ˇclenov´e t´ ymu dodrˇzovat. 2.4.3.12
Sjednocen´ e pracovn´ı prostˇ red´ı
Usnadˇ nuje pr´ aci v p´ aru. Jsou nastaveny jednotn´e kl´avesov´e zkratky a podobnˇe.
2.5
Testovac´ı framework JUnit
JUnit je n´ astroj pro jednotkov´e (unit) testov´an´ı v Javˇe, jehoˇz autoˇri jsou Kent Beck a Erich Gamma. Vych´ az´ı z p˚ uvodn´ıho n´ astroje SUnit pro Smalltalk napsan´eho Kentem Beckem pˇri pˇredstaven´ı XP. Je stˇeˇzejn´ım prostˇredkem TDD. Dnes pˇrevl´ adaj´ı 2 major verze tohoto testovac´ıho frameworku, a to JUnit 3.x (3.8.1) a JUnit 4.x (4.4). Pˇredstav´ım zde obˇe verze, zaˇcneme st´ale nejrozˇs´ıˇrenˇejˇs´ı verz´ı 3.x. 2.5.1 2.5.1.1
JUnit 3.x Architektura
• Soubor test˚ u se skl´ ad´ a ze soubor˚ u testovac´ıch sad, tj. tˇr´ıd, kter´e dˇed´ı od pˇredka junit.framework.TestCase. Testovac´ı sada obsahuje testy. • Testy jsou metody public void, pokud je oznaˇc´ıme poˇc´ateˇcn´ım slovem test, napˇr. public void testAddTwoIntegers(), test se spust´ı automaticky. Kaˇzd´ y test by mˇel b´ yt spuˇstˇen nez´avisle na ostatn´ıch a neovlivˇ novat je. • private void setUp() - provede se pˇred kaˇzd´ ym testem, nastavuje stejn´e prostˇred´ı, stejn´a testovac´ı data • private void tearDown() - provede se bezprostˇrednˇe po skonˇcen´ı kaˇzd´eho testu, ukl´ız´ı pro dalˇs´ı test, zav´ır´ a datov´e proudy 2.5.1.2
Assert
Pomoc´ı pˇredpoklad˚ u (assert˚ u) definujeme jak se m´a testovan´ y k´od zachovat. Jsou implementov´any ve tˇr´ıdˇe junit.framework.Assert, vˇsechny maj´ı hlaviˇcku public static void. V testovac´ı sadˇe je p´ıˇseme bez pˇr´ıstupu ke tˇr´ıdˇe. • assertTrue(boolean expression) - oˇcek´av´a true, • assertFalse(boolean expression) - oˇcek´av´a false, • assertNotNull(Object object) - oˇcek´av´a nenullovou instanci, • assertNull(Object object) - oˇcek´av´a nullovou instanci, • assertEquals(Object x, Object y) - oˇcek´av´a rovnost na equals() objekt˚ u x a y,
ˇ SE ˇ KAPITOLA 2. RESER
31
• assertSame(Object x, Object y) - oˇcek´av´a, ˇze maj´ı objekty x a y stejnou pamˇet’ovou referenci, • assertNotSame(Object x, Object y) - oˇcek´av´a, ˇze nemaj´ı objekty x a y stejnou pamˇet’ovou referenci, • fail() - skonˇc´ı test chybou, Vˇsechny uveden´e metody maj´ı jeˇstˇe jeden nepovinn´ y parametr String message, kter´ y se zobraz´ı pokud assert selˇze. 2.5.1.3
Integrace s IDE
Pˇri TDD je d˚ uleˇzit´e, abychom testy pouˇstˇeli ˇcasto a byly co nejv´ıce automatizovan´e. K tomu n´ am pom´ ah´ a integrace s v´ yvojov´ ym prostˇred´ım. JUnit je standardn´ı souˇc´ast´ı bˇeˇzn´ ych n´astroj˚ u jako Eclipse, Netbeans a IntelliJ Idea. 2.5.2
JUnit 4.x
Tento odstavec by mˇel pokr´ yt poˇc´ateˇcn´ı u ´vod do nov´e ˇrady JUnitu, kter´ y se hodnˇe inspiroval frameworky TestNG, Popper a JMock. Pˇredstav´ım hlavn´ı zmˇeny oproti minul´e verzi a vˇse uk´aˇzu na pˇr´ıkladu, kter´ y jsem pˇrevzal z [8]. 2.5.2.1
Hlavn´ı zmˇ eny oproti 3.x
• Testovac´ı sada nemus´ı dˇedit od junit.framework.TestCase. • Testy nemus´ı zaˇc´ınat prefixem “test”, m´ısto toho jsou oznaˇceny anotac´ı @Test. • M´ısto metod setUp() a tearDown() se pˇred a po kaˇzd´em testu volaj´ı metody oznaˇcen´e anotacemi @Before a @After. • Pokud potˇrebujeme spustit metodu v r´amci testovac´ı sady jen jednou pˇri startu a jednou na konci, oznaˇc´ıme metodu anotacemi @BeforeClass a @AfterClass. • Anotace @Test m˚ uˇze m´ıt parameter Timeout, kter´ y kdyˇz prov´adˇen´ı testu pˇrekroˇc´ı, test selˇze. • Spustiteln´e jen od JDK 5. 2.5.2.2
assertThat
Konstrukt assertThat() doplˇ nuje a rozˇsiˇruje pouˇzit´ı pˇredpoklad˚ u z 3.x, doslovn´ y pˇreklad m˚ uˇze zn´ıt “oˇcek´ av´ am ˇze” a takto se i zapisuje. Prvn´ı argument je objekt, na kter´ y prov´ad´ıme pˇredpoklad. Druh´ y parametr je tzv. matcher, tj. funkce, kterou formulujeme pˇredpoklad. assertThat(something, eq("Hello")); assertThat(something, isA(Color.class)); assertThat(something, contains("World")); assertThat(myList, hasItem("3")); assertThat(something, not(contains("Cheese"))); assertThat(responseString, either(containsString("color")) .or(containsString("colour")))
ˇ SE ˇ KAPITOLA 2. RESER
32 2.5.2.3
Pˇ redpoklady a teorie
Pˇredpoklady slouˇz´ı k explicitn´ımu vyj´adˇren´ı podm´ınek, za kter´ ych mus´ı test proj´ıt, obecnˇe z´avislost´ı mimo rozsah vlastn´ıho testu. Teorie se skl´ad´a ze vstupn´ıch testovac´ı dat, pˇredpokladu, za kter´eho je schopen test s daty pracovat a vlastn´ıho testu. D´ıky teorii m˚ uˇzeme ˇr´ıci, za pˇredpokladu tˇechto vstupn´ıch podm´ınek se testovan´ y k´od mus´ı chovat takto. D´ıky podm´ınk´am je moˇzn´e nechat JUnit generovat vstupn´ı data, kter´a jdou za rozsah toho, jak bychom oˇcek´avali nebo toho, co by bylo pracn´e vyj´adˇrit klasicky. @RunWith(Theories.class) public class UserTest { @DataPoint public static String GOOD_USERNAME = "optimus"; @DataPoint public static String USERNAME_WITH_SLASH = "optimus/prime"; @Theory public void filenameIncludesUsername(String username) { assumeThat(username, not(containsString("/"))); assertThat(new User(username).configFileName(), containsString(username)); } }
Anotace @DataPoint definuje vstupn´ı data teorie. Spouˇstˇeˇc testu s´am zad´av´a do testovac´ı metody filenameIncludesUsername vˇsechny kompatibiln´ı veˇrejn´e promˇenn´e oznaˇcen´e anotac´ı @DataPoint (shoduj´ı se typy promˇenn´e a parametru). Pˇredpoklad je vyj´adˇren´ y pomoc´ı assertThat. Tato teorie by se dala pˇreloˇzit do ˇceˇstiny asi takto:“Za pˇredpokladu, ˇze username neobsahuje /, mus´ı platit, ˇze jm´eno konfiguraˇcn´ıho souboru uˇzivatele obsahuje jeho jm´eno.”
2.6
Testovac´ı framework JMock
V t´eto kapitole pˇredstav´ım d˚ uvody, kdy je vhodn´e pouˇz´ıt mockov´an´ı, pˇredstav´ım jeden z nejpouˇz´ıvanˇejˇs´ıch mockovac´ıch framework˚ u v Javˇe. Z´akladn´ı syntaxi a pouˇzit´ı uk´aˇzu na kr´atk´em pˇr´ıkladu. 2.6.1
Mockov´ an´ı, Mock objekty
Vˇsude tam, kde je klasick´e unit testov´an´ı obt´ıˇzn´e, pomal´e nebo se jedn´a o takˇrka neˇreˇsiteln´ y probl´em, si mus´ıme poradit, jak danou vˇec otestovat. Napˇr´ıklad, pokud testovan´a tˇr´ıda: • produkuje nedeterministick´e v´ ysledky (aktu´aln´ı ˇcas, aktu´aln´ı teplota), • m´a stavy, kter´e je tˇeˇzk´e vytvoˇrit nebo reprodukovat (s´ıt’ov´a chyba), • m´a pomalou odezvu (inicializace kompletn´ıho DB spojen´ı), • jeˇstˇe neexistuje nebo bude mˇenit chov´an´ı, • bude muset obsahovat rozd´ıln´e informace pro testy a pro re´aln´e nasazen´ı. [11] Mock objekt je faleˇsn´ y z´ astupce, kter´ y se navenek chov´a stejnˇe jako jeho re´aln´ y protˇejˇsek. M´ame na v´ ybˇer z v´ıce implementac´ı: • implementovat faleˇsnou tˇr´ıdu (fake) s oˇcek´avanou vlastn´ı implementac´ı, • pouˇz´ıt pˇredem vytvoˇren´e faleˇsn´e implementace zn´am´ ych tˇr´ıd (napˇr. HttpServletRequest), Spring nab´ız´ı mock implementace k bˇeˇzn´ ym komponent´am syst´emu (MockHttpServletRequest),
ˇ SE ˇ KAPITOLA 2. RESER
33
• pouˇz´ıt statick´ y mock creator, napˇr. MockCreator, kter´ y vytvoˇr´ı kostru tˇr´ıdy implementuj´ıc´ı testovan´e rozhran´ı, • pouˇz´ıt dynamick´ y mock creator, kter´ y za bˇehu v testu vytvoˇr´ı objekt implementuj´ıc´ı testovan´e rozhran´ı. Pˇredstaviteli t´eto kategorie jsou JMock, EasyMock a rMock. 2.6.2
Architektura
Spouˇstˇen´ı JMock testu se liˇs´ı podle pouˇzit´e verze JUnitu. Pˇredstav´ım pouˇz´ıvanˇejˇs´ı verzi s JUnit 3.x. • Testovac´ı sada dˇed´ı od abstraktn´ı tˇr´ıdy MockTestCase. • Mock objekt vytv´ aˇr´ıme pomoc´ı metody mock(Clazz.class). • K objektu, kter´ y napodobuje, se dostaneme vˇzdy pomoc´ı mockObject.proxy(). 2.6.3
Pˇ r´ıklad
Pˇredstavme si jednoduch´ y pˇr´ıklad [7]. Vyv´ıj´ıme software pro nˇejakou bankovn´ı spoleˇcnost. Budeme potˇrebovat rozhran´ı Account, kter´e zaobaluje informace o u ´ˇctech a rozhran´ı AccountManager, kter´e nad nimi prov´ ad´ı operace. public interface AccountManager { void transfer(Account from, Account to, double amount) throws OutOfMoneyException; } public interface Account { void charge(double amount) throws OutOfMoneyException; void deposit(double amount); }
Banka je chamtiv´ a. Kdykoliv je proveden pˇrevod z jednoho u ´ˇctu na druh´ y, strhne se 10 Kˇc + 10% z pˇrev´ adˇen´e ˇc´ astky. Tyto pen´ıze se pˇrip´ıˇsou na bonusov´ y u ´ˇcet banky, kde si je bankovn´ı manaˇzeˇri rozdˇel´ı. Naimplementujeme chov´an´ı tˇr´ıdy AccountManagerImpl pomoc´ı TDD, tedy nap´ıˇseme test pˇred vlastn´ı implementac´ı. public class AccountManagerImplTest extends MockObjectTestCase { public void testTransferWithoutOvercharging() throws Exception { Mock mockAccount1 = mock(Account.class); mockAccount1 .expects(once()) .method("charge") .with(eq(100.0 * 1.10 + 10.0));
Vytvoˇrili jsme nov´ y Mock objekt pomoc´ı JMock, kter´ y bude pozdˇeji pouˇzit jako zdrojov´ yu ´ˇcet. Ve drud´e ˇr´ adce jsme pˇridali pˇredpoklad. Oˇcek´av´ame, ˇze AccountManagerImpl zavol´a jednou metodu charge() s argumentem rovn´ ym 100*1.1 + 10 = 120. Mock mockAccount2 = mock(Account.class); mockAccount2 .expects(once()) .method("deposit") .with(eq(100.0)) .after(mockAccount1, "charge");
ˇ SE ˇ KAPITOLA 2. RESER
34
Vytvoˇrili jsme Mock objekt, kter´ y bude pouˇzit jako c´ılov´ yu ´ˇcet. Pˇredpokl´ad´ame, ˇze AccountManagerImpl bude volat metodu deposit() s argumentem rovn´ ym 100. Posledn´ı ˇr´adka ˇr´ık´a, ˇze bude metoda vol´ana aˇz po vol´ an´ı metody mockAccount1.charge(), ˇc´ımˇz nepˇrip´ıˇseme pen´ıze na c´ılov´ y u ´ˇcet, pokud se pˇredt´ım neodepsaly ze zdrojov´eho. Mock mockBonusAccount = mock(Account.class); mockBonusAccount .expects(once()) .method("deposit") .with(eq(100.0 * 0.10 + 10.0)) .after(mockAccount1, "charge");
Nakonec jsme vytvoˇrili Mock objekt pro bonusov´ yu ´ˇcet. AccountManagerImpl na nˇej pˇrip´ıˇse pen´ıze pomoc´ı metody deposit() s argumentem 100*0.1+10=20, kterou zavol´a aˇz po u ´spˇeˇsn´em odeps´an´ı penˇez ze zdrojov´eho u ´ˇctu. Account account1 = (Account) mockAccount1.proxy(); Account account2 = (Account) mockAccount2.proxy(); Account bonusAccount = (Account) mockBonusAccount.proxy();
Nyn´ı jednoduˇse z Mock objekt˚ u vytvoˇr´ıme u ´ˇcty AccountManagerImpl man = new AccountManagerImpl(bonusAccount); man.transfer(account1, account2, 100.0); } }
a nakonec vytvoˇr´ıme AccountManagerImpl, na kter´em zavol´ame metodu transfer(). V pr˚ ubˇehu cel´eho testu jsme nikde nevolali JUnit asserty. Aˇz test skonˇc´ı, pˇredek MockObjectTestCase pˇrekontruje zda vˇsechny zadan´e pˇredpoklady na Mock objekty byly splnˇeny. Pokud nebyly, pokud byla metoda vol´ana v jin´em poˇrad´ı nebo s jin´ ym typem argumentu, test skonˇc´ı s fail() vyj´ımkou AssertionFailedError.
KAPITOLA 3. IMPLEMENTACE
35
3 Implementace V reˇserˇsn´ı ˇc´ asti jsem uk´ azal z´aklady technologi´ı tvorby webov´ ych aplikac´ı na platformˇe Java, v t´eto ˇc´ asti uk´ aˇzu praktick´e aplikov´an´ı nabyt´ ych znalost´ı implementac´ı ˇc´asti port´alu pro hled´an´ı spolubydl´ıc´ıch.
3.1
Zad´ an´ı
Vylepˇsit st´ avaj´ıc´ı port´ al pro hled´an´ı spolubydlen´ı www.chcispolubydlici.cz o moˇznost inzerovat popt´ avku. Pomoc´ı t´eto nov´e funkcionality p˚ ujde v inzer´atech vyhled´avat. Pˇridat automatick´e maz´ an´ı inzer´ at˚ u a umoˇznit export inzer´at˚ u do form´atu RSS.
3.2
Anal´ yza
Uˇzivatel´e v anketˇe vyplnili nejpalˇcivˇejˇs´ı vˇeci, kter´e na port´alu postr´adaj´ı. Kromˇe pˇreloˇzen´ı do angliˇctiny je inzerov´ an´ı popt´avky na druh´em m´ıstˇe. Neimplementov´an´ı inzerov´ an´ı popt´avky mˇelo sv˚ uj d˚ uvod, lid´e nab´ızej´ıc´ı spolubydlen´ı vˇetˇsinou tuto kategorii inzer´at˚ u pˇr´ıliˇs nevyhled´ avaj´ı. Avˇsak niˇcemu neuˇskod´ı, pokud tato funkcionalita bude a sami si statisticky ovˇeˇr´ıme, jestli se n´ aˇs n´ azor potvrdil ˇci vyvr´atil. 3.2.1
Co uˇ z je naimplementov´ ano?
Port´ al se neust´ ale vyv´ıj´ı. Kdyˇz jsem poprv´e pˇriˇsel do firmy Simple Way s.r.o., port´ al uˇz byl nasazen a mˇeli jsme p˚ ulroˇcn´ı zkuˇsenosti s provozem. V´ yvoj aplikace do t´eto doby a nesazen´ı je v podstatˇe obsahem bakal´ aˇrsk´e pr´ace studenta Luboˇse Raˇcansk´eho. 3.2.2
Pr˚ uzkum trhu
Na internetov´em trhu se spolubydlen´ım vynikaj´ı pˇrev´aˇznˇe servery www.spolubydlici.cz, www.espolubydleni.cz a www.bydlim.com. Server www.spolubydlici.cz umoˇzn ˇuje inzerovat nab´ıdku i popt´avku, inzer´at nen´ı detailn´ı a tak i vyhled´ av´ an´ı inzer´ at˚ u postr´ad´a v´ ybˇer parametr˚ u, kter´e uˇzivatele zaj´ımaj´ı. Hled´an´ım ˇ jde omezit v´ ybˇer jen v Praze, Moravˇe a Slezsku a ve zbytku CR, pomoc´ı maxim´aln´ı ceny a pohlav´ı. Evidentnˇe je to nedostaˇcuj´ıc´ı a uˇzivatel mus´ı hled´an´ım str´avit zbyteˇcnˇe v´ıce ˇcasu. Navzdory tomu je nejpouˇz´ıvanˇejˇs´ım port´alem v t´eto kategorii. Server www.espolubydleni.cz je na tom o pozn´an´ı l´epe. Umoˇzn ˇuje inzerovat nab´ıdku i popt´avku, inzer´ at je v´ıce detailn´ı, jelikoˇz se server objevil po u ´spˇeˇsn´em nasazen´ı spolubydl´ıc´ıch, m˚ uˇzeme pˇredpokl´ adat, ˇze se j´ım nechal inspirovat. Adresa se zad´av´a pomoc´ı okresu z html selectu a mˇesta, kter´e je v html text area. Hled´an´ım lze omezit v´ ybˇer podle druhu domu, okresu a ceny n´ ajemn´eho od/do, coˇz opˇet nedostaˇcuje. Zaj´ımav´a funkce je napˇr´ıklad sumarizovan´ a ˇ pr˚ umˇern´a cena n´ ajmu v Praze, v Brnˇe a ve zbytku CR. Posledn´ı zm´ınˇen´ y server www.bydlim.com je sponzorov´an medi´aln´ımy partnery jako ˇıp, Annonce, 24hodin. Nasadil dokonce let´akovou kampaˇ atlas.cz, S´ n po Praze. Adresa je jen mˇesto a nav´ıc jen velk´e (cca. 11 krajsk´ ych mˇest). Vybrat lze druh bytu, jestli je dotyˇcn´ y student nebo kuˇr´ ak. Inzer´ at je m´enˇe detailn´ı neˇz v pˇredchoz´ım pˇr´ıpadˇe. Co jsem nikde nenaˇsel? Ohlednˇe lokalit, kde by chtˇel uˇzivatel bydlet, jsem nikde nevidˇel ˇ moˇznost zapsat lokalit v´ıce. Student CVUT napˇr´ıklad hled´a bydlen´ı pobl´ıˇz Dejvic, ˇcili P6 Dejvice, P6 Bubeneˇc nebo P6 Stˇreˇsovice. M˚ uˇze nastat dalˇs´ı pˇr´ıpad, uˇzivatel chce bydlet na
36
KAPITOLA 3. IMPLEMENTACE
Praze 1, ale je mu jedno v jak´e ˇctvrti a mimo to by se spokojil s Prahou 2 Nov´e Mˇesto. Nen´ı nic jednoduˇsˇs´ıho neˇz zadat nˇeco jako P1 nez´aleˇz´ı a P2 Nov´e Mˇesto. S podobnou logikou vˇeci bychom mohli zadat celou Prahu, popˇr. cel´ y kraj. 3.2.3
Prototypy
Po prozkoum´ an´ı trhu jsem vytvoˇril z´akladn´ı prototyp vkl´ad´an´ı nov´eho inzer´atu. UI prototypy pom´ ahaj´ı s ujasnˇen´ım poˇzadavk˚ u a k vyjasnˇen´ı pojm˚ u se z´akazn´ıkem.
Obr´ azek 3.1: Z´akladn´ı UI prototyp
V nov´em inzer´ atu n´ as zaj´ım´ a kromˇe poˇzadovan´ ych lokalit tak´e maxim´aln´ı pˇrijateln´a cena za bydlen´ı, datum odkdy se chce uˇzivatel nastˇehovat a na jak dlouho, jestli je student, kuˇr´ak, jestli m´a zv´ıˇre, velikost bytu, druh domu. D´ale pak poˇzadovan´e vybaven´ı bytu (internet, kabelov´a televize, telefon,..) a dalˇs´ı vybaven´ı v domu (ledniˇcka, praˇcka, mikrovlnka,...).
3.3
Implementace
V r´amci release pl´ anov´ an´ı jsem rozdˇelil vˇsechny uˇzivatelsk´e pˇr´ıbˇehy do requirement˚ u a ty do jednotliv´ ych iterac´ı. Jak jsem psal v kapitole 2.4.3.2, iterace by mˇela prob´ıhat 1-4 t´ ydny. Tam, kde to bylo moˇzn´e, jsem psal testy nejdˇr´ıve. Nˇekter´e ˇc´asti vznikaly p´arov´ ym programov´an´ım s vedouc´ım moj´ı pr´ ace Ing. Andrejem Zacharem a studentem Luboˇsem Raˇcansk´ ym. Veˇsker´ y k´od, kter´ y uv´ad´ım v t´eto ˇc´ asti, je v rozsahu nutn´em pro pochopen´ı dan´eho ˇreˇsen´ı. Cel´ y projekt je k dispozici na pˇriloˇzen´em CD. 3.3.1
Iterace 1 Do prvn´ı iterace jsem napl´ anoval implementaci z´akladn´ıho requirementu.
• Umoˇznit vkl´ ad´ an´ı nov´eho inzer´ atu.
KAPITOLA 3. IMPLEMENTACE
37
Podle zvyklosti pouˇz´ıvan´e u vkl´ad´an´ı nab´ıdky jsem u ´kon rozdˇelil na nˇekolik obrazovek. Na prvn´ı obrazovce se zad´ avaj´ı stˇeˇzejn´ı parametry inzer´atu, na druh´e obrazovce poˇzadovan´e lokality, na tˇret´ı obrazovce potom pˇrihlaˇsovac´ı u ´daje na server. Posledn´ı obrazovka slouˇz´ı pro rekapitulaci zadan´ ych u ´daj˚ u.
Obr´ azek 3.2: Vkl´ad´an´ı nov´eho inzer´atu V prvn´ı ˇradˇe potˇrebujeme datab´azovou reprezentaci inzer´atu a uˇzivatele, definujeme proto BO Demand a HomelessInzerent. Demand.java @Entity public class Demand extends AbstractBo { private static final long serialVersionUID = -4713748752937686298L; private HomelessInzerent homelessInzerent; private Set flatSizeTypeSet = new HashSet(); //and many other properties with appropriate getters and setters @OneToOne(cascade = { CascadeType.ALL }) public HomelessInzerent getHomelessInzerent() { return homelessInzerent; } public void setHomelessInzerent(HomelessInzerent homelessInzerent) { this.homelessInzerent = homelessInzerent; } @ManyToMany(fetch = FetchType.EAGER) public Set getFlatSizeTypeSet() { return flatSizeTypeSet; } public void setFlatSizeTypeSet(Set flatSizeType) { this.flatSizeTypeSet = flatSizeType;
38
KAPITOLA 3. IMPLEMENTACE
} }
HomelessInzerent.java @Entity public class HomelessInzerent extends Inzerent { private static final long serialVersionUID = -546574687654564L; }
Vytvoˇrili jsme BO, d´ ale mus´ıme d´at Hibernatu vˇedˇet, aby s nimi mohl pracovat. Pˇrid´ame je beanˇe sessionFactory do property annotatedClasses. core-service.java ... <property name="annotatedClasses"> <list> cz.sw.getroommate.bo.HomelessInzerentcz.sw.getroommate.bo.Demand ...
Podle obr´ azku 2.6 se webov´ a aplikace skl´ad´a z v´ıce vrstev. Datov´ y model jiˇz m´ame naimplementovan´ y, kde budeme pokraˇcovat? Zaˇcneme od servisn´ı vrstvy, respektive od jej´ıho testu. DemandServiceTest.java public class DemandServiceTest extends TestCase { DemandService demandService; Demand demand; // OVERRIDE protected void setUp() throws Exception { super.setUp(); demandService = SpringTestUtil.getDemandService(); setupDemand(); } public void setupDemand() { demand = new Demand(); //and set all properties } public void testSaveAndLoad() { try { demandService.saveOrUpdate(demand); } catch (Exception e) { fail(e.getMessage()); } long id = demand.getId(); Demand loadeDemand = demandService.getDemand(id); assertEquals(demand, loadeDemand); assertNotNull(loadeDemand.getHomelessInzerent()); } }
KAPITOLA 3. IMPLEMENTACE
39
Test n´ am spadne kv˚ uli tomu, ˇze jsme zat´ım nenapsali metody demandService.getDemand() a demandService.saveOrUpdate(Demand demand). Ted’ je ta spr´avn´a chv´ıle je doplnit. DemandService.java public class DemandService extends AbstractService { public void saveOrUpdate(Demand demand) throws Exception { assert (demand != null); Date now = new Date(); if (demand.getCreated() == null) { demand.setCreated(now); } demand.setUpdated(now); demand.setCancellingEmailSent(null); dao.saveOrUpdate(demand); } public Demand getDemand(long id) { return dao.getDemandById(id); } }
D´ ale zb´ yv´ a zapsat beanu, kterou bude Spring IoC kontejner injektovat. core-service.xml <property name="dao" ref="demandHibernateDao" />
Test poˇr´ ad neproch´ az´ı, jeˇstˇe je tˇreba napsat DAO vrstvu, rozhran´ı IDemandDao respektive jeho implementaci DemandHibernateDao. DemandHibernateDao.java public class DemandHibernateDao extends SimpleCrudHibernateDao implements IDemandDao { public Demand getDemandById(long id) { return get(Demand.class, id); } public void saveOrUpdate(Demand demand) { getHibernateTemplate().saveOrUpdate(demand); } }
Opˇet je potˇreba zapsat beanu do xml deskriptoru Springu, za zm´ınku stoj´ı beana abstractHibernateDao, pomoc´ı kter´e dos´ahneme deklarativn´ıch transakc´ı, viz. kapilota 2.3.8.1. core-service.xml <property name="target">
40
Naimplementovali jsme tedy servisn´ı, perzistentn´ı vrstvu a datov´ y model. Vˇsechnu funkˇcnost m´ame ovˇeˇrenou testy. Vrhneme se tedy na UI a webovou vrstvu. Jelikoˇz budeme m´ıt v´ıce obrazovek, kter´ ymi budeme navigovat na dalˇs´ı a na pˇredchoz´ı obrazovku, v u ´vahu pˇripad´a implementace pomoc´ı Spring Web MVC a jeho kontrol´eru AbstractWizardFormController nebo pomoc´ı SWF. Kv˚ uli uˇzivatelsk´e pˇr´ıvˇetivosti SWF a tak´e proto, ˇze nab´ıdkov´ a ˇc´ ast je dˇelan´ a pr´avˇe takto, jsem zvolil SWF. Zaˇcneme od definice URL mapov´ an´ı, pomoc´ı kter´eho budeme pˇristupovat k vkl´ad´an´ı inzer´at˚ u, pˇrid´ame property do beany urlMapping do property mappings, action-servlet.java <property name="alwaysUseFullPath" value="true" /> <property name="mappings"> <props> <prop key="/hledam-bydleni.html">flowController
kter´a ukazuje na beanu flowController. SWF je nakonfigurov´ano podobnˇe jako jsem ukazoval v kapitole 2.2.8.7, s t´ım rozd´ılem, ˇze pro vazbu URL na flow je pouˇzita tˇr´ıda FlowIdExtractor, kter´ a n´ am dovoluje zapsat do mapy property mappings kl´ıˇc celou URL (bez koncov´eho .html) a jako hodnotu flow, kter´e se spust´ı. action-servlet.java <property name="flowExecutor" ref="flowExecutor" /> <property name="argumentHandler"> <property name="mappings"> <map> <entry key="hledam-bydleni" value="new-demand-flow" />
KAPITOLA 3. IMPLEMENTACE
41
Na Dispacher servlet (viz. kapitola 2.2.7.4) pˇrijde URL, kterou se odk´aˇzeme do flowControlleru, kter´ y spust´ı new-demand-flow.xml. new-demand-flow.xml <start-state idref="newDemand" /> <subflow-state id="demandDetails" flow="demand-details-flow"> ... <end-state id="finish" view="externalRedirect:spolubydlici.html" />
Ve stavu newDemand vytvoˇr´ım nov´e BO Demand a HomelessInzerent, kter´e uloˇz´ım do SWF Conversation scope (viz. kapitola 2.2.8.5) a pˇri u ´spˇechu pˇrech´az´ım do subflow demandDetails, kde bude definovan´ a prvn´ı obrazovka. Po skonˇcen´ı subflow (pokud uˇzivatel klikne na button dalˇs´ı) pˇrech´ az´ım na druhou, tˇret´ı obrazovku... Na konci ve stavu saveDemand vol´am metodu demandAction.saveDemand(), kter´ a inzer´at uloˇz´ı do DB a loginAction.setCurrentUser(), kter´a zaloguje uˇzivatele t´ım, ˇze jej uloˇz´ı do HTTP session (viz. kapitola 2.2.7.5). Pod´ıvejme se napˇr´ıklad na metodu demandAction.createDemand() action-servlet.java <property name="validator" ref="demandValidator" /> <property name="propertyEditorRegistrar" ref="enumsEditorRegistrar" />
DemandFormAction.java public class DemandFormAction extends FormAction implements ApplicationContextAware { public DemandFormAction() { super(DemandFormObject.class); } public Event createDemand(RequestContext context) {
Vˇsimnˇeme si konstruktoru DemandFormAction(), j´ımˇz vol´ame kostruktor FormAction s parametrem, kter´ y je typ tˇr´ıdy Form Objectu1 . Vytvoˇrili jsme nov´ y inzer´at a uˇzivatele, ted’ n´as zaj´ım´a subflow demandDetails. demand-details-flow.xml <start-state idref="demandDetails" /> <end-state id="finish" />
Startovn´ı stav je view state demandDetails, pˇred vlastn´ım “vyrenderov´an´ım” JSP str´anky vol´ame metodu demandAction.setupForm(), kter´a je zdˇedˇena od Springovsk´e tˇr´ıdy FormAction. Pokud nenajde ve sv´em flow scopu Form Object, tak zavol´a metodu createFormObject() a ta jej uloˇz´ı do scope. DemandFormAction.java @Override protected Object createFormObject(RequestContext context) throws Exception { DemandFormObject demandFormObject = (DemandFormObject) super.createFormObject(context); BeanUtils.copyProperties(getDemand(context), demandFormObject); demandFormObject.setAllFlatEquipments(enumService.getAllEquipments()); return demandFormObject; }
V createFormObject() resp. v setupForm() je ta spr´avn´a chv´ıle naplnit vˇsechny poloˇzky z Form Objectu urˇcen´e pro data html prvk˚ u, ze kter´ ych si m˚ uˇzeme vyb´ırat z nˇejak´ ych hodnot z datab´aze (html select, checkbox, radio,...). Vˇsechny takov´e poloˇzky jsem pro lepˇs´ı orientaci oznaˇcoval slovy allXXXs. Dobr´a, nap´ıˇseme tedy koneˇcnˇe JSP str´anku. Tolik vˇec´ı, co jsme museli kv˚ uli jedn´e str´ance udˇelat, se n´ am zaplat´ı v pˇr´ıpadˇe rozˇsiˇrov´an´ı nebo u ´prav´ach syst´emu. NewDemandForm.jsp
Form Object je jin´ y n´ azev pro Command Object, tj. objekt do/z kter´eho “bindujeme” data v html formu.
KAPITOLA 3. IMPLEMENTACE
43
var="equipment"> ${equipment.name}
<%-- and other attributes--%> " />
Vˇsimnˇeme si prvn´ıho tagu , ten definuje do/z kter´eho Form Objectu se maj´ı data bindovat. Z kolekce allFlatEquipments vybereme pomoc´ı html checkboxu data do flatEquipmentSet. Dalˇs´ı vˇec, kterou je tˇreba zm´ınit, jsou pole , kde v prvn´ım pˇred´av´ame flowExecutionKey, kter´ y urˇcuje uˇzivatelovu session s flow a button dalˇs´ı, kter´ y vol´ a event submit, na kter´ y reagujeme v demand-details-flow.xml. DemandFormObject.jsp public class DemandFormObject implements Serializable{ private Set flatEquipmentSet = new HashSet(); private List allFlatEquipments; //and appropriate getters and setters }
Vrat’me se do demand-details-flow, jdeme vyˇsetˇrit, co se stane po kliknut´ı na button dalˇs´ı, resp. po odp´ alen´ı eventu “submit”, vol´ame metodu demandAction.bindAndValidate(), kter´a je zdˇedˇen´ a od Springovsk´e tˇr´ıdy FormAction. Data z html formul´aˇre se nabinduj´ı do Form Objectu a provede se nad nimi validace. Valid´ator pro demandFormAction uˇz jsme beanˇe nainjektovali, uk´ aˇzeme si jeho implementaci pomoc´ı Valangu.
Obr´azek 3.3: Validace poloˇzky Pomoc´ı Valangu snadno nap´ıˇseme krit´eria pro spr´avn´e hodnoty zad´avan´ ych poloˇzek. Valang m´ a dost vestavˇen´ ych funkc´ı a oper´ator˚ u, nic n´am ale v pˇr´ıpadˇe potˇreby nebr´an´ı si nov´e funkce napsat sami. Pro kompletn´ı specifikaci jazyka odkazuji ˇcten´aˇre na referenˇcn´ı dokumentaci. web-core-validators.xml <property name="valang"> ’ : ’errors.betweenvalues’ : 15, 99 } ]]>
Pokud validovac´ı funkce BETWEEN vrac´ı true, vˇse probˇehne u ´spˇeˇsnˇe. Pokud vr´ at´ı false, metoda bindAndValidate() vr´at´ı error(), nepˇrejde se na dalˇs´ı stranu, zobraz´ı se chybov´ a hl´aˇska a ˇcek´ a se na opravu, jak vid´ıme na obr´azku 3.3. Chybov´e hl´aˇsen´ı se hled´a pomoc´ı kl´ıˇce ’errors.betweenvalues’, ten se hled´ a ve vˇsech souborech .properties, kter´e definujeme v beanˇe messageSource. core-resources.xml
Hl´aˇska m˚ uˇze b´ yt, jak je vidˇet v t´eto uk´azce, parametrizov´ana, hodnoty se dosazuj´ı pomoc´ı sloˇzen´ ych z´ avorek ˇc´ıslovan´ ych od nuly. ErrorMessages.properties errors.betweenvalues = Mus´ ı b´ yt od {0} do {1}
3.3.2
Iterace 2 Do druh´e iterace jsem napl´ anoval zprovoznit n´asleduj´ıc´ı funkcionalitu:
• Umoˇznit mazat inzer´ aty. • Umoˇznit editovat inzer´ aty. Syst´em rozezn´ av´ a v tuto chv´ıli 3 typy uˇzivatel˚ u, vˇsichni dˇed´ı od abstraktn´ı tˇr´ıdy AbstractUser: • Inzerent - uˇzivatel inzeruj´ıc´ı nab´ıdku po bydlen´ı, • WatchDog - uˇzivatel, kter´ y si zˇr´ıdil SMS notifikaci, • HomelessInzerent - uˇzivatel inzeruj´ıc´ı popt´avku. Pro jakoukoliv zmˇenu inzer´ atu je nutn´e pˇrihl´aˇsen´ı. Pro editaci se pos´ıl´a URL /edit.html, kter´ y DispacherServlet deleguje na flowController, jeˇz vol´a edit-flow.xml. V edit-flow.xml se podle typu uˇzivatele rozhodne, co chce vlastnˇe editovat. Vytvoˇril jsem tedy subflow editdemand-flow.xml, kter´e zhruba odpov´ıd´ a new-demand-flow.xml, jeˇz jsem podrobnˇe rozebral minulou iteraci. Jedin´ y rozd´ıl je, ˇze se nevytv´aˇr´ı nov´e Byznys Objecty, ale podle aktu´aln´ıho uˇzivatele se z datab´ aze nahraje jeho inzer´at a data se pˇreklop´ı do Form Objectu, z kter´eho se naloaduj´ı do JSP str´ anky. Pro smaz´ an´ı inzer´ atu je situace jeˇstˇe jasnˇejˇs´ı. Poˇsleme request na /delete.html, DispacherServlet jej deleguje na flowController, kter´ y spust´ı delete-flow.xml, kde podle typu uˇzivatele spust´ıme delete-demand-flow.xml. Zobraz´ıme potvrzovac´ı dialog a po potvrzen´ı nejdˇr´ıve podle uˇzivatele naˇcteme inzer´ at z datab´ aze a vz´apˇet´ı pomoc´ı t´eto reference jej smaˇzeme. 3.3.3
Iterace 3
Zprovoznil jsem vkl´ ad´ an´ı, editaci a maz´an´ı nov´eho inzer´atu. Ohlednˇe popt´avky zb´ yv´a naimplementovat uˇz jen tyto uˇzivatelsk´e poˇzadavky: • Umoˇznit vkl´ adat do inzer´ atu v´ıce lokalit.
KAPITOLA 3. IMPLEMENTACE
45
Obr´ azek 3.4: Nov´ y inzer´at v´ıce lokalit
• Umoˇznit vyhled´ avat v inzer´ atech. Zprovozn´ıme nejdˇr´ıve prvn´ı requirement - vkl´ad´an´ı v´ıce lokalit, kde ˇc´ast´ı lokality m˚ uˇze b´ yt poloˇzka “nez´ aleˇz´ı”, jak vid´ıme na obr´azku 3.4. Jak´ a bude reprezentace lokalit? V syst´emu je BO Location oznaˇcen´ y anotac´ı @Emˇ beddable. Reˇsen´ı, kter´e vyp´ al´ıme “od boku” je, ˇze BO Demand bude obsahovat mnoˇzinu lokalit ˇ sen´ı se zd´ ve vztahu 1:N. Reˇ a v poˇr´adku, jedin´ y fakt, kter´ y n´am vad´ı, je, ˇze Location nevytv´aˇr´ı tabulku (viz. kapitola 2.3.4), ˇcili pom˚ uˇzeme si tak, ˇze vytvoˇr´ıme nov´ y BO, ˇreknˇeme DemandLocation, kter´ y bude oznaˇcen anotac´ı @Entity. Obˇe reprezentace lokalit spoj´ıme extrahov´an´ım metod do rozhran´ı ILocation. Bohuˇzel ˇreˇsen´ı vyp´alen´e “od boku” jsem kv˚ uli pozdˇejˇs´ımu probl´emu pˇredˇelal a tak si potom uk´aˇzeme refaktorov´an´ı v praxi. Naimplementujeme BO DemandLocation a pˇrid´ame jej do BO Demand. DemandLocation.java @Entity public class DemandLocation extends AbstractBo implements ILocation { private static final long serialVersionUID = -358439507774392169L; private Region region; private District district; private City city; private String street; @ManyToOne public City getCity() {return city;} public void setCity(City city) {this.city = city;} @ManyToOne public District getDistrict() {return district;} public void setDistrict(District district) {this.district = district;} @ManyToOne public Region getRegion() {return region;} public void setRegion(Region region) {this.region = region;} public String getStreet() {return street;} public void setStreet(String street) {this.street = street;} }
Property street nen´ı pˇr´ımo potˇreba, pokud nechceme spojit obˇe implementace lokalit rozhran´ım, coˇz je pr´ avˇe n´ aˇs pˇr´ıpad. ILocation.java
46
KAPITOLA 3. IMPLEMENTACE
public interface ILocation extends IBO{ public City getCity(); public void setCity(City city); public District getDistrict(); public void setDistrict(District district); public Region getRegion(); public void setRegion(Region region); public String getStreet(); public void setStreet(String street); }
Zmˇen´ıme tak´e hlaviˇcku BO Location, aby implementoval ILocation, d´ale pˇrid´ame do BO Demand mnoˇzinu DemandLocation. Demand.java @Entity public class Demand extends AbstractBo { private static final long serialVersionUID = -4713748752937686298L; //... private Set locations = new HashSet(); @OneToMany(cascade={CascadeType.ALL}) public Set getLocations() { return locations;} public void setLocations(Set locations) { this.locations=locations;} //... }
Servisn´ı ani DAO vrstvu zat´ım mˇenit nemus´ıme. Pˇrid´ame dalˇs´ı obrazovku do wizardu vkl´ad´an´ı nov´eho inzer´ atu, jin´ ymi slovy pˇrid´ame dalˇs´ı subflow do new-demand-flow.xml. new-demand-flow.xml ... <subflow-state id="demandDetails" flow="demand-details-flow"> <subflow-state id="demandLocation" flow="demand-location-flow"> ...
Vytvoˇr´ıme nov´e flow, kter´e bude zodpov´ıdat za volbu lokalit. Dobr´ ym zvykem je vytv´aˇret pro r˚ uzn´e akce r˚ uzn´e Form Objecty a Form Action. Proto vytvoˇr´ıme tak´e DemandLocationFormObject a DemandLocationFormAction, kter´ y bude s t´ımto form objectem pracovat. demand-location-flow.xml <start-state idref="demandLocation" />
KAPITOLA 3. IMPLEMENTACE
47
<end-state id="back" /> <end-state id="next" />
Metoda demandLocationAction.setupForm() respektive metoda createFormObject(), kterou zavol´ a, “naset´ı” hodnoty ve form objectu bud’ nov´e nebo uloˇzen´e v datab´azi, jelikoˇz toto flow bude vol´ ano i pˇri editaci. Jak vid´ıme na obr´azku 3.4 m´ame zde 4 r˚ uzn´e akce: • Akce addLocation pˇrid´ a zvolenou lokalitu do locationListu. • Akce delete odebere lokalitu z locationListu a pˇrid´a j´ı do deletedLocationListu, protoˇze vˇsechny akce se ukl´ adaj´ı aˇz v posledn´ı f´azi wizardu vkl´ad´an´ı/editace inzer´atu. List m´ısto mnoˇziny je zvolen proto, ˇze v HTTP RequestParameters pˇri akci deleteDemandLocation se pˇred´ av´ a id poloˇzky, kterou chceme smazat, toto id je poˇrad´ı v listu, nikoliv id z datab´ aze. • Akce submit uloˇz´ı form object do SWF conversation scope a pˇrejde do koncov´eho stavu “next”. • Akce back jednoduˇse pˇrejde do koncov´eho stavu “back”. Spolu s form objectem, form action potˇrebujeme JSP str´anku demandLocationForm.jsp. Jejich implementace je na pˇriloˇzen´em CD. Je ˇcas zprovoznit druh´ y requirement - hled´an´ı v inzer´atech, jak vid´ıme na obr´azku 3.5. Na tuto str´ anku se dostaneme po zad´an´ı requestu o /hledani-poptavek.html. Jelikoˇz jedin´e o co n´am jde, je pomoc´ı buttonu “search” zavolat vlastn´ı v´ ybˇer inzer´at˚ u a pomoc´ı odkazu “detail” pˇrej´ıt na detail inzer´ atu, web flow bude v tomto pˇr´ıpadˇe kan´onem na vrabce. Zvolil jsem tedy technologii Spring Web MVC, resp. implementaci pomoc´ı BaseCommandController, resp. jeho potomka AbstractSearchController, kter´ y je v projektu kv˚ uli hled´an´ı v nab´ıdk´ ach a MultiActionController pro zobrazen´ı detailu inzer´atu. Zaˇcneme opˇet od zaˇc´atku, pˇrid´ ame URL mapov´ an´ı do beany urlMapping. action-servlet.xml <property name="alwaysUseFullPath" value="true" /> <property name="mappings"> <props> <prop key="/hledani-poptavek.html">searchDemandFormController
Jelikoˇz se na str´ ance vyskytuje formul´aˇr, budeme potˇrebovat form object, resp. v terminologii Spring MVC command object.2 2
Nen´ı to u ´plnˇe pravda, mapovat data z formul´ aˇre m˚ uˇzeme pˇr´ımo do BO, avˇsak toto ˇreˇsen´ı se hod´ı jen v
KAPITOLA 3. IMPLEMENTACE
49
SearchDemandCommand.java public class SearchDemandCommand implements Serializable { private static final long serialVersionUID = 3659969246628625452L; private Region region;AbstractSearchController //and appropriate getters/setters }
Pro pochopen´ı SearchDemandController, resp. jeho pˇredka AbstractSearchController potˇrebujeme zn´ at jeˇstˇe jeden framework, kter´ y jsem v reˇserˇsn´ı ˇc´asti nepˇredstavil - ValueList. Opensource projekt ValueList je implementac´ı core j2ee paternu ValueList handler. Slouˇz´ı pro postupn´e naˇc´ıt´ an´ı hodnot a str´ ankuje v´ ysledky. Nejˇcastˇeji je pouˇzit s datab´az´ı a tabulkou, pˇredan´a data mohou b´ yt ale jak´ akoliv, napˇr. DynaBean, DynaClass, cokoliv z file syst´emu apod., pokud pro nˇe existuje adapt´er. Kromˇe toho se firma Simple Way s.r.o. pod´ılela na v´ yvoji, tak nen´ı divu, ˇze je pouˇzit pr´avˇe ValueList. V´ıce nalezneme na ofici´aln´ıch webov´ ych str´ank´ach projektu [4]. Nap´ıˇseme vlastn´ı kontrol´er a pod´ıv´ame se na pouˇzit´ı s ValueListem. SearchDemandController.java public class SearchDemandController extends AbstractSearchController { private static final Logger logger = Logger.getLogger(SearchDemandController.class); public SearchDemandController() { setCommandClass(SearchDemandCommand.class); setCommandName(SearchDemandCommand.COMMAND_NAME); } @Override protected Object formBackingObject(@SuppressWarnings("unused") HttpServletRequest request) throws Exception { SearchDemandCommand searchDemandCommand = (SearchDemandCommand) createCommand(); searchDemandCommand.setAllRegions(EnumService.addEmptyRegion( enumService.getAllRegions())); searchDemandCommand.setRegion(EnumHelper. getDefaultEnum(searchDemandCommand.getAllRegions())); //... return searchDemandCommand; } protected ModelAndView showGrid(HttpServletRequest request, @SuppressWarnings("unused") HttpServletResponse response, Object command, BindException errors) throws Exception { SearchDemandCommand searchCommand = (SearchDemandCommand) command; fillGrid(request, searchCommand); // Trigger rendering of the specified view, using the final model. return new ModelAndView(getFormView(), errors.getModel()); } @SuppressWarnings("unchecked") private void fillGrid(HttpServletRequest request, SearchDemandCommand searchDemandCommand) { ValueListInfo info = valueListHandler.getValueListInfo(request); info.getFilters().put(SearchDemandCommand.COMMAND_NAME, searchDemandCommand); ValueList valueList = valueListHandler. getValueList("searchDemandContentProvider", info); valueListHandler.backupAndSet(request, valueList, "list", "t1"); } } z´ akladn´ıch pˇr´ıpadech.
50
KAPITOLA 3. IMPLEMENTACE
Jelikoˇz je AbstractSearchController potomkem BaseCommandController, hlavn´ı metoda, kter´a se zavol´ a po delegov´ an´ı poˇzadavku na kontrol´er, je handleRequestInternal(). Ta ze session vezme command object. • Pokud je null, tak zavol´ a metodu showNewForm(), kter´a pˇres getErrorsForNewForm() vol´a metodu formBackingObject(), kterou jsem pˇrekryl v SearchDemandController, je to obdoba createFormObject() ze SWF, getErrorsForNewForm() pak d´al zavol´a bindAndValidate(), zjist´ı objekt BindException a uloˇz´ı command object do session scopu a vol´ a metodu showGrid(). • Pokud byl command object nenullov´ y, tak opˇet zavol´a bindAndValidate(), zjist´ı objekt BindException a vol´ a metodu showGrid(). N´asleduj´ıc´ı 2 metody jsou nutn´e ke spolupr´aci s ValueListem. Metodu showGrid() jsem opˇet pˇrekryl, ta vezme command object, zavol´a metodu fillGrid() a vr´at´ı novou instanci ModelAndView, kterou zobraz´ıme poˇzadovan´ y v´ ysledek. Metoda fillGrid() vytvoˇr´ı instanci ValueListInfo, kter´ a drˇz´ı vˇsechny informace potˇrebn´e k vytvoˇren´ı vlastn´ıho ValueListu a vloˇz´ı do mapy Filters command object, vytvoˇr´ı ValueList a zavol´a metodu backupAndSet(), kter´a uloˇz´ı ValueList do session, odkud ho na JSP str´ance zobraz´ıme. Command object do mapy Filters v instanci ValueListInfo vkl´ad´ame kv˚ uli dat˚ um, kter´e m´a ValueList zobrazit. Pokud chceme napˇr´ıklad zobrazit jen inzer´aty, kter´e maj´ı jako jednu z poloˇzek region, jeˇz hled´ ame. Vytvoˇr´ıme content provider, kter´ y se kromˇe tohoto bude starat o defaultn´ı tˇr´ıd´ıc´ı sloupec, defaultn´ı smˇer ˇrazen´ı nebo o poˇcet v´ ysledk˚ u na str´ance. action-servlet-valuelist.xml <property name="valueListHandler" ref="valueListHandler" /> <property name="config.adapters"> <map> <entry key="searchDemandContentProvider"> <property name="sessionFactory" ref="sessionFactory"/> <property name="defaultNumberPerPage" value="20" /> <property name="defaultSortColumn" value="updated" /> <property name="defaultSortDirection" value="desc" />
Naimplementujeme jeho z´ akladn´ı kostru, kter´a bude vracet vˇsechny poloˇzky BO Demand z datab´aze pomoc´ı Hibernate Criteria API. SearchDemandContentProvider.java public class SearchDemandContentProvider extends AbstractCriteriaContentProvider { protected Criteria getCriteria(ValueListInfo info, Session session) { return session.createCriteria(Demand.class); } }
KAPITOLA 3. IMPLEMENTACE
51
Pˇredek AbstractCriteriaContentProvider zat´ım nen´ı v ValueList API, oˇcividnˇe tam ale patˇr´ı. Je zodpovˇedn´ y za celou maˇsin´erii nahr´an´ı dat a nastaven´ı ValueListu, vzhledem k rozsahu t´eto pr´ ace ho nebudu popisovat. Zb´ yv´ a napsat JSP str´ anku searchDemand.jsp, kterou jsme nainjektovali do beany searchDemandFormController. searchDemand.jsp Seznam popt´ avek
V tagu nen´ı nic nov´eho, mapujeme hodnotu z HTML selectu do property region, jak jsem uk´ azal v kapitole 3.3.1. • Tag zobrazuje ValueList. • Pomoc´ı iterujeme jednotliv´e ˇr´adky tabulky z Listu. • S nadefinujeme sloupce, jejich n´azev a hodnotu z iter´atoru, kterou v tomto pˇr´ıpadˇe reprezentuje tag , kter´ y jsem pro tento pˇr´ıpad 3 napsal. M´ısto toho bychom jednoduˇse mohli napsat ${demand.description}. • Tag um´ıstˇen´ y v pˇr´ıd´av´a atributy do html
tagu, um´ıstˇen´ y v pˇrid´av´a atributy do html
. • Tag je urˇcen pro akce nad daty, speci´alnˇe s vytvoˇr´ıme html odkaz. Ve jsme napsali atribut includeParameters="#", t´ım jsme ˇrekli, ˇze vˇsechno, co pˇriˇslo ValueListu z requestu, tak´e dosad´ı do , ˇcili 3
Argumentem pˇred´ am property ${demand.description}, kterou podle d´elky bud’ vr´ at´ım celou, nebo zkr´ acenou na 100 znak˚ u plus ..., implementace je v definiˇcn´ım souboru /WEB-INF/tags/shorterDescription.tag a ve tˇr´ıdˇe TagHelper v metodˇe getShorterDescription().
52
KAPITOLA 3. IMPLEMENTACE m˚ uˇzeme napsat poˇzadavek rovnou . • Pomoc´ı pˇrid´ ame id inzer´atu do HTTP ReqestParameters. • S nastavujeme poˇcet str´anek ze str´ankovac´ıho menu.
Napsali jsme z´ akladn´ı kostru str´ anky, vyhled´av´an´ı nen´ı zat´ım zapojen´e, stejnˇe jako odkaz na detail inzer´ atu. Zprovozn´ıme nejdˇr´ıve vyhled´av´an´ı pˇrid´an´ım krit´eri´ı do tˇr´ıdy SearchDemandContentProvider. Tady je vhodn´a chv´ıle napsat test, jedn´a se o uˇcebnicov´ y pˇr´ıpad, kdy zad´ ame nˇejak´e hodnoty do datab´aze a v simulaci hled´an´ı chceme vr´atit patˇriˇcn´ y poˇcet v´ ysledk˚ u. Tento test je ve tˇr´ıdˇe SearchDemandContentProviderTest na pˇriloˇzen´em CD. Chtˇeli bychom podle zadan´ ych lokalit k vyhled´av´an´ı vybrat inzer´aty, kter´e je obsahuj´ı. Jenomˇze v tomto pˇr´ıpadˇe s Criteria API to tak jednoduch´e nen´ı. V´ yznamnˇe bychom si pomohli, kdyby v BO DemandLocation byla reference na Demand a tak obr´atili vlastn´ıka vztahu. Je to d˚ uvod k refaktorov´ an´ı, tak se do nˇeho pust´ıme. Odebereme mnoˇzinu lokalit v BO Demand a pˇrid´ame referenci do BO DemandLocation. DemandLocation.java @Entity public class DemandLocation extends AbstractBo implements ILocation { //... private Demand demand; @ManyToOne(optional = false) public Demand getDemand() {return demand;} public void setDemand(Demand demand) {this.demand = demand;} //... }
Vˇsude tam, kde jsme pracovali s jedn´ım BO, nyn´ı mus´ıme pracovat s dvˇema, coˇz nen´ı aˇz tak velk´a reˇzie. Horˇs´ı je, pokud chceme na JSP k dan´e instanci Demand zobrazit lokality. JSP nem´a prostˇredek k vol´ an´ı metod s parametrem, pouˇz´ıv´ame pro to TagHelper. Napsal jsem tag , kter´emu pˇred´am instanci Demand, helper zavol´a metodu servisn´ı vrstvy, kter´a vr´ at´ı vˇsechny lokality k dan´emu inzer´atu a helper vr´at´ı HTML v´ ystup, kter´ y oˇcek´av´ame. Tento pˇr´ıstup je na hranici u ´nosnosti, bohuˇzel mˇe nenapadlo lepˇs´ı ˇreˇsen´ı. Potom m˚ uˇzeme pomoc´ı Criteria API zapsat SearchDemandContentProvider.java public class SearchDemandContentProvider extends AbstractCriteriaContentProvider { protected Criteria getCriteria(ValueListInfo info, Session session) { Criteria locationCriteria = session.createCriteria(DemandLocation.class); addId(locationCriteria, "region.id", searchDemandCommand.getRegion()); addId(locationCriteria, "district.id", searchDemandCommand.getDistrict()); addId(locationCriteria, "city.id", searchDemandCommand.getCity()); Criteria criteria = locationCriteria.createCriteria("demand", CriteriaSpecification.INNER_JOIN); if (searchDemandCommand.getMaxPrice() != null) criteria.add(Restrictions.le("maxPrice", searchDemandCommand.getMaxPrice())); locationCriteria.setProjection(Projections.distinct( Projections.property("demand"))); return criteria;
Ze vˇsech lokalit pomoc´ı metody addId() se vyberou jen ty, kter´e uˇzivatel hledal; (m˚ uˇzou se st´at tˇri pˇr´ıpady, bud’ je v inzer´ atu nˇejak´a ˇc´ast lokality a hled´ame pr´avˇe tuto ˇc´ast, nebo je v inzer´atu nˇejak´ a ˇc´ ast lokality, ale hled´ame “nez´aleˇz´ı”, ˇcili nekladame podm´ınku, nebo je v inzer´atu “nez´ aleˇz´ı” a my hled´ ame nˇejakou ˇc´ast, ˇcili bychom mˇeli vr´atit i “nez´aleˇz´ı”). Z krit´eri´ı pro lokality vytvoˇr´ıme pˇres referenci na BO Demand krit´eria, kter´a v z´avˇeru vrac´ıme. Nastav´ıme krit´eria pro dalˇs´ı hledan´e poloˇzky, v tomto pˇr´ıkladu je to maxim´aln´ı cena. Nakonec vybereme z kart´ezsk´eho souˇcinu projekc´ı distinct ze sloupce demand a vrac´ıme popt´avkov´a krit´eria. Zb´ yv´ a uˇz jen naimplementovat detail vyhled´avan´eho inzer´atu. Budeme potˇrebovat jednoduch´ y kontrol´er, kter´ y vezme id z HTTP RequestParameters, naloaduje z datab´aze inzer´ at podle tohoto id a nastav´ı ho do scopu viditeln´eho z JSP a pˇrejde na nˇej. Toho jsem doc´ılil pomoc´ı potomka od Springovsk´eho kontrol´eru MultiActionController. Jedn´a se o trivi´ aln´ı pˇr´ıklad a na konec kapitoly se nehod´ı. 3.3.4
Iterace 4
V minul´e iteraci jsem dokonˇcil vˇse k inzerov´an´ı popt´avky, do posledn´ı iterace jsem napl´anoval zprovoznit posledn´ı uˇzivatelsk´e pˇr´ıbˇehy: • Pˇridat automatick´e maz´ an´ı inzer´at˚ u. • Pˇridat export do RSS. Export do RSS je zajiˇst’ov´ an pomoc´ı opensource knihovny Informa dostupn´e na serveru sourceforge.net. Vlastn´ı integrace do syst´emu je zaˇr´ızena pomoc´ı helperu RSSUtil, kter´ y abstrahuje z´akladn´ı metody tvorby RSS a umoˇzn ˇuje zmˇenit pouˇzitou RSS knihovnu (napˇr. ROME) pomoc´ı zmˇeny jen tohoto helperu. Cel´ a aplikaˇcn´ı logika je um´ıstˇena v servisn´ı tˇr´ıdˇe RSSService, kde se vytvoˇr´ı nov´ y RSS feed, do kter´eho z datab´ aze pˇrijdou inzer´aty, kter´e maj´ı b´ yt v RSS, tento feed se exportuje do souboru, dostupn´eho z port´ alu. Pro vytv´aˇren´ı popisu poloˇzky RSS feedu je pouˇzit templatovac´ı framework Apache Velocity. Feed se updatuje kaˇzd´e 3 hodiny pomoc´ı tasku BuildRSSTask napojen´eho na Quarz scheduler. Pˇredstaven´ı Apache Velocity se neveˇslo do z´abˇeru t´eto bakal´aˇrsk´e pr´ace, z´ajemce odkazuji na referenˇcn´ı dokumentaci nebo patˇriˇcnou literaturu. Pr´aci s Quarz schedulerem uk´ aˇzu na n´ asleduj´ıc´ım pˇr´ıkladu. Inzer´ aty se po nˇejak´e dobˇe stanou neaktu´aln´ı a v syst´emu se zaˇcnou postupnˇe hromadit. C´ılem je naimplementovat ˇreˇsen´ı, kter´e po x dnech poˇsle uˇzivateli email, ˇze by si mˇel inzer´ at zaktualizovat, nebo bude po y dnech vymaz´an. Pokud ho zaktualizuje, cel´a vˇec se za x dn´ı opakuje. Kaˇzd´ y den se spust´ı job, kter´ y poˇsle uˇzivatel˚ um potvrzovac´ı emaily a smaˇze star´e inzer´aty.
54
KAPITOLA 3. IMPLEMENTACE
Potˇrebujeme tedy nˇejak zjiˇst’ovat, jestli se m´a poslat email, jestli m´a b´ yt inzer´at smaz´an nebo nedˇelat nic. Dobr´ ym zvykem v OOP je um´ıst’ovat metody nejbl´ıˇze k jejich dat˚ um, ˇcili ide´alnˇe je um´ıstit do BO Offer a Demand reprezentuj´ıc´ı nab´ıdkov´ y a popt´avkov´ y inzer´at. Tento u ´kol je jako stvoˇren´ y pro TDD, nejprve tedy nap´ıˇseme test. OfferEraseTest.java public class OfferEraseTest extends TestCase { Calendar calendar; Offer offer; protected void setUp() { calendar = Calendar.getInstance(); } public void testDontDeleteOffersWithoutCancellingMailSent() { offer = new Offer(); offer.setCancellingEmailSent(null); assertFalse(offer.willBeDeletedToday()); }
Pro tuto funkci n´ am staˇc´ı pˇridat jednu promˇennou typu Date cancellingEmailSent, pokud je null, jeˇstˇe nebyl potvrzovac´ı email posl´an, pokud nen´ı null a ˇcasov´ y rozd´ıl mezi touto promˇennou a dneˇskem je vˇetˇs´ı neˇz y, bude oznaˇcen pro smaz´an´ı. Tedy pˇrid´ame dalˇs´ı poloˇzku do BO Offer. Prvn´ı metoda ˇr´ık´ a, ˇze inzer´ at nebude smaz´an, pokud nebyl posl´an email. Metoda willBeDeletedToday() je um´ıstˇena v BO, ale nechceme aby pro n´ı Hibernate hledal property k mapov´an´ı do datab´ aze, oznaˇc´ıme j´ı anotac´ı @Transient. public void testDeleteOffersWithCancellingMailSent() { offer = new Offer(); calendar.roll(Calendar.DAY_OF_YEAR, -CancelAdvertisementTask. MAX_DAYS_OFFER_TO_CANCEL); offer.setCancellingEmailSent(calendar.getTime()); assertTrue(offer.willBeDeletedToday()); }
Pokud u inzer´ atu byl posl´ an potvrzuj´ıc´ı email pr´avˇe pˇred x dny, bude smaz´an. public void testNotCancelWithCancellingMailSent() { offer = new Offer(); calendar.roll(Calendar.DAY_OF_YEAR, -CancelAdvertisementTask. MAX_DAYS_OFFER_TO_CANCEL + 1); offer.setCancellingEmailSent(calendar.getTime()); assertFalse(offer.willBeDeletedToday()); }
A obr´acenˇe, pokud byl posl´ an mail, ale jeˇstˇe neuplynula doba x dn´ı, smaz´an nebude. public void testSentCancellingMailWithCancellingMailNull() { offer = new Offer(); calendar.roll(Calendar.DAY_OF_YEAR, -CancelAdvertisementTask. MAX_DAYS_OFFER_TO_SEND_CANCEL); offer.setUpdated(calendar.getTime()); offer.setCancellingEmailSent(null); assertFalse(offer.willBeDeletedToday()); assertTrue(offer.willBeSentCancellingMail()); }
Zaˇcali jsme testovat odes´ıl´ an´ı email˚ u, pokud byl inzer´at naposledy editov´an pˇred y dny a potvrzovac´ı email jeˇstˇe nebyl posl´ an, tak se poˇsle.
KAPITOLA 3. IMPLEMENTACE
55
public void testSentCancellingMailWithCancellingMailNotNull() { offer = new Offer(); offer.setUpdated(new Date()); //or something else offer.setCancellingEmailSent(new Date()); assertFalse(offer.willBeSentCancellingMail()); }
Tady ˇr´ık´ ame, ˇze pokud byl uˇz email posl´an, znovu se nepoˇsle, podm´ınka v metodˇe willBeSentCancellingMail() testuje cancellingMailSent na r˚ uznost od null, jej´ı hodnota m˚ uˇze b´ yt jak´ekoliv datum, v tomto pˇr´ıpadˇe n´as to nezaj´ım´a. public void testDontSendCancelingMail(){ offer = new Offer(); calendar.roll(Calendar.DAY_OF_YEAR, -CancelAdvertisementTask. MAX_DAYS_OFFER_TO_SEND_CANCEL+1); offer.setCancellingEmailSent(null); offer.setUpdated(calendar.getTime()); assertFalse(offer.willBeSentCancellingMail()); } }
Posledn´ı test ˇr´ık´ a, ˇze pokud email nebyl posl´an a inzer´at byl editov´an pˇred (y - 1) dny, email posl´ an nebude. Postupn´ ym zprovozˇ nov´an´ım test˚ u jsme naimplementovali tranzientn´ı metody v BO Offer. Offer.java @Entity public class Offer extends AbstractBo { //... @Transient public boolean willBeDeletedToday() { if (cancellingEmailSent != null && MultifunctionHelper. getTimeDifferenceBetweenTodayAnd(cancellingEmailSent) >= CancelAdvertisementTask.MAX_DAYS_OFFER_TO_CANCEL) { return true; } return false; } @Transient public boolean willBeSentCancellingMail() { Assert.notNull(updated); if (cancellingEmailSent != null) { return false; } else if (cancellingEmailSent == null && MultifunctionHelper.getTimeDifferenceBetweenTodayAnd(updated) >= CancelAdvertisementTask.MAX_DAYS_OFFER_TO_SEND_CANCEL) { return true; } return false; } }
Metoda MultifunctionHelper.getTimeDifferenceBetweenTodayAnd() vrac´ı rozd´ıl dn˚ u mezi dneˇskem a pˇredan´ ym argumentem. Instance inzer´atu v´ı, co se m´a s n´ı udˇelat. Zb´ yv´ a naimplementovat vlastn´ı job, kter´ y prov´ad´ı tyto u ´kony. Nejprve jej nakonfigurujeme. Pro tyto u ´ˇcely slouˇz´ı Quartz scheduler, st´ ahl jsem knihovnu, pˇridal do classpath a nakonfiguroval Spring. core-service.xml
Vytvoˇr´ıme job a nainjektujeme mu properties offer a demandService pro pr´aci s inzer´aty, mimeMailService pro odes´ıl´ an´ı mail˚ u a mailTemplate Velocity ˇsablonu mailu. <property name="jobDetail" ref="cancelAdJobDetail" /> <property name="cronExpression" value="0 0 2 * * ?" /> <property name="triggers"> <list>
Nastav´ıme spouˇstˇen´ı jobu na kaˇzd´ y den ve 2:00 a pˇrid´ame trigger do SchedulerFactoryBean. Nakonfigurovali jsme job, zb´ yv´ a ho naimplementovat, zaˇcneme opˇet od test˚ u, uk´aˇzeme si zde jen jeho kostru. CancelAdvertisementTask.java public class CancelAdvertisementTask extends QuartzJobBean implements ICronTask { public static final int MAX_DAYS_OFFER_TO_SEND_CANCEL = 3; public static final int MAX_DAYS_OFFER_TO_CANCEL = 3; public static final int MAX_DAYS_DEMAND_TO_SEND_CANCEL = 7; public static final int MAX_DAYS_DEMAND_TO_CANCEL = 3; private OfferService offerService; private DemandService demandService; private MimeMailService mimeMailService; private CancellingEmailTemplate mailTemplate; //apropriate getters and setters @Override protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException { doTask(); } public void doTask() { //no code (yet) } }
Metoda executeInternal() je protected a tud´ıˇz nejde volat jinde neˇz ve tˇr´ıdˇe (nebo v potomkovi), potˇrebujeme nˇejak simulovat spuˇstˇen´ı jobu, proto nedˇel´a nic jin´eho, neˇz ˇze vol´a public metodu doTask(), kterou v testu bez obav volat m˚ uˇzeme.
KAPITOLA 3. IMPLEMENTACE
57
Test na tento job nalezneme spolu s jeho implementac´ı v pˇriloˇzen´em CD ve tˇr´ıdˇe CancellAdTaskOfferTest a CancelAdvertisementTask. Nedˇel´a nic jin´eho, neˇz ˇze nastav´ı testovac´ı data a testuje smaz´ an´ı z datab´aze nebo odes´ıl´an´ı email˚ u. Pro testov´an´ı odes´ıl´an´ı email˚ u je pouˇzit embedded SMTP server subethamail a emailov´ y klient wiser.
58
KAPITOLA 3. IMPLEMENTACE
KAPITOLA 4. ZHODNOCEN´I
59
4 Zhodnocen´ı 4.1
Shrnut´ı
V reˇserˇsn´ı ˇc´ asti jsem pˇredstavil stˇeˇzejn´ı technologie pouˇz´ıvan´e pˇri v´ yvoji webov´ ych aplikac´ı na platformˇe Java. Popsat jsem pr´aci se Springem, Hibernatem a webov´ ymi frameworky Spring Web MVC a Spring WebFlow. Popsal jsem metodiku Extr´emn´ıho programov´an´ı a Programov´an´ı ˇr´ızen´e testy. V implementaˇcn´ı ˇc´ asti jsem popsal v´ yvoj ˇc´asti aplikace pˇri zprovozˇ nov´an´ı uˇzivatelsk´ ych pˇr´ıbˇeh˚ u. Uk´ azal jsem z´ akladn´ı pracovn´ı postupy a n´avyky. Pr´ace sv´ ym rozsahem nesupluje patˇriˇcn´e dokumentace ani referenˇcn´ı manu´aly, d˚ uraz jsem kladl na pouˇzit´ı vˇsech technologi´ı v kontextu a vˇse popsal na re´ aln´em netrivi´aln´ım pˇr´ıkladu.
4.2
Moˇ znosti rozˇ s´ıˇ ren´ı
Naimplementovala se celkem velk´a ˇc´ast nov´e funkcionality. Nˇejak´ y ˇcas se budou zjiˇst’ovat zpˇetn´e ohlasy od uˇzivatel˚ u. Z dalˇs´ıch funkc´ı, kter´e na port´alu postr´ad´am, je ukl´ad´an´ı fotek bytu a potenci´ aln´ıch z´ ajemc˚ u o bydlen´ı, jeˇz dokonˇcuji v dobˇe psan´ı tohoto odstavce.
4.3
Osobn´ı zkuˇ senosti
Pevnˇe vˇeˇr´ım, ˇze moje pr´ ace m˚ uˇze pomoci zaˇc´ınaj´ıc´ım v´ yvoj´aˇr˚ um webov´ ych aplikac´ı v Javˇe, ˇze m˚ uˇze poslouˇzit jako z´ akladn´ı sezn´amen´ı a u ´vod do problematiky. Spolu s touto prac´ı jsem z´ıskal zkuˇsenosti i na dalˇs´ım projektu ve firmˇe Simple Way s.r.o., a tak m˚ uˇzu ˇr´ıci, ˇze tento zp˚ usob v´ yvoje aplikac´ı je zaj´ımav´ y, snadno se osvojuje a nebr´ an´ı pˇrem´ yˇslet. Pokud jste po pˇreˇcten´ı moj´ı pr´ace z´ıskali podobn´ y n´azor na vˇec, jsem potˇeˇsen.
60
KAPITOLA 4. ZHODNOCEN´I
ˇ ˇ EHO ´ KAPITOLA 5. OBSAH PRILO ZEN CD
5 Obsah pˇ riloˇ zen´ eho CD /getroommate/src - ve sloˇzce jsou zdrojov´e k´ody, adres´aˇr je projektem v Eclipse IDE. /getroommate/war - webov´ y archiv spustiteln´ y na AS. /getroommate/readme.txt - podrobn´ y n´avod pro spuˇstˇen´ı aplikace. /getroommate/bak.zip - zdrojov´ y text t´eto pr´ace.
61
62
ˇ ˇ EHO ´ KAPITOLA 5. OBSAH PRILO ZEN CD
KAPITOLA 6. LITERATURA
63
6 Literatura [1] Servlets and jsp pages best practices, 2003. http://java.sun.com/developer/technicalArticles/javaserverpages/. [2] Client-server - wikipedia, the free encyclopedia, 2007. http://en.wikipedia.org/wiki/Client-server. [3] Core j2ee patterns, 2007. http://java.sun.com/blueprints/corej2eepatterns/Patterns. [4] Core j2ee patterns - value list handler, 2007. http://valuelist.sourceforge.net/. [5] Hollywood principle, 2007. http://en.wikipedia.org/wiki/Hollywood_Principle. [6] Java ee 5 apis, 2007. http://java.sun.com/javaee/5/docs/tutorial/doc/Overview9.html. [7] jmock-solid testing, 2007. http://therning.org/niklas/node/3&title=jMock-solid+Testing. [8] Junit 4.4 kladivo na testy, 2007. http://www.sweb.cz/pichlik/archive/2007_08_05_archive.html. [9] Key xp roles, 2007. www.softwarereality.com/lifecycle/xp/extreme_programming_roles.jsp. [10] Manifesto for agile software development, 2007. http://agilemanifesto.org/. [11] Mock object, 2007. http://en.wikipedia.org/wiki/Mock_object. [12] Programming language wars, part one, 2007. http://radar.oreilly.com/archives/2007/03/programming_lan.html. [13] The spring framework - reference documentation, 2007. http://static.springframework.org/spring/docs/2.0.x/reference. [14] Spring web flow reference documentation, 2007. static.springframework.org/spring-webflow/docs/current/reference. [15] Web frameworky v jave, 2007. http://www.sweb.cz/pichlik/archive/2006_12_10_archive.html. [16] K. Beck. Programov´ an´ı ˇr´ızen´e testy. Grada, 1 edition, 2004. [17] G. K. Christian Bauer. Java Persistence with Hibernate (Hibernate in action revised). Apress, 2 edition, 2007. [18] M. Fowler. Refactoring: Improving the Design of Existing Code. 1 edition, 2002. [19] M. S. Mike Keith. Pro EJB3 Java Persistence API. Apress, 1 edition, 2006. [20] D. D. Seth Ladd. Expert Spring MVC and Web Flow. Apress, 1 edition, 2006.
64
KAPITOLA 6. LITERATURA
ˇ YCH ´ KAPITOLA 7. SEZNAM POUZIT ZKRATEK
7 Seznam pouˇ zit´ ych zkratek AOP Aspect Oriented Programming API Application Programming Interface AS Application Server BO Bussiness Object DAO Data Access Object EJB Enterprise Java Beans EL Expression language HQL Hibernate Query Langugage HTML HyperText Markup Language HTTP HyperText Transfer Protocol IoC Inversion of Control JDBC Java Database Connection JMS Java Message Service JMX Java Management Extensions JNDI Java Naming and Directory Interface JSF Java Sever Faces JSTL Java Standard Tag Library JPA Java Persistence API JPQL Java Persistence Query Language ORM Object to relation mapping OOP Object oriented programming POJO Plain Old Java Object RAD Rapid application development SWF Spring WebFlow SWT Standard Widget Toolkit TDD Test-driven development UI User Interface WYSIWYG What You See Is What You Get XML Extensible Markup Language XP eXtream Programming