ecmarchitect.
com
AlfrescoDeveloper:IntrototheWebScriptFramework
October,2007
JeffPotts
[Link], visit[Link] SanFrancisco,California,94105,USA.
[Link]
AlfrescoDeveloper:IntrototheWebScriptFramework
October2007 JeffPotts
Introduction
ThisarticleisanintroductiontotheAlfrescoWebScriptFrameworkthatbecameavailablewithrelease 2.1oftheproduct. We'[Link] refresher,inthosearticles,weextendedtheoutoftheboxcontentmodelsothatSomeCocouldstore custommetadataaboutoneoftheirdocumenttypes,[Link] [Link],inthecustombehaviorarticle wewrotebusinesslogicassociatedwiththerateableaspectthatknewhowtocalculatetheaverageuser [Link] [Link]'t aninterfaceavailablethatenduserscouldusetoratewhitepapers. SomeCoisnowreadytomovetothenextstep:[Link] theirinfinitewisdom,theteamatSomeCorealizesthatAlfresco'sWebScriptsprovideanicewayto exposealightweight,[Link],we'll rollourownRESTAPIforretrievingalistofwhitepapers,retrievingtheaverageratingforagiven whitepaper,retrievingaspecificrating,postinganewratingforawhitepaper,anddeletingallratings [Link]'lluseJavaScriptformostofourcontrollerlogicbutwe'llseehowtouse [Link]. [Link] [Link],thezip includesthecodecreatedinthefirsttwoSomeCoarticlessoifyoudon'thavetodigaroundforthe codewebuilduponinthisarticle. Sounddecent?Okay,let'sgetstarted.
WhatistheWebScriptFramework?
Contentcentricapplications,whethertheyareinsideoroutsidethefirewall,arebecomingmoreand [Link]. Ratherthanhavingasingle,monolithicsystemresponsibleforallaspectsofacontentcentricweb AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page2of28
[Link]
application,looselycoupledsubsystemsarebeingintegratedtocreatemoreagilesolutions. Thisapproachrequiresthatyourcontentmanagementsystemhaveaflexibleandlightweightinterface. Youdon'twanttobelockedintoapresentationapproachbasedonthecontentrepositoryyouare [Link],insomecases,youmighthaveverylittlecontroloverthetoolsthatwillbeused totalktoyourCMS. ConsidertheexplodingrateofNextGenerationInternet(NGI)solutions,thegrowingadoptionofwikis andblogswithinanEnterprise(Enterprise2.0),andtheincreasingpopularityofmashupsboth [Link] blackboxcomponentwiththefrontend(orperhapsmanydifferentfrontends)interactingwiththe CMSandothercomponentsviaREST. Amongopensourcecontentmanagementsystems,Alfrescoisoneofthefewthatreallylendsitselfto [Link] [Link] Alfrescorepositorypriortorelease2.1:
[Link]'[Link] [Link],thedownside isthatyou'[Link]'twejusttalkabout howimportantanopen,looselycoupledarchitectureis?Movingon... [Link] time,butSOAPbasedWebServiceshaveheavierclientsiderequirementsthantheirRESTful [Link] processingoverheadtoscalewellsotheyendedupwritingtheirownservicesandexposing [Link] cases. [Link] [Link] interactingwiththerepositorywhichtheoreticallyreducesswitchingcostsandmakesthe [Link] everythingyouneedtodosoyouendupusingtheJCRincombinationwithoneoftheabove [Link] Javaonly. [Link], FreeMarkertemplatesandserversideJavaScriptcanbeappliedtoanynodeintherepository. So,forexample,[Link] appcanthenpostanXMLHttpRequesttoAlfrescothatspecifiesanodereferenceanda
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page3of28
[Link]
[Link] [Link] functionalityoftheWebScriptFrameworkpriorto2.1. Theseoptionsareallstillavailableandmaymakesensedependingonexactlywhatyouaretryingto do.Butwith2.1there'sapotentiallybetterwayforinteractingwiththerepositorytheWebScript Framework. TheWebScriptFrameworkessentiallyimprovesonthebasicideathatstartedwithURLaddressability. Thinkofawebscriptasachunkofcodethatismappedtoahumanreadable(andsearchengine readable)[Link],forexample,aURLthatreturnsexpensereportspendingapprovalmightlooklike:
/alfresco/service/expenses/pending
whileaURLthatreturnsexpensespendingapprovalforaspecificusermightlooklike:
/alfresco/service/expenses/pending/jpotts
IntheURLabove,[Link] explicitwaytoprovideanargumentwouldbelike:
/alfresco/service/expenses/pending?user=jpotts
Ormaybependingisanargumentaswellwhichtellsthewebscriptwhatstatusofexpensereportsto [Link](andif)yourURLincludesargumentsis completelyuptoyou. [Link],XML,JSON,or evenaJSR168Portlet. TheWebScriptFrameworkmakesiteasytofollowtheModelViewController(MVC)pattern, althoughitisn'[Link],aJavaBean,[Link] handlestherequest,performsanybusinesslogicthatisneeded,populatestheModelwithdata,and [Link] [Link] ControllerandtheView. ThemappingofURLtocontrollerisdonethroughanXMLdescriptorwhichisresponsiblefor declaringtheURLpattern,whetherthescriptrequiresatransactionornot,andtheauthentication [Link] scriptaswellastheresponseformatsthatareavailable. [Link],for example,theFreeMarkertemplatethatreturnsexpensesasHTMLwouldbenamedwithanextension ofhtmlwhiletheonethatreturnsXMLwouldbenamedwithanextensionofxml.
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page4of28
[Link]
Thedescriptor,theJavaScriptfile,andtheFreeMarkertemplatescanresideeitherintherepositoryor [Link],theclassmustresidesomewhereontheclasspath. Withthesebuildingblocksinmindyoumayalreadybethinkingofdifferentwaysyoucouldleverage [Link],[Link] 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 [Link] [Link].AJSR168runtimeallows portletstoinvokewebscriptsdirectly. Youcanwriteyourownruntimeifthesedon'[Link]. Atsomepoint,youcouldseewebscriptexecutionseparatedentirelyfromtheAlfrescowebapplication intoitsownprocesswhichwouldlenditselftoloadbalancing,scalability,etc. Inthisarticle,we'llfocusontheservletruntimeforHTTP. WebScriptsDirectory [Link],go to[Link] [Link] thatresideintherepositorydoesnotrequirearestart,youmayhavetorefreshtheindexwiththis buttonafterachange. [Link]'llnoticethatthetoolitselfisbuilt usingwebscripts.
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page5of28
[Link]
Examples
Let'[Link]'regoingtostartwithaverysimpleHelloWorldwebscript. Afterthat,we'llgetprogressivelymorecomplexuntil,attheend,wehaveaRESTbasedinterfacefor creating,reading,anddeletingSomeCowhitepaperratings.
HelloWorldExample
Let'simplementthemostbasicwebscriptpossible:AHelloWorldscriptthatechoesbackanargument. We'[Link]: 1. LogintoAlfresco. 2. Navigateto/CompanyHome/DataDictionary/WebScriptsExtensions. 3. [Link]:
<webscript> <shortname>Hello World</shortname> <description>Hello world web script</description> <url>/helloworld?name={nameArgument}</url> </webscript>
4. [Link]:
<html> <body> <p>Hello, ${[Link]}!</p> </body> </html>
5. Goto[Link] clicktheListWebScriptslinkyoushouldbeabletofindthewebscriptyoujustdefined. 6. Nowgoto[Link]
Hello, Jeff!
[Link],[Link]'stheHTTPmethodusedtocallthe [Link]'[Link] method,youcanhavemultiplecontrollersforthesameservicedependingonhowtheserviceis called([Link],etc.).Second,inthiscaseweonlyhadoneargumentbutwecouldaddasmany [Link],though!DescriptorsmustbevalidXMLwhichmeansampersandsmustbe [Link]:
<url>/helloworld?name={nameArgument}&secondArg={anotherArg}</url>
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page6of28
[Link]
Third,noticewedidn'tincludeaJavaScriptfileinthisexamplebutthescriptstillranbecause controllersareoptional. Mostscriptsaregoingtouseacontroller,though,solet'sgoaheadandaddone. 1. [Link]:
[Link] = "bar";
2. [Link]:
<html> <body> <p>Hello, ${[Link]}!</p> <p>Foo: ${foo}</p> </body> </html>
3. Goto[Link] becauseyouaddedacontrollerthatthewebscriptruntimedidn'tknowabout. 4. NowgotoyourwebbrowserandenterthesameURLfromthefirstexamplewhichwas [Link]
Hello, Jeff! Foo: bar
What'[Link] [Link],wedidn'tleveragethe JavaScriptAPIatallwejustputsomedataintothemodelobjectwhichwasthenreadbythe [Link], we'lluseJavainsteadofJavaScriptforthecontroller.
SomeCoWhitepaperUsercontributedRatingsExamples
WewanttocreateaRESTAPIthatfrontenddeveloperscanusetofindwhitepapersandratingsaswell [Link],itprobablymakessensetoroughouttheAPI.
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page7of28
[Link]
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 [Link]'regoing [Link] [Link]'tevenbothertochangethelogo.
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page8of28
[Link]
Illustration1:TheindexofwhitepapersusesanAJAXcalltoretrievewhitepaper metadataandratings. Youcan'ttellfromthescreenshots,[Link] asynchronousposttothe/someco/[Link] Paperlinkisclicked,[Link] [Link] [Link] standardDownloadURLtogivetheuserdirectaccesstothecontent.
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page9of28
[Link]
Illustration2:ThedetailpagealsousesanAJAXcallandincludesthesameratings widgetaswellasadownloadlink
ListingallWhitepapers
Asaquickreview,recallthatSomeCowriteswhitepapersandmanagesthosepaperswithAlfresco. [Link] [Link] article,we'[Link]'lljustassumeanysubtype ofsc:whitepaperfoundinthe/Someco/Whitepapersfolderisfairgame.(Ifthisbugsyou,seethe sidebar). Let'[Link]. HTMLwillallowustoeasilytesttheserviceandJSONwillmakeiteasyforcodeonthefrontendto [Link]:onedescriptor,oneJavaScriptcontroller,andtwo FreeMarkertemplatesoneforeachformat. AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page10of28
[Link]
Beforewestartcoding,let'stalkabitaboutorganization. First,[Link] organizescriptassetsintoahierarchicalfolderorpackage [Link] [Link] [Link]'llbeusing com/somecoforourpackagewhichmeansourfileswill resideunder/CompanyHome/DataDictionary/WebScripts Extensions/com/someco.1 Next,URLscanfollowanypatternwewant,buttheywill alwaysstartwith<alfrescowebapp>/servicewhere <alfrescowebapp>isthenameoftheAlfrescoweb applicationcontext(usuallyalfresco).BecausetheURL patternmustbeunique,itisprobablyagoodideato [Link]'ll prefixallURLswithsomeco. AlfrescoreservescertainpackagenamesandURLsfortheir ownuse(seetheAlfrescowiki)butbyfollowingthe conventionsproposedhere,you'llsteerclearofthose.
Sidebar:Enablingasubsetofwhitepapers forwebdisplay Therealworldsolutionthisexampleisbased onusesUIactionstoenableanddisablethe sc:[Link] orshowstheEnableWeborDisable WebUIactionlinkbasedonthevalueof theflagandthegroupmembershipofthe [Link] yourown,thesc:webableaspectisinthe modelincludedwiththesourcecodeforthis [Link],I'veincludedtwoscripts ([Link])thatyou canusetoattachthewebableaspectandset [Link] thewhitepaperservicetofilterthelistbased ontheisActiveflag,theLucenequeryinthe [Link] with@sc\\:isActive:truetoshowonly [Link] outoftheexamplebecauseitisn'tcoretothe topic.
Finally,you'veseenthatwebscriptassetscanresideinthe repository,[Link] classpath,butIsuggestthattheyresideinthealfresco/extensiondirectoryjustlikeyourother [Link],ifweweretostoreourscriptsonthefile system,ratherthantherepository,we'dputtheminalfresco/extension/templates/webscripts/com/ someco. Theadvantageofusingthefilesystemisthatthewebscriptsthatmakeupyoursolutioncanbe deployedalongsideyourotherextensionswithoutrequiringanyonetouploadthemtotherepository. Thedisadvantageisthatchangesrequirearestart. InthesourcecodeI'veprovidedwiththisarticle,thewebscriptsaresetuptodeploytothefilesystem [Link],insteadofdeployingthesamplecode,youwanttofollowalongandyou wanttoavoidrestarts,movemyscriptsoutofthealfrescoextensiondirectorybeforeyoudeploy,then uploadyourscriptstotherepositorylikewedidfortheHelloWorldexample.
1 Itisn'[Link] withhowwebclientcustomizationsaredeployed(usingthealfresco/extensionfolder).WebscriptsplacedintheWeb ScriptsExtensionsfolderthathavethesamefilenamesasthoseintheWebScriptsfolderwilloverridethescripts [Link],ifyouwantotherstobeabletooverrideyourscripts,usetheWebScripts [Link].
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page11of28
[Link]
Ifscriptsaredefinedintherepositoryaswellastheclasspath,thefilesintherepositorytake [Link] scripts(SeeWheretofindmoreinformationattheendofthisarticleforalistofreferences). Step1:Thedescriptor Withthat,[Link] [Link]:
<webscript> <shortname>Get all whitepapers</shortname> <description>Returns a list of active whitepapers</description> <url>/someco/whitepapers</url> <url>/someco/[Link]</url> <url>/someco/[Link]</url> <format default="json">extension</format> <authentication>guest</authentication> <transaction>none</transaction> </webscript>
Thereareafewelementsinthisdescriptorwedidn'[Link],noticethat [Link] [Link] informat,itisn'tstrictlyrequiredthattheybelistedinthedescriptor,butitisagoodpractice. Inthiscase,we'[Link] alternativesyntaxistousetheargumentsyntaxlikethis:
<url>/someco/whitepapers?format=json</url> <url>/someco/whitepapers?format=html</url>
Mycurrentthinkingisthattheextensionsyntaxispreferredbecauseitisfriendliertosearchengines buttheremaybereasonstousetheargumentsyntax. Theformatelementdeclaresthetypeofsyntaxwe'[Link] wanttoaccepteithersyntax,[Link],usingthisdescriptorif someoneusestheargumentsyntax,they'[Link] specifyingaformat,they'llgetJSON. [Link] [Link],user, andadmin. [Link] doesn'tneedatransactionsowe'[Link] requiresnew. Next,[Link]: AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page12of28
[Link]
<import resource="classpath:alfresco/extension/scripts/[Link]"> var whitepapers = [Link]("PATH:\"/{[Link] y_home/{[Link] +TYPE:\"{[Link] if (whitepapers == null || [Link] == 0) { [Link]("No whitepapers found"); [Link] = 404; [Link] = "No whitepapers found"; [Link] = true; } else { var whitepaperInfo = new Array(); for (i = 0; i < [Link]; i++) { var whitepaper = new whitepaperEntry(whitepapers[i], getRating(whitepapers[i])); whitepaperInfo[i] = whitepaper; } [Link] = whitepaperInfo; } function whitepaperEntry(whitepaper, rating) { [Link] = whitepaper; [Link] = rating; }
Thefirstthingtonoticeaboutthescriptisthatwe'[Link] [Link] thatretrievingaratingisalsobusinesslogicrelatedtoarating,[Link] [Link]'llseetheupdatedversionofthe [Link].(Theabilitytoimportascriptfromanotherscriptwasaddedwithrelease [Link]). ThenextthingtonoticeisthatthescriptqueriestherepositoryusingLucenetogetalistof [Link].Theresponsecodegetssetto404 [Link] templateforerrorcodesbutyoucanoverrideitwithyourownbycreatingFreeMarkertemplatesthat [Link],wecouldhaveacustom404responsetemplatefor [Link] information. [Link] [Link] somedatatotheresultset,soI'mbuildinganewArrayandsettingthattothemodel.(Iknowthe averageratingisapropertyofawhitepaper,soitmaynotyetbeobviouswhyIhaveaseparatefunction AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page13of28
[Link]
forretrievingtheratingorwhyIwouldstoretheratinginthemodelseparatefromthewhitepaper. Trustthatitwillmakesenselater). [Link] [Link] aroundfromthecustombehaviorsarticle,thedifferencebetweenitandthisoneisanewfunction calledgetRatingasshownbelow:
function getRating(curNode) { var rating = {}; [Link] = [Link]["{[Link] [Link] = [Link]["{[Link] return rating; }
ThefunctionsimplyretrievestheaverageRatingandratingCountpropertiesfromthespecifiednode [Link]. Assumingthereareitemsinthesearchresults,we'[Link]'s [Link] 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>${[Link]}</td> </tr> <tr> <td><b>Title</b></td><td>${[Link]["cm:title"]}</td> </tr> <tr> <td><b>Link</b></td><td><a href="${[Link]}${[Link]}?guest=true">${[Link]}${[Link] [Link]}</a></td> </tr> <tr> <td><b>Type</b></td><td>${[Link]}</td> </tr> <tr> <td><b>Size</b></td><td>${[Link]}</td> </tr>
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page14of28
[Link]
<tr> <td><b>Id</b></td><td>${[Link]}</td> </tr> <tr> <td><b>Description</b></td> <td><p><#if [Link]["cm:description"]?exists && [Link]["cm:description"] != "">${[Link]["cm:description"]}</#if></p> </td> </tr> <tr> <td><b>Pub Date</b></td><td>${[Link]["cm:modified"]?string(datetimeforma t)}</td> </tr> <tr> <td><b><a href="${[Link]}/[Link]?id=${[Link]}&guest=true">Rati ng</a></b></td> <td> <table> <tr> <td><b>Average</b></td><td>${[Link]}</td> </tr> <tr> <td><b>Count</b></td><td>${[Link]}</td> </tr> </table> </td> </tr> <#if !([Link] == whitepapers?[Link])> <tr><td colspan="2" bgcolor="999999"> </td></tr> </#if> </#list> </table> </body> </html>
Thistemplateiteratesthroughthequeryresultspassedinbythecontroller,andbuildsanHTMLtable withpropertiesofeachwhitepaper.(Yes,[Link],youcoulduseCSStospruceitup [Link],thisresponsetemplateisreallyfor debuggingpurposesonlyandIdidn'twanttofoolwiththeCSSsoatableitis). [Link] [Link]:
<#assign datetimeformat="EEE, dd MMM yyyy HH:mm:ss zzz">
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page15of28
[Link]
{"whitepapers" : [ <#list whitepapers as child> { "name" : "${[Link]}", "title" : "${[Link]["cm:title"]}", "link" : "${[Link]}${[Link]}", "type" : "${[Link]}", "size" : "${[Link]}", "id" : "${[Link]}", "description" : "<#if [Link]["cm:description"]?exists && [Link]["cm:description"] != "">${[Link]["cm:description"]}</#if>", "pubDate" : "${[Link]["cm:modified"]?string(datetimeformat)}", "rating" : { "average" : "${[Link]}", "count" : "${[Link]}", } } <#if !([Link] == whitepapers?[Link])>,</#if> </#list> ] }
Again,justliketheHTMLresponsetemplate,thescriptiteratesthroughtheresultsetbutthisone [Link]. Assumingyouhavesometestdatainyourrepository(SomecoWhitepaperobjectsinyour Someco/Whitepapersfolder)youshouldbeabletorefreshthewebscriptlistandrunthewebscript. BecausewetoldAlfrescothatthisscriptrequiresGuestaccessorhigher,you'llneedtoeitherloginto Alfrescobeforerunningthescript,authenticatewithavaliduserandpasswordwhenthebasic authenticationdialogispresented,orappend&guest=truetotheURLlikethis: [Link] you'llgetaJSONresponsebecausewesetthattothedefault. Ifallgoeswellyoushouldseesomethingsimilartothefigurebelow:
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page16of28
[Link]
Debugging Diditwork?Ifnot,it'[Link]'regoingtowanttodoistogointo [Link] [Link]. Anothertoolyou'[Link](1)ifAlfrescoknows aboutyourscriptand(2)[Link],youcango to[Link] willdumpthedescriptorandalloftheresponsetemplates. [Link],forexample,we'rerunninga [Link]'tfindinganywhitepaperseventhoughyou've createdtestdata,[Link]'treturnresults,there's somethingwrongwithyourtestdata.
RetrievingtheRatingforaWhitepaper
Gettingaspecificratingisroughlythesameasgettingawhitepaperbutitisabiteasierbecauseofour AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page17of28
[Link]
[Link],locatethe node,thencallgetRatingasshownbelow:
<import resource="classpath:alfresco/extension/scripts/[Link]"> if ([Link] == null || [Link] == 0) { [Link]("ID arg not set"); [Link] = 400; [Link] = "Node ID has not been provided"; [Link] = true; } else { [Link]("Getting current node"); var curNode = [Link]("workspace://SpacesStore/" + [Link]); if (curNode == null) { [Link]("Node not found"); [Link] = 404; [Link] = "No node found for id:" + [Link]; [Link] = true; } else { [Link] = getRating(curNode, [Link]); } }
ThedescriptorandresponsetemplatesareverysimilartothewhitepaperexamplesoIwon'tinclude [Link],we'llrevisittheHTMLresponsetemplatebymakingsome updatesthathelpustest. Fornow,here'swhatasuccessfulJSONcalltotheratingservicereturns:
{"rating" : { "average" : "1.923", "count" : "13", } }
PostingaRatingwithaJavabackedWebScript
[Link]/someco [Link] established,wehavetoappend&guest=truetotheURL,orwehavetologinwhenthebrowser presentsuswithabasicauthenticationdialog.(Anotheroptionistogetaticketfromawebservicecall, butthat'snotinthescopeofthisarticle). AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page18of28
[Link]
SomeCodoesn'twanttoopenupwriteaccesstothe/Someco/WhitepapersfoldertoGuestuserswho mighttrytoaccesstherepositoryviathewebclientsothatmeansweneedarealuseraccountinorder [Link]'twant tosetupuseraccountsforeveryuserthatmightratecontent. ThesolutionistoletGuestcallthePOSTURLbutleveragetheAlfrescoJavaAPItorunasa differentuser.(Inourcasewe'lluseadminbutauseraccountdedicatedtothepurposeofcreating ratingsisprobablyabetteridea).ThepostlogicwillresideinaJavaBeanratherthanaserverside JavaScriptfile. Thedescriptorandtheresponsetemplateslookliketheexampleswe'veseensofarsoIwon'trepeat [Link]. ThepiecethatisnewistheuseofJavaasthecontrollersolet'[Link]'ll [Link] [Link],we're goingtousetheRatingbeanwecreatedinthepreviousarticleforthenewcreate()[Link] [Link] controllerbeanviaSpringsothatAlfrescoknowstoinvokeitwhenthewebscriptiscalled. StepOne:Businesslogic [Link].
public void create(NodeRef nodeRef, int rating, String user) { boolean switchUser = false; String currentUser = [Link](); if () { [Link]("Current user is not admin so switching to admin"); [Link]("admin"); switchUser = true; } UserTransaction txn = [Link](); try { [Link](); // add the aspect to this document if it needs it if ([Link](nodeRef, [Link](SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASPECT_SC_RATEABLE))) { [Link]("Document already has aspect"); } else { [Link]("Adding rateable aspect");
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page19of28
[Link]
[Link](nodeRef, [Link](SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASPECT_SC_RATEABLE), null); } Map<QName, Serializable> props = new HashMap<QName, Serializable>(); [Link]([Link](SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, "rating"), rating); [Link]([Link](SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, "rater"), user); [Link](nodeRef, [Link](SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASSN_SC_RATINGS), [Link](SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, "rating" + new Date().getTime()), [Link](SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.TYPE_SC_RATING), props); [Link](); } catch(Throwable e) { try { if ([Link]() == Status.STATUS_ACTIVE) [Link](); } catch (Throwable ee) { [Link](); } } if (switchUser) [Link](currentUser); }
Thisisabitpainfultolookatbutbasicallywhat'sgoingonis:
Ifthecurrentuserisnotadmin,thecurrentuserissettoadmin Anewtransactionisstarted Ifthenodedoesn'tyethavetherateableaspect,itisadded Theratingandraterpropertiesareset Thetransactioniscommitted Ifthecurrentuserwasswitchedtoadmin,thecurrentuserisswitchedbacktowhomeveritwas beforetheswitchtoadmin
StepTwo:Webscriptcontrollerbean
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page20of28
[Link]
Withthecreate()methodinplace,allwehavetodoiswriteaJavaclassthatgrabstheid,rating,and [Link],createanewclasscalled [Link]'tsignificantbutitseemslikefollowingsomesort [Link] [Link] shownbelow.
public class PostRating extends [Link] { Logger logger = [Link]([Link]); private Rating ratingBean; @Override protected status) { String String String Map<String, Object> executeImpl(WebScriptRequest req, WebScriptStatus id = [Link]("id"); rating = [Link]("rating"); user = [Link]("user");
if (id == null || rating == null || [Link]("0") || user == null) { [Link]("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) { [Link]("Node not found"); status.jsSet_code(404); status.jsSet_message("No node found for id:" + id); status.jsSet_redirect(true); } else { [Link](curNode, [Link](rating), user); } } Map<String, Object> model = new HashMap<String, Object>(); [Link]("node", id); [Link]("rating", rating); [Link]("user", user); } return model;
public Rating getRatingBean() {
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page21of28
[Link]
return ratingBean; } public void setRatingBean(Rating ratingBean) { [Link] = ratingBean; } }
[Link] checksthearguments,setsanerrorcodeiftheargumentsaremissing,andthenwritessomedatatothe model. [Link]'llconfigurethatinour Springconfig,whichisthenextstep. StepThree:SpringconfigfortheWebscriptcontrollerbean [Link]'timportant, butitmustendwith*[Link].
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' '[Link] <beans> <bean id="[Link]" class="[Link]" parent="webscript"> <property name="ratingBean"> <ref bean="ratingBehavior" /> </property> </bean> </beans>
ThisshouldlooklikeanyotherSpringbeanconfigfileyou'[Link] [Link]:
[Link]
PaycloseattentiontotheuseofthesingularwebscripthereversusthepluralwebscriptsintheData [Link]'sapotentialmultihourdebuggingsessionendinginaforeheadslapwitha Doh!ifyouaren'tcareful. ItisprobablyworthmentioningthatadecisiontouseaJavabackedwebscriptdoesn'texcludetheuse [Link],theJavaclassgets [Link] modelandcanupdatethemodelbeforepassingitalongtotheresponsetemplate.
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page22of28
[Link]
[Link]
Nowwehaveeverythingweneedinplacebutwedon'[Link], whatwe'[Link] Iwantedtotestoutthewidgetbeforeincorporatingitintoarealpage. First,let'sseewhatitlookslikewhenwecall/someco/[Link]/id=[Link] acallwhenthewhitepapernodehas13ratingsandanaverageof1.923.
[Link],itgraphicallydisplaystheaverageratingfora [Link],[Link],an asynchronouspostismadeto/someco/[Link] [Link] basedonsomesortofcredential,[Link],therater getspulledfromthefield. Let'slookatHTMLfirst,thensomeoftheJavaScript:
<p><a href="${[Link]}/[Link]?guest=true">Back to the list</a> of whitepapers</p> <p>Node: ${[Link]}</p> <p>Average: ${[Link]}</p> <p># of Ratings: ${[Link]}</p> <form name="login"> Rater:<input name="userId"></input> </form> Rating: <div class="rating" id="rating_${[Link]}" style="display:inline">${[Link]}</div>
ThisisallbasicHTML/FreeMarkerstuffyou'[Link]
1 Iusedthecodeat[Link] ofitisunchangedwiththeexceptionofchangingtheratingsfrombeing0indexedtobeing1indexedandfollowingthe author'sinstructionstohookthewidgetintothepageusingPrototypewhichIalreadyhappenedtohavelyingaround.
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page23of28
[Link]
[Link] onthesamepageandmakesiteasyfortheJavaScripttopassthenoderefontothe/someco/rating URL. ThesecondpieceistheJavaScript.I'mgoingtoomitsomeofthelessinterestingfunctionsandjust [Link].
function submitRating(evt) { var tmp = [Link](evt).getAttribute('id').substr(5); var widgetId = [Link](0, [Link]('_')); var starNbr = [Link]([Link]('_')+1); alert("Post to URL:" + widgetId + "," + starNbr); if ([Link] != undefined && [Link] != "") { curUser = [Link]; } else { curUser = "jpotts"; } postRating(widgetId, starNbr, curUser); } function postRating(id, rating, user) { if ([Link] == 4 || [Link] == 0) { [Link]("POST", "/alfresco/service/someco/rating?id=" + id + "&rating=" + rating + "&guest=true&user=" + user, true); [Link] = handleRatingPosted; [Link](null); } } function handleRatingPosted() { if ([Link] == 4) { alert("Post successful"); } }
ThoseofyoufamiliarwithAJAXtechniquesmaybewonderingwhyIdidn'tusePrototypetomakethe [Link] [Link]'[Link] [Link]'llalsonoticethatIdon'tdynamicallyupdate [Link].
Deletingratings
[Link] [Link] AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page24of28
[Link]
todeleteallratingsforagivennodebuthighlylikelythatifyouaregoingtoexposeit,itshouldbefor adminsonly. Asinpreviousexamples,thecontrollerJavaScriptreadsandcheckstheargumentsthencallsafunction. [Link]:
function deleteRatings(curNode) { // check the parent to make sure it has the right aspect if ([Link]("{[Link] // continue, this is what we want } else { [Link]("Node did not have rateable aspect."); return; } // get the node's children var children = [Link]; if (children != null && [Link] > 0) { [Link]("Found children...iterating"); for (i in children) { var child = children[i]; [Link]("Removing child: " + [Link]); [Link](); } }
Thescriptbailsifthenodedoesn'thavetherateableaspects(becausetherewouldn'tbeanyratings). Otherwise,[Link] [Link]'sapossibilityofotherchildassociations,you'dobviouslywanttobemore discriminating.
Examplesummary
We'veimplementedtwoGETscripts(oneforwhitepapersandoneforrating),aPOSTscriptfor creatingnewratings,[Link] [Link] Ididn'tshow,butI'veincludedinthesource,istheabilityforanoptionaluserargumenttobepassed [Link],thescriptwillreturnthelastratingforthespecifieduser.I'll leaveittoyoutofollowthesourcetofigureouthowthatworks.
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page25of28
[Link]
Dealingwiththecrossdomainscriptinglimitation
YoumayhavenoticedthatinallofmyURLexamples,I'[Link],thestaticHTML pages(whitepaperindexandwhitepaperdetail)[Link] simplifytheexamplebutinreallife,itishighlylikelythatthecodemakinganAJAXcalltoyourweb [Link] [Link]'tletyouopen [Link] handlethisdependingonyoursituation.
[Link] [Link] [Link] [Link]. [Link]'tsubjecttothebrowser'[Link] [Link] [Link]. [Link] [Link] argumenttothewebscriptlike&alf_callback=[Link] [Link]'tgetitworkingandendedupfilingaJiraticket. [Link] implementationbutit'stheoneIchoseforthisarticlesowewouldn'thavetospendalotoftime [Link]/javascript andsomeco/images,respectively,[Link] whitepaperdetailspagesIusedforthescreenshotsatthebeginningofthearticleareenhanced copiesofthefilesusedfortheOptaroswebsitedeployedtotheROOTwebapplicationfolder.
DeployingandTesting
Torunthesampleasis,allyouhavetodois: 1. [Link]. 2. [Link]. 3. RunthedefaultAnttask. ThedefaultAnttaskwillcompileallnecessarycode,JARitup,zipuptheJARandtheextensionsinto AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page26of28
[Link]
theappropriatefolderstructure,andthenunzipontopoftheAlfrescowebrootwhichdeploysthe custommodel,Springconfigfiles,webclientcustomizations,scripts,webscripts,andtheimagesand [Link]. Afteranerrorfreestartup,createSomeco/WhitepapersinyourCompanyHomeanduploadacoupleof [Link] tocreatetestratingobjects. Youshouldthenbeabletorunanyofthewebscriptsidentifiedinthisarticlewithoutanyproblems. Incaseyouarecurious,myenvironmentis:
UbuntuDapperDrake MySQL4.1(withversion5.0.3oftheJDBCdriver) Java1.5.0_12 Tomcat5.5.x Alfresco2.1.0Enterprise,WARonlydistribution
Obviously,otheroperatingsystems,databases,[Link], however,onlyworkstartingwithAlfresco2.1.
Conclusion
[Link] simpleHelloWorldscriptandthengraduallymovedtomorecomplexexampleswhichculminatedina RESTAPIforretrievingwhitepapers,gettingtheaverageratingforaspecificwhitepaper,postingnew ratingsforagivenwhitepaper,[Link] [Link] [Link]. [Link] [Link] [Link]?Hopefully, you'[Link]'llevenblogabout [Link],orifyouhaveanyotherfeedback,pleaseletmeknow.I'dlovetohearfromyou.
Wheretofindmoreinformation
[Link]. [Link]: AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page27of28
[Link]
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. [Link]. ThejMakiProjectisaframeworkforbuildingAjaxenabled,[Link] partofitisaproxyyoucanusetoworkaroundthecrossdomainscriptinglimitationifyou don'twanttowriteyourown. TheJSR168PortletSpecificationisavailableontheJavaCommunityProcesssite.
AbouttheAuthor
JeffPottsistheEnterpriseContentManagementPracticeLeadatOptaros,a [Link] yearsofexperienceimplementingcontentmanagement,collaboration,andother [Link] livesinDallas,[Link].
AlfrescoDeveloper:IntrototheWebScriptFramework
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page28of28