You are on page 1of 28

ecmarchitect.

com

AlfrescoDeveloper:IntrototheWebScriptFramework
October,2007
JeffPotts

ThisworkislicensedundertheCreativeCommonsAttributionShareAlike2.5License.Toviewacopyofthislicense, visithttp://creativecommons.org/licenses/bysa/2.5/orsendalettertoCreativeCommons,543HowardStreet,5thFloor, SanFrancisco,California,94105,USA.

ecmarchitect.com
AlfrescoDeveloper:IntrototheWebScriptFramework
October2007 JeffPotts

Introduction
ThisarticleisanintroductiontotheAlfrescoWebScriptFrameworkthatbecameavailablewithrelease 2.1oftheproduct. We'llcontinuetoextendtheSomeCoWhitepapersexamplestartedinpreviousarticles.Asaquick refresher,inthosearticles,weextendedtheoutoftheboxcontentmodelsothatSomeCocouldstore custommetadataaboutoneoftheirdocumenttypes,whitepapers.Wecreatedacustomaspectcalled rateablethatcouldbeattachedtoanyobjectthatwasuserrateable.Then,inthecustombehaviorarticle wewrotebusinesslogicassociatedwiththerateableaspectthatknewhowtocalculatetheaverageuser ratingforagivenpieceofcontent.Thecalculationwastriggeredeverytimearatingwascreatedor deleted.WeusedserversideJavaScripttocreateratingobjectstotestoutourbehaviorbuttherewasn't aninterfaceavailablethatenduserscouldusetoratewhitepapers. SomeCoisnowreadytomovetothenextstep:Exposingtheratingfunctionalitytothefrontend.In theirinfinitewisdom,theteamatSomeCorealizesthatAlfresco'sWebScriptsprovideanicewayto exposealightweight,RESTfulAPIforworkingwithwhitepapersandratings.Sointhisarticle,we'll rollourownRESTAPIforretrievingalistofwhitepapers,retrievingtheaverageratingforagiven whitepaper,retrievingaspecificrating,postinganewratingforawhitepaper,anddeletingallratings foragivenwhitepaper.We'lluseJavaScriptformostofourcontrollerlogicbutwe'llseehowtouse Javaaswell.TheviewwillbeimplementedusingFreeMarkertemplatesthatreturnHTMLandJSON. Thecompletesourcecodethataccompaniesthisarticleisavailableatecmarchitect.com.SeetheMore Informationsectionattheendofthisarticleforthelink.Inadditiontothecodeforthisarticle,thezip includesthecodecreatedinthefirsttwoSomeCoarticlessoifyoudon'thavetodigaroundforthe codewebuilduponinthisarticle. Sounddecent?Okay,let'sgetstarted.

WhatistheWebScriptFramework?
Contentcentricapplications,whethertheyareinsideoroutsidethefirewall,arebecomingmoreand morecomponentized.Ithinkofthisasturningtraditionalcontentmanagementapproachesinsideout. Ratherthanhavingasingle,monolithicsystemresponsibleforallaspectsofacontentcentricweb AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page2of28

ecmarchitect.com
application,looselycoupledsubsystemsarebeingintegratedtocreatemoreagilesolutions. Thisapproachrequiresthatyourcontentmanagementsystemhaveaflexibleandlightweightinterface. Youdon'twanttobelockedintoapresentationapproachbasedonthecontentrepositoryyouare workingwith.Infact,insomecases,youmighthaveverylittlecontroloverthetoolsthatwillbeused totalktoyourCMS. ConsidertheexplodingrateofNextGenerationInternet(NGI)solutions,thegrowingadoptionofwikis andblogswithinanEnterprise(Enterprise2.0),andtheincreasingpopularityofmashupsboth insideandoutsidetheEnterprise.ThesetrendsaredrivingimplementationswheretheCMSisseenasa blackboxcomponentwiththefrontend(orperhapsmanydifferentfrontends)interactingwiththe CMSandothercomponentsviaREST. Amongopensourcecontentmanagementsystems,Alfrescoisoneofthefewthatreallylendsitselfto thisapproachbecauseithasmanyoptionsforinteractingwiththerepository.Theseoptionshave evolvedovertime.Thefollowingsummarizeswaysinwhichyourfrontendcouldworkwiththe Alfrescorepositorypriortorelease2.1:

Embedtherepository.Alfresco'srepositorycanbeembeddedinacustomapplication.Using thisapproachyouhavethefullpoweroftheAlfrescofoundationAPI.Ofcourse,thedownside isthatyou'vejusttightlycoupledtherepositorywithyourapplication.Didn'twejusttalkabout howimportantanopen,looselycoupledarchitectureis?Movingon... WebServices.AlfrescohashadaSOAPbasedWebServicesAPIavailableforquitesome time,butSOAPbasedWebServiceshaveheavierclientsiderequirementsthantheirRESTful cousins.Someclientsfoundthattheoutoftheboxservicesweretoochattyandhadtoomuch processingoverheadtoscalewellsotheyendedupwritingtheirownservicesandexposing themthroughtheembeddedApacheAxisserver.SoWebServicesmaynotbetherightfitinall cases. JCR.TheJCRAPIisastandardwayofworkingwithcontentinarepositoryandcanbe leveragedremotelythroughRMI.Thishasthebenefitofusingastandardsbasedapproachfor interactingwiththerepositorywhichtheoreticallyreducesswitchingcostsandmakesthe applicationeasiertosupport.OnechallengewiththisapproachisthattheJCRAPImaynotdo everythingyouneedtodosoyouendupusingtheJCRincombinationwithoneoftheabove approacheswhichreducestheswitchingcostsbenefit.Anotherpotentialissueisthatitis Javaonly. URLAddressability.ObjectsintheAlfrescorepositoryareURLaddressable.And, FreeMarkertemplatesandserversideJavaScriptcanbeappliedtoanynodeintherepository. So,forexample,youcanwriteaFreeMarkertemplatethatreturnsXMLorJSON.Afrontend appcanthenpostanXMLHttpRequesttoAlfrescothatspecifiesanodereferenceanda

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page3of28

ecmarchitect.com
referencetotheFreeMarkertemplate.AlfrescowillprocesstheFreeMarkertemplateinthe contextofthenodespecifiedandreturntheresults.Thisistheclosestyoucangettothe functionalityoftheWebScriptFrameworkpriorto2.1. Theseoptionsareallstillavailableandmaymakesensedependingonexactlywhatyouaretryingto do.Butwith2.1there'sapotentiallybetterwayforinteractingwiththerepositorytheWebScript Framework. TheWebScriptFrameworkessentiallyimprovesonthebasicideathatstartedwithURLaddressability. Thinkofawebscriptasachunkofcodethatismappedtoahumanreadable(andsearchengine readable)URL.So,forexample,aURLthatreturnsexpensereportspendingapprovalmightlooklike:
/alfresco/service/expenses/pending

whileaURLthatreturnsexpensespendingapprovalforaspecificusermightlooklike:
/alfresco/service/expenses/pending/jpotts

IntheURLabove,youcouldreadthejpottscomponentoftheURLasanimpliedargument.Amore explicitwaytoprovideanargumentwouldbelike:
/alfresco/service/expenses/pending?user=jpotts

Ormaybependingisanargumentaswellwhichtellsthewebscriptwhatstatusofexpensereportsto return.ThepointisthatthestructureoftheURLandhow(andif)yourURLincludesargumentsis completelyuptoyou. TheresponsetheURLreturnsisalsouptoyou.YourresponsemightreturnHTML,XML,JSON,or evenaJSR168Portlet. TheWebScriptFrameworkmakesiteasytofollowtheModelViewController(MVC)pattern, althoughitisn'trequired.TheControllerisserversideJavaScript,aJavaBean,orboth.TheController handlestherequest,performsanybusinesslogicthatisneeded,populatestheModelwithdata,and thenforwardstherequesttotheView.TheViewisaFreeMarkertemplateresponsibleforconstructing aresponseintheappropriateformat.TheModelisessentiallyadatastructurepassedbetweenthe ControllerandtheView. ThemappingofURLtocontrollerisdonethroughanXMLdescriptorwhichisresponsiblefor declaringtheURLpattern,whetherthescriptrequiresatransactionornot,andtheauthentication requirementsforthescript.Thedescriptoroptionallydescribesargumentsthatcanbepassedtothe scriptaswellastheresponseformatsthatareavailable. TheresponseformatsaremappedtoFreeMarkertemplatesthroughnamingconvention.So,for example,theFreeMarkertemplatethatreturnsexpensesasHTMLwouldbenamedwithanextension ofhtmlwhiletheonethatreturnsXMLwouldbenamedwithanextensionofxml.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page4of28

ecmarchitect.com
Thedescriptor,theJavaScriptfile,andtheFreeMarkertemplatescanresideeitherintherepositoryor onthefilesystem.IfaWebScriptusesaJavaBean,theclassmustresidesomewhereontheclasspath. Withthesebuildingblocksinmindyoumayalreadybethinkingofdifferentwaysyoucouldleverage WebScripts.Ifnot,letmehelp.YoucanuseWebScriptstoexposetheAlfrescocontentrepository throughaRESTfulAPIto:

EnableafrontendwebapplicationwritteninanylanguagethatcantalkHTTPtoretrieve repositorydatainXML,JSON,oranyotherformatortopersistdatatotherepository; PopulateJSR168portlets; Captureusercontributedcontent/data; Interactwithabusinessprocess(e.g.,aJBPMworkflow)throughnonwebclientinterfaces suchasemail; CreateATOMorRSSfeedsforrepositorycontentorbusinessprocessdata;and Decomposetheexistingwebclientintosmallercomponentswhichcouldpotentiallylenditself tobeingreborninnewandexcitingways!

Okay,youprobablyshouldn'ttacklethatlastonebutrestassuredthatAlfrescoisalreadyworkingonit. ThelastthingtomentionisthatWebScriptsareexecutedinaWebScriptRuntime.In2.1,thereare threeruntimesavailableoutofthebox.Theservletruntimeexecutesallwebscriptsrequestedvia HTTP.TheJSFruntimethatallowsJSFcomponentstoexecutescripts.AJSR168runtimeallows portletstoinvokewebscriptsdirectly. Youcanwriteyourownruntimeifthesedon'tmeetyourneeds.Alfrescomayaddmoreinthefuture. Atsomepoint,youcouldseewebscriptexecutionseparatedentirelyfromtheAlfrescowebapplication intoitsownprocesswhichwouldlenditselftoloadbalancing,scalability,etc. Inthisarticle,we'llfocusontheservletruntimeforHTTP. WebScriptsDirectory Thewebclientcomeswithatoolforlistingandreloadingwebscriptdefinitions.Togettothetool,go tohttp://localhost:8080/alfresco/service/index.You'llseelinksthatletyoubrowsethelistofdeployed webscriptsandabuttonlabeledRefreshlistofWebScripts.Althoughmakingchangestowebscripts thatresideintherepositorydoesnotrequirearestart,youmayhavetorefreshtheindexwiththis buttonafterachange. Clickthroughthelinkstoseewhatisavailableoutofthebox.You'llnoticethatthetoolitselfisbuilt usingwebscripts.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page5of28

ecmarchitect.com
Examples
Let'swalkthroughsomeexamples.We'regoingtostartwithaverysimpleHelloWorldwebscript. Afterthat,we'llgetprogressivelymorecomplexuntil,attheend,wehaveaRESTbasedinterfacefor creating,reading,anddeletingSomeCowhitepaperratings.

HelloWorldExample
Let'simplementthemostbasicwebscriptpossible:AHelloWorldscriptthatechoesbackanargument. We'llneedonedescriptorandoneFreeMarkertemplate.Dothefollowing: 1. LogintoAlfresco. 2. Navigateto/CompanyHome/DataDictionary/WebScriptsExtensions. 3. Createafilecalledhelloworld.get.desc.xmlwiththefollowingcontent:
<webscript> <shortname>Hello World</shortname> <description>Hello world web script</description> <url>/helloworld?name={nameArgument}</url> </webscript>

4. Createafilecalledhelloworld.get.html.ftlwiththefollowingcontent:
<html> <body> <p>Hello, ${args.name}!</p> </body> </html>

5. Gotohttp://localhost:8080/alfresco/service/indexandpresstheRefreshbutton.Ifyouthen clicktheListWebScriptslinkyoushouldbeabletofindthewebscriptyoujustdefined. 6. Nowgotohttp://localhost:8080/alfresco/service/helloworld?name=Jeff.Youshouldsee:


Hello, Jeff!

Afewthingstonote.First,noticethefilenamesincludeget.That'stheHTTPmethodusedtocallthe URL.Inlaterexampleswe'llseehowtousePOSTandDELETE.BydifferentiatingontheHTTP method,youcanhavemultiplecontrollersforthesameservicedependingonhowtheserviceis called(GETvs.POST,etc.).Second,inthiscaseweonlyhadoneargumentbutwecouldaddasmany asweneed.Watchout,though!DescriptorsmustbevalidXMLwhichmeansampersandsmustbe escaped.SotheproperwaytodefineaURLwithmultipleargumentsis:


<url>/helloworld?name={nameArgument}&amp;secondArg={anotherArg}</url>

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page6of28

ecmarchitect.com
Third,noticewedidn'tincludeaJavaScriptfileinthisexamplebutthescriptstillranbecause controllersareoptional. Mostscriptsaregoingtouseacontroller,though,solet'sgoaheadandaddone. 1. Createafilecalledhelloworld.get.jswiththefollowingcontent:
model.foo = "bar";

2. Updateyourhelloworld.get.html.ftlfilewiththefollowingcontent:
<html> <body> <p>Hello, ${args.name}!</p> <p>Foo: ${foo}</p> </body> </html>

3. Gotohttp://localhost:8080/alfresco/service/indexandpresstheRefreshbutton.Thisisrequired becauseyouaddedacontrollerthatthewebscriptruntimedidn'tknowabout. 4. NowgotoyourwebbrowserandenterthesameURLfromthefirstexamplewhichwas http://localhost:8080/alfresco/service/helloworld?name=Jeff.Youshouldsee:


Hello, Jeff! Foo: bar

What'sgoingonhereisthatthecontrollerisgettingexecutedbeforetheFreeMarkertemplate.Inthe controllerwecandoanythingtheAlfrescoJavaScriptAPIcando.Inthiscase,wedidn'tleveragethe JavaScriptAPIatallwejustputsomedataintothemodelobjectwhichwasthenreadbythe FreeMarkertemplate.Insubsequentexamplesthecontrollerwillhavemoreworktodoandinonecase, we'lluseJavainsteadofJavaScriptforthecontroller.

SomeCoWhitepaperUsercontributedRatingsExamples
WewanttocreateaRESTAPIthatfrontenddeveloperscanusetofindwhitepapersandratingsaswell aspostnewratings.Beforewedivein,itprobablymakessensetoroughouttheAPI.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page7of28

ecmarchitect.com
URL /someco/whitepapers /someco/rating?id={id} Method GET GET Description Responseformats Returnsalistofwhitepapers. HTML,JSON Getstheaverageratingfora HTML,JSON givenwhitepaperbypassing inthewhitepaper'snoderef. Createsanewratingforthe HTML,JSON specifiedwhitepaperby passinginaratingvalueand theuserwhopostedthe rating. HTML

/someco/rating?id={id}&rating= POST {rating}&user={user}

/someco/rating?id={id} Table1:PlannedratingsAPI

DELETE Deletesallratingsfora specifiedwhitepaper.

WhenthisAPIisinplace,frontenddeveloperscanincorporatewhitepapersandusercontributed ratingsintotheSomeCowebsite.ThefollowingscreenshotsshowpagesthatusetheAPIwe'regoing tobuildtoqueryforwhitepaperandratingsdata.ItlookslikethefolksatSomeCohaveshamelessly rippedofftheOptarospublicationssection.Theydidn'tevenbothertochangethelogo.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page8of28

ecmarchitect.com

Illustration1:TheindexofwhitepapersusesanAJAXcalltoretrievewhitepaper metadataandratings. Youcan'ttellfromthescreenshots,buttheratingswidgetisclickable.Whenclickeditsendsan asynchronousposttothe/someco/ratingURLdescribedinthetableabove.WhentheGettheWhite Paperlinkisclicked,thepageinIllustration2isdisplayed.Ireusedthedescriptionfromtheindex pagefortheExecutiveSummary.Intherealworldthiswouldprobablybeamorelengthydescription separatefromtheintroductionontheindexpage.TheDownloadthiswhitepaperlinkusesthe standardDownloadURLtogivetheuserdirectaccesstothecontent.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page9of28

ecmarchitect.com

Illustration2:ThedetailpagealsousesanAJAXcallandincludesthesameratings widgetaswellasadownloadlink

ListingallWhitepapers
Asaquickreview,recallthatSomeCowriteswhitepapersandmanagesthosepaperswithAlfresco. Somewhitepapersarepublishedtothewebsite.Acustomaspectcalledwebablehasaflagcalled isActive.WhitepaperswiththeisActiveflagsettotrueshouldbeshownonthewebsite.Forthis article,we'regoingtoignorethewebableaspectandtheisActiveflag.We'lljustassumeanysubtype ofsc:whitepaperfoundinthe/Someco/Whitepapersfolderisfairgame.(Ifthisbugsyou,seethe sidebar). Let'swriteawebscriptthatreturnsallwhitepapers.WewantthelistintwoformatsHTMLandJSON. HTMLwillallowustoeasilytesttheserviceandJSONwillmakeiteasyforcodeonthefrontendto processthelist.Thiswillrequirefourfiles:onedescriptor,oneJavaScriptcontroller,andtwo FreeMarkertemplatesoneforeachformat. AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page10of28

ecmarchitect.com
Beforewestartcoding,let'stalkabitaboutorganization. First,packages.TheWebScriptFrameworkallowsusto organizescriptassetsintoahierarchicalfolderorpackage structure.JustasitiswithJavaitisprobablyagoodideato dothisforallwebscripts.Followingareversedomainname patternisprobablyagoodconvention.Sowe'llbeusing com/somecoforourpackagewhichmeansourfileswill resideunder/CompanyHome/DataDictionary/WebScripts Extensions/com/someco.1 Next,URLscanfollowanypatternwewant,buttheywill alwaysstartwith<alfrescowebapp>/servicewhere <alfrescowebapp>isthenameoftheAlfrescoweb applicationcontext(usuallyalfresco).BecausetheURL patternmustbeunique,itisprobablyagoodideato incorporatethepackagenameintheURL.Inthisarticlewe'll prefixallURLswithsomeco. AlfrescoreservescertainpackagenamesandURLsfortheir ownuse(seetheAlfrescowiki)butbyfollowingthe conventionsproposedhere,you'llsteerclearofthose.
Sidebar:Enablingasubsetofwhitepapers forwebdisplay Therealworldsolutionthisexampleisbased onusesUIactionstoenableanddisablethe sc:isActiveflag.AnEvaluatorclasshides orshowstheEnableWeborDisable WebUIactionlinkbasedonthevalueof theflagandthegroupmembershipofthe user.Ifyouwanttodosomethingsimilaron yourown,thesc:webableaspectisinthe modelincludedwiththesourcecodeforthis article.And,I'veincludedtwoscripts (enableWeb.jsanddisableWeb.js)thatyou canusetoattachthewebableaspectandset theisActiveflagappropriately.Ifyouwant thewhitepaperservicetofilterthelistbased ontheisActiveflag,theLucenequeryinthe whitepaper.get.jsfileneedstobeappended with@sc\\:isActive:truetoshowonly activewhitepapers.Ileftthisfunctionality outoftheexamplebecauseitisn'tcoretothe topic.

Finally,you'veseenthatwebscriptassetscanresideinthe repository,buttheycanalsoresideinthefilesystem.Theonlyrequirementisthattheybeonthe classpath,butIsuggestthattheyresideinthealfresco/extensiondirectoryjustlikeyourother extensions.Followingthepackagestructuresuggestedearlier,ifweweretostoreourscriptsonthefile system,ratherthantherepository,we'dputtheminalfresco/extension/templates/webscripts/com/ someco. Theadvantageofusingthefilesystemisthatthewebscriptsthatmakeupyoursolutioncanbe deployedalongsideyourotherextensionswithoutrequiringanyonetouploadthemtotherepository. Thedisadvantageisthatchangesrequirearestart. InthesourcecodeI'veprovidedwiththisarticle,thewebscriptsaresetuptodeploytothefilesystem withtheotherextensions.If,insteadofdeployingthesamplecode,youwanttofollowalongandyou wanttoavoidrestarts,movemyscriptsoutofthealfrescoextensiondirectorybeforeyoudeploy,then uploadyourscriptstotherepositorylikewedidfortheHelloWorldexample.
1 Itisn'trequiredthatyouusetheWebScriptsExtensionsfolderintherepository.Ididitbecauseitseemedconsistent withhowwebclientcustomizationsaredeployed(usingthealfresco/extensionfolder).WebscriptsplacedintheWeb ScriptsExtensionsfolderthathavethesamefilenamesasthoseintheWebScriptsfolderwilloverridethescripts storedinWebScripts.Forthisreason,ifyouwantotherstobeabletooverrideyourscripts,usetheWebScripts folderratherthanWebScriptsExtensions.SeetheAlfrescowikiformoreonwebscriptfoldersearchorder.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page11of28

ecmarchitect.com
Ifscriptsaredefinedintherepositoryaswellastheclasspath,thefilesintherepositorytake precedenceoverthefilesontheclasspath.TheAlfrescowikidocumentsthesearchorderforweb scripts(SeeWheretofindmoreinformationattheendofthisarticleforalistofreferences). Step1:Thedescriptor Withthat,weshouldbegoodtogo.Thefirststepistocreatethedescriptorfile.Itshouldbenamed whitepapers.get.desc.xmlandshouldlooklikethis:
<webscript> <shortname>Get all whitepapers</shortname> <description>Returns a list of active whitepapers</description> <url>/someco/whitepapers</url> <url>/someco/whitepapers.json</url> <url>/someco/whitepapers.html</url> <format default="json">extension</format> <authentication>guest</authentication> <transaction>none</transaction> </webscript>

Thereareafewelementsinthisdescriptorwedidn'tseeintheHelloWorldexample.First,noticethat therearemultipleURLelements.ThereisoneURLforeachformatplusaURLwithoutaformat.This showshowtorequestadifferentoutputformatfromthesamebaseURL.BecausetheURLsdifferonly informat,itisn'tstrictlyrequiredthattheybelistedinthedescriptor,butitisagoodpractice. Inthiscase,we'reusingtheextensionsyntaxtheextensionontheURLspecifiestheformat.An alternativesyntaxistousetheargumentsyntaxlikethis:


<url>/someco/whitepapers?format=json</url> <url>/someco/whitepapers?format=html</url>

Mycurrentthinkingisthattheextensionsyntaxispreferredbecauseitisfriendliertosearchengines buttheremaybereasonstousetheargumentsyntax. Theformatelementdeclaresthetypeofsyntaxwe'reusinganddefinesadefaultoutputformat.Ifyou wanttoaccepteithersyntax,youcanuseanyastheformat.Inourcase,usingthisdescriptorif someoneusestheargumentsyntax,they'llgetanError500.IfsomeoneusestheURLwithout specifyingaformat,they'llgetJSON. Theauthenticationelementdeclaresthelowestlevelofauthenticationrequiredforthisscript.Ifyour scripttouchestherepositoryyouwillwantthistobeGuestorhigher.Otheroptionsarenone,user, andadmin. Thetransactionelementspecifiestheleveloftransactionrequiredbythescript.Listingwhitepapers doesn'tneedatransactionsowe'vegotitsettonone.Otherpossiblevaluesarerequiredand requiresnew. Next,weneedacontroller.Createafilecalledwhitepapers.get.jswiththefollowingcontent: AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page12of28

ecmarchitect.com
<import resource="classpath:alfresco/extension/scripts/rating.js"> var whitepapers = search.luceneSearch("PATH:\"/{http://www.alfresco.org/model/application/1.0}compan y_home/{http://www.alfresco.org/model/content/1.0}Someco\" +TYPE:\"{http://www.someco.com/model/content/1.0}whitepaper\""); if (whitepapers == null || whitepapers.length == 0) { logger.log("No whitepapers found"); status.code = 404; status.message = "No whitepapers found"; status.redirect = true; } else { var whitepaperInfo = new Array(); for (i = 0; i < whitepapers.length; i++) { var whitepaper = new whitepaperEntry(whitepapers[i], getRating(whitepapers[i])); whitepaperInfo[i] = whitepaper; } model.whitepapers = whitepaperInfo; } function whitepaperEntry(whitepaper, rating) { this.whitepaper = whitepaper; this.rating = rating; }

Thefirstthingtonoticeaboutthescriptisthatwe'reimportinganotherscript.Therating.jsscriptwas createdaspartofthelastarticletocontainlogicusedtocalculatetheaveragerating.Theideahereis thatretrievingaratingisalsobusinesslogicrelatedtoarating,soitshouldresideintherating.jsfileas well.Thismakesiteasyforustoreusethatlogicinotherscripts.We'llseetheupdatedversionofthe rating.jsscriptmomentarily.(Theabilitytoimportascriptfromanotherscriptwasaddedwithrelease 2.1.TheimporttagisnotnativetotheRhinoJavaScriptimplementation). ThenextthingtonoticeisthatthescriptqueriestherepositoryusingLucenetogetalistof whitepapers.Lookatwhathappensiftherearenowhitepapersfound.Theresponsecodegetssetto404 whichisthestandardHTTPresponsecodeforFilenotfound.Alfrescohasastandardresponse templateforerrorcodesbutyoucanoverrideitwithyourownbycreatingFreeMarkertemplatesthat followaspecificnamingconvention.Forexample,wecouldhaveacustom404responsetemplatefor whitepapersbycreatingafilecalledwhitepapers.get.html.404.ftl.SeetheAlfrescowikiformore information. ThelastthingthathappensisthatwebuildanewArrayforourresults.Icouldjustset model.whitepapersequaltothewhitepapersvariablethatcontainsthequeryresultsbutIwanttoadd somedatatotheresultset,soI'mbuildinganewArrayandsettingthattothemodel.(Iknowthe averageratingisapropertyofawhitepaper,soitmaynotyetbeobviouswhyIhaveaseparatefunction AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page13of28

ecmarchitect.com
forretrievingtheratingorwhyIwouldstoretheratinginthemodelseparatefromthewhitepaper. Trustthatitwillmakesenselater). Rememberthatourcontrollerimportsascriptcalledrating.js.Thisscriptresidesinourclasspathbut theimporttagalsosupportsincludingscriptsthatresideintherepository.Ifyoustillhaverating.js aroundfromthecustombehaviorsarticle,thedifferencebetweenitandthisoneisanewfunction calledgetRatingasshownbelow:
function getRating(curNode) { var rating = {}; rating.average = curNode.properties["{http://www.someco.com/model/content/1.0}averageRating"]; rating.count = curNode.properties["{http://www.someco.com/model/content/1.0}ratingCount"]; return rating; }

ThefunctionsimplyretrievestheaverageRatingandratingCountpropertiesfromthespecifiednode andreturnstheminaratingobject.Thefullsourceforrating.jsisintheaccompanyingsourcecode. Assumingthereareitemsinthesearchresults,we'llneedFreeMarkertemplatestoprocessthem.Let's createtheHTMLresponsetemplatefirst.Createanewfilecalledwhitepapers.get.html.ftlwiththe followingcontent:


<#assign datetimeformat="EEE, dd MMM yyyy HH:mm:ss zzz"> <html> <body> <h3>Whitepapers</h3> <table> <#list whitepapers as child> <tr> <td><b>Name</b></td><td>${child.whitepaper.properties.name}</td> </tr> <tr> <td><b>Title</b></td><td>${child.whitepaper.properties["cm:title"]}</td> </tr> <tr> <td><b>Link</b></td><td><a href="${url.context}${child.whitepaper.url}?guest=true">${url.context}${child.whit epaper.url}</a></td> </tr> <tr> <td><b>Type</b></td><td>${child.whitepaper.mimetype}</td> </tr> <tr> <td><b>Size</b></td><td>${child.whitepaper.size}</td> </tr>

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page14of28

ecmarchitect.com
<tr> <td><b>Id</b></td><td>${child.whitepaper.id}</td> </tr> <tr> <td><b>Description</b></td> <td><p><#if child.whitepaper.properties["cm:description"]?exists && child.whitepaper.properties["cm:description"] != "">${child.whitepaper.properties["cm:description"]}</#if></p> </td> </tr> <tr> <td><b>Pub Date</b></td><td>${child.whitepaper.properties["cm:modified"]?string(datetimeforma t)}</td> </tr> <tr> <td><b><a href="${url.serviceContext}/rating.html?id=${child.whitepaper.id}&guest=true">Rati ng</a></b></td> <td> <table> <tr> <td><b>Average</b></td><td>${child.rating.average}</td> </tr> <tr> <td><b>Count</b></td><td>${child.rating.count}</td> </tr> </table> </td> </tr> <#if !(child.whitepaper == whitepapers?last.whitepaper)> <tr><td colspan="2" bgcolor="999999">&nbsp;</td></tr> </#if> </#list> </table> </body> </html>

Thistemplateiteratesthroughthequeryresultspassedinbythecontroller,andbuildsanHTMLtable withpropertiesofeachwhitepaper.(Yes,thetableisugly.Yes,youcoulduseCSStospruceitup tremendouslyorevenremovethetableentirely.ButforSomeCo,thisresponsetemplateisreallyfor debuggingpurposesonlyandIdidn'twanttofoolwiththeCSSsoatableitis). ThelastthingwehavetodobeforewetestthewebscriptiscreatetheJSONresponsetemplate.Create afilecalledwhitepapers.get.json.ftlwiththefollowingcontent:


<#assign datetimeformat="EEE, dd MMM yyyy HH:mm:ss zzz">

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page15of28

ecmarchitect.com
{"whitepapers" : [ <#list whitepapers as child> { "name" : "${child.whitepaper.properties.name}", "title" : "${child.whitepaper.properties["cm:title"]}", "link" : "${url.context}${child.whitepaper.url}", "type" : "${child.whitepaper.mimetype}", "size" : "${child.whitepaper.size}", "id" : "${child.whitepaper.id}", "description" : "<#if child.whitepaper.properties["cm:description"]?exists && child.whitepaper.properties["cm:description"] != "">${child.whitepaper.properties["cm:description"]}</#if>", "pubDate" : "${child.whitepaper.properties["cm:modified"]?string(datetimeformat)}", "rating" : { "average" : "${child.rating.average}", "count" : "${child.rating.count}", } } <#if !(child.whitepaper == whitepapers?last.whitepaper)>,</#if> </#list> ] }

Again,justliketheHTMLresponsetemplate,thescriptiteratesthroughtheresultsetbutthisone outputsJSON.TheJSONstructureiscompletelyarbitrary. Assumingyouhavesometestdatainyourrepository(SomecoWhitepaperobjectsinyour Someco/Whitepapersfolder)youshouldbeabletorefreshthewebscriptlistandrunthewebscript. BecausewetoldAlfrescothatthisscriptrequiresGuestaccessorhigher,you'llneedtoeitherloginto Alfrescobeforerunningthescript,authenticatewithavaliduserandpasswordwhenthebasic authenticationdialogispresented,orappend&guest=truetotheURLlikethis: http://localhost:8080/alfresco/service/someco/whitepapers.html&guest=true.Ifyouforgetthe.html you'llgetaJSONresponsebecausewesetthattothedefault. Ifallgoeswellyoushouldseesomethingsimilartothefigurebelow:

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page16of28

ecmarchitect.com

Debugging Diditwork?Ifnot,it'stimetodebug.Thefirstthingyou'regoingtowanttodoistogointo log4j.propertiesandsetlog4j.logger.org.alfresco.repo.jscripttoDEBUG.Thiswillcauseanylogger.log statementsinyourcontrollertowritetocatalina.out. Anothertoolyou'llwanttoleverageisthewebscriptlist.Youcanuseittosee(1)ifAlfrescoknows aboutyourscriptand(2)theversionofthescriptstheruntimeknowsabout.Forexample,youcango tohttp://localhost:8080/alfresco/service/script/com/someco/whitepapers/whitepapers.getandAlfresco willdumpthedescriptorandalloftheresponsetemplates. TheNodeBrowsercanbehelpfultodebugproblemsaswell.Inthiscase,forexample,we'rerunninga LucenequeryinourJavaScript.Ifthecontrollerisn'tfindinganywhitepaperseventhoughyou've createdtestdata,tryexecutingthequeryintheNodeBrowser.Ifitdoesn'treturnresults,there's somethingwrongwithyourtestdata.

RetrievingtheRatingforaWhitepaper
Gettingaspecificratingisroughlythesameasgettingawhitepaperbutitisabiteasierbecauseofour AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page17of28

ecmarchitect.com
existinggetRatingfunctioninrating.js.AllthecontrollerhastodoisgrabtheIDargument,locatethe node,thencallgetRatingasshownbelow:
<import resource="classpath:alfresco/extension/scripts/rating.js"> if (args.id == null || args.id.length == 0) { logger.log("ID arg not set"); status.code = 400; status.message = "Node ID has not been provided"; status.redirect = true; } else { logger.log("Getting current node"); var curNode = search.findNode("workspace://SpacesStore/" + args.id); if (curNode == null) { logger.log("Node not found"); status.code = 404; status.message = "No node found for id:" + args.id; status.redirect = true; } else { model.rating = getRating(curNode, args.user); } }

ThedescriptorandresponsetemplatesareverysimilartothewhitepaperexamplesoIwon'tinclude themhere.Afterwegetthepostinplace,we'llrevisittheHTMLresponsetemplatebymakingsome updatesthathelpustest. Fornow,here'swhatasuccessfulJSONcalltotheratingservicereturns:


{"rating" : { "average" : "1.923", "count" : "13", } }

PostingaRatingwithaJavabackedWebScript
BeforewetalkaboutthePOSTwebscriptweshouldtalkaboutauthentication.Allofour/someco scriptsrequireGuestaccessorhigher.Thatmeansweeitherhavetohaveanactivesessionalready established,wehavetoappend&guest=truetotheURL,orwehavetologinwhenthebrowser presentsuswithabasicauthenticationdialog.(Anotheroptionistogetaticketfromawebservicecall, butthat'snotinthescopeofthisarticle). AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page18of28

ecmarchitect.com
SomeCodoesn'twanttoopenupwriteaccesstothe/Someco/WhitepapersfoldertoGuestuserswho mighttrytoaccesstherepositoryviathewebclientsothatmeansweneedarealuseraccountinorder towritenewratingobjects.WecoulduseuserauthenticationforthePOSTbutSomeCodoesn'twant tosetupuseraccountsforeveryuserthatmightratecontent. ThesolutionistoletGuestcallthePOSTURLbutleveragetheAlfrescoJavaAPItorunasa differentuser.(Inourcasewe'lluseadminbutauseraccountdedicatedtothepurposeofcreating ratingsisprobablyabetteridea).ThepostlogicwillresideinaJavaBeanratherthanaserverside JavaScriptfile. Thedescriptorandtheresponsetemplateslookliketheexampleswe'veseensofarsoIwon'trepeat themhere.Takealookattheaccompanyingsourcecodeifyouarecurious. ThepiecethatisnewistheuseofJavaasthecontrollersolet'sspendsometimeonthat.We'll implementthisJavabackedwebscriptinthreesteps.Thefirststepistowritethebusinesslogicfor creatingtherating.Justlikewhenweputthebusinesslogicintherating.jsfiletopromotereuse,we're goingtousetheRatingbeanwecreatedinthepreviousarticleforthenewcreate()method.Thesecond stepistowritetheJavabeanthatfunctionsasourcontroller.Thethirdstepistoconfigurethe controllerbeanviaSpringsothatAlfrescoknowstoinvokeitwhenthewebscriptiscalled. StepOne:Businesslogic Addthefollowingmethodtothecom.someco.behavior.Ratingclasswecreatedinthepreviousarticle.
public void create(NodeRef nodeRef, int rating, String user) { boolean switchUser = false; String currentUser = AuthenticationUtil.getCurrentUserName(); if (!currentUser.equals("admin")) { logger.debug("Current user is not admin so switching to admin"); AuthenticationUtil.setCurrentUser("admin"); switchUser = true; } UserTransaction txn = transactionService.getUserTransaction(); try { txn.begin(); // add the aspect to this document if it needs it if (nodeService.hasAspect(nodeRef, Qname.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASPECT_SC_RATEABLE))) { logger.debug("Document already has aspect"); } else { logger.debug("Adding rateable aspect");

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page19of28

ecmarchitect.com
nodeService.addAspect(nodeRef, Qname.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASPECT_SC_RATEABLE), null); } Map<QName, Serializable> props = new HashMap<QName, Serializable>(); props.put(QName.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, "rating"), rating); props.put(QName.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, "rater"), user); nodeService.createNode(nodeRef, Qname.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASSN_SC_RATINGS), Qname.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, "rating" + new Date().getTime()), Qname.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.TYPE_SC_RATING), props); txn.commit(); } catch(Throwable e) { try { if (txn.getStatus() == Status.STATUS_ACTIVE) txn.rollback(); } catch (Throwable ee) { e.printStackTrace(); } } if (switchUser) AuthenticationUtil.setCurrentUser(currentUser); }

Thisisabitpainfultolookatbutbasicallywhat'sgoingonis:

Ifthecurrentuserisnotadmin,thecurrentuserissettoadmin Anewtransactionisstarted Ifthenodedoesn'tyethavetherateableaspect,itisadded Theratingandraterpropertiesareset Thetransactioniscommitted Ifthecurrentuserwasswitchedtoadmin,thecurrentuserisswitchedbacktowhomeveritwas beforetheswitchtoadmin

StepTwo:Webscriptcontrollerbean

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page20of28

ecmarchitect.com
Withthecreate()methodinplace,allwehavetodoiswriteaJavaclassthatgrabstheid,rating,and raterargumentsandcallsthemethod.Todothat,createanewclasscalled com.someco.scripts.PostRating.Theclassnameisn'tsignificantbutitseemslikefollowingsomesort ofdescriptiveconventioncouldbehelpfulhereiftherearealargenumberofJavabackedscripts.The classneedstoextendorg.alfresco.webscripts.DeclarativeWebScript.OurlogicgoesinexecuteImplas shownbelow.
public class PostRating extends org.alfresco.web.scripts.DeclarativeWebScript { Logger logger = Logger.getLogger(PostRating.class); private Rating ratingBean; @Override protected status) { String String String Map<String, Object> executeImpl(WebScriptRequest req, WebScriptStatus id = req.getParameter("id"); rating = req.getParameter("rating"); user = req.getParameter("user");

if (id == null || rating == null || rating.equals("0") || user == null) { logger.debug("ID, rating, or user not set"); status.jsSet_code(400); status.jsSet_message("Required data has not been provided"); status.jsSet_redirect(true); } else { NodeRef curNode = new NodeRef("workspace://SpacesStore/" + id); if (curNode == null) { logger.debug("Node not found"); status.jsSet_code(404); status.jsSet_message("No node found for id:" + id); status.jsSet_redirect(true); } else { ratingBean.create(curNode, Integer.parseInt(rating), user); } } Map<String, Object> model = new HashMap<String, Object>(); model.put("node", id); model.put("rating", rating); model.put("user", user); } return model;

public Rating getRatingBean() {

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page21of28

ecmarchitect.com
return ratingBean; } public void setRatingBean(Rating ratingBean) { this.ratingBean = ratingBean; } }

ThiscodeshouldlookstrikinglysimilartoaJavaScriptcontrollerandinfactitdoesthesamething.It checksthearguments,setsanerrorcodeiftheargumentsaremissing,andthenwritessomedatatothe model. ThecontrollergetstheRatingclassthroughSpringdependencyinjection.We'llconfigurethatinour Springconfig,whichisthenextstep. StepThree:SpringconfigfortheWebscriptcontrollerbean Thefollowingshowsthecontentsofsomecoscriptscontext.xml.Thenameofthefileisn'timportant, butitmustendwith*context.xml.


<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <beans> <bean id="webscript.com.someco.ratings.rating.post" class="com.someco.scripts.PostRating" parent="webscript"> <property name="ratingBean"> <ref bean="ratingBehavior" /> </property> </bean> </beans>

ThisshouldlooklikeanyotherSpringbeanconfigfileyou'veseen.Thewebscriptmagicisintheid andparentattributes.Theidfollowsanamingconvention.Theconventionis:
webscript.package.service-id.method

PaycloseattentiontotheuseofthesingularwebscripthereversusthepluralwebscriptsintheData Dictionaryfolders.That'sapotentialmultihourdebuggingsessionendinginaforeheadslapwitha Doh!ifyouaren'tcareful. ItisprobablyworthmentioningthatadecisiontouseaJavabackedwebscriptdoesn'texcludetheuse ofJavaScriptforthatwebscript.IfyouhavebothaJavaclassandaJavaScriptfile,theJavaclassgets executedfirstfollowedbytheJavaScript.ThescripthasaccesstoeverythingtheJavaclassputinthe modelandcanupdatethemodelbeforepassingitalongtotheresponsetemplate.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page22of28

ecmarchitect.com
Revisitingtherating.get.html.ftltemplate
Nowwehaveeverythingweneedinplacebutwedon'thaveagreatwaytotesttheratingPOST.So, whatwe'lldoisaddalittleratingwidget1totherating.get.html.ftltemplate.Asimplelinkwoulddobut Iwantedtotestoutthewidgetbeforeincorporatingitintoarealpage. First,let'sseewhatitlookslikewhenwecall/someco/rating.html/id=someid.Thefigurebelowshows acallwhenthewhitepapernodehas13ratingsandanaverageof1.923.

Thepurposeoftheratingwidgetistwofold.First,itgraphicallydisplaystheaverageratingfora whitepaper.Second,eachstarinthewidgetishot.Sowhenyouclickoneoftheratingstars,an asynchronouspostismadeto/someco/ratingwhichcausesanewratingobjecttogetcreated.The ratingposteddependsonthestarclicked.Thepersonsubmittingtheratingwouldnormallybepassedin basedonsomesortofcredential,maybefromaportalsessionoracookie.Inourlittletest,therater getspulledfromthefield. Let'slookatHTMLfirst,thensomeoftheJavaScript:


<p><a href="${url.serviceContext}/whitepapers.html?guest=true">Back to the list</a> of whitepapers</p> <p>Node: ${args.id}</p> <p>Average: ${rating.average}</p> <p># of Ratings: ${rating.count}</p> <form name="login"> Rater:<input name="userId"></input> </form> Rating: <div class="rating" id="rating_${args.id}" style="display:inline">${rating.average}</div>

ThisisallbasicHTML/FreeMarkerstuffyou'veseenbefore.Thelastlinesetsupadivfortheratings
1 Iusedthecodeathttp://www.progressivecoding.com/tutorial.php?id=6asthestartingpointfortheratingwidget.Most ofitisunchangedwiththeexceptionofchangingtheratingsfrombeing0indexedtobeing1indexedandfollowingthe author'sinstructionstohookthewidgetintothepageusingPrototypewhichIalreadyhappenedtohavelyingaround.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page23of28

ecmarchitect.com
widget.Theidofthedivusesthenoderefofthewhitepaper.Thisallowsmultipleratingswidgetstobe onthesamepageandmakesiteasyfortheJavaScripttopassthenoderefontothe/someco/rating URL. ThesecondpieceistheJavaScript.I'mgoingtoomitsomeofthelessinterestingfunctionsandjust showthefunctionsrelatedtopostingratings.Theaccompanyingsourcecodehasthefullsource.
function submitRating(evt) { var tmp = Event.element(evt).getAttribute('id').substr(5); var widgetId = tmp.substr(0, tmp.indexOf('_')); var starNbr = tmp.substr(tmp.indexOf('_')+1); alert("Post to URL:" + widgetId + "," + starNbr); if (document.login.userId.value != undefined && document.login.userId.value != "") { curUser = document.login.userId.value; } else { curUser = "jpotts"; } postRating(widgetId, starNbr, curUser); } function postRating(id, rating, user) { if (receiveReq.readyState == 4 || receiveReq.readyState == 0) { receiveReq.open("POST", "/alfresco/service/someco/rating?id=" + id + "&rating=" + rating + "&guest=true&user=" + user, true); receiveReq.onreadystatechange = handleRatingPosted; receiveReq.send(null); } } function handleRatingPosted() { if (receiveReq.readyState == 4) { alert("Post successful"); } }

ThoseofyoufamiliarwithAJAXtechniquesmaybewonderingwhyIdidn'tusePrototypetomakethe postsinceIwasalreadyusingitwiththeratingwidget.IhadtroublegettingPrototypetoplaynicely withtheWebScriptFramework.Forsomereasontheargumentsweren'tgettingrecognized.SoI puntedandusedthelowerlevelXMLHttpRequest.You'llalsonoticethatIdon'tdynamicallyupdate theratingorreinitthewidgetafterthesuccessfulpost.Myonlyexcuseforthatoneislaziness.

Deletingratings
SettingupawebscriptfordeleteissimilartotheGETforratings.Thedescriptorisnamed rating.delete.desc.xml.Isetminetorequireadminauthentication.Itseemsrarethatyouwouldwant AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page24of28

ecmarchitect.com
todeleteallratingsforagivennodebuthighlylikelythatifyouaregoingtoexposeit,itshouldbefor adminsonly. Asinpreviousexamples,thecontrollerJavaScriptreadsandcheckstheargumentsthencallsafunction. InthiscaseitisthedeleteRatingsfunctionthathasbeenaddedtorating.js.Thebodyofthefunctionis:
function deleteRatings(curNode) { // check the parent to make sure it has the right aspect if (curNode.hasAspect("{http://www.someco.com/model/content/1.0}rateable")) // continue, this is what we want } else { logger.log("Node did not have rateable aspect."); return; } // get the node's children var children = curNode.children; if (children != null && children.length > 0) { logger.log("Found children...iterating"); for (i in children) { var child = children[i]; logger.log("Removing child: " + child.id); child.remove(); } }

Thescriptbailsifthenodedoesn'thavetherateableaspects(becausetherewouldn'tbeanyratings). Otherwise,itgrabsthechildrenanddeletesthem.Notetheimportantassumptionthattheonlychildren thatexistareratings.Ifthere'sapossibilityofotherchildassociations,you'dobviouslywanttobemore discriminating.

Examplesummary
We'veimplementedtwoGETscripts(oneforwhitepapersandoneforrating),aPOSTscriptfor creatingnewratings,andaDELETEforclearingoutratings.AtthispointSomeCohaseverythingthey needforbuildingafrontendthattalkstotheAlfrescorepositoryviaREST.Onepieceoffunctionality Ididn'tshow,butI'veincludedinthesource,istheabilityforanoptionaluserargumenttobepassed intothetwoGETscripts.Whenpresent,thescriptwillreturnthelastratingforthespecifieduser.I'll leaveittoyoutofollowthesourcetofigureouthowthatworks.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page25of28

ecmarchitect.com
Dealingwiththecrossdomainscriptinglimitation
YoumayhavenoticedthatinallofmyURLexamples,I'musinglocalhost.Infact,thestaticHTML pages(whitepaperindexandwhitepaperdetail)thatmakeAJAXcallsarealsoonlocalhost.Ididthisto simplifytheexamplebutinreallife,itishighlylikelythatthecodemakinganAJAXcalltoyourweb scriptwillresideonadifferenthostthantheonewhereAlfrescolives.Thiscreatesaproblemcalledthe crossdomainscriptinglimitation.Theissueisthatforsecurityreasonsbrowsersdon'tletyouopen anXMLHttpRequesttoadifferenthostthantheoneservingthepage.Thereareafewwaysyoucan handlethisdependingonyoursituation.

Usescripttags.Onewaytoworkaroundtheproblemistouseascripttaginwhichthesrc attributepointstoalocationonadifferenthost.ThebrowserthinksitisloadingaJavaScript filebutwhatitisreallydoingiscallingyourwebscriptwhichreturnsJSON.Thescripttagscan beoutputdynamicallythroughdocument.write. Useaproxy.Serversaren'tsubjecttothebrowser'ssecurityconstraints.Youcaneasilywrite yourownJavaservletthatactsasareverseproxy.AJAXcallsgoagainsttheproxyandpassin therealURLasanargument.TheservlettheninvokestheURLandreturnstheresults. Useacallbackmechanism.Alfrescoclaimstohaveacallbackmechanismbuiltintotheweb scriptruntime.Thewayitissupposedtoworkisthatyoupassinafunctionnameasan argumenttothewebscriptlike&alf_callback=someFunction.Thefunctionissupposedtoget calledwhenthepageisloaded.Icouldn'tgetitworkingandendedupfilingaJiraticket. Deployeverythingtothesameserver.Thisistheleastlikelyscenariotoworkinaproduction implementationbutit'stheoneIchoseforthisarticlesowewouldn'thavetospendalotoftime ontheissue.Thescriptsandimagestheratingswidgetdependsonresideinsomeco/javascript andsomeco/images,respectively,undertheAlfrescowebroot.Thewhitepaperindexand whitepaperdetailspagesIusedforthescreenshotsatthebeginningofthearticleareenhanced copiesofthefilesusedfortheOptaroswebsitedeployedtotheROOTwebapplicationfolder.

DeployingandTesting
Torunthesampleasis,allyouhavetodois: 1. Importthewebscriptarticleproject.zipfileintoEclipse. 2. Changebuild.propertiestomatchyourenvironment. 3. RunthedefaultAnttask. ThedefaultAnttaskwillcompileallnecessarycode,JARitup,zipuptheJARandtheextensionsinto AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page26of28

ecmarchitect.com
theappropriatefolderstructure,andthenunzipontopoftheAlfrescowebrootwhichdeploysthe custommodel,Springconfigfiles,webclientcustomizations,scripts,webscripts,andtheimagesand JavaScriptfortherating.get.htmlpagetotheappropriatedirectories. Afteranerrorfreestartup,createSomeco/WhitepapersinyourCompanyHomeanduploadacoupleof testwhitepapers.UploadandexecutetheaddTestRating.jsscriptinthecontextofeachtestwhitepaper tocreatetestratingobjects. Youshouldthenbeabletorunanyofthewebscriptsidentifiedinthisarticlewithoutanyproblems. Incaseyouarecurious,myenvironmentis:

UbuntuDapperDrake MySQL4.1(withversion5.0.3oftheJDBCdriver) Java1.5.0_12 Tomcat5.5.x Alfresco2.1.0Enterprise,WARonlydistribution

Obviously,otheroperatingsystems,databases,andapplicationserverswillworkaswell.WebScripts, however,onlyworkstartingwithAlfresco2.1.

Conclusion
ThisarticlehasgivenyouanintroductiontotheAlfrescoWebScriptFramework.Webeganwithavery simpleHelloWorldscriptandthengraduallymovedtomorecomplexexampleswhichculminatedina RESTAPIforretrievingwhitepapers,gettingtheaverageratingforaspecificwhitepaper,postingnew ratingsforagivenwhitepaper,anddeletingallratingsforaspecificwhitepaper.Weusedboth JavaScriptandJavatoimplementcontrollerlogic.WeusedFreeMarkertooutputHTMLaswellas JSON.Wesawsomeoptionsforworkingaroundthecrossdomainscriptinglimitation. Therearestilltopicslefttoexplore.OneexampleisusingWebScriptstointegrateaportallikeLiferay orJBossPortalwithAlfresco.AnotherisMicrosoftOfficeAlfrescointegrationwhichisbasedonWeb Scripts.AndwhataboutusingWebScriptstocustomizetheWebClientuserinterface?Hopefully, you'vebeeninspiredenoughtotakealookatthosetopicsonyourown.Maybeyou'llevenblogabout yourexperience.Ifso,orifyouhaveanyotherfeedback,pleaseletmeknow.I'dlovetohearfromyou.

Wheretofindmoreinformation

Thecompletesourcecodethataccompaniesthisarticleisavailableherefromecmarchitect.com. YoumayalsoenjoypreviousarticlesintheAlfrescoDeveloperseriesatecmarchitect.com: AlfrescoDeveloper:IntrototheWebScriptFramework


ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page27of28

ecmarchitect.com
Implementingcustombehaviors ,September,2007. WorkingwithCustomContentTypes,June,2007. Developingcustomactions ,January,2007. Alfrescowikipagesrelatedtothistopic: AlfrescoWebScripts wikipage AlfrescoWebScriptRuntimes wikipage AlfrescoJavaScriptAPI wikipage AlfrescoTemplateGuide (FreeMarkerinfo)wikipage Fordeploymenthelp,seetheClientConfigurationGuideandPackagingandDeploying ExtensionsintheAlfrescowiki. Forgeneraldevelopmenthelp,seetheDeveloperGuide. Forhelpcustomizingthedatadictionary,seetheDataDictionarywikipage. LuisSala'spresentationonWebScriptsattheWestCoastAlfresco+LiferayMeetupalongwith apodcastoftheaudioportionofthepresentationisavailableatLuis'FreshTalkblog. LearnmoreaboutJSONatjson.organdFreeMarkeratfreemarker.sourceforge.net. ThejMakiProjectisaframeworkforbuildingAjaxenabled,Javawebapplications.Includedas partofitisaproxyyoucanusetoworkaroundthecrossdomainscriptinglimitationifyou don'twanttowriteyourown. TheJSR168PortletSpecificationisavailableontheJavaCommunityProcesssite.

AbouttheAuthor
JeffPottsistheEnterpriseContentManagementPracticeLeadatOptaros,a leadingOpenSourceandNextGenerationInternetconsultancy.Jeffhasfifteen yearsofexperienceimplementingcontentmanagement,collaboration,andother knowledgemanagementtechnologiesforavarietyofFortune500companies.Jeff livesinDallas,Texaswithhiswifeandtwokids.Readmoreatecmarchitect.com.

AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page28of28

You might also like