0% found this document useful (0 votes)
19 views28 pages

Intro to Alfresco Web Script Framework

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)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views28 pages

Intro to Alfresco Web Script Framework

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)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

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}&amp;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">&nbsp;</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]("admin")) { [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

You might also like