Sie sind auf Seite 1von 18

ecmarchitect.

com

AlfrescoDeveloper:Implementingcustombehaviors
September,2007
JeffPotts

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

ecmarchitect.com
AlfrescoDeveloper:Implementingcustombehaviors
September2007 JeffPotts

Introduction
ThisarticlediscusseshowtowriteyourowncustombehaviorcodeinJavaorJavaScriptandthenbind thatcodetonodeeventsorpolicies. InpreviousarticlesI'vediscussedhowtocreatecustomcontentmodelsandhowtowritecustom actions.Inbothcases,we'veseenhowtowritecodethatworkswithcustomcontenttypes,properties, aspects,andassociations,butthecodewasn'ttightlycoupledtotheobjectsonwhichitoperated.With anaction,thebusinesslogicistriggeredbysomethinganitemintheuserinterface,aschedule,ora workflowratherthanbeingboundtothecontenttypeoraspect. Actionsareveryusefulwhenthebusinesslogictheactioncarriesoutisgenericenoughtobeappliedto manytypesofobjects.Theoutoftheboxmoveoraddaspectactionsareobviousexamples. Therearetimes,though,whenyouwantcodetobetightlycoupledtoacontenttypebecauseyouneed tobesureitgetsexecutedratherthanleavingituptoaruleonaspacethattriggersanactionora workflowthatdoesthesame.Fortunately,Alfrescoprovidesjustsuchamechanismit'scalled behaviors. BehaviorsareusedthroughoutAlfresco.Auditingandversioningareexampleswherebehaviorsare involved.InourworkwithcustomAlfrescoimplementationsbehaviorshavecomeinhandyseveral times.Inonecaseweneededtodefaultsomemetadatavaluesusinglogicthatcouldn'tbeexpressed usingtheAlfrescocontentmodelsowewroteacustombehaviorthatsetthemetadataappropriately.In another,weneededawaytosynchronizemetadatabetweenfoldersandtheitemsinthosefolderssowe wroteacustombehaviortohandlethesync. Inthisarticlewe'llseeasimpleexamplealsobasedonarealworldimplementation:usingacustom behaviortocomputetheaverageuserratingforapieceofcontent. YoushouldalreadybefamiliarwithgeneraldocumentmanagementandAlfrescowebclientconcepts. Ifyouwanttofollowalong,youshouldalsoknowhowtowritebasicJavacode.SeeWheretofind moreinformationattheendofthisdocumentforalinktothecodesamplesthataccompanythis article.Specifically,youmaywanttoreadWorkingwithCustomContentTypesonecmarchitect.com ifyouaren'talreadyfamiliarwithhowtoextendAlfresco'scontentmodel.

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page2of18

ecmarchitect.com
Example:Userratings
RecallfromtheCustomContentTypesarticlethatwecreatedacustomtypecalledaWhitepaperfora fictitiouscompanycalledSomeCo.Weusedanaspectcalledwebablethatwasattachedtocontent objectswewantedtoshowontheweb.So,forexample,SomeComightuseAlfrescotomanageallof itswhitepapers,butshowonlyasubsetontheweb.Whitepaperstobeshownonthewebgetthe webableaspectattachedandtheisActiveflagsettotrue.Thefrontendcanthenqueryforwhitepapers basedonthatflag. Nowlet'sextendthatexamplefurther.SupposethattheMarketingfolksatSomeCohavecaughtthe Web2.0bugandthey'vedecidedtoadduserratingstotheirwebsite.Theywouldlikeuserstobeable toassignaratingtoawhitepaperandtodisplaytheaverageofallratingsreceivedforaspecific whitepaper. AssumingwewanttostoretheratingsinAlfrescoinsteadofdirectlyinarelationaldatabase,oneway todothiswouldbetocreateacustomratingtypethatwouldberelatedtoawhitepaperthrougha childassociation.Wecouldcreatearateableaspectthatwoulddefinetheassociationaswellasa propertytostoretheaverageratingforthatwhitepaper.

Drawing1:SomeCo'scontentmodelmodifiedtosupportratings AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page3of18

ecmarchitect.com
Drawing1showstheoriginalcustomcontentmodelwiththeseenhancements. Thattakescareofthedatamodel,butwhereshouldweputthecodethatcomputestheaverage?One waytohandleitwouldbetowriteanactionthatgetscalledbyarule.Anytimearatingisaddedtoa folder,therulewouldtriggertheactiontoupdatetheaverage.Butthisisn'tthebestoptionbecause everytimeyouwantedtouseuserratingsyou'dhavetomakesuretosetuparuleonthefolder.A scheduledactionmightnotbebaditcouldbewrittentofindallobjectswiththerateableaspectand thencomputetheaverage.Butifyouwanttheaverageratingcomputedinrealtime(andlet'sassume wedo)ascheduledactionisn'taagreatoption. Asyou'vealreadyguessed,thebestoptioninourexampleistouseabehavior.We'llwritethelogicwe needtocomputetheaverageandthenbindittotheappropriatepoliciesontheratingcontenttype.Any timearatinggetscreated(ordeleted),itwillknowhowtofinditsparent(thewhitepaper)and recalculatetheaverage.

Whatcantriggerabehavior?
Sotheratingcontenttypewillbeboundtobusinesslogicthatknowshowtocomputetheoverall averageratingforawhitepaper.Butwhatwilltriggerthatlogic?Theansweristhatthereareabunchof policiestowhichyourbehaviorcanbebound.Tofindoutwhat'savailable,youneedonlylookasfaras thesourcecode(orJavadocs).IfyougreptherepositoryprojectintheAlfrescosourcecodefor classesthatendin*Policies.javayou'llfindfourinterfaces.Eachofthoseinterfacescontainsinner interfacesthatrepresentthepoliciesyoucanhookinto.ChecktheJavadocsorsourcecodeforspecifics I'mlistingthemethodsinTable1soyoucanseethebreadthofwhat'savailable. Note:Tomakeiteasiertoread,I'momittingtheinnerinterfacewhichfollowsthepatternof<method name>Policy.Forexample,theonContentUpdatemethodisamethodoftheinnerinterface OnContentUpdatePolicy. Interface org.alfresco.repo.content.ContentServicePolicies onContentUpdate onContentRead org.alfresco.repo.copy.CopyServicePolicies onCopyNode onCopyComplete org.alfresco.repo.node.NodeServicePolicies beforeCreateStore Method

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page4of18

ecmarchitect.com
Interface onCreateStore beforeCreateNode onCreateNode onMoveNode beforeUpdateNode onUpdateNode onUpdateProperties beforeDeleteNode onDeleteNode beforeAddAspect onAddAspect beforeRemoveAspect onRemoveAspect beforeCreateNodeAssociation onCreateNodeAssociation beforeCreateChildAssociation onCreateChildAssociation beforeDeleteChildAssociation onDeleteChildAssociation onCreateAssociation onDeleteAssociation AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Method

Page5of18

ecmarchitect.com
Interface Method

org.alfresco.repo.version.VersionServicePolicies

beforeCreateVersion afterCreateVersion onCreateVersion calculateVersionLabel

Table1:Policiesavailableforbehaviorbinding Whichpolicyshallweuse?Weneedtorecalculateawhitepaper'sratingeitherwhenanewratingis createdorwhenaratingisdeleted.OnepossibilitywouldbetobindourbehaviortotheNodeService policy'sonCreateChildAssociationandonDeleteChildAssociationpolicyforthewhitepapernode.But thatwouldmeanwe'dconstantlybeinspectingtheassociationtypetoseeifitwewantedtotakeany actionbecausetherecouldbeotherchildassociationsbesidesratings.Instead,we'llbindtotherating node'sonCreateNodeandonDeleteNodepolicies.

JavaorJavaScript?
Therearetwooptionsforwritingthecodeforthebehavior:JavaorJavaScript.Thedecisionasto whichonetousedependsonthestandardsyou'vesettledonforthesolutionyouarebuilding.Inthis article,we'llimplementtheratingsexampleusingJavabutI'llalsoshowyouhowtouseJavaScriptas analternative.

Implementinganddeployingthecustombehavior
Beforewestart,acoupleofnotesaboutmysetup:

UbuntuDapperDrake(Iknow,Iknow.Pastdueforanupgrade). MySQL4.1 Tomcat5.5.x Alfresco2.1.0Enterprise,WARonlydistribution

Obviously,otheroperatingsystems,databases,andapplicationserverswillworkaswell.Someofthe functionalityusedinthisexampleisavailableonlywith2.1soyourmileagemyvaryifyouuseanolder AlfrescoDeveloper:Implementingcustombehavior


ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page6of18

ecmarchitect.com
release. I'llalsobebuildingonthecustommodeldefinedintheCustomTypesarticle.Thatcodewasoriginally writtenforAlfresco2.0.1butwillrunfineonAlfresco2.1.0withnochangesneeded.Youdon'tneedto downloadthecodefromthatarticleforthecodeinthisarticletowork.Thecodesuppliedwiththis articleisasuperset.

JavaExample
Let'sdotheJavaexamplefirst.Herearethestepswearegoingtofollow: 1. ExtendtheSomeComodeldefinedinthepreviousarticlewithournewrateableaspectand ratingtype(andalltheUIconfigstepsthatgowithit). 2. WriteserversideJavaScriptthatcreatestestratings. 3. Writethecustombehaviorbeanandbindittotheappropriatepolicies. 4. ConfigureaSpringbeantoinitializeourbehaviorclassandpassinanydependencies. 5. Build,deploy,restartandtest. Let'sgetstarted. Extendthemodel OpenthescModel.xmlfilewecreatedinthepreviousarticle.Weneedtoaddanewtypeandanew aspect.Insertthesc:ratingtypedefinitionaftertheexistingsc:whitepaperdefiniton:
<typename="sc:rating"> <title>SomecoRating</title> <parent>sys:base</parent> <properties> <propertyname="sc:rating"> <type>d:int</type> <mandatory>true</mandatory> </property> <propertyname="sc:rater"> <type>d:text</type> <mandatory>true</mandatory> </property> </properties> </type>

Listing1:TheratingtypeinthescModel.xmlfile. Notethatsc:ratinginheritsfromsys:base.That'sbecausewedon'tintendtostoreanycontentinarating object,onlyproperties.

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page7of18

ecmarchitect.com
Nowaddthesc:rateableaspectaftertheexistingsc:productRelatedaspect.Therateableaspectwill haveonepropertytostoretheaverageratingandwillalsodefinethechildassociationforapieceof content'srelatedratings.Usinganaspectgivesustheabilitytoaddratingsfunctionalitytoanypieceof contentintherepository.(Infact,youdon'tneedanyoftheSomeComodelfromthepreviousarticleto usetheratingsfunctionality.That'sthebeautyofaspects).
<aspectname="sc:rateable"> <title>SomecoRateable</title> <properties> <propertyname="sc:averageRating"> <type>d:double</type> <mandatory>false</mandatory> </property> </properties> <associations> <childassociationname="sc:ratings"> <title>Rating</title> <source> <mandatory>false</mandatory> <many>true</many> </source> <target> <class>sc:rating</class> <mandatory>false</mandatory> <many>true</many> </target> </childassociation> </associations> </aspect>

Listing2:TherateableaspectinthescModel.xmlfile. Therateablepropertiesandassociationsneedtoshowuponpropertysheetsforobjectswiththeaspect soaddthefollowingtowebclientconfigcustom.xml:


<!addrateableaspectpropertiestopropertysheet> <configevaluator="aspectname"condition="sc:rateable"> <propertysheet> <showpropertyname="sc:averageRating"displaylabel id="average"readonly="true"/> <showchildassociationname="sc:ratings"displaylabel id="ratings"readonly="false"/> </propertysheet> </config>

Listing3:Updatedwebclientconfigcustom.xmlfiletoshowrateableaspectproperties. Therateableaspectneedstoshowupontheaddaspectlistsoaddtherateableaspecttotheexisting listofSomeCocustomaspectsinwebclientconfigcustom.xml.Notethatthissnippetonlyshowsthe <aspects>elementoftheActionWizardsconfig.Therestisunchangedfromthepreviousarticle: AlfrescoDeveloper:Implementingcustombehavior


ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page8of18

ecmarchitect.com
<configevaluator="stringcompare"condition="ActionWizards"> <!Thelistofaspectstoshowintheadd/removefeaturesaction> <!andthehasaspectcondition> <aspects> <aspectname="sc:webable"/> <aspectname="sc:productRelated"/> <aspectname="sc:rateable"/> </aspects> </config> <!Remainingconfigisunchanged>

Listing4:Updatedwebclientconfigcustom.xmlfiletoaddrateabletoaspectlist. ThelabelIDsneedvaluesinthewebclient.propertiesfile:
#sc:rateable average=AvgRating ratings=Ratings

Listing5:Updatedwebclient.propertiesfileforexternalizedstrings. Recallthatinthepreviousarticlewedecideditwasagoodideatouseaclassforourmodeltostore constantssuchasthenamesoftypes,properties,andaspects.Modify com.someco.model.SomeCoModel.javatoincludenewconstantsforthetype,aspect,andpropertieswe justaddedtothemodel.


publicstaticfinalStringTYPE_SC_RATING="rating"; publicstaticfinalStringASPECT_SC_RATEABLE="rateable"; publicstaticfinalStringPROP_RATING="rating"; publicstaticfinalStringPROP_AVERAGE_RATING="averageRating";

Listing6:UpdatedSomeCoModelwithnewconstants That'sallweneedtodoforthemodel.AfterrestartingAlfrescoyoushouldseetheaspectintheAdd Aspectactionconfigurationwizard. WriteaserversideJavaScriptfilethatcreatestestdata InthelastarticleweusedJavatotestoutthemodelbywritingcodeagainsttheAlfrescoWebServices API.Thistime,let'suseJavaScripttocreateaserversidescriptthatwecanrunanytimeweneedto createsometestratingnodesforapieceofcontent. First,logintotheAlfrescowebclientandnavigatetoCompanyHome/DataDictionary/Scripts. Next,createanewpieceofcontentnamedaddTestRating.jswiththefollowingcontent:


//addtheaspecttothisdocumentifitneedsit if(document.hasAspect("sc:rateable")){ logger.log("Documentalreadyasaspect"); }else{ logger.log("Addingrateableaspect"); document.addAspect("sc:rateable");

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page9of18

ecmarchitect.com
} //randomlypickanumb/w1and5inclusive varratingValue=Math.floor(Math.random()*5)+1; varprops=newArray(2); props["sc:rating"]=ratingValue; props["sc:rater"]=person.properties.userName; //createanewratingsnodeandsetitsproperties varratingsNode=document.createNode("rating"+newDate().getTime(), "sc:rating",props,"sc:ratings"); ratingsNode.save(); logger.log("Ratingsnodesaved.");

Listing7:TheaddTestRating.jsscriptcanbeexecutedtocreatetestratingnodes. NowcreateapieceofcontentinyourrepositoryandthenuseRunActiononthatpieceofcontentto executetheaddTestRating.jsscript.Everytimeyourunit,anewrating(witharandomvalue)willbe createdasachildnodeofthatcontent. Atthispoint,youshouldseetheratingsonthecontent'spropertysheetaschildassociationsbutthe averagewon'tbesetuntilthebehaviorcodeisinplace. Note,ifyouwantthelogmessagestoshowupyouhavetosetlog4j.logger.org.alfresco.repo.jscriptto DEBUGinlog4j.properties. Writethecustombehavior ThecustombehaviorisimplementedasaJavaclassthatimplementstheinterfacesthatcorrespondto thepoliciestowhichwewanttobind.Inthisexample,thetwopolicyinterfacesare: NodeServicePolicies.OnDeleteNodePolicyandNodeServicePolicies.OnCreateNodePolicysotheclass declarationis:
publicclassRating implementsNodeServicePolicies.OnDeleteNodePolicy, NodeServicePolicies.OnCreateNodePolicy{

Listing8:TheclassdeclarationfortheRatingbean. TheclasshastwodependenciesthatSpringwillhandleforus.OneistheNodeServicewhichwillbe usedintheaveragecalculationlogicandtheotheristhePolicyComponentwhichisusedtobindthe behaviortothepolicies.


//Dependencies privateNodeServicenodeService; privatePolicyComponentpolicyComponent; //Behaviours privateBehaviouronCreateNode;

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page10of18

ecmarchitect.com
privateBehaviouronDeleteNode;

Listing9:DependencyandbehaviordeclarationintheRatingbean. AtsomepointAlfrescohastoknowthatthebehaviorneedstobeboundtoapolicy.IntheJavaScript examplewe'llseehowtodothatinaSpringbeanconfigfile.Inthisexample,we'llcreateamethod calledinittohandlethebinding.TheinitmethodwillgetcalledwhenSpringloadsthebean.


publicvoidinit(){ //Createbehaviours this.onCreateNode=newJavaBehaviour( this, "onCreateNode", NotificationFrequency.TRANSACTION_COMMIT); this.onDeleteNode=newJavaBehaviour( this, "onDeleteNode", NotificationFrequency.TRANSACTION_COMMIT); //Bindbehaviourstonodepolicies this.policyComponent.bindClassBehaviour( Qname.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), Qname.createQName( SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.TYPE_SC_RATING), this.onCreateNode); this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), Qname.createQName( SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.TYPE_SC_RATING), this.onDeleteNode); }

Listing10:TheRatingbean'sinitmethodbindsthebean'smethodstospecificpolicies. Thefirstthingtonoticehereisthatyoucandecidewhenthebehaviorshouldbeinvokedbyspecifying theappropriateNotificationFrequency.BesidesTRANSACTION_COMMIT,otherchoicesinclude FIRST_EVENTandEVERY_EVENT. AlsonotethatthereareafewdifferentoverloadedmethodsforbindClassBehaviour(notetheUK spelling).Inthiscasewe'rebindingtheQnameofabehaviortotheQnameofourtype(Rating)and tellingAlfrescotocalltheonCreateNodeandonDeleteNodebehaviorsofourbean. Therearealsoadditionalbindmethodsforassociations(bindAssociationBehaviour)andproperties (bindPropertyBehaviour)thatyoushouldusedependingonthetypeofpolicyyouarebindingto. AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page11of18

ecmarchitect.com
Nextweneedtowritethemethodsrequiredbythetwopolicyinterfaces.Regardlessofwhethera ratingsnodeiscreatedordeleted,weneedtorecalculatetheaverage.SoouronCreateNodeand onDeleteNodemethodssimplycallcomputeAverageandpassintheratingnodereference.
publicvoidonCreateNode(ChildAssociationRefchildAssocRef){ computeAverage(childAssocRef); } publicvoidonDeleteNode(ChildAssociationRefchildAssocRef,boolean isNodeArchived){ computeAverage(childAssocRef); }

Listing11:TheonCreateNodeandonDeleteNodemethodsimplementthebehavior. ThecomputeAveragemethodasksthechild(therating)foritsparentnodereference(thewhitepaper, forexample)andaskstheparentforalistofitschildren.Ititeratesoverthechildren,computesan average,andsetstheaveragepropertyonthecontent.


publicvoidcomputeAverage(ChildAssociationRefchildAssocRef){ //gettheparentnode NodeRefparentRef=childAssocRef.getParentRef(); //checktheparenttomakesureithastherightaspect if(nodeService.hasAspect( parentRef, Qname.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASPECT_SC_RATEABLE))){ //continue,thisiswhatwewant }else{ return; } //gettheparentnode'schildren List<ChildAssociationRef>children= nodeService.getChildAssocs(parentRef); //iteratethroughthechildrentocomputethetotal Doubleaverage=0d; inttotal=0; for(ChildAssociationRefchild:children){ intrating=(Integer)nodeService.getProperty( child.getChildRef(), Qname.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.PROP_RATING)); total+=rating; } //computetheaverage

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page12of18

ecmarchitect.com
average=total/(children.size()/1.0d); //storetheaverageontheparentnode nodeService.setProperty( parentRef, QName.createQName(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.PROP_AVERAGE_RATING), average); } return;

Listing12:ThecomputeAveragemethodcalculatestheaverageratingforapieceofcontent. Theonlyitemsremaining,then,arethegettersandsettersfortheNodeServiceandPolicyComponents, butI'llleavethoseouthere. ConfigureaSpringbean ThelaststepbeforewetestistoconfigurethebehaviorclassasaSpringbean.Thebeanconfigcango inanycontextfile.Ifyouhadseveralbehaviorsitmightmakesensetoputthemintheirown.Forthis example,we'llusethesomecomodelcontext.xmlfile.Addthefollowingbeforetheclosing</beans> tag:


<beanid="ratingBehavior"class="com.someco.behavior.Rating"initmethod="init"> <propertyname="nodeService"> <refbean="nodeService"/> </property> <propertyname="policyComponent"> <refbean="policyComponent"/> </property> </bean>

Listing13:Theupdatedsomecomodelcontext.xmlfileincludestheSpringbeanconfigfortheRating behaviorbean. Build,deploy,restartandtest Modifythebuild.propertiesfiletomatchyourenvironment,thenuseAnttoruntheDeploytarget.The codewillbecompiled,JAR'd,andunzippedontopoftheexistingAlfrescoinstallation. Hopefully,Alfrescowillstartuperrorfree.Ifso,loginandexecutetheaddTestRating.jsscriptagainst apieceofcontent.Theaverageshouldnowgetcomputed. Finally,deleteoneofthetestratingsbyeditingthepropertiesforapieceofratedcontentandclicking thetrashcanicon.Whenyouclickoktosaveyourchanges,theaverageratingshouldget recalculated.Ifyoudeletealloftheratings,theaverageshouldgetsetto0. Note:Ifyouarerunningthedownloadedsamplecodeinsteadofcreatingitbyfollowingalong,set com.somecotoDEBUGinlog4j.propertiestodisplaytheloggermessages.I'veomittedtheminthe AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page13of18

ecmarchitect.com
codesamplesforbrevity.

JavaScriptExample
We'veseenhowtoimplementtheaverageratingcalculationbehaviorinJava,butwhatifyouwantedto implementthebehaviorusingJavaScriptinstead?BehaviorscanbeimplementedinJavaScriptand boundtopoliciesthroughSpring.Let'sreimplementtheRatingbeanusingJavaScript. Thehighlevelstepswe'regoingtofolloware: 1. Writethecustombehaviorasoneormoreserversidescripts. 2. ConfigureaSpringbeantobindthescriptstotheappropriatepolicies. 3. Deploy,restartandtest. WritethecustombehaviorasaserversideJavaScript ForthisexampleI'mgoingtoshamelesslystealaJavaScriptfilethatispartoftheAlfrescosourceand thentweakit.TheoriginalscriptisusedbyAlfrescototestPolicyfunctionality.(Asasidenote,thetest codethatisburiedintheAlfrescosourcetreeisagreatresourceforexamplecode). We'reactuallygoingtowritethreescripts.TheonCreateRating.jsandonDeleteRating.jsscriptswillbe boundtotheonCreateNodeandonDeleteNodepoliciesrespectively.Therating.jsscriptwillcontain ouraverageratingcalculationlogicandwillbeimportedbytheothertwoscriptsusingthenew <import>tagthatbecameavailablewiththe2.1release. Inthisexamplethescriptsaregoingtoresideaspartofthewebapplicationratherthanbeinguploaded totherepository.Nothingrequiresthatthisbethecasewe'vewrittenonescriptalreadythatresidesin therepositorysoIthoughtwe'dchangeitupalittle.Thisapproachdoeshavethedownsidethat modificationsrequirearedeploy.Ofcourse,dependingonwhetherornotyouwantendusers monkeyingaroundwithyourscript,thiscouldbeabenefitinaproductionenvironment. InyourEclipseproject,createascriptsdirectoryunderthesrc/alfresco/extensionfolderandaddafile calledonCreateRating.jswiththefollowingcontent:
<importresource="classpath:alfresco/extension/scripts/rating.js"> varscriptFailed=false; //Havealookatthebehaviourobjectthatshouldhavebeenpassed if(behaviour==null){ logger.log("Thebehaviourobjecthasnotbeenset."); scriptFailed=true; } //Checkthenameofthebehaviour

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page14of18

ecmarchitect.com
if(behaviour.name==null&&behaviour.name!="onCreateNode"){ logger.log("Thebehaviournamehasnotbeensetcorrectly."); scriptFailed=true; }else{ logger.log("Behaviourname:"+behaviour.name); } //Checkthearguments if(behaviour.args==null){ logger.log("Theargshavenotbeenset."); scriptFailed=true; }else{ if(behaviour.args.length==1){ varchildAssoc=behaviour.args[0]; logger.log("Callingcomputeaverage"); computeAverage(childAssoc); }else{ logger.log("Thenumberofargumentsisincorrect."); scriptFailed=true; } }

Listing14:TheonCreateRating.jsisinvokedwhenanewratingnodeiscreated. ThecodeforonDeleteRating.jsisidenticalwiththeexceptionofthebehaviornameandthenumberof argumentsexpected(2insteadof1)soIwon'tduplicatethelistinghere. AsinourJavaexample,bothcallthesamecomputeAveragefunction.CreateathirdJavaScriptfile calledrating.jsandaddthecontentasfollows:


//calculaterating functioncomputeAverage(childAssocRef){ varparentRef=childAssocRef.parent; //checktheparenttomakesureithastherightaspect if(parentRef.hasAspect("{http://www.someco.com/model/content/1.0}rateable")){ //continue,thisiswhatwewant }else{ logger.log("Rating'sparentrefdidnothaverateableaspect."); return; } //gettheparentnode'schildren varchildren=parentRef.children; //iteratethroughthechildrentocomputethetotal varaverage=0.0; vartotal=0; if(children!=null&&children.length>0){ for(iinchildren){

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page15of18

ecmarchitect.com
varchild=children[i]; varrating= child.properties["{http://www.someco.com/model/content/1.0}rating"]; total+=rating; } //computetheaverage average=total/children.length;

//storetheaverageontheparentnode parentRef.properties["{http://www.someco.com/model/content/1.0}averageRating"]= average; parentRef.save(); } return;

Listing15:Therating.jsfilecontainsthesharedcomputeAveragefunction. Asyoucansee,thisisthesamelogicweusedintheJavaexamplemodifiedtofollowtheAlfresco JavaScriptAPIsyntax. ConfigureaSpringbeantobindthescripttotheappropriatepolicies IntheJavaexample,weusedaninitmethodontheRatingbeantomakecallstothebindingmethodof thePolicyComponent.TheJavaScriptexampledoesn'tdothat.Instead,itusesSpringtoassociatethe JavaScriptfileswiththeonCreateNodeandonDeleteNodepolicies. Editthesomecomodelcontext.xmlfile.CommentoutthebeanconfigweusedfortheJavaexample andaddtwonewbeanconfigsbelowitfortheJavaScriptbehaviorcodeoneforthecreateandonefor thedelete:
<beanid="onCreateRatingNode" class="org.alfresco.repo.policy.registration.ClassPolicyRegistration" parent="policyRegistration"> <propertyname="policyName"> <value>{http://www.alfresco.org}onCreateNode</value> </property> <propertyname="className"> <value>{http://www.someco.com/model/content/1.0}rating</value> </property> <propertyname="behaviour"> <beanclass="org.alfresco.repo.jscript.ScriptBehaviour" parent="scriptBehaviour"> <propertyname="location"> <bean class="org.alfresco.repo.jscript.ClasspathScriptLocation"> <constructorarg> <value>alfresco/extension/scripts/onCreateRating.js</value> </constructorarg>

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page16of18

ecmarchitect.com
</bean> </property> </bean> </property> </bean>

Listing16:TheSpringbeanconfigusedtobindtheonCreateNodepolicytotheonCreateRating.js script.TheconfigfortheonDeleteNodepolicyisnotshown. Deploy,restartandtest Ifyouhaven'talready,makesureyou'vesetlog4j.logger.org.alfresco.repo.jscripttoDEBUGin log4j.propertiesoryouwon'tseeanyoftheloggeroutput. UseAnttoruntheDeploytarget. AssumingAlfrescostartsuperrorfree,youshouldbeabletouseaddTestRating.jstocreateRating nodeswhichshouldtriggertheonCreateRatingJavaScript.Deletingratingswilltriggerthe onDeleteRatingJavaScript.Ineithercase,theaverageratingshouldgetcalculatedasitdidwiththe Javaexample.

Conclusion
ThisarticlehasshownhowtobindcustombehaviortoAlfrescopolicies.Specifically,weimplemented acustomrateableaspectandacustomratingtypethatcanbeusedtopersistuserratingsofcontent storedintherepository.Thecustombehaviorisresponsibleforcalculatingtheaverageratingfora pieceofcontentanytimearatingiscreatedordeleted.Thearticleshowedhowtoimplementthe averageratingcalculationbehaviorinJavaaswellasJavaScript.

Wheretofindmoreinformation

Thecompletesourcecodethataccompaniesthisarticleisavailableherefromecmarchitect.com. ThepreviousarticlethatdiscussescustomcontentmodelsiscalledWorkingwithCustom ContentTypesandisavailableatecmarchitect.com. TheAlfrescoSDKcomeswithacustombehaviorexamplethat'salittlemorecomplexthanthe onepresentedhere.TheSDKCustomAspectprojectimplementsahitcounterthat incrementseverytimeapieceofcontentisread. Fordeploymenthelp,seetheClientConfigurationGuideandPackagingandDeploying ExtensionsintheAlfrescowiki. Forgeneraldevelopmenthelp,seetheDeveloperGuide. Forhelpcustomizingthedatadictionary,seetheDataDictionarywikipage.

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page17of18

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

AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License

Page18of18

Das könnte Ihnen auch gefallen