Anda di halaman 1dari 40

Struts for Beginners: Introduction

Struts is a mature open source MVC(Model-View-Controller) frame work for web applications. With Struts we can easily integrate arious technologies like !"## $ %ibernate$ i&'()S$ !S(* and !S+$ Velocity (emplates$ ,S*( etc. 'part from being a framework Struts also pro ides utilities and custom tags to cut down de elopment time. Struts is a ery popular framework and has now become a de-facto standard for !a a applications. (he reasons for this popularity are gi en below 'd antages of Struts 'd antage MVC Struts imposes the MVC framework so that no body goes off loop and does his own non standard implementation. &efore Struts MVC implementations were de elopment team-s perception of MVC which lead to lots of confusion$ errors and increased the learning cur e for that implementation. With Struts this human element is gone and a standard process of MVC is imposed on the de elopers 'd antage ConfigurationsStruts works completely on configurations in ,M* and property files rather than hard coding the components in the !a a code. 'll the elements like Controller actions$ redirects$ alidations$ e.ceptions can be incorporated by configurations and can be easily tracked at a later stage. Advantage Utilities Struts pro ides lots of utilities deri ed from 'pache Commons to make our life easier like ready made +orm &eans for re/uest parameters$ Validation controls and Custom tags 0isad antages of Struts Disadvantage Performance +rameworks mean bigger call stacks due to arious function calls which results in lesser performance. %owe er this is a performance sacrifice is offset by ma1or ad antage of standardi2ed de elopment process ad antage. Disadvantage Beginners (hough this is not an issue with e.perienced de elopers$ the new de elopers ha e a bigger learning cur e. (he documentation at 'pache is mostly targeted at the e.perienced audience.

Disadvantage Imposing )f you want to use Struts then you will ha e to 1ust follow the rules set by this framework. 3oing in a different way is pretty tedious. Disadvantage Small Programs Struts is not always useful for a smaller programs because there is no point in wasting time on arious Struts configurations when you can write your program in a 1iffy. (he program re/uirements should be carefully e aluated before using Struts

Struts for Beginners: Model View Controller (MVC Design Pattern


&ack 4 (utorial %ome 4 5e.t # ery program has a 6ser )nterface$ e ent handling$ business logic and data access. )f these components are mi.ed together what we get is a spaghetti code. (his kind of code though functional is difficult to maintain and small changes to a part of code will ha e a spiral effect to other sections of the program. (o get around this problem Model View Controller approach was de eloped by small talk de elopers and was widely embraced by the de eloper community. Various !a a technologies like Swing$ !"##$ Struts draw a lot of features from MVC design pattern. )n MVC the programs are di ided into three parts 7 Model$ View and Controller. (his way there is segregation of arious layers and the changes can be done to indi idual layers without effect or minimal effect to the other layers. *ets us look into these layers in more detail

View View manages the display to the end user. View should not contain any business logic.(his layer does following tasks

Show hyperte.t$ images$ forms to the user 8etrie e processed data from the model and display to the user along with hyperte.t$ images and forms. Client side alidations Ser er side alidations

)f you are web programmer these are your !S9 files. Model Model is back bone of any program. (his modal consists of data in a particular state and actions that can modify this state. (hese actions are usually called business logic. )n simpler words this layer does all the data related work gi en below

&eans with get and set methods &usiness logic 0ata 'ccess from any data source database$ flat file$ web ser ice$ #ntity &ean$ *0'9 ser er etc.

)f you are web programmer these are your &:-s (&uisness :b1ects)$ 0':-s (0ata 'ccess :b1ects) $ V:-s(Value :b1ects) etc. Controller Controller is the layer between View and Model. (his layer does following tasks

When e er a View sends the re/uest$ the Controller finds appropriate Model component and forwards the re/uest to this component. 8ecei es the response from the model and routes the data to appropriate View component.

)f you web programmer these are your Ser let files. 's you go through ne.t few sections you will understand how this design pattern is implemented in Struts.

Struts for Beginners: Installation


&ack 4 (utorial %ome 4 5e.t !ava "nvironment 0ownload !0;<.= and abo e from 1a a.sun.com and install on the de elopment machine. 8efer to installation manual for procedure. #omcat Installation 0ownload (omcat >., and abo e from http?@@1akarta.apache.org@tomcat@ and install on the de elopment machine. 8efer to installation manual for procedure. (omcat is an :pen source !S9@Ser let web ser er. We will use this web ser er for our tutorial. Struts Installation 0ownload Struts <.A., release from struts.apache.org. (his 2ip file has following folders

apps 7 Sample applications and struts-blank war file. &lank war file(struts-blankB ersion numberC.1ar) is used as the starting point for all Struts applications. docs 7 '9) documentation and release notes. lib 7 'll the standard libraries needed o build a Struts application src 7 Source code for all the e.amples.

Copy blank war file to (omcat at location B(omcat %omeCDwebapps and rename the war file as salsa-tutorial.1ar.

C$ec% &our installation (ry 68* - http?@@localhost?EFEF@Bpro1ect-folderC@ on your browser. )f you can iew welcome screen then your struts set up is done.

Setting up &our 'uild environment wit$ "clipse Create a new pro1ect called G0ocManagerH in your #clipse )0# and set build path to include all the 1ar files in the lib folder of the Struts download. 'lso set the build path to include !"##.1ar or ser let.1ar.

Struts for Beginners: Dot do((do and Action Servlet


&ack 4 (utorial %ome 4 5e.t *ets in estigate the deployment descriptor W#&-)5+@web..ml for struts. Iou will find following .ml configurations. BJ-- Standard 'ction Ser let Mapping --C Bser let-mappingC Bser let-nameCactionB@ser let-nameC Burl-patternCK.doB@url-patternC B@ser let-mappingC (he abo e Gser let-mappingH configuration says that what e er re/uest that comes with a mime type .do should go to ser let called GactionH. 'nd the ser let GactionH is defined in the below configuration. (he action is the alias name of the ser let org.apache.struts.action.'ctionSer let. (his 'ctionSer let is the Struts controller and e.tends %((9Ser let class. BJ-- Standard 'ction Ser let Configuration --C Bser letC Bser let-nameCactionB@ser let-nameC Bser let-classCorg.apache.struts.action.'ctionSer letB@ser let-classC Binit-paramC Bparam-nameCconfigB@param-nameC Bparam- alueC@W#&-)5+@struts-config..mlB@param- alueC B@init-paramC Bload-on-startupC"B@load-on-startupC B@ser letC 'ny other e.tension other than .do can be used by configuring accordingly in web..ml. ActionServlet 5ow that your re/uest has reached the 'ctionSer et class. What 5e.tL 'ctionSer let performs following tasks

's 'ctionSer let e.tends %((9Ser let class so it has init() method where W#&)5+@struts-config..ml is loaded. )n struts-config we define all the tasks re/uired for respecti e re/uests. 9rocesses re/uests and delegates re/uest handling to 8e/uest9rocessor ob1ect

8e/uest9rocessor delegates these 1obs to arious 'ction classes and recei es responses from these 'ction classes and call the respecti e iews. 8e/uest9rocessor is not isible to us. (his is an internal class used by 'ctionSer let. Struts Configuration )ile *struts+config(,ml-

Struts Configuration +ile defines all the mappings re/uired by a Struts application like controller actions$ redirects$ resource information$ alidations$ +orm &eans$ data sources etc. (his is the file where the process flow of the Struts application is defined. 'ction class is a where the arious tasks to be performed for an incoming re/uest are defined. (he controller ('ctionSer let) will call an 'ction for each re/uest based on the below actionmappings. Set the following mappings in the struts-config..ml. Baction-mappingsC Baction pathMN@helloWorldN typeMNcom.salsa.%elloWorldNC Bforward nameMNhelloN pathMN@helloWorld.1spN@C B@actionC B@action-mappingsC

Struts for Beginners: .ello /orld Program


&ack 4 (utorial %ome 4 5e.t *ets follow the con entions and kick start the coding with a %ello World program. %elloWorld.1a a
package com.salsa; import import import import import import javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; org.apache.struts.action.ActionMapping; org.apache.struts.action.Action; org.apache.struts.action.ActionForward; org.apache.struts.action.ActionForm;

pu lic class Hello!orld extends Action " pu lic ActionForward execute# ActionMapping mapping$ ActionForm %orm$ HttpServletRequest request$ HttpServletResponse response& throws 'xception " return mapping.%indForward#(hello(&; ) )

%elloWorld e.tends 'ction class which is the is a class which manipulated the Model. %ere Models like re/uest parameters$ !a a &eans can be manipulated. 'ction class has a method called e.ecute() which needs to be o erridden in your program to make state changes to the Model.
ActionForward execute# ActionMapping mapping$ ActionForm %orm$ HttpServletRequest request$ HttpServletResponse response&

'ctionMapping? (his ob1ect holds all the mappings regarding this particular action like paths$ forwards etc. 'ction+orm? (hese are the re/uest parameters associated with the re/uest. We will take a closer look into this class later. %ttpSer let8e/uest? 8e/uest :b1ect %ttpSer let8e/uest? 8esponse :b1ect. *ets ha e a closer look at the action-mapping$ We ha e 1ust seen the %elloWorld class where the re/uest is forwarded to GhelloH. G%elloH here is alias to GhelloWorld.1spH Baction-mappingsC Baction pathMN@helloWorldN typeMNcom.salsa.%elloWorldNC Bforward nameMNhelloN pathMN@helloWorld.1spN@C B@actionC B@action-mappingsC Write a simple !S9 file(helloWorld.1sp) which displays G%ello WorldH. 9lace this file in the salsa-tutorial folder in webapps. BhtmlC BheadC BtitleC Struts Classic %ello World B@titleC B@headC BbodyC Bh<C%elloWorldJJJJJB@h<C B@bodyC B@htmlC 0eploy and run your first Struts program using 68* http?@@localhost?EFEF@salsatutorial@helloWorld.do. if the browser displays G%elloWorldH then you ha e 1ust successfully written your first Struts program.

(he picture below shows the flow of your program

We will update this picture as the tutorial progresses.

Struts for Beginners: )orms 0 Action )orms


&ack 4 (utorial %ome 4 5e.t *ets now e.plore the forms. +orms are the %(M* elements where the user enters some alues and submits the form and gets the results page. We will now create a login page to see how it works in struts. Struts comes with to tag libraries. Iou need to import these libraries into a !S9 page. BOP taglib uriMNhttp?@@struts.apache.org@tags-beanN prefi.MNbeanN OC BOP taglib uriMNhttp?@@struts.apache.org@tags-htmlN prefi.MNhtmlN OC (his works in Struts <.A ersion but for struts <." and below (he following code has to be inserted in web..ml BtaglibC Btaglib-uriC@tags@struts-beanB@taglib-uriC Btaglib-locationC@W#&-)5+@struts-bean.tld B@taglib-locationC B@taglibC BtaglibC Btaglib-uriC@tags@struts-htmlB@taglib-uriC Btaglib-locationC@W#&-)5+@struts-html.tld B@taglib-locationC B@taglibC (hen import the tag libraries in your code by inserting the following code into !S9 page BOP taglib uriMN@tags@struts-htmlN prefi.MNhtmlNOC BOP taglib uriMN@tags@struts-beanN prefi.MNbeanNOC %(M* tag library (struts-html.tld@ tags-html.tld) are used to create %(M* pages and forms. Sample )orm Bhtml?form actionMNlogin'ctionNC Bhtml?te.t propertyMNuser5ameN @C Bhtml?password propertyMNpasswordN @C Bhtml?submit @C Bhtml?cancel @C B@html?formC

action property in Bhtml?formC element the and is mapped in the struts-config..ml to repecti e action. (he other elements like Bhtml?te.tC and Bhtml?passwordC are mapped to 'ction+orm &ean. (hese mappings ha e to be inserted into struts-config..ml. We will study these mappings as we create the re/uisite classes struts+config(,ml Baction pathMN@login'ctionN typeMNcom.salsa.6ser*ogin'ctionN nameMNuser*ogin+ormN inputMN@login+orm.1spN cancellableMNtrueNC Bforward nameMNloginsuccessN pathMN@loginStatus.1spN @C Bforward nameMNlogincancelN pathMN@loginCancel.1spN @C B@actionC Complete code of *ogin+orm.1sp BOP taglib uriMNhttp?@@struts.apache.org@tags-beanN prefi.MNbeanN OC BOP taglib uriMNhttp?@@struts.apache.org@tags-htmlN prefi.MNhtmlN OC BhtmlC BheadC BtitleC*ogin +ormB@titleC B@headC BbodyC Bh<C*ogin +ormB@h<C Bhtml?errors@C BtableC Bhtml?form actionMNlogin'ctionNC BtrC BtdC 6ser )0K B@tdC BtdC Bhtml?te.t propertyMNuser5ameN @C B@tdC B@trC BtrC BtdC 9asswordK B@tdC BtdC Bhtml?password propertyMNpasswordN @C B@tdC B@trC

BtrC BtdC Bhtml?submit @C B@tdC BtdC Bhtml?cancel @C B@tdC B@trC B@html?formC B@tableC B@bodyC B@htmlC Action)orm 'ction +orm is the bean where all the parameters sent from the from are stored. (o create an this bean your class should e.tend from 'ction+orm
pu lic class *oginForm extends ActionForm " +reate varia les$ setter and getter methods %or %orm su mission parameters private String user,ame; private String password; pu lic String getuser,ame#& " S-stem.out.println#(*oginForm..getuser,ame#&(&; return user,ame; ) pu lic void setuser,ame#String user& " user,ame / user; S-stem.out.println#(*oginForm..setuser,ame#&(&; ) pu lic String get0assword#& " return password;

6se reset() method to reset bean alues.


pu lic void reset#ActionMapping mapping$ HttpServletRequest request& " S-stem.out.println#(*oginForm..reset#&(&; user,ame/null; password/null; )

5ow that the parameters are recei ed$ 5e.t alidations ha e to be done for the re/uest parameters. Struts pro ides a method alidate(). 'll the re/uisite alidations are done in this method and the errors are logged in an 'ction#rrors ob1ect. )f there are no errors then the controller forwards the re/uest to 'ction class defined in the struts-config. 'nd if error is detected the login+orm.1sp is in oked with error messages displayed in place of Bhtml?errors@C tag in the login form.

)n the below code the error handling is done using 'ction#rrors class 'ction#rrors errors M new 'ction#rrors()Q When the error is detected 'ctionMessage ob1ect with error massage is added to 'ction#rrors ob1ect errors.add(Nuser5ameN$ new 'ctionMessage(Nlogin.user5ame.errorN))Q )n case of Struts<." and below 'ction#rror ob1ect is used instead of 'ctionMessage. 'ction#rror class is deprecated in Struts <.A
pu lic Action'rrors validate# ActionMapping mapping$ HttpServletRequest request& " S-stem.out.println#(*oginForm..validate#&(&; Action'rrors errors / new Action'rrors#&; i% #user,ame//null 11 user,ame.trim#&.equals#((&&" errors.add#(user,ame($ new ActionMessage#(login.user,ame.error(&&; ) i% #password//null 11 password.trim#&.equals#((&&" errors.add#(password($ new ActionMessage#(login.password.error(&&; ) return errors; )

Struts for Beginners: Message 1esources


&ack 4 (utorial %ome 4 5e.t Struts framework pro ides property files functionality with internationali2ation ()<E5) features. (hese message resources can be configured in Struts configuration file by adding the below .ml. Bmessage-resources parameterMNMessage8esourcesN @C (his configuration in config file and the related property file(Message8esources.properties) is already there in blank Struts war file. (he message resource is at location B(omcat%omeCDwebappsDsalsa-tutorialDW#&-)5+DclassesDMessage8esources.properties :pen Message8esources.properties and add the following lines to define the error messages used in alidate() method.

login.user5ame.errorM)n alid user name login.password.errorM)n alid password Complete listing of *ogin+orm.1a a
package com.salsa; import import import import import pu lic org.apache.struts.action.ActionForm; org.apache.struts.action.ActionMapping; org.apache.struts.action.Action'rrors; org.apache.struts.action.ActionMessage; javax.servlet.http.HttpServletRequest; class *oginForm extends ActionForm " private String user,ame; private String password; pu lic String getuser,ame#& " S-stem.out.println#(*oginForm..getuser,ame#&(&; return user,ame; ) pu lic void setuser,ame#String user& " user,ame / user; S-stem.out.println#(*oginForm..setuser,ame#&(&; ) pu lic String get0assword#& " return password; ) pu lic void set0assword#String pass!ord& " password / pass!ord; ) pu lic void reset#ActionMapping mapping$ HttpServletRequest request& " S-stem.out.println#(*oginForm..reset#&(&; user,ame/null; password/null; ) pu lic Action'rrors validate# ActionMapping mapping$ HttpServletRequest request& " S-stem.out.println#(*oginForm..validate#&(&; Action'rrors errors / new Action'rrors#&; i% #user,ame//null 11 user,ame.trim#&.equals#((&&" errors.add#(user,ame($ new ActionMessage#(login.user,ame.error(&&; ) i% #password//null 11 password.trim#&.equals#((&&" errors.add#(password($ new ActionMessage#(login.password.error(&&; )

return errors; ) )

Create an alias for this form bean in within Bform-beansC tag of struts-config..ml Bform-beansC Bform-bean nameMNuser*ogin+ormN typeMNcom.salsa.*ogin+ormN @C 8ememberL Guser*ogin+ormH is used in action mapping for parameter name. Baction pathMN@login'ctionN typeMNcom.salsa.6ser*ogin'ctionN nameMNuser*ogin+ormN inputMN@login+orm.1spN cancellableMNtrueNC +inally add logic for cancel button. 6ser*ogin'ction.1a a
package com.salsa; import import import import import import javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; org.apache.struts.action.ActionMapping; org.apache.struts.action.Action; org.apache.struts.action.ActionForward; org.apache.struts.action.ActionForm;

pu lic ActionForward execute# ActionMapping mapping$ ActionForm %orm$ HttpServletRequest request$ HttpServletResponse response& throws 'xception " S-stem.out.println#(2ser*oginAction..execute#&(&; i% #is+ancelled#request&&" return mapping.%indForward#(logincancel(&; ) else " return mapping.%indForward#(loginsuccess(&; ) ) )

Submit and Cancel &utton configurations in the Struts Config file are highlighted below Baction pathMN@login'ctionN typeMNcom.salsa.6ser*ogin'ctionN

nameMNuser*ogin+ormN inputMN@login+orm.1spN cancellableMNtrueNC Bforward nameMNloginsuccessN pathMN@loginStatus.1spN @C Bforward nameMNlogincancelN pathMN@loginCancel.1spN @C B@actionC )f you do not set cancellableMNtrueN in action mapping$ )n alidCancel#.ception will be thrown 'nd finally create the !S9 files to display successful logins and cancelled logins. (his login is incomplete without alidating the user with the database alues for login and password. We will complete the database part in our database section. loginStatus.1sp BOP taglib uriMNhttp?@@struts.apache.org@tags-beanN prefi.MNbeanN OC BhtmlC BheadC BtitleC *ogin Status B@titleC B@headC BbodyC Bbean?message keyMNlogin.statusN @C B@bodyC B@htmlC *ogin status uses a Struts tag library struts-bean(tags-bean) to display messages from Message8esources.properties. 'dd (he key H login.statusH in the property file. login.statusM*ogin Successful. loginCancel.1sp BhtmlC BheadC BtitleC *ogin Status B@titleC B@headC BbodyC *ogin Cancelled B@bodyC B@htmlC (o check your login program fire 68* http?@@localhost?EFEF@salsa-tutorial@login+orm.1sp in your browser.

*ook at the S:9-s(Sys :uts) in (omcat console to understand the work flow. 'lternati ely you can use *og=! for better understanding of the work flow.

user*ogin.1sp *ogin+orm??reset() - 'ction+orm *ogin+orm??setuser5ame() - 'ction+orm *ogin+orm??set9assword() - 'ction+orm *ogin+orm?? alidate() - 'ction+orm 6ser*ogin'ction??e.ecute() - 'ction loginStatus.1sp

5ow let us see the workflow diagram to clearly understand the flow

Struts for Beginners: Data Base Connections


&ack 4 (utorial %ome 4 5e.t 5ow let-s con ert our dumb login program to an intelligent login program which connects to the data base and alidates the user name and password before spitting out the response. We will use open source database My SR*. Iou can use any other database you are comfortable with. (his tutorial section uses the general 0ata Source and pooling pro ided by (omcat. 'lternati ely you can also use Struts datasources which is not ery popular and is not co ered in this tutorial. Create a database firing the following /uery in the My SR* client create database 0:CSM'5'3#8Q Create a table to store login data C8#'(# ('&*# CMSS6S#8( )0 )5( '6(:S)5C8#M#5($ 6S#8S5'M# V'8C%'8(EF)$ 9'SSW:80 V'8C%'8(EF)$ C:5S(8')5( CMSS6S#8S9; 98)M'8I ;#I ()0) )Q )nsert alues in the table insert into cmsSuser (userSname$ password) alues (TadminT$ Tadmin<"AT)Q Configuring My SR* in (omcat Ser er 0ownload official !0&C dri er for My SR* at de .mys/l.com@downloads@ Copy downloaded My SR* dri er to B(omcat %omeCDwebappsDlib. Change the 6ser*ogin'ction.1a a to accommodate the login checking code. We will use the general database access methodology we used in (omcat. )mport following classes
import import import import java.sql.+onnection; java.sql.0reparedStatement; java.sql.ResultSet; javax.sql.3ataSource;

import javax.naming.+ontext; import javax.naming.4nitial+ontext;

0o following changes in the e.ecute method


oolean loginFlag / %alse;

Change e.ecute method to create following ariables


+onnection conn/null; 55 For data ase connection ResultSet rs / null; 55 For data ase connection oolean loginFlag / %alse; 55 %lag %or login check

5ow go ahead and write database access code.


i% #is+ancelled#request&&" return mapping.%indForward#(logincancel(&; ) else " tr-" +ontext context / new 4nitial+ontext#&; +ontext env+ontext / #+ontext&context.lookup#(java.5comp5env(&; 3ataSource dataSource /#3ataSource&env+ontext.lookup#(jd c5docmanager(&; conn / dataSource.get+onnection#&; *oginForm loginForm / #*oginForm& %orm; 0reparedStatement statement / conn.prepareStatement# (S'*'+6 2S'R7,AM'$ 0ASS!8R3 FR8M +MS72S'R !H'R' 2S'R7,AM'/9 A,3 0ASS!8R3/9(&; statement.setString#:$loginForm.getuser,ame#&&; statement.setString#;$loginForm.get0assword#&&; rs / statement.execute<uer-#&; while#rs.next#&& " loginFlag/true; ) ) catch#'xception e& " S-stem.out.println#(d message. ( =

e.getMessage#&&;

) %inall-" conn.close#&; ) i% #loginFlag& return mapping.%indForward#(loginsuccess(&; else return mapping.%indForward#(login%ailure(&; )

(he complete code listing of 6ser*ogin'ction.1a a

package com.salsa; import import import import import import java.sql.+onnection; java.sql.0reparedStatement; java.sql.ResultSet; javax.sql.3ataSource; javax.naming.+ontext; javax.naming.4nitial+ontext;

import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import import import import org.apache.struts.action.ActionMapping; org.apache.struts.action.Action; org.apache.struts.action.ActionForward; org.apache.struts.action.ActionForm;

pu lic class 2ser*oginAction extends Action " pu lic ActionForward execute# ActionMapping mapping$ ActionForm %orm$ HttpServletRequest request$ HttpServletResponse response& throws 'xception " S-stem.out.println#(2ser*oginAction..execute#&(&; +onnection conn/null; ResultSet rs / null; oolean loginFlag / %alse; i% #is+ancelled#request&&" return mapping.%indForward#(logincancel(&; ) else " tr-" +ontext context / new 4nitial+ontext#&; +ontext env+ontext / #+ontext&context.lookup#(java.5comp5env(&; 3ataSource dataSource /#3ataSource&env+ontext.lookup#(jd c5docmanager(&; conn / dataSource.get+onnection#&; *oginForm loginForm / #*oginForm& %orm; 0reparedStatement statement / conn.prepareStatement# (S'*'+6 2S'R7,AM'$ 0ASS!8R3 FR8M +MS72S'R !H'R' 2S'R7,AM'/9 A,3 0ASS!8R3/9(&; statement.setString#:$loginForm.getuser,ame#&&; statement.setString#;$loginForm.get0assword#&&; rs / statement.execute<uer-#&; while#rs.next#&& " loginFlag/true; ) ) catch#'xception e& " S-stem.out.println#(d ) %inall-" conn.close#&; message. ( =

e.getMessage#&&;

) i% #loginFlag& return mapping.%indForward#(loginsuccess(&; else return mapping.%indForward#(login%ailure(&; ) ) )

Configure conte.t and datasource as shown below in ser er..ml. BConte.t pathMN@salsa-tutorialN doc&aseMNsalsa-tutorialN debugMN<N reloadableMNtrueN crossConte.tMNtrueN use5amingMNtrueNC B8esource nameMN1dbc@docmanagerN authMNContainerN typeMN1a a..s/l.0ataSourceN dri erClass5ameMNcom.mys/l.1dbc.0ri erN urlMN1dbc?mys/l?@@localhost?AAFU@docSmanagerN usernameMNdbuserN passwordMNdbpasswordN ma.'cti eMN"FN ma.)dleMN<FN ma.WaitMN-<N@C B@Conte.tC 'dd a new forward in struts-config..ml for failed logins after database check. Bforward nameMNloginfailureN pathMN@login+ailure.1spN @C +inally create a new file login+ailure.1sp BhtmlC BheadC BtitleC *ogin Status B@titleC B@headC BbodyC Wrong 6ser 5ame or 9assword B@bodyC B@htmlC (o check your login program fire 68* http?@@localhost?EFEF@salsa-tutorial@login+orm.1sp in your browser. +or simplicity this tutorial has database access code in the e.ecute method of the 6ser*ogin'ction.1a a. %owe er in real time systems this code goes into separate classes called 0': (0ata 'ccess :b1ect).

Struts for Beginners: ",ception .andling


&ack 4 (utorial %ome 4 5e.t *ike all other configurable features in Struts$ #.ceptions can be also handled using configurations. (his configuration methodology is usually called declarati e e.ception handling. 5ormal e.ception handling we do is programmatic e.ception handling. Struts configuration file can be configured to catch e.ception. 5ot a single line is needed in your code needs to be changed to handle these e.ceptions. Shut down your database and e.ecute the login page. When you submit the login details$ the browser o erflows with e.ception messages. )f you properly analy2e these error messages$ you will find that the root cause is G1a a.s/l.SR*#.ceptionH. 5ow let us handle this e.ception the S(86(S W'I. Create a !S9 page GdbSer er0own.1spH with error message. (his is page to be displayed when the e.ception occurs. BOP taglib uriMNhttp?@@struts.apache.org@tags-htmlN prefi.MNhtmlN OC BhtmlC BheadC BtitleC Ser ice 6na ailable B@titleC B@headC BbodyC Bhtml?errors@C B@bodyC B@htmlC 'dd the e.ception handling declarations in the Struts configuration file Baction pathMN@login'ctionN typeMNcom.salsa.6ser*ogin'ctionN nameMNuser*ogin+ormN inputMN@login+orm.1spN cancellableMNtrueNC Bforward nameMNloginsuccessN pathMN@loginStatus.1spN @C Bforward nameMNlogincancelN pathMN@loginCancel.1spN @C Bforward nameMNloginfailureN pathMN@login+ailure.1spN @C Be.ception typeMN1a a.s/l.SR*#.ceptionN

keyMNdb.e.ceptionN pathMN@dbSer er0own.1spN @C B@actionC +inally create the error message referred to as GkeyH in the abo e declaration. (his error message should be configured in Message8esources.properties db.e.ceptionM)nternal #rror$ 9lease (ry after some time.................... 8un the login program in the browser. 0o a proper login once and then shut down your database. #.ecute the login page again. When you submit the login details you will be shown the error message you ha e configured in the Message8esources.properties. )f we properly analy2e the code abo e we ha en-t done any changes in the 'ction class$ all we ha e done is do configurations in the configuration files.

Struts for Beginners: Accessing Action )orms from !SP Pages


&ack 4 (utorial %ome 4 5e.t 'ction +orms can be accessed from the !S9-s using Gtags-beanH tag library. (he below section describes the procedure to display alues of the bean G*ogin+orm.1a aH in the !S9 page. 'dd attribute tag to the action mapping. (he parameter attribute sets the *ogin+orm(aliased as user*ogin+orm in the bean defination) ob1ect to be in session scope and can be accessed from !S9 pages. Baction pathMN@login'ctionN typeMNcom.salsa.6ser*ogin'ctionN nameMNuser*ogin+ormN attributeMNlogin&eanN inputMN@login+orm.1spN cancellableMNtrueNC (he alue of the attribute parameter i.e. login&ean will now become the alias for *ogin+orm bean and can be accessed from any !S9 page using Bbean? writeC tag. Bbean?write nameMNlogin&eanN propertyMNuser5ameN @C Make sure the re/uired tag lib is imported. BOP taglib uriMNhttp?@@struts.apache.org@tags-beanN prefi.MNbeanN OC Complete code of loginStatus.1sp

BOP taglib uriMNhttp?@@struts.apache.org@tags-beanN prefi.MNbeanN OC BhtmlC BheadC BtitleC *ogin Status B@titleC B@headC BbodyC Bbean?message keyMNlogin.statusN @C BbrCIou are logged in as BbCBbean?write nameMNlogin&eanN propertyMNuser5ameN @CB@bC B@bodyC B@htmlC

Struts for Beginners: 2ogic #ag 2i'rar&


&ack 4 (utorial %ome *et us assume that we ha e to display a number of fields in a table iew from the database. (his will need iteration through the result set which should be done using 1a a code in your !S9 pages or use of !S(* tags. Struts gi es you a ready made tag library called GlogicH for this kind of display logic. *et-s modify our login program to learn about logic tag library. We will modify this proram to list all the users in the database. 'dd more users to the database though your SR* client. Create a new bean G6sersV:H$ also called the alue ob1ect by some programmers which will be a place holder for the names retrie ed from the database.
package com.salsa; import java.io.Seriali>a le; pu lic class 2sers?8 implements Seriali>a le" private String user,ame; pu lic String getuser,ame#& " return user,ame; ) pu lic void setuser,ame#String user& " user,ame / user; ) )

'dd the code to get user names from the data base upon successful login.. Statement statementMconn.createStatement()Q rs M statement.e.ecuteRuery(NS#*#C( 6S#8S5'M# +8:M CMSS6S#8N)Q

'dd these resultset alues an 'rray*ist


*ist user*ist / new Arra-*ist#;@&; while#rs.next#&& " String user,ame / rs.getString#:&; S-stem.out.println#(user.(=user,ame&; 2sers?8 users / new 2sers?8#&; users.setuser,ame#user,ame&; user*ist.add#users&; )

Set the 'rray*ist as re/uest attribute so that it can be accessed from the !S9 page. re/uest.set'ttribute(NusersN$ user*ist)Q Complete Code listing of the 'ction class
pu lic class 2ser*oginAction extends Action " pu lic ActionForward execute# ActionMapping mapping$ ActionForm %orm$ HttpServletRequest request$ HttpServletResponse response& throws 'xception " S-stem.out.println#(2ser*oginAction..execute#&(&; +onnection conn/null; ResultSet rs / null; oolean loginFlag / %alse; i% #is+ancelled#request&&" return mapping.%indForward#(logincancel(&; ) else " tr-" +ontext context / new 4nitial+ontext#&; +ontext env+ontext / #+ontext&context.lookup#(java.5comp5env(&; 3ataSource dataSource /#3ataSource&env+ontext.lookup#(jd c5docmanager(&; conn / dataSource.get+onnection#&; *oginForm loginForm / #*oginForm& %orm; 0reparedStatement statement / conn.prepareStatement# (S'*'+6 2S'R7,AM'$ 0ASS!8R3 FR8M +MS72S'R !H'R' 2S'R7,AM'/9 A,3 0ASS!8R3/9(&; statement.setString#:$loginForm.getuser,ame#&&; statement.setString#;$loginForm.get0assword#&&; rs / statement.execute<uer-#&; while#rs.next#&& " loginFlag/true; ) message. ( =

) catch#'xception e& " S-stem.out.println#(d e.getMessage#&&; ) i% #loginFlag&"

tr-" statement/conn.createStatement#&; 2S'R7,AM' FR8M +MS72S'R(&;

Statement rs / statement.execute<uer-#(S'*'+6 *ist user*ist / new Arra-*ist#;@&; while#rs.next#&& " String user,ame /

rs.getString#:&; S-stem.out.println#(user.(=user,ame&; 2sers?8 users / new 2sers?8#&; users.setuser,ame#user,ame&; user*ist.add#users&;

) request.setAttri ute#(users($ user*ist&; ) catch#'xception e& " S-stem.out.println#(d

message. ( =

e.getMessage#&&;

) %inall-" conn.close#&; ) return mapping.%indForward#(loginsuccess(&; ) else " conn.close#&; return mapping.%indForward#(login%ailure(&; )

) ) )

Modify loginStatus.1sp to access the 'rray*ist of user names. )mport the logic tag library BOP taglib uriMNhttp?@@struts.apache.org@tags-logicN prefi.MNlogicNOC Check if the 'rray*ist GusersH is in scope using Blogic?presentC tag )terate through the 'rray*ist of beans using Blogic?iterateC tag 0isplay the alues in the beans using Bbean?writeC (he partial listing of abo e three points Blogic?present nameMNusersNC Blogic?iterate idMNusernameN nameMNusersNC Bbean?write nameMNusernameN propertyMNuser5ameN@C

B@logic?iterateC B@logic?presentC Complete listing of loginStatus.1sp BOP taglib uriMNhttp?@@struts.apache.org@tags-beanN prefi.MNbeanN OC BOP taglib uriMNhttp?@@struts.apache.org@tags-logicN prefi.MNlogicNOC BhtmlC BheadC BtitleC *ogin Status B@titleC B@headC BbodyC Bbean?message keyMNlogin.statusN @C BbrCIou are logged in as BbCBbean?write nameMNlogin&eanN propertyMNuser5ameN @CB@bC Blogic?present nameMNusersNC Btable borderMN<NC BtrCBthC6ser 5amesB@thCB@trC Blogic?iterate idMNusernameN nameMNusersNC BtrCBtdCBbean?write nameMNusernameN propertyMNuser5ameN@CB@tdCB@trC B@logic?iterateC B@tableC B@logic?presentC B@bodyC B@htmlC

A!A3+ As&nc$ronous !avaScript and 3M2


(his tutorial teaches you basic concepts of '!', to get you started with '!',. (his tutorial teaches you '!', with the help of toy e.amples. 5e.t

)ntroduction? '!', is acronym for 'synchronous !a aScript and ,M*. '!', is used to do %((9 re/uests for a small portion of the web page instead of the whole web page and thus increasing the performance. ,M*%ttp8e/uest? (raditionally in web applications a %(M* page has a form tag or a link and the re/uest is sent to ser er by 9:S( or 3#( and whole new page is recei ed by the browser. %owe er in the '!', the re/uest can be sent through a !a aScript ob1ect called ,M*%ttp8e/uest ob1ect. 9roperties of ,M*%ttp8e/uest? (here are arious properties associated with ,M*%ttp8e/uest which need to be used to complete '!', life cycle. *ets learn what these properties are all about. +unctions of ,M*%ttp8e/uest? (here are arious functions associated with ,M*%ttp8e/uest which need to be used to complete '!', life cycle. *ets learn what these functions are all about. +irst '!', 9rogram? Combine all your abo e learnings to write your first '!', program - GWord of the 0ayH.

A!A3: Introduction
&ack 4 (utorial %ome 4 5e.t '!', is acronym for 'synchronous !a aScript and ,M*. '!', is used to do %((9 re/uests for a small portion of the web page instead of the whole web page and thus increasing the performance. Iour browser needs to be '!', Compatible for '!', scripts to work. Most of the latest browsers are '!', compatible. (he popular '1a. compatible browsers are

)nternet #.plorer >.FV +irefo. <.FV :pera EV

Safari <."V

'!', is a browser technology and coding is done in !a aScript. '!', is ser er technology independent. 'ny ser er technology like Ser et@!S9$ 'S9.5#($ 9%9$ Cold +usion can be used for web re/uests. Advantages of A!A3

'!', gi es your application better look and feel. '!', takes a web application-s look and feel nearer to desktop application-s look and feel. Web pages in '!', are 'synchronous which means only parts of the page are updated$ this makes the response faster compared to the traditional complete page transition method. &est e.ample here is 3oogle suggest. Minimal amount of data can be sent and recei ed from the ser er.

Disadvantages of A!A3

&ack@forward buttons don-t work properly as '!', re/uests are not cached in the browser history and only the 68*-s in location bar are stored in history &ookmaking cannot be done easily for '!', re/uests. '!', is presently no compatible for Search #ngine :ptimi2ation. )f your site is content dri en and 8:)(8eturn of )n estment) is purely isibility of content on search engines the '!', is not the right choice. '!', does not work if !a a Script is disabled. '!', is not compatible in W'9 websites as most of the mobile browsers do not support !a a Script A!A3 is Popular

5owadays all ma1or websites use '!', e.tensi ely. Some of the popular e.amples are

3oogle suggest where the search bo. gi es you the dropdown menu of the popular searches as soon as you start typing. )n fact 3oogle suggest is the most popular '!', implementation to date and is regarded as an '!', e angelist. 3Mail Iahoo Mail &&C C55 +ace &ook

3oogle Maps

'nd this list can go on and on$ &ottom line is '!', has now become an essential component in the web programming bou/uet.

A!A3 .##P 1e4uests


&ack 4 (utorial %ome 4 5e.t (raditionally in web applications a %(M* page has a form tag or a link and the re/uest is sent to ser er by 9:S( or 3#( and whole new page is recei ed by the browser. %owe er in the '!', the re/uest can be sent through a !a aScript ob1ect called ,M*%ttp8e/uest ob1ect. (his ob1ect has the capability of re/uesting the ser er in the background while the user is able to iew the same page. A!A3 and Browsers (hough all the browsers use ,M*%ttp8e/uest ob1ect for creating an '!', re/uest ob1ect. 0ifferent browsers create it in a different manner. #arliest ersion of ,M*%ttp8e/uest was first created by Microsoft for &rowser Web Mail access to #.change ser er. (hen it was implemented as an 'cti e , :b1ect and not as a nati e !a a Script ob1ect. %owe er )# W.F supports this ob1ect. 3i en below are the arious ways of creating this ob1ect for arious browsers. )or )irefo,5 6pera 7(895 Safari 0 Internet ",plorer :
ajaxRequest8 ject /new AM*HttpRequest#&;

Some ersions of Safari and Mo2illa based browsers do not process the re/uest properly if the response in not in proper ,M*. +ollowing mime o erriding will do the trick. )# W does not support function o errideMime(ype(). (hat-s the reason an if statement before calling this function.
i% #ajaxRequest8 ject.overrideMime6-pe& " ajaxRequest8 ject.overrideMime6-pe#Btext5xmlB&; )

)or I" ;(8 and I" <(8


ajaxRequest8 ject/new ActiveA8 ject#(Msxml;.AM*H660(&; ajaxRequest8 ject/new ActiveA8 ject#(Microso%t.AM*H660(&;

(hough there are lots of methods to create ,M*%ttp8e/uest. (he most popular approach is the try-catch approach listed below.
CscriptD

var ajaxRequest8 ject; %unction +reateAmlHttp8 ject#& " var ajaxRequest8 ject/null; tr" 55 +reate native o ject 55 For Fire%ox$ 8pera E.@=$ Sa%ari F 55 4nternet 'xplorer G ajaxRequest8 ject /new AM*HttpRequest#&; 55 4'G does not support overrideMime6-pe#& i% #ajaxRequest8 ject.overrideMime6-pe& " ajaxRequest8 ject.overrideMime6-pe#Btext5xmlB&; ) ) catch #e& " 55 +reate Active A o ject 55 For 4nternet 'xplorer H.@ F I.@ tr" ajaxRequest8 ject/new ActiveA8 ject#(Msxml;.AM*H660(&; ) catch #e& " ajaxRequest8 ject/new ActiveA8 ject#(Microso%t.AM*H660(&; ) ) return ajaxRequest8 ject; )C5scriptD

A!A3: Properties of 3M2.ttp1e4uest


&ack 4 (utorial %ome 4 5e.t (here are arious properties associated with ,M*%ttp8e/uest which need to be used to complete '!', life cycle. +ollowing are the arious properties of ,M*%ttp8e/uest ob1ect

onreadystatechange readyState response(e.t response,M* status status(e.t

#$e onread&statec$ange Propert& onreadystatechange property of ,M*%ttp8e/uest needs to be set before the re/uest is sent to the ser er. (he function set to this property acts as the e ent handler function when the state of the re/uest changes. (he arious states of the re/uest are

8e/uest not initiali2ed 8e/uest set up 8e/uest sent 8e/uest in process 8e/uest completed

When e er there is change in the state the function set for onreadystatechange property is in oked .ml%ttpMCreate,ml%ttp:b1ect()Q .ml%ttp.onreadystatechangeMstateChanged()Q stateChanged() X @@ Code for handling the arious state changes. Y read&State Propert& readyState property of ,M*%ttp8e/uest stores arious states of the re/uest mention in the abo e section. #ach time this property changes the function set for onreadystatechange property is in oked. 3i en below is the arious states and their respecti e alues %ere are the possible alues for the readyState property?
State 8e/uest not initiali2ed 8e/uest set up 8e/uest sent 8e/uest in process 8e/uest completed Value F < " A =

(his property is usually checked in the function set for onreadystatechange property for processing the re/uest .ml%ttpMCreate,ml%ttp:b1ect()Q .ml%ttp.onreadystatechangeMstateChanged()Q stateChanged() X if (.ml%ttp.readyStateMM=)

X @@ Code for handling re/uest after the re/uest is completed Y Y response#e,t Propert& response(e.t 9roperty holds the data sent by ser er in te.t format. .ml%ttpMCreate,ml%ttp:b1ect()Q .ml%ttp.onreadystatechangeMstateChanged()Q stateChanged() X if (.ml%ttp.readyStateMM=) X response0ata M .ml%ttp.response(e.tQ Y Y response3M2 Propert& response,M* 9roperty holds the data sent by ser er in ,M* format. .ml%ttpMCreate,ml%ttp:b1ect()Q .ml%ttp.onreadystatechangeMstateChanged()Q stateChanged() X if (.ml%ttp.readyStateMM=) X .ml8esponse0ata M .ml%ttp.response,M*Q Y Y status Propert& (his property holds the %((9 status codes from the ser er. (he most popularly used status codes are

"FF - :; AF" - +ound =FA - +orbidden =F= - 5ot +ound >FF - )nternal Ser er #rror

5ormally we don-t need to handle all these status codes unless your re/uirements need to handle them. 'll the status codes other than "FF can assumed to be errors.

.ml%ttpMCreate,ml%ttp:b1ect()Q .ml%ttp.onreadystatechangeMstateChanged()Q stateChanged() X if (.ml%ttp.readyStateMM=) X if (.ml%ttp.status MM "FF) X @@ re/uest successfull Y else X @@ show error message. Y Y Y status#e,t Propert& (his property holds the %((9 status message from the ser er. status(e.t property is related to status property.

:; for "FF +ound for AF" +orbidden for =FA 5ot +ound for =F= )nternal Ser er #rror for >FF

.ml%ttpMCreate,ml%ttp:b1ect()Q .ml%ttp.onreadystatechangeMstateChanged()Q stateChanged() X if (.ml%ttp.readyStateMM=) X if (.ml%ttp.status MM "FF) X @@ re/uest successfull Y else X errorMessage M .ml%ttp.status(e.t Y Y Y

A!A3: )unctions of 3M2.ttp1e4uest

&ack 4 (utorial %ome 4 5e.t (here are arious functions associated with ,M*%ttp8e/uest which need to be used to complete '!', life cycle. +ollowing are the arious functions of ,M*%ttp8e/uest ob1ect

abort() get'll8esponse%eaders() get8esponse%eader(header) open(method$ url) send(body) set8e/uest%eader(header$ alue)

a'ort( 'borts the current e.ecuting re/uest. getAll1esponse.eaders( 6se this method to get response headers as a key- alue pairs separated by a colon get1esponse.eader($eader 8eturns the alue of the specified header. open( (his method is used to populate the ,ml%ttp8e/uest ob1ect before firing a %((9 re/uest to the ser er. Simplest form of this method takes two alues as parameters Method? Can be 3#($ 9:S( and 96( 68*? 68* of the ser er 68* ar urlMN@inc@word.phpNQ .ml%ttp.onreadystatechangeMstateChangedQ .ml%ttp.open(N3#(N$url)Q send( (his method finally sends the re/uest to the ser er. (his method has following method signature send(String re4uestParameters

re/uest9arameters is null for 3#( method and a string similar to Ruery string in case of 9:S( method. *ets see how we can do a login re/uest using send() method Send using ="# ar urlMNlogin.phpLidMtomZpassMsalsa<"AHQ .ml%ttp.onreadystatechangeMstateChangedQ .ml%ttp.open(N3#(N$url$true)Q .ml%ttp.send(null)Q Send using P6S# ar urlMNlogin.phpHQ .ml%ttp.onreadystatechangeMstateChangedQ .ml%ttp.open(N9:S(N$url$true)Q .ml%ttp.send(GidMtomZpassMsalsa<"AH)Q set8e/uest%eader() (his method is used to set a alue to the re/uest header set8e/uest%eader(header$ alue) foo.set8e/uest%eader(NContent-typeN$ Napplication@.-www-form-urlencodedN)Q

A!A3: )irst A!A3 Program


&ack 4 (utorial %ome 5ow that we ha e e.plored the ,M*%ttp8e/uest *ets get on with the simple GWord of the 0ayH e.ample. 9aste the below code on any portion of your web page.
CtrD CtdD Cdiv id/(wordtext(DC5divD Ca hre%/(javascript.show!ord#B*oading Another !ord...B&(DAnother word..C5aD CscriptD show!ord#B*oading !ord 8% 6he 3a- ...B&; C5scriptD C5tdD C5trD

)n the abo e code di tag identified by Gwordte.tH is our '!', playground. (his di tag is initially empty and is populated by following two !a a Script methods. showWord(T*oading Word :f (he 0ay ...T)? (his method called on page load and a random word of the day is displayed showWord(T*oading 'nother Word...T)? (his method called when the user click G'nother wordH link. )f you study the abo e two methods carefully these methods are same but input parameters are different. (his di tag can be accessed in the '!', code using document.get#lement&y)d(Nwordte.tN). (he handle to the display area within in the di tags can be updated using inner%(M* property
document.get'lementJ-4d#(wordtext(&.innerH6M*/ (C%ont %ace/Arial si>e/;DC D(=loadStatus=(C DC5%ontD(;

(he below code should be placed in the within the head tags and is a complete listing of the '!', code needed for this e.ample.
CscriptD var xmlHttp %unction +reateAmlHttp8 ject#&

"

var ajaxRequest8 ject/null; tr" 55 +reate native o ject 55 For Fire%ox$ 8pera E.@=$ Sa%ari F 4nternet 'xplorer G 55 ajaxRequest8 ject/new AM*HttpRequest#& 55 4'G does not support overrideMime6-pe#& i% #ajaxRequest8 ject.overrideMime6-pe& " ajaxRequest8 ject.overrideMime6-pe#Btext5xmlB&; ) ) catch #e& " 55 +reate Active A o ject 55 For 4nternet 'xplorer H.@ F I.@ tr" ajaxRequest8 ject/new ActiveA8 ject#(Msxml;.AM*H660(&; ) catch #e& " ajaxRequest8 ject/new ActiveA8 ject#(Microso%t.AM*H660(&; ) ) return ajaxRequest8 ject;

) %unction state+hanged#& " i% #xmlHttp.read-State//K& " i% #xmlHttp.status // ;@@& " document.get'lementJ-4d#(wordtext(&.innerH6M*/xmlHttp.response6ext; ) else " document.get'lementJ-4d#(wordtext(&.innerH6M*/('rror %etching data$ 0lease tr- again.(; ) ) ) %unction show!ord#loadStatus& " var loadStatus/loadStatus; 55 Show loading within div tags screen e%ore making a request. document.get'lementJ-4d#(wordtext(&.innerH6M*/(C%ont %ace/Arial si>e/;DC D(=loadStatus=(C DC5%ontD(; 55 +reate AmlHttp8 ject xmlHttp/+reateAmlHttp8 ject#&; i% #xmlHttp//null& " alert #(Lour rowser does not support AMAAN(&; return; ) 55 use a random sid to avoid rowser caching var url/(5inc5word.php(=(9sid/(=Math.random#&; xmlHttp.onread-statechange/state+hanged;

xmlHttp.open#(O'6($url$true&; xmlHttp.send#null&; ) C5scriptD

Server Code (his is a simple Word of the 0ay ser er written in 9%9 which returns a random word of the day for each re/uest
C9 Prandom7word / arra-#(C D?isual JasicC5 D. Microso%ts most popular programming language.($ (C DMavaC5 D. SunBs pupolar prommramming language.($ (C DM;M'C5 D. Mava version %or mo ile devices.($ (C D*inuxC5 D. 8pen Source 8S.($ (C D8rkutC5 D. 0opular social networking we site.($ (C DOoogleC5 D. 0opular search engine.($ (C D3iggC5 D. 0opular ookmarking we site.(&; Prandom7num / rand#@$ #count#Prandom7word&Q:&&; print#(Prandom7wordRPrandom7numS(&; 9D

Anda mungkin juga menyukai