0% found this document useful (0 votes)
102 views

Web Script Article

This document introduces the Alfresco Web Script Framework, which allows exposing a RESTful API for interacting with Alfresco content. Web scripts map URLs to controllers implemented with JavaScript or Java that perform logic, populate a model, and forward to FreeMarker templates to generate responses in HTML, XML, JSON, or other formats. This provides a lightweight way to build custom interfaces and APIs for Alfresco without tightly coupling clients. The document discusses how web scripts can be used to build REST APIs and frontend applications that retrieve and modify Alfresco content over HTTP.
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
102 views

Web Script Article

This document introduces the Alfresco Web Script Framework, which allows exposing a RESTful API for interacting with Alfresco content. Web scripts map URLs to controllers implemented with JavaScript or Java that perform logic, populate a model, and forward to FreeMarker templates to generate responses in HTML, XML, JSON, or other formats. This provides a lightweight way to build custom interfaces and APIs for Alfresco without tightly coupling clients. The document discusses how web scripts can be used to build REST APIs and frontend applications that retrieve and modify Alfresco content over HTTP.
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 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