Beruflich Dokumente
Kultur Dokumente
com
AlfrescoDeveloper:Implementingcustombehaviors
September,2007
JeffPotts
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
JavaorJavaScript?
Therearetwooptionsforwritingthecodeforthebehavior:JavaorJavaScript.Thedecisionasto whichonetousedependsonthestandardsyou'vesettledonforthesolutionyouarebuilding.Inthis article,we'llimplementtheratingsexampleusingJavabutI'llalsoshowyouhowtouseJavaScriptas analternative.
Implementinganddeployingthecustombehavior
Beforewestart,acoupleofnotesaboutmysetup:
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>
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>
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
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{
AlfrescoDeveloper:Implementingcustombehavior
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
Page10of18
ecmarchitect.com
privateBehaviouronDeleteNode;
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); }
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;
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; } }
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;
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