Documente Academic
Documente Profesional
Documente Cultură
com
AlfrescoDeveloper:WorkingwithCustomContentTypes
June,2007
JeffPotts
ecmarchitect.com
AlfrescoDeveloper:WorkingwithCustomContentTypes
June,2007 JeffPotts
Introduction
Alfrescoisaflexibleplatformfordevelopingcontentmanagementapplications.Thefirststepinthe processofdesigningacustomcontentmanagementapplicationiscreatingthecontentmodel. ThecontentmodelAlfrescoprovidesoutoftheboxisfairlycomprehensive.Infact,forbasic documentmanagementneeds,youcouldprobablygetbywiththeoutoftheboxmodel.Ofcourse, you'dbemissingoutonalotofthepowerandfunctionalitythathavingamodelcustomizedforyour businessneedsprovides. Thisarticlediscusseshowtocreateyourowncustomcontentmodel,butitdoesn'tstopthere.What goodwouldacustomcontentmodelbeifyoudidnothingexcitingwiththecontent?Oncewegetour examplemodelinplace,we'lltweakthewebclientuserinterfacetoexposeourcustommodel,then we'llwritesomecodetocreate,searchfor,anddeletecontent. YoushouldalreadybefamiliarwithgeneraldocumentmanagementandAlfrescowebclientconcepts. Ifyouwanttofollowalong,youshouldalsoknowhowtowritebasicJavacode.SeeWheretofind moreinformationattheendofthisdocumentforalinktothecodesamplesthataccompanythis article.
Modelingbasics
Acontentmodeldescribesthedatabeingstoredintherepository.Thecontentmodeliscritical withoutit,Alfrescowouldbelittlemorethanafilesystem.Hereisalistofkeyinformationthecontent modelprovidesAlfresco:
ecmarchitect.com
Alfrescocontentmodelsarebuiltusingasmallsetofbuildingblocks:Types,Properties,Property types,Constraints,Associations,andAspects.
Types
Typesareliketypesorclassesintheobjectorientedworld.Theycanbeusedtomodelbusinessobjects, theyhaveproperties,andtheycaninheritfromaparenttype.Content,Person,andFolderare threeimportanttypesdefinedoutofthebox.Customtypesarelimitedonlybyyourimaginationand businessrequirements.ExamplesincludethingslikeExpenseReport,MedicalRecord,Movie, Song,andComment. Notethattypes,properties,constraints,associations,andaspectshavenames.Namesaremadeunique acrosstherepositorybyusinganamespacespecifictothemodel.Thenamespacehasanabbreviation. So,forexample,atOptaroswemightdefineacustommodelwhichdeclaresanamespacewiththeURI ofhttp://www.optaros.com/model/content/1.0andaprefixofopt.Anytypedefinedaspartofthat modelwouldhaveanameprefixedwithopt:.We'lltalkmoreabouthowmodelsareactuallydefined usingXML,butIwantedtointroducetheconceptofnamespacesandprefixessoyouwouldknowwhat theyarewhenyouseethem.Usingnamespacesinthiswayhelpspreventnamecollisionswhencontent modelsaresharedacrossrepositories.
Properties
Propertiesarepiecesofmetadataassociatedwithaparticulartype.Forexample,thepropertiesofan ExpenseReportmightincludethingslikeEmployeeName,Datesubmitted,Project,Client, ExpenseReportNumber,Totalamount,andCurrency.TheExpenseReportmightalsoincludea contentpropertytoholdtheactualexpensereportfile(maybeitisaPDForanExcelspreadsheet,for example).
Propertytypes
Propertytypes(ordatatypes)describethefundamentaltypesofdatatherepositorywillusetostore AlfrescoDeveloper:WorkingwithCustomContentTypes Page3of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
properties.Examplesincludethingslikestrings,dates,floats,andbooleans.Becausethesedatatypes literallyarefundamental,theyareprettymuchthesameforeveryonesotheyaredefinedforusoutof thebox.(IfyouwantedtochangethefactthattheAlfrescodatatypetextmapstoyourowncustom classratherthanjava.lang.String,youcould,butlet'snotgetaheadofourselves).
Constraints
ConstraintscanoptionallybeusedtorestrictthevaluethatAlfrescowillstoreinaproperty.Thereare fourtypesofconstraintsavailable:REGEX,LIST,MINMAX,andLENGTH.REGEXisusedtomake surethatapropertyvaluematchesaregularexpressionpattern.LISTisusedtodefinealistofpossible valuesforaproperty.MINMAXprovidesanumericrangeforapropertyvalue.LENGTHsetsa restrictiononthelengthofastring. Constraintscanbedefinedonceandreusedacrossamodel.Forexample,outofthebox,Alfresco makesavailableaconstraintnamedcm:filenamethatdefinesaregularexpressionconstraintforfile names.Ifapropertyinacustomtypeneedstorestrictvaluestothosematchingthefilenamepattern, thecustommodeldoesn'thavetodefinetheconstraintagain,itsimplyreferstothecm:filename constraint.
Associations
Associationsdefinerelationshipsbetweentypes.Withoutassociations,modelswouldbefulloftypes withpropertiesthatstorepointerstootherpiecesofcontent.Goingbacktotheexpensereport example,wemightwanttostoreeachexpenseasanindividualobject.InadditiontoanExpenseReport typewe'dalsohaveanExpensetype.UsingassociationswecantellAlfrescoabouttherelationship betweenanExpenseReportandoneormoreExpenses. Associationscomeintwoflavors:PeerAssociationsandChildAssociations.(NotethatAlfrescorefers toPeerAssociationssimplyasAssociationsbutIthinkthat'sconfusingsoI'llrefertothemwiththe Peerdistinction).PeerAssociationsarejustthattheydefinearelationshipbetweentwoobjectsbut neitherissubordinatetotheother.ChildAssociations,ontheotherhand,areusedwhenthetargetof theassociation(orchild)shouldnotexistwhenthesource(orparent)goesaway.Thisworkslikea cascadeddeleteinarelationaldatabase:Deletetheparentandthechildgoesaway. Anoutoftheboxassociationthat'seasytorelatetoiscm:contains.Thecm:containsassociation definesaChildAssociationbetweenfolders(cm:folder)andallotherobjects(instancesofsys:base oritschildtypes).So,forexample,afoldernamedHumanResources(aninstanceofcm:folder) wouldhaveacm:containsassociationbetweenitselfandallofitsimmediatechildren.Thechildren couldbeinstancesofcustomtypeslikeResume,Policy,orPerformanceReview.
AlfrescoDeveloper:WorkingwithCustomContentTypes Page4of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
AnotherexamplemightbeaWhitepaperanditsRelatedDocuments.Supposethatacompany publisheswhitepapersontheirwebsite.Thewhitepapermightberelatedtootherdocumentssuchas productmarketingmaterialsorotherresearch.Iftherelationshipbetweenthewhitepaperanditsrelated documentsisformalizeditcanbeshownintheuserinterface.Toimplementthis,aspartofthe Whitepapercontenttype,you'ddefineaPeerAssociation.Youcouldusesys:baseasthetargettype toallowanypieceofcontentintherepositorytobeassociatedwithaWhitepaperoryoucouldrestrict theassociationtoaspecifictype.
Aspects
BeforewetalkaboutAspects,let'sfirstconsiderhowinheritanceworksandtheimplicationsonour contentmodel.SupposewearegoingtouseAlfrescotomanagecontenttobedisplayedinaportal (quiteacommonrequirement,bytheway).Supposefurtherthatonlyasubsetofthecontentinour repositoryiscontentwewanttoshowintheportal.And,whencontentistobedisplayedintheportal, therearesomeadditionalpiecesofmetadataweneedtocapture.Asimpleexamplemightbethatwe wanttoknowthedateandtimeapieceofcontentwasapprovedtobeshownintheportal. Usingthecontentmodelingconceptsdiscussedsofar,wewouldhaveonlytwooptions.First,wecould definearootcontenttypewiththepublishdateproperty.Allsubsequentcontenttypeswouldinherit fromthisroottypethusmakingthepublishdateavailableeverywhere.Second,wecouldindividually definethepublishdatepropertyonlyinthecontenttypesweknewweregoingtobepublishedtothe portal. Neitherofthesearegreatoptions.Inthefirstoption,wewouldwindupwithapropertyineachand everypieceofcontentintherepositorythatmayormaynotultimatelybeusedwhichcanleadto performanceandmaintenanceproblems.Thesecondoptionisn'tmuchbetterforafewreasons.First,it assumesweknowwhichcontenttypeswewanttopublishintheportalaheadoftime.Second,itopens upthepossibilitythatthesametypeofmetadatamightgetdefineddifferentlyacrosscontenttypes. Last,itdoesn'tgiveusaneasywaytoencapsulatebehaviororbusinesslogicwemightwanttotietothe publishdate. Asyouhaveprobablyfiguredoutbynow,thereisathirdoptionthataddressestheseissues:Aspects. Aspectsallowustocrosscutourcontentmodelwithpropertiesandassociationsbyattachingthemto contenttypes(orevenspecificinstancesofcontent)whenandwhereweneedthem. Goingbacktotheportalexample,wewoulddefineaPortalDisplayableaspectwithapublishdate property.Theaspectwouldthenbeaddedtoanypieceofcontent,regardlessoftype,thatneededtobe displayedintheportal.
AlfrescoDeveloper:WorkingwithCustomContentTypes Page5of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
CustomBehavior
Youmayfindthatyourcustomaspectorcustomtypeneedstohavebehaviororbusinesslogic associatedwithit.Forexample,everytimeanExpenseReportischeckedinyouwanttorecalculatethe totalbyiteratingthroughtheassociatedExpenses.Oneoptionwouldbetoincorporatethislogicinto rulesoractionsintheAlfrescowebclientoryourcustomwebapplication.Butsomebehaviorisso fundamentaltotheaspectortypethatitshouldreallybeboundtotheaspectortypeandinvokedany timeAlfrescoworkswiththoseobjects.I'llshowhowtodothisinafuturearticle,butyoushouldknow thatassociatingbusinesslogicwithyourcustomaspectsandtypes(oroverridingoutofthebox behavior)ispossible.
ContentModelingBestPractices
Nowthatyouknowthebuildingblocksofacontentmodel,itmakessensetoconsidersomebest practices.Herearethetopten: 1. Don'tchangeAlfresco'soutoftheboxcontentmodel.Ifyoucanpossiblyavoidit,donot changeAlfresco'soutoftheboxcontentmodel.Instead,extenditwithyourowncustom contentmodel.Ifrequirementscallforseveraldifferenttypesofcontenttobestoredinthe repository,createacontenttypeforeachonethatextendsfromcm:contentorfroman enterprisewiderootcontenttype. 2. Considerimplementinganenterprisewideroottype.Althoughtheneedforacommonancestor typeislessenedthroughtheuseofaspects,itstillmightbeagoodideatodefineanenterprise widerootcontenttypefromwhichallothercontenttypesintherepositoryinheritiffornoother reasonthanitgivescontentmanagersacatchalltypetousewhennoothertypewilldo. 3. Beconservativeearlyonbyaddingonlywhatyouknowyouneed.Acorollarytothatisprepare yourselftoblowawaytherepositorymultipletimesuntilthecontentmodelstabilizes.Onceyou getcontentintherepositorythatimplementsthetypesinyourmodel,makingmodeladditions iseasy,butsubtractionsaren't.Alfrescowillcomplainaboutintegrityerrorsandmaymake contentinaccessiblewhenthecontent'stypeorpropertiesdon'tmatchthecontentmodel definition.Whenthishappenstoyou(anditwillhappen)youroptionsaretoeither(1)leavethe oldmodelinplace,(2)attempttoexportthecontent,modifytheACPXMLfile,andreimport, or(3)droptheAlfrescotables,clearthedatadirectory,andstartfresh.Aslongaseveryoneon theteamisawareofthis,optionthreeisnotabigdealindevelopment,butmakesure expectationsaresetappropriatelyandhaveaplanforhandlingmodelchangesonceyougetto production.ThismightbeanareawhereAlfrescowillimproveinfuturereleases,butfornowit issomethingyouhavetowatchoutfor.
AlfrescoDeveloper:WorkingwithCustomContentTypes Page6of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
4. Avoidunnecessarycontentmodeldepth.IamnotawareofanyAlfrescoContentModeling Commandmentsthatsay,ThoushallnotexceedXlevelsofdepthinthinecontentmodellest thousufferthewrathofpoorperformancebutitseemslogicalthatdegradationwouldoccurat somepoint.Ifyourmodelhasseverallevelsofdepthbeyondcm:content,youshouldatleastdo aproofofconceptwitharealisticamountofdata,software,andhardwaretomakesureyou aren'tcreatingaproblemforyourselfthatmightbeverydifficulttoreversedowntheroad. 5. Takeadvantageofaspects.Inadditiontothepotentialperformanceandoverheadsavings throughtheuseofaspects,aspectspromotereuseacrossthemodel,thebusinesslogic,andthe presentationlayer.Whenworkingonyourmodelyoufindthattwoormorecontenttypeshave propertiesincommon,askyourselfifthosepropertiesarebeingusedtodescribesomehigher levelcharacteristiccommonacrossthetypesthatmightbetterbemodeledasanaspect. 6. Itmaymakesensetodefinetypesthathavenopropertiesorassociations.Youmayfind yourselfdefiningatypethatgetseverythingitneedsthrougheitherinheritancefromaparent typeorfromanaspect(orboth).Inthosecasesyoumightaskyourselfiftheemptytypeis reallynecessary.Inmyopinion,itshouldatleastbeconsidered.Itmightbeworthitjustto distinguishthecontentfromothertypesofcontentforsearchpurposes,forexample.Or,while youmightnothaveanyspecializedpropertiesorassociationsforthecontenttypeyoucould havespecializedbehaviorthat'sonlyapplicabletoinstancesofthecontenttype. 7. Rememberthatfoldersaretypestoo.Likeeverythingelseinthemodel,foldersaretypeswhich meanstheycanbeextended.Contentthatcontainsothercontentiscommon.Intheearlier expensereportexample,onewaytokeeptrackoftheexpensesassociatedwithanexpense reportwouldbetomodeltheexpensereportasasubtypeofcm:folder. 8. Don'tbeafraidtohavemorethanonecontentmodelXMLfile.Wehaven'ttalkedaboutexactly howcontentmodelsaredefinedyet,butwhenitistimetoimplementyourmodel,keepthisin mind:Itmightmakesensetosegmentyourmodelsintomultiplenamespacesandmultiple XMLfiles.Namesshouldbedescriptive.Don'tdeployamodelfilecalledcustomModel.xml ormyModel.xml. 9. ImplementaJavaclassthatcorrespondstoeachcustomcontentmodelyoudefine.Withineach contentmodelJavaclass,defineconstantsthatcorrespondtomodelnamespaces,typenames, propertynames,aspectnames,etc.You'llfindyourselfreferringtotheQnameoftypes, properties,andaspectsquiteoftensoithelpstohaveconstantsdefinedinanintuitiveway. 10. Usethesource!Theoutoftheboxcontentmodelisagreatexampleofwhat'spossible.The forumModelandrecordsModelhavesomeparticularlyusefulexamples.InthenextsectionI'll tellyouwherethemodelfilesliveandwhat'sineachsoyou'llknowwheretolooklaterwhen yousaytoyourself,Surely,thefolksatAlfrescohavedonethisbefore.
AlfrescoDeveloper:WorkingwithCustomContentTypes Page7of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
Outoftheboxmodels
TheAlfrescosourcecodeisanindispensablereferencetoolwhichyoushouldalwayshaveattheready, alongwiththedocumentation,wiki,forums,andJira.Withthatsaid,ifyouarefollowingalongwith thisarticlebuthavenotyetdownloadedthesource,youareinluck.Theoutoftheboxcontentmodel filesarewritteninXMLandgetdeployedwiththewebclient.Theycanbefoundinthealfresco.war filein/WEBINF/classes/alfresco/model.Thetablebelowdescribesseveralofthemodelfilesthatcan befoundinthedirectory. File Namespaces* d sys reg module cm d sys Typesandaspects extendedmostoftenby yourmodelslikeContent, Folder,Versionable,and Auditable. Advancedworkflowtypes. Extendthesewhenwriting yourowncustom advancedworkflows. Typesandaspectsrelated toaddingdiscussion threadstoobjects. Prefix Imports None d Description Fundamentaldatatypes usedinallothermodels. Systemlevelobjectslike base,storeroot,and reference.
bpmModel.xml
model/bpm/1.0
bpm
d sys cm
forumModel.xml
model/forum/1.0
fm
d cm
ecmarchitect.com
itdefinestheXMLvocabularyAlfrescocontentmodelXMLfilesmustadhereto.
CustomModelExample
Timeforadetailedexample.SupposeweworkforacompanycalledSomeCo.SomeCoisa commercialopensourcecompanythat'sbehindtheeverpopularopensourceproject,SomeSoftware. SomeCohasdecidedtorevampitswebpresencebyaddingnewtypesofcontentandcommunity functionalitytotheirwebsite.Forthisexample,we'llfocusonthewhitepapersSomeCowantstomake available. SomeCohasselectedAlfrescoastheirEnterpriseContentManagementsolution.Inadditionto managingthecontentonthenewsite,SomeCowantstouseAlfrescotomanageallofitsrichcontent. Let'sassumethatafteralotofdiscussion,SomeCohaselectedtogowithstraightAlfrescoforthis projectratherthanleveragingAlfresco'sWCMfeatures. Thefirststepistoconsiderthetypesandpropertieswearedealingwith.Therearesomepiecesof metadataSomeCowantstotrackaboutallcontent,regardlessofwhetherornotitwillbeshownonthe website.Alldocumentswillhaveanaudiencepropertythatidentifieswhowillbemostinterestedin thecontent.DocumentsrelatedtoSomeCo'ssoftwarewillhavepropertiesidentifyingtheSoftware ProductandSoftwareVersion. Contentthatneedstobeshownonthewebsiteneedstohaveaflagthatindicatesthecontentis activeandadatewhenthecontentwassettoactive. Nowlet'sthinkaboutassociations.Forsomedocuments,SomeCowouldliketoexplicitlydefineoneor morerelateddocuments.Onthewebsite,SomeComightchoosetoshowalistofrelateddocuments atthebottomofawhitepaper,forexample. Takingtheserequirementsintoconsideration,theteamcomesupwiththecontentmodeldepictedin Drawing1:SomeCo'sinitialcontentmodel.Asthedrawingshows,wehavedefinedacommonroot typecalledsc:docwithonechild,sc:whitepaper.Neithertypecurrentlyhasanypropertiesoftheirown. It'snotshownonthemodeldiagram,butwewilldefineaPeerAssociationaspartofsc:doctokeep trackofrelateddocuments.Thetargetclassoftheassociationwillbesc:docbecausewewanttobeable toassociateanyinstanceofsc:docoritschildrenwithoneormoreinstancesofsc:docoritschildren. Inaddition,therearetwoaspects.One,thewebableaspect,isusedforcontentthatistobeshownon thewebsiteitcontainstheactiveflagandactivedate.Theproductrelatedaspectisusedonlyfor contentthatrelatestoaSomeCoproduct.Itcapturesthespecificproductnamethecontentisrelatedto aswellastheproductversion.
AlfrescoDeveloper:WorkingwithCustomContentTypes Page9of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
Drawing1:SomeCo'sinitialcontentmodel Itshouldbeeasytoseehowthemodelmightbeextendedovertime.Therequirementsmentioned communityfeaturesbeingneededatsomepoint.Arateableaspectcouldbeaddedalongwitharating type.Commentscouldworkthesameway,orcommentscouldleveragetheexistingforumscontent model. Asnewcontenttypesareidentifiedtheywillbeaddedundersc:doc. Usingtheaspecttodeterminewhetherornottoshowthecontentontheportalishandy,particularlyin lightoftheSomeCodecisiontouseAlfrescoforallofitscontentmanagementneeds.Therepository willcontaincontentthatmayormaynotbeontheportal.Portalcontentwillbeeasilydistinguishable fromnonportalcontentbythepresenceofthewebableaspect.
AlfrescoDeveloper:WorkingwithCustomContentTypes Page10of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
Implementinganddeployingthemodel
Beforewestart,acoupleofnotesaboutmysetup:
Obviously,otheroperatingsystems,databases,applicationservers,andAlfrescoversionswillworkas well. Okay,let'sconfigureAlfrescotouseournewcontentmodel.Herearethestepswearegoingtofollow: 1. Createanextensiondirectory 2. Createacustommodelcontextfile 3. Createamodelfile 4. RestartTomcat Thefirststepistocreateanextensiondirectoryifyoudonotalreadyhaveone.Anextension directorykeepsyourcustomizationsseparatefromAlfresco'scode.Thismakesiteasiertoupgrade Alfrescolateron. Theextensiondirectorycanbeanywhereonthewebapplication'sclasspath.Irecommendusingtwo. Onesshouldbeforserverspecificsettingssuchastherepositoryproperties(specifiesthedatabase username,password,andconnectionstring)andLDAPauthenticationcontext.ForTomcat installations,thisextensiondirectorywouldbe$TOMCAT_HOME/shared/classes/alfresco/extension. TheotherextensiondirectoryisforyourAlfrescowebclientcustomizationsandyourcontentmodel. ForthatIusetheextensiondirectorythatisincludedintheAlfrescowebapplicationwhichisunder $TOMCAT_HOME/webapps/alfresco/WEBINF/classes/alfresco/extension. It'sfineifyouwanttokeepitsimplefornowandjustuseoneextensiondirectory. Thesecondstepistocreateacustommodelcontextfile.Acontextfileisafilethatcontainsoneor moreSpringbeanconfigurations.ThereareseveralcontextfilesusedtoconfigureAlfrescoSpring beans.DependingontheAlfrescodistributionyoudownloadedyoumayhaveasetofsamplecontext filesinyourextensiondirectory. Alfrescoloadsanyfileontheclasspaththatendswithcontext.xml.Allwehavetodoiscreateafile thatendswiththatsuffix,andinit,overridethebeanthatreferstoourcontentmodel.Howdoweknow whichbeantoextend?Recallfromearlierthatthereisanoutoftheboxcontentmodelfilecalled AlfrescoDeveloper:WorkingwithCustomContentTypes Page11of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
contentModel.xml.Ifyouhavethesourcehandy,gototherepositoryprojectanddoarecursivegrep forthestringcontentModel.xml.You'llfindthatitisreferencedinafilecalledconfig/alfresco/core servicescontext.xmlasshownbelow.
<beanid="dictionaryBootstrap"parent="dictionaryModelBootstrap"depends on="resourceBundles"> <propertyname="models"> <list> <!Systemmodels> <value>alfresco/model/dictionaryModel.xml</value> <value>alfresco/model/systemModel.xml</value> <value>org/alfresco/repo/security/authentication/userModel.xml</va lue> <!Contentmodels> <value>alfresco/model/contentModel.xml</value> <value>alfresco/model/bpmModel.xml</value> ...
AlfrescoDeveloper:WorkingwithCustomContentTypes Page12of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
useitasareferencetobuildourscModel.xmlfile.Theresultshouldlooklikethis:
<?xmlversion="1.0"encoding="UTF8"?> <!DefinitionofnewModel> <modelname="sc:somecomodel"xmlns="http://www.alfresco.org/model/dictionary/1.0"> <!Optionalmetadataaboutthemodel> <description>SomecoModel</description> <author>Optaros</author> <version>1.0</version> > <!Importsarerequiredtoallowreferencestodefinitionsinothermodels <imports> <!ImportAlfrescoDictionaryDefinitions> <importuri="http://www.alfresco.org/model/dictionary/1.0"prefix="d"/> <!ImportAlfrescoContentDomainModelDefinitions> <importuri="http://www.alfresco.org/model/content/1.0"prefix="cm"/> </imports> <!Introductionofnewnamespacesdefinedbythismodel> <namespaces> <namespaceuri="http://www.someco.com/model/content/1.0"prefix="sc"/> </namespaces> <types> <!Enterprisewidegenericdocumenttype> <typename="sc:doc"> <title>SomecoDocument</title> <parent>cm:content</parent> <associations> <associationname="sc:relatedDocuments"> <title>RelatedDocuments</title> <source> <mandatory>false</mandatory> <many>true</many> </source> <target> <class>sc:doc</class> <mandatory>false</mandatory> <many>true</many> </target> </association> </associations> <mandatoryaspects> <aspect>cm:generalclassifiable</aspect> </mandatoryaspects> </type> <typename="sc:whitepaper"> <title>SomecoWhitepaper</title>
AlfrescoDeveloper:WorkingwithCustomContentTypes Page13of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
<parent>sc:doc</parent> </type> </types> <aspects> <aspectname="sc:webable"> <title>SomecoWebable</title> <properties> <propertyname="sc:published"> <type>d:date</type> </property> <propertyname="sc:isActive"> <type>d:boolean</type> <default>false</default> </property> </properties> </aspect> <aspectname="sc:productRelated"> <title>SomecoProductMetadata</title> <properties> <propertyname="sc:product"> <type>d:text</type> <mandatory>true</mandatory> </property> <propertyname="sc:version"> <type>d:text</type> <mandatory>true</mandatory> </property> </properties> </aspect> </aspects> </model>
Exposingthecustommodelinthewebclientuserinterface
Nowthatthemodelisdefined,youcouldbeginusingitrightawaybywritingcodeagainstoneof Alfresco'sAPI'sthatcreatesinstancesofyourcustomtypes,addsaspects,etc.Inpracticeitisusuallya AlfrescoDeveloper:WorkingwithCustomContentTypes Page14of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
goodideatodojustthattomakesurethemodelbehaveslikeyouexpect.Beforewegettothat,though, let'stalkaboutwhatweneedtodoinordertobeabletoworkwithournewmodelintheAlfrescoweb clientuserinterface. First,thinkaboutthewebclientandalloftheplacesthecontentmodelcustomizationsneedtoshow up:
PropertySheet.Whenauserlooksatapropertysheetforapieceofcontentstoredasoneof thecustomtypesorwithoneofthecustomaspectsattached,thepropertysheetshouldshowthe customproperties. Createcontent/addcontent.WhenauserclicksCreateorAddContent,thecustomtypes shouldbeachoiceinthelistofcontenttypes. Issubtypecriteria.Whenauserconfiguresaruleonaspaceandusescontenttypesasa criteria,thecustomtypesshouldbeachoiceinthelistofpossiblecontenttypes. Specializetypeaction.Whenauserrunsthespecializetypeaction,thecustomtypesshould beavailable. Addaspect.Whenauserrunstheaddaspectaction,thecustomaspectsshouldbeavailable. Advancedsearch.Whenauserrunsanadvancedsearch,theyshouldbeabletorestrictsearch resultstoinstancesofourcustomtypesand/orcontentwithspecificvaluesforthepropertiesof ourcustomtypes.
Allofthesearehandledthroughafilecalledwebclientconfig.Lookingatthewebclientprojectinthe Alfrescosource,youcanseetheoutoftheboxwebclientconfig.xmlfileunderconfig/alfresco.The webclientconfig.xmlfileisaproprietaryfilecomposedofnumerousconfigelements.Eachconfig elementhasanevaluatorandacondition.Extendingthewebclientconfig.xmlfileisamatterof knowingwhichevaluator/conditiontouse. Tocustomizetheuserinterface,createawebclientconfigcustom.xmlfileinyourextensiondirectory witharootelementcalledalfrescoconfig.Addtheappropriateconfigelementsaschildrenof alfrescoconfig. Let'slookateachoftheareasmentionedaboveinordertounderstandhowthecustomcontentmodel canbeexposedintheuserinterface. Propertysheet Whenauserlooksatapropertysheetforapieceofcontentstoredasoneofthecustomtypesorwith oneofthecustomaspectsattached,thepropertysheetshouldshowthecustompropertiesasshown below:
AlfrescoDeveloper:WorkingwithCustomContentTypes Page15of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
/>
AlfrescoDeveloper:WorkingwithCustomContentTypes Page16of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
Drawing5:Customaspectin"add aspect"dialog
Drawing4:Customaspectin"has aspect"dialog
ecmarchitect.com
childelementsthatcanbeused.Theaspectselementdefinesthelistofaspectsshownwhentheadd aspectactionisconfigured.Thesubtypeselementliststypesthatshowupinthedropdownwhen configuringthecontenttypecriteriaforarule.Thespecialisetypeselement(notetheUKspelling) liststhetypesavailabletothespecializetypeaction.
<configevaluator="stringcompare"condition="ActionWizards"> <!Thelistofaspectstoshowintheadd/removefeaturesaction> <!andthehasaspectcondition> <aspects> <aspectname="sc:webable"/> <aspectname="sc:productRelated"/> </aspects> <!Thelistoftypesshownintheissubtypecondition> <subtypes> <typename="sc:doc"/> <typename="sc:whitepaper"/> </subtypes> <!Thelistofcontentand/orfoldertypesshowninthespecialise typeaction> <specialisetypes> <typename="sc:doc"/> <typename="sc:whitepaper"/> </specialisetypes> </config>
AlfrescoDeveloper:WorkingwithCustomContentTypes Page18of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
AlfrescoDeveloper:WorkingwithCustomContentTypes Page19of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
Listing7:Snippetfromwebclientconfigcustom.xml
Stringexternalization
Thelaststepinexposingourcustomcontentmodeltotheuserinterfaceisexternalizingthestrings usedforlabelsintheinterface.Thisisaccomplishedbycreatingawebclient.propertiesfile.The propertiesfileisastandardresourcebundle.Itholdskeyvaluepairs.Thekeysmatchthedisplay labelidattributevaluesinthewebclientconfigcustom.xmlfile. Inourexample,wehavefourpropertiesweneedlabelsfor.Theentirecontentsofour webclient.propertiesfilewouldbeasfollows:
#sc:webable published=Published isActive=Active? #sc:productRelated product=Product version=Version
Listing8:webclient.properties
Testthewebclientuserinterfacecustomizations
Nowthatyourwebclientcustomconfig.xmlandwebclient.propertiesfileshavebeenmodifiedand placedinyourextensiondirectory,youshouldbeabletorestartTomcatandseeyourchanges.Ifyou don't,checktheTomcatlogforerrormessages,thenrecheckyourmodelandwebclientfiles.
Workingwithcontentprogrammatically
Sofarwe'vecreatedacustommodelandwe'veexposedthemodelinAlfrescowebclient.Forsimple documentmanagementsolutions,thismaybeenough.Often,codewillalsorequired.Itcouldbecode inawebapplicationthatneedstoworkwiththerepository,codethatimplementscustombehaviorfor customcontenttypes,orcodethatimplementsAlfrescowebclientcustomizations. ThereareseveralAPI'savailabledependingonwhatyouwanttodo.Thetablebelowoutlinesthe choices: Solutiontype Alfrescowebclientuserinterfacecustomizations Language AlfrescoAPI
FreemarkerTemplating AlfrescoFoundation
AlfrescoDeveloper:WorkingwithCustomContentTypes Page20of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
Solutiontype Language Language Java/JSP CustomapplicationswithanembeddedAlfresco Java repository(Repositoryrunsinthesameprocessas theapplication) CustomapplicationsusingaseparateAlfresco repository Java PHP COM/.Net Anylanguagethat supportsWebServices ServersideScripts JavaScript AlfrescoJavaScriptAPI AlfrescoFoundation API JCRAPI AlfrescoWebServices API JCRAPIoverJNDI API AlfrescoAPI
CreatingcontentprogrammaticallywithJava
First,let'screatesomecontentandaddsomeaspects.Thegeneralgististhatwe'regoingto:
ecmarchitect.com
Alsonotethatallofthiscodeiscontainedwithinatrycatchfinallyblock(weendthesessionin finally)butI'veleftitouthereforbrevity.Theclassisrunnablewithargumentsbeingpassedinforthe username,password,folderinwhichtocreatethecontent,typeofcontentwe'recreating,andaname forthenewcontent.Again,I'veleftthatoutbutyoucanseethefullclassinitsentiretyifyoudownload thecodethataccompaniesthisdocument(SeeWheretoFindMoreInformation). Thefirstthingthecodedoesisstartasession.Next,itgetsareferencetothefolderwherethecontent willbecreated.Thetimestampisincorporatedintothecontentnamesothatifthecodeisrunmultiple times,theobjectnameswillbeunique.
AuthenticationUtils.startSession(getUser(),getPassword()); StorestoreRef=newStore(Constants.WORKSPACE_STORE,"SpacesStore"); StringfolderPath="/app:company_home/cm:"+getRootFolder(); StringtimeStamp=newLong(System.currentTimeMillis()).toString(); ParentReferencedocParent=newParentReference( storeRef, null, folderPath, Constants.ASSOC_CONTAINS, Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, getContentName()+timeStamp));
ecmarchitect.com
to.
CMLCreatecreateDoc=newCMLCreate( "ref1", docParent, null, null, null, Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.TYPE_SC_DOC), contentProps); CMLAddAspectaddWebableAspectToDoc=newCMLAddAspect( Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASPECT_SC_WEBABLE), null, null, "ref1"); CMLAddAspectaddProductRelatedAspectToDoc=newCMLAddAspect( Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASPECT_SC_PRODUCT_RELATED), null, null, "ref1");
AlfrescoDeveloper:WorkingwithCustomContentTypes Page23of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
ContentFormatcontentFormat=newContentFormat("text/plain","UTF8"); StringdocText="Thisisasample"+getContentType()+"documentcalled"+ getContentName(); ContentdocContentRef=contentService.write(docRef,Constants.PROP_CONTENT, docText.getBytes(),contentFormat); System.out.println("ContentLength:"+docContentRef.getLength());
Listing13:SnippetfromSomeCoDataCreator.java RunningtheJavasnippetproduces:
Command=create;Source=none;Destination=b901941e12d311dc9bf3 e998e07a8da1 Command=addAspect;Source=b901941e12d311dc9bf3e998e07a8da1;Destination= b901941e12d311dc9bf3e998e07a8da1 Command=addAspect;Source=b901941e12d311dc9bf3e998e07a8da1;Destination= b901941e12d311dc9bf3e998e07a8da1 ContentLength:26
Listing14:CommandlineresultsafterrunningSomeCoDataCreator.java
CreatingcontentprogrammaticallywithPHP
Javaisnotarequirementyoucoulduseanylanguagethatunderstandswebservicestoworkwiththe repository.Forexample,AlfrescoincludesaPHPAPI.SettingupPHPandgettingittoworkwith Alfrescoisbeyondthescopeofthisarticle(SeeWheretoFindMoreInformation),butforthe curious,here'showtodothesamethingwejustdidinJava,inPHP.
<?php require_once"Alfresco/Service/Session.php"; require_once"Alfresco/Service/SpacesStore.php"; require_once"Alfresco/Service/Node.php"; ...snip... functioncreateContent($username,$password,$rootFolder,$contentType, $contentName){ //Startandcreatethesession $repository=newRepository("http://localhost:8080/alfresco/api"); $ticket=$repository>authenticate($username,$password); $session=$repository>createSession($ticket); $store=newStore($session,"SpacesStore"); $folderPath="/app:company_home/cm:".$rootFolder; //GrabareferencetotheSomeCofolder $results=$session>query($store,'PATH:"'.$folderPath.'"'); $rootFolderNode=$results[0];
AlfrescoDeveloper:WorkingwithCustomContentTypes Page24of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
if($rootFolderNode==null){ echo"Rootfoldernode(".$folderPath.")isnull<br>"; exit; } $timestamp=time(); $newNode=$rootFolderNode >createChild("{http://www.someco.com/model/content/1.0}".$contentType, "cm_contains","{http://www.someco.com/model/content/1.0}".$contentType."_". $timestamp); if($newNode==null){ echo"Newnodeisnull<br>"; exit; } //Addthetwoaspects $newNode>addAspect("{http://www.someco.com/model/content/1.0}webable"); $newNode>addAspect("{http://www.someco.com/model/content/1.0}productRelated"); echo"Aspectsadded<br>"; //Settheproperties $properties=$newNode>getProperties(); $properties["{http://www.alfresco.org/model/content/1.0}name"]=$contentName. "(".$timestamp.")"; $properties["{http://www.someco.com/model/content/1.0}isActive"]="true"; $properties["{http://www.someco.com/model/content/1.0}published"]="200704 01T00:00:00.00005:00"; $newNode>setProperties($properties); echo"Propsset<br>"; $newNode>setContent("cm_content","text/plain","UTF8","Thisisasample". $contentType."documentnamed".$contentName); echo"Contentset<br>"; $session>save(); echo"Savedchangesto".$newNode>getId()."<br>"; } ?>
ecmarchitect.com
Aspectsadded Propsset Contentset Savedchangesto5a8dac5e131411dcab933b56af79ba48
Listing16:Resultsofrunningindex.php
Creatingassociations
Nowlet'sswitchbacktoJavaandwriteaclasstocreatetherelateddocumentsassociationbetween twodocuments. Themechanicsareessentiallythesame.We'regoingtosetupandexecutesomeCML.Ourclasswill acceptasourceUUIDandatargetUUIDasargumentswhicharepassedintothe CMLCreateAssociationconstructor,executetheCML,andthendumptheresults.
ReferencedocRefSource=newReference(storeRef,getSourceUuid(),null); ReferencedocRefTarget=newReference(storeRef,getTargetUuid(),null); CMLCreateAssociationrelatedDocAssoc=newCMLCreateAssociation(new Predicate(newReference[]{docRefSource},null,null), null, newPredicate(newReference[]{docRefTarget},null,null), null, Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASSN_RELATED_DOCUMENTS)); //SetupCMLblock CMLcml=newCML(); cml.setCreateAssociation(newCMLCreateAssociation[]{relatedDocAssoc}); //ExecuteCMLBlock UpdateResult[]results=WebServiceFactory.getRepositoryService().update(cml); dumpUpdateResults(results); System.out.println("AssociationsofsourceUuid:"+getSourceUuid()); dumpAssociations(docRefSource, Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.ASSN_RELATED_DOCUMENTS));
AlfrescoDeveloper:WorkingwithCustomContentTypes Page26of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
bd0bd57d160c11dca66fbb03ffd77ac6 {http://www.alfresco.org/model/content/1.0}name:TestDocument2(1181340487582)
Searchingforcontentprogrammatically
NowthatwehavesomecontentintherepositorywecantestoutAlfresco'sfulltextsearchengine, Lucene.ContentintherepositoryisautomaticallyindexedbyLuceneandquerystringsusetheLucene querysyntaxtofindcontentbasedonfulltextcontents,propertyvalues,andcontenttype.(XPathis alsoasupportedsearchdialectbutwewon'tcoverthathere). Let'swritesomecodethatwillshowseveraldifferentexamplesofAlfrescoqueriesusingLucene.Our codewill:
AlfrescoDeveloper:WorkingwithCustomContentTypes Page27of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
false); //Displaytheresults ResultSetresultSet=queryResult.getResultSet(); ResultSetRow[]rows=resultSet.getRows();
{ true){
Listing20:GenericmethodforexecutingqueriesfromSomeCoDataQueries.java Here'sthecodethatcallsthismethodonceforeachqueryexample.
System.out.println(RESULT_SEP); System.out.println("Findingcontentoftype:"+ SomeCoModel.TYPE_SC_DOC.toString()); queryString="+TYPE:\""+ Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.TYPE_SC_DOC)+"\""; dumpQueryResults(getQueryResults(queryString)); System.out.println(RESULT_SEP); System.out.println("Findcontentintherootfolderwithtextlike'sample'"); queryString="+PARENT:\"workspace://SpacesStore/"+ getRootFolderUuid()+"\"+TEXT:\"sample\"";
AlfrescoDeveloper:WorkingwithCustomContentTypes Page28of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
dumpQueryResults(getQueryResults(queryString)); System.out.println(RESULT_SEP); System.out.println("Findactivecontent"); queryString="+@sc\\:isActive:true"; dumpQueryResults(getQueryResults(queryString)); System.out.println(RESULT_SEP); System.out.println("Findactivecontentwithaproductequalto'SomePortal'"); queryString="+@sc\\:isActive:true+@sc\\:product:SomePortal"; dumpQueryResults(getQueryResults(queryString)); System.out.println(RESULT_SEP); System.out.println("Findcontentoftypesc:whitepaperpublishedbetween1/1/2006 and6/1/2007"); queryString="+TYPE:\""+ Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.TYPE_SC_WHITEPAPER)+ "\""+ "+@sc\\:published:[2006\\01\\01T00:00:00TO2007\\06\\01T00:00:00]"; dumpQueryResults(getQueryResults(queryString));
AlfrescoDeveloper:WorkingwithCustomContentTypes Page29of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
Result2: id=1fe9cf04160b11dca66fbb03ffd77ac6 name=TestDocument(1181339794431) created=20070608T16:56:35.02805:00 Result3: id=1355e60e160b11dca66fbb03ffd77ac6 name=TestWhitepaper(1181339773331) created=20070608T16:56:13.93205:00 ====================== Findactivecontent Result1: id=bd0bd57d160c11dca66fbb03ffd77ac6 name=TestDocument2(1181340487582) created=20070608T17:08:08.15005:00 Result2: id=1fe9cf04160b11dca66fbb03ffd77ac6 name=TestDocument(1181339794431) created=20070608T16:56:35.02805:00 Result3: id=1355e60e160b11dca66fbb03ffd77ac6 name=TestWhitepaper(1181339773331) created=20070608T16:56:13.93205:00 ====================== Findactivecontentwithaproductequalto'SomePortal' ====================== Findcontentoftypesc:whitepaperpublishedbetween1/1/2006and6/1/2007 Result1: id=1355e60e160b11dca66fbb03ffd77ac6 name=TestWhitepaper(1181339773331) created=20070608T16:56:13.93205:00
Deletingcontentprogrammatically
Nowitistimetocleanupafterourselvesbydeletingcontentfromtherepository.Deletingislike searchingexceptthatinsteadofdumpingtheresults,we'llcreateCMLDeleteobjectsforeachresult, andthenwe'llexecutetheCMLtoperformthedelete. AlfrescoDeveloper:WorkingwithCustomContentTypes Page30of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
//Createaqueryobject,lookingforallitemsofaparticulartype Queryquery=newQuery(Constants.QUERY_LANG_LUCENE,"TYPE:\""+ Constants.createQNameString(SomeCoModel.NAMESPACE_SOMECO_CONTENT_MODEL, SomeCoModel.TYPE_SC_DOC)"\""); //Executethequery QueryResultqueryResult=repositoryService.query(storeRef,query,false); //Gettheresultset ResultSetresultSet=queryResult.getResultSet(); ResultSetRow[]rows=resultSet.getRows(); //ifwefoundsomerows,createanarrayofDeleteCMLobjects if(rows!=null){ System.out.println("Found"+rows.length+"objectstodelete."); CMLDelete[]deleteCMLArray=newCMLDelete[rows.length]; for(intindex=0;index<rows.length;index++){ ResultSetRowrow=rows[index]; deleteCMLArray[index]=newCMLDelete(newPredicate(newReference[]{new Reference(storeRef,row.getNode().getId(),null)},null,null)); } //ConstructCMLBlock CMLcml=newCML(); cml.setDelete(deleteCMLArray); //ExecuteCMLBlock UpdateResult[]results= WebServiceFactory.getRepositoryService().update(cml); dumpUpdateResults(results); }//endif
Listing24:CommandlineresultsforSomeCoDataCleaner.java
AlfrescoDeveloper:WorkingwithCustomContentTypes Page31of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License
ecmarchitect.com
Conclusion
ThisarticlehasshownhowtoextendAlfresco'soutoftheboxcontentmodelwithyourownbusiness specificcontenttypes,howtoexposethosetypes,aspects,andpropertiesintheAlfrescowebclient userinterface,andhowtoworkwithcontentviathewebservicesAPIusingbothJavaandPHP.I've throwninafewrecommendationsthatwillhopefullysaveyousometimeoratleastsparksome discussion. There'splentyofadditionaldatamodelrelatedcustomizationstocoverinfuturearticlesincluding custombehavior,custommetadataextractors,customtransformers,andcustomdatarenderers.
Wheretofindmoreinformation
Thecompletesourcecodefortheseexamplesisavailableherefromecmarchitect.com. TheAlfrescoSDKcomeswithasamplesforworkingwithwebservices,theJCR,andthe foundationAPI. TheSearchDocumentationontheAlfrescowikiprovidesqueryexamplesusingbothLucene andXPath. SeeUsingAlfresco'sPHPAPIonecmarchitect.comforpointersongettingAlfrescoworking fromPHP. Fordeploymenthelp,seetheClientConfigurationGuideandPackagingandDeploying ExtensionsintheAlfrescowiki. Forgeneraldevelopmenthelp,seetheDeveloperGuide. Forhelpcustomizingthedatadictionary,seetheDataDictionarywikipage. Ifyouarereadytocovernewground,seetheecmarchitect.comtutorialonCustomActions.
AbouttheAuthor
JeffPottsistheEnterpriseContentManagementPracticeLeadatOptaros,a leadingOpenSourceandNextGenerationInternetconsultancy.Jeffhasnearly fifteenyearsofexperienceimplementingcontentmanagement,collaboration,and otherknowledgemanagementtechnologiesforavarietyofFortune500companies. JefflivesinDallas,Texaswithhiswifeandtwokids.Readmoreat ecmarchitect.com.
AlfrescoDeveloper:WorkingwithCustomContentTypes Page32of32
ThisworkislicensedundertheCreativeCommonsAttributionShare Alike2.5License